Compare commits

..

18 commits
dev-pe ... dev

Author SHA1 Message Date
875166b683
Replace WMIC sections
Addresses issue #227
2024-11-23 15:27:17 -08:00
179748c469
Add Windows 11 24H2 to version list 2024-11-22 07:27:44 -08:00
ffcee1156a
Remove stale docopt files 2024-09-30 01:52:07 -07:00
edd944a325
Hide extra partitions when building UFDs
Addresses issue #226
2024-09-28 23:22:31 -07:00
75119c15ad
Fix bug in ddrescue-tui argument parsing 2024-09-11 18:11:12 -07:00
e6db63c8b0
Fix typo 2024-09-09 21:43:28 -07:00
e388b77639
Add OSFMount 2024-09-09 21:40:19 -07:00
bbdef56df2
Add OSFMount 2024-09-09 21:05:36 -07:00
6a5a944ea0
Refactor notepad replacement in WinPE
The registry method has proved problematic.  This DOSKEY method seems
less error prone since it's evaluated at runtime.
2024-09-09 20:12:54 -07:00
3621914312
Remove temporary WinPE WIM file 2024-09-08 13:28:31 -07:00
0ef9412995
Switch back to ConEmu in WinPE 2024-09-08 03:53:48 -07:00
0335797a5d
Update WinPE sections 2024-09-08 03:04:33 -07:00
2a07aebff3
Move macOS icons into a tar archive 2024-09-08 00:13:47 -07:00
244d917c73
Drop remaining docopt references 2024-09-04 01:14:34 -07:00
13b8dc8696
Replace remaining docopt sections 2024-09-04 00:53:42 -07:00
58576cbdb4
Use FAT32 for all UFD partitions
This breaks macOS images but those are deprecated at this point.
2024-09-04 00:24:03 -07:00
a3a7b25512
Fix case_insensitive_search 2024-09-04 00:16:54 -07:00
50033f42f6
Move to argparse for ddrescue-tui and hw-diags 2024-09-03 01:12:00 -07:00
155 changed files with 3507 additions and 13798 deletions

7
LICENSE.txt Normal file
View file

@ -0,0 +1,7 @@
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

BIN
images/ConEmu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 KiB

BIN
images/Linux.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
images/Pxelinux.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

BIN
images/Syslinux.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 168 KiB

BIN
images/WizardHat.xcf Normal file

Binary file not shown.

122
images/logo.svg Normal file
View file

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="32"
id="svg2"
version="1.1"
inkscape:version="0.91 r"
sodipodi:docname="logo.svg"
inkscape:export-filename="/home/thewizardpp/projects/logos/logo512x512.png"
inkscape:export-xdpi="1440"
inkscape:export-ydpi="1440">
<defs
id="defs4">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" />
<filter
id="filter3668"
inkscape:label="Drop shadow"
width="1.5"
height="1.5"
x="-.25"
y="-.25">
<feGaussianBlur
id="feGaussianBlur3670"
in="SourceAlpha"
stdDeviation="1,000000"
result="blur" />
<feColorMatrix
id="feColorMatrix3672"
result="bluralpha"
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0,500000 0 " />
<feOffset
id="feOffset3674"
in="bluralpha"
dx="1,000000"
dy="1,000000"
result="offsetBlur" />
<feMerge
id="feMerge3676">
<feMergeNode
id="feMergeNode3678"
in="offsetBlur" />
<feMergeNode
id="feMergeNode3680"
in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.839192"
inkscape:cx="16.469461"
inkscape:cy="15.775995"
inkscape:document-units="px"
inkscape:current-layer="layer2"
showgrid="false"
inkscape:window-width="1152"
inkscape:window-height="844"
inkscape:window-x="-2"
inkscape:window-y="93"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer"
style="display:inline">
<g
id="g3613"
transform="matrix(1.0696952,0,0,1.0696952,-1.9682871,1.2767394)">
<path
sodipodi:nodetypes="cssssss"
d="m 28.466519,15.480445 c -1.690444,-0.411311 -3.880242,0.0024 -6.862802,1.703057 -4.343818,2.477 -5.647804,4.7124 -10.531132,6.5262 -2.7416801,1.0184 -7.1725478,1.2727 -6.7296333,-1.9563 0.4055207,-2.9564 4.8746766,-5.683963 10.7473903,-5.268022 7.253753,0.513753 7.780294,2.643843 11.236758,2.445771 4.073631,-0.233438 3.02577,-3.235043 2.139419,-3.450706 z"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path2822"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="ccscsc"
id="path2832"
d="m 22.349625,16.595174 c -5.498466,2.959917 -4.603518,5.10607 -10.999048,3.821601 1.40216,-4.418086 4.962036,-16.95097 7.147841,-17.2692571 1.878431,-0.2735287 4.924495,4.2931483 4.924495,4.2931483 0,0 -3.661803,-2.9673231 -4.16688,-1.7046325 -0.593183,1.4829546 2.39459,8.4145833 3.093592,10.8591403 z"
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="ccc"
id="path3611"
d="m 22.074942,15.74979 c 1.515307,-0.313608 1.831341,-0.3546 3.377477,-0.485523 1.799175,-0.173029 3.187957,0.237433 3.187957,0.237433"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 KiB

BIN
images/macOS.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 98 KiB

View file

@ -45,7 +45,6 @@ BASE_MENUS = {
MenuEntry('CHKDSK', 'auto_chkdsk'),
MenuEntry('DISM RestoreHealth', 'auto_dism'),
MenuEntry('SFC Scan', 'auto_sfc'),
MenuEntry('Fix File Associations', 'auto_fix_file_associations'),
MenuEntry('Clear Proxy Settings', 'auto_reset_proxy'),
MenuEntry('Disable Pending Renames', 'auto_disable_pending_renames'),
MenuEntry('Registry Repairs', 'auto_repair_registry'),
@ -53,23 +52,15 @@ BASE_MENUS = {
MenuEntry('Reset Windows Policies', 'auto_reset_windows_policies'),
),
'Malware Cleanup': (
MenuEntry('Disable Defender Scans', 'auto_disable_defender', selected=False),
MenuEntry('BleachBit', 'auto_bleachbit'),
MenuEntry('HitmanPro', 'auto_hitmanpro'),
MenuEntry('KVRT', 'auto_kvrt'),
MenuEntry('EmsisoftCmd', 'auto_emsisoft_cmd_run', selected=False),
MenuEntry(REBOOT_STR, 'auto_reboot', selected=False),
MenuEntry('EmsisoftCmd (Uninstall)', 'auto_emsisoft_cmd_uninstall', selected=False),
MenuEntry('Enable Defender Scans', 'auto_enable_defender', selected=False),
MenuEntry('Windows Defender', 'auto_microsoft_defender', selected=False),
MenuEntry('Windows Defender', 'auto_microsoft_defender'),
MenuEntry('Remove Custom Power Plan', 'auto_remove_power_plan'),
MenuEntry(REBOOT_STR, 'auto_reboot'),
),
'Manual Steps': (
MenuEntry('Malwarebytes (Install)', 'auto_mbam_install'),
MenuEntry(REBOOT_STR, 'auto_reboot'),
MenuEntry('AdwCleaner', 'auto_adwcleaner'),
MenuEntry('Malwarebytes (Run)', 'auto_mbam_run'),
MenuEntry('Malwarebytes (Uninstall)', 'auto_mbam_uninstall'),
MenuEntry('Bulk Crap Uninstaller', 'auto_bcuninstaller'),
MenuEntry('Enable Windows Updates', 'auto_windows_updates_enable'),
),
@ -90,112 +81,6 @@ BASE_MENUS = {
}
PRESETS = {
'Default': { # Will be expanded at runtime using BASE_MENUS
'Options': (
'Run AVRemover (once)',
'Run RKill',
'Sync Clock',
),
},
'Data Transfer (to a clean Windows install)': {
'Backup Settings': (
'Enable RegBack',
'Enable System Restore',
'Set System Restore Size',
'Reset Power Plans',
'Set Custom Power Plan',
),
'Malware Cleanup': (
'Disable Defender Scans',
'BleachBit',
'HitmanPro',
'KVRT',
'Enable Defender Scans',
'Remove Custom Power Plan',
),
'Manual Steps': (
'Malwarebytes (Install)',
REBOOT_STR,
'AdwCleaner',
'Malwarebytes (Run)',
'Malwarebytes (Uninstall)',
'Enable Windows Updates',
),
'Options': (
'Sync Clock',
),
},
'Full Malware/Virus Cleanup': {
'Backup Settings': (
'Enable RegBack',
'Enable System Restore',
'Set System Restore Size',
'Create System Restore',
'Backup Browsers',
'Backup Power Plans',
'Reset Power Plans',
'Set Custom Power Plan',
'Backup Registry',
),
'Windows Repairs': (
'Disable Windows Updates',
'Reset Windows Updates',
REBOOT_STR,
'CHKDSK',
'DISM RestoreHealth',
'SFC Scan',
'Fix File Associations',
'Clear Proxy Settings',
'Disable Pending Renames',
'Registry Repairs',
'Reset UAC',
'Reset Windows Policies',
),
'Malware Cleanup': (
'Disable Defender Scans',
'BleachBit',
'HitmanPro',
'KVRT',
'EmsisoftCmd',
REBOOT_STR,
'EmsisoftCmd (Uninstall)',
'Enable Defender Scans',
'Windows Defender',
'Remove Custom Power Plan',
),
'Manual Steps': (
'Malwarebytes (Install)',
REBOOT_STR,
'AdwCleaner',
'Malwarebytes (Run)',
'Malwarebytes (Uninstall)',
'Bulk Crap Uninstaller',
'Enable Windows Updates',
),
'Options': (
'Run AVRemover (once)',
'Run RKill',
'Sync Clock',
),
},
'Minimal Repairs': {
'Backup Settings': (
'Enable RegBack',
'Enable System Restore',
'Set System Restore Size',
'Create System Restore',
'Backup Browsers',
'Backup Power Plans',
'Backup Registry',
),
'Windows Repairs': (
'CHKDSK',
'DISM RestoreHealth',
'SFC Scan',
'Fix File Associations',
'Clear Proxy Settings',
'Disable Pending Renames',
'Registry Repairs',
),
'Options': (
'Run RKill',
'Sync Clock',

View file

@ -34,24 +34,17 @@ BASE_MENUS = {
),
'Install Software': (
MenuEntry('Winget', 'auto_install_winget'),
MenuEntry('ESET NOD32 AV', 'auto_install_eset_nod32_av', selected=False),
MenuEntry('ESET NOD32 AV (MSP)', 'auto_install_eset_nod32_av_msp', selected=False),
MenuEntry('Firefox', 'auto_install_firefox'),
MenuEntry('LibreOffice', 'auto_install_libreoffice', selected=False),
MenuEntry('Open-Shell', 'auto_install_open_shell', selected=False),
MenuEntry('Open Shell', 'auto_install_open_shell'),
MenuEntry('Software Bundle', 'auto_install_software_bundle'),
MenuEntry('Software Upgrades', 'auto_install_software_upgrades', selected=False),
MenuEntry('Software Upgrades', 'auto_install_software_upgrades'),
MenuEntry('Visual C++ Runtimes', 'auto_install_vcredists'),
),
'Configure System': (
MenuEntry('Apply ITS Settings', 'auto_apply_its_settings', selected=False),
MenuEntry('Open-Shell', 'auto_config_open_shell', selected=False),
MenuEntry('Disable Chrome Notifications', 'auto_disable_chrome_notifications', selected=False),
MenuEntry('Disable Fast Startup', 'auto_disable_fast_startup', selected=False),
MenuEntry('Open Shell', 'auto_config_open_shell'),
MenuEntry('Disable Password Expiration', 'auto_disable_password_expiration'),
MenuEntry('Disable Telemetry', 'auto_shutup_10'),
MenuEntry('Enable BSoD MiniDumps', 'auto_enable_bsod_minidumps'),
MenuEntry('Enable Hibernation', 'auto_enable_hibernation', selected=False),
MenuEntry('Enable RegBack', 'auto_enable_regback'),
MenuEntry('Enable System Restore', 'auto_system_restore_enable'),
MenuEntry('Set System Restore Size', 'auto_system_restore_set_size'),
@ -63,7 +56,7 @@ BASE_MENUS = {
MenuEntry('Create System Restore', 'auto_system_restore_create'),
),
'System Information': (
MenuEntry('AIDA64 Report', 'auto_export_aida64_report', selected=False),
MenuEntry('AIDA64 Report', 'auto_export_aida64_report'),
MenuEntry('Backup Registry', 'auto_backup_registry'),
),
'System Summary': (
@ -77,9 +70,9 @@ BASE_MENUS = {
),
'Run Programs': (
MenuEntry('Device Manager', 'auto_open_device_manager'),
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('Webcam Tests', 'auto_open_mic_and_webcam_tests'),
MenuEntry('Windows Activation', 'auto_open_windows_activation'),
MenuEntry('Windows Updates', 'auto_open_windows_updates'),
MenuEntry('XMPlay', 'auto_open_xmplay'),
@ -96,7 +89,7 @@ PRESETS = {
'Additional User': {
'Configure System': (
'Configure Browsers',
'Open-Shell',
'Open Shell',
'uBlock Origin',
'Enable BSoD MiniDumps',
'Enable RegBack',
@ -143,6 +136,7 @@ PRESETS = {
),
'Run Programs': (
'Device Manager',
'HWiNFO Sensors',
'XMPlay',
),
},

13
scripts/check_av.ps1 Normal file
View 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

View 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

View file

@ -2,19 +2,10 @@
"""WizardKit: ddrescue TUI"""
# vim: sts=2 sw=2 ts=2
from docopt import docopt
import wk
if __name__ == '__main__':
try:
docopt(wk.clone.ddrescue.DOCSTRING)
except SystemExit:
print('')
wk.ui.cli.pause('Press Enter to exit...')
raise
try:
wk.clone.ddrescue.main()
except SystemExit:

View 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

View file

@ -1,79 +0,0 @@
# Export Bitlocker info
# Init
$REPORT = ""
$SKIPPED_PROPERTIES = @(
"CapacityGB",
"MountPoint",
"KeyProtector"
)
# Functions
function Convert-BytesToString ($bytes) {
If ($bytes -gt 1PB) {
return ("{0:0.#} PB" -f ($bytes / 1PB) )
} ElseIf ($bytes -gt 1TB) {
return ("{0:0.#} TB" -f ($bytes / 1TB) )
} ElseIf ($bytes -gt 1GB) {
return ("{0:0.#} GB" -f ($bytes / 1GB) )
} ElseIf ($bytes -gt 1MB) {
return ("{0:0.#} MB" -f ($bytes / 1MB) )
} ElseIf ($bytes -gt 1KB) {
return ("{0:0.#} KB" -f ($bytes / 1KB) )
} Else {
return ("{0} B" -f $bytes)
}
}
# Build report
$system_drive = $env:SystemDrive
if ($system_drive -eq "X:") {
# Assuming we're running in WinPE
$system_drive = "C:"
}
Get-BitlockerVolume -MountPoint $system_drive | ForEach-Object {
$bitlocker_volume = $_
$REPORT += ("`n`nDrive {0}`n---`n" -f $bitlocker_volume.MountPoint)
# Size info
$volume = Get-Volume -DriveLetter $bitlocker_volume.MountPoint[0]
$total = Convert-BytesToString ($volume.Size)
$used = Convert-BytesToString ($volume.Size - $volume.SizeRemaining)
if ($volume.Size -gt 0) {
$REPORT += ("Size: {0} ({1} used)`n" -f $total, $used)
} else {
$REPORT += "Size: Unknown`n"
}
# Volume info
$bitlocker_volume |
Get-Member -MemberType Property |
Where-Object {! $SKIPPED_PROPERTIES.Contains($_.Name)} |
ForEach-Object {
$name = $_.Name
if ($bitlocker_volume.$name -ne $null) {
$REPORT += ("{0}: {1}`n" -f $name, $bitlocker_volume.$name)
}
}
# Key info
$bitlocker_volume.KeyProtector |
ForEach-Object {
$key = $_
$REPORT += "Key Slot:`n"
$key |
Get-Member -MemberType Property |
ForEach-Object {
$name = $_.Name
if ($key.$name -ne $null -and $key.$name -ne "") {
$REPORT += ("... {0}: {1}`n" -f $name, $key.$name)
}
}
}
}
# Show report
Write-Host $REPORT.Trim()
# vim: sts=2 sw=2 ts=2

View file

@ -2,19 +2,10 @@
"""WizardKit: Hardware Diagnostics"""
# vim: sts=2 sw=2 ts=2
from docopt import docopt
import wk
if __name__ == '__main__':
try:
docopt(wk.hw.diags.DOCSTRING)
except SystemExit:
print('')
wk.ui.cli.pause('Press Enter to exit...')
raise
try:
wk.hw.diags.main()
except SystemExit:

View file

@ -1,4 +0,0 @@
#!/bin/zsh
#
sudo hw-info | less -S --use-color

View file

@ -56,7 +56,6 @@ for %%f in (%*) do (
if defined _backups mkdir "%client_dir%\Backups">nul 2>&1
if defined _logs (
mkdir "%log_dir%\%KIT_NAME_FULL%">nul 2>&1
mkdir "%log_dir%\d7II">nul 2>&1
mkdir "%log_dir%\Tools">nul 2>&1)
if defined _office mkdir "%client_dir%\Office">nul 2>&1
if defined _quarantine mkdir "%client_dir%\Quarantine">nul 2>&1

View file

@ -1,15 +1,78 @@
"""WizardKit: Launch Snappy Driver Installer Origin"""
# vim: sts=2 sw=2 ts=2
import wk
from subprocess import CompletedProcess
import wk
from wk.cfg.net import SDIO_SERVER
# STATIC VARIABLES
MOUNT_EXCEPTIONS = (
RuntimeError,
wk.exe.subprocess.CalledProcessError,
)
SDIO_LOCAL_PATH = wk.kit.tools.get_tool_path("SDIO", "SDIO")
SDIO_REMOTE_PATH = wk.io.get_path_obj(
(
fr'\\{SDIO_SERVER["Address"]}\{SDIO_SERVER["Share"]}\{SDIO_SERVER["Path"]}'
fr'\SDIO{"64" if wk.os.win.ARCH == "64" else ""}.exe'
),
resolve=False,
)
# Functions
def try_again() -> bool:
"""Ask to try again or quit."""
if wk.ui.cli.ask(' Try again?'):
return True
if not wk.ui.cli.ask(' Use local version?'):
wk.ui.cli.abort()
return False
def use_network_sdio() -> bool:
"""Try to mount SDIO server."""
use_network = False
def _mount_server() -> CompletedProcess:
print('Connecting to server... (Press CTRL+c to use local copy)')
return wk.net.mount_network_share(SDIO_SERVER, read_write=False)
# Bail early
if not SDIO_SERVER['Address']:
return use_network
# Main loop
while True:
try:
proc = _mount_server()
except KeyboardInterrupt:
break
except MOUNT_EXCEPTIONS as err:
wk.ui.cli.print_error(f' {err}')
if not try_again():
break
else:
if proc.returncode == 0:
# Network copy available
use_network = True
break
# Failed to mount
wk.ui.cli.print_error(' Failed to mount server')
if not try_again():
break
# Done
return use_network
if __name__ == '__main__':
wk.ui.cli.set_title(
f'{wk.cfg.main.KIT_NAME_FULL}: Snappy Driver Installer Origin Launcher',
)
log_dir = wk.log.format_log_path(tool=True).parent
USE_NETWORK = False
# Windows 11 workaround
if wk.os.win.OS_VERSION == 11:
@ -19,6 +82,18 @@ if __name__ == '__main__':
if any([wk.os.win.get_service_status(s) != 'stopped' for s in appid_services]):
raise wk.std.GenericWarning('Failed to stop AppID services')
sdio_path = wk.kit.tools.get_sdio_path(interactive=True)
cmd = [sdio_path, '-log_dir', log_dir]
wk.exe.popen_program(cmd, cwd=sdio_path.parent)
# Try to mount server
try:
USE_NETWORK = use_network_sdio()
except KeyboardInterrupt:
wk.ui.cli.abort()
# Run SDIO
EXE_PATH = SDIO_LOCAL_PATH
if USE_NETWORK:
EXE_PATH = SDIO_REMOTE_PATH
print('Using network copy!')
else:
print('Using local copy!')
cmd = [EXE_PATH, '-log_dir', log_dir]
wk.exe.run_program(cmd, check=False, cwd=EXE_PATH.parent)

View file

@ -1,9 +0,0 @@
"""WizardKit: Launch Stress Tests Tool"""
# vim: sts=2 sw=2 ts=2
import wk
wk.kit.tools.run_tool('CoreTemp', 'CoreTemp', popen=True)
wk.kit.tools.run_tool('FurMark2', 'FurMark_GUI', popen=True)
wk.kit.tools.run_tool('Prime95', 'prime95', popen=True)
wk.kit.tools.run_tool('RUST', 'RUST', popen=True)

View file

@ -1,9 +0,0 @@
"""WizardKit: Launch Stress Tests Tool"""
# vim: sts=2 sw=2 ts=2
import wk
wk.kit.tools.run_tool('CoreTemp', 'CoreTemp', popen=True)
wk.kit.tools.run_tool('FurMark', 'FurMark', popen=True)
wk.kit.tools.run_tool('Prime95', 'prime95', popen=True)
wk.kit.tools.run_tool('RUST', 'RUST', popen=True)

View file

@ -11,13 +11,9 @@
"wk/ui/__init__.py" = ["F401"]
# Long lines
"auto_setup.py" = ["E501"]
"wk/borrowed/acpi.py" = ["E501", "F841"]
"wk/cfg/ddrescue.py" = ["E501"]
"wk/cfg/hw.py" = ["E501"]
"wk/cfg/launchers.py" = ["E501"]
"wk/cfg/setup.py" = ["E501"]
"wk/cfg/sources.py" = ["E501"]
# Misc
"set_lp8550_slope.py" = ["E501", "E741"]
"wk/borrowed/acpi.py" = ["E501", "F841"]

View file

@ -1,103 +0,0 @@
#!/usr/bin/env python3
###
# Dependencies: Linux, python3, python3-smbus
# Usage: `sudo ./set_lp8550_slope.py`
#
# Default slope is 0b101=200ms, can be adjusted below
# Tested with Ubuntu 20.04 on A1466 2013
# All original EEPROM registers value:
# - A0: 0b01111111
# - A1: 0b10110101
# - A2: 0b10111111
# - A3: 0b01111011
# - A4: 0b00101000
# - A5: 0b11001111
# - A6: 0b01100100
# - A7: 0b00101101
###
import time
import smbus2
config = {
'smbus_address': 0, # SMBus address, can use `i2cdetect -l` to find it
'device_address': 0x2C, # LP8550 7 bits address
'slope': (1, 0, 1) # The new slope to set, MSB first, see lp8550 datasheet 8.6.2.3 SLOPE
}
lp8550_regs = {
'EEPROMControl': 0x72,
'EEPROMAddress1': 0xA1 # default value for this register is 0b10110101 on original 820-3437
}
def bitstolist(val, regsize=8):
return [(val >> i) & 0b1 for i in range(0, regsize)]
def listtobits(l, regsize=8):
val = 0
for i in range(0, regsize):
val += l[i] << i
return val
def read_reg(bus, dev_address, reg_name):
return bitstolist(bus.read_byte_data(dev_address, lp8550_regs[reg_name]))
def write_reg(bus, dev_address, reg_name, reg):
print(f'Writing { listtobits(reg)} to {dev_address}, {lp8550_regs[reg_name]}')
bus.write_byte_data(dev_address, lp8550_regs[reg_name], listtobits(reg))
def get_slope(bus, dev_address):
return read_reg(bus, dev_address, 'EEPROMAddress1')[0:3][::-1]
def set_slope(bus, dev_address, slope):
print('Original slope: ', get_slope(bus, dev_address))
EEPROMAddress1 = read_reg(bus, dev_address, 'EEPROMAddress1')
EEPROMAddress1[0:3] = slope[::-1]
write_reg(bus, dev_address, 'EEPROMAddress1', EEPROMAddress1)
print('New slope: ', get_slope(bus, dev_address))
def write_nvm(bus, dev_address):
print('Writing to non-volatile memory')
EEPROMControl = [0]*8
EEPROMControl[2] = 1 # EE_INIT=1
write_reg(bus, dev_address, 'EEPROMControl', EEPROMControl)
EEPROMControl[1] = 1 # EE_PROG=1
EEPROMControl[2] = 0 # EE_INIT=0
write_reg(bus, dev_address, 'EEPROMControl', EEPROMControl)
time.sleep(0.2) # wait 200ms
EEPROMControl[1] = 0 # EE_PROG=0
write_reg(bus, dev_address, 'EEPROMControl', EEPROMControl)
def load_nvm(bus, dev_address):
print('Loading from non-volatile memory')
EEPROMControl = [0]*8
EEPROMControl[2] = 1 # EE_INIT=1
write_reg(bus, dev_address, 'EEPROMControl', EEPROMControl)
EEPROMControl[0] = 1 # EE_READ=1
EEPROMControl[2] = 0 # EE_INIT=0
write_reg(bus, dev_address, 'EEPROMControl', EEPROMControl)
time.sleep(0.2) # wait 200ms
EEPROMControl[0] = 0 # EE_READ=0
write_reg(bus, dev_address, 'EEPROMControl', EEPROMControl)
def main():
bus = smbus2.SMBus(config['smbus_address'])
set_slope(bus, config['device_address'], config['slope'])
write_nvm(bus, config['device_address'])
load_nvm(bus, config['device_address'])
print('Slope in non-volatile memory: ', get_slope(bus,config['device_address']))
if __name__ == "__main__":
main()

View file

@ -25,53 +25,6 @@ if PLATFORM not in ('macOS', 'Linux'):
# Functions
def compress(reason):
dest = pathlib.Path(f'~/{reason}_{NOW.strftime("%Y-%m-%dT%H%M%S%z")}.txz')
dest = dest.expanduser().resolve()
cmd = ['tar', 'caf', dest.name, LOG_DIR.name]
wk.exe.run_program(cmd, check=False)
return dest
def compress_live_macos(reason):
"""Compress log_dir using the RAM disk."""
dest = pathlib.Path(
f'/Volumes/RAM_Disk/{reason}_{NOW.strftime("%Y-%m-%dT%H%M%S%z")}.txz',
)
tmp_file = dest.with_suffix('.tar')
cmd = ['tar', '-cf', tmp_file.name, LOG_DIR.name]
wk.exe.run_program(cmd, check=False)
cmd = ['xz', '-z', tmp_file.name]
wk.exe.run_program(cmd, check=False)
tmp_file = tmp_file.with_suffix('.tar.xz')
tmp_file.rename(dest.name)
return dest
def compress_and_upload(reason='Testing'):
"""Upload compressed log_dir to the crash server."""
server = wk.cfg.net.CRASH_SERVER
upload_args = {
'headers': server['Headers'],
'auth': (server['User'], server['Pass']),
}
# Compress LOG_DIR (relative to parent dir)
if os.path.exists('/.wk-live-macos'):
dest = compress_live_macos(reason)
else:
dest = compress(reason)
upload_args['data'] = dest.read_bytes()
# Upload compressed data
url = f'{server["Url"]}/{dest.name}'
result = requests.put(url, **upload_args)
# Check result
if not result.ok:
raise wk.std.GenericError('Failed to upload logs')
def main() -> None:
"""Upload logs for review."""
lines = []
@ -89,7 +42,7 @@ def main() -> None:
# Get reason note
while True:
text = wk.ui.cli.input_text('> ', allow_empty=True)
text = wk.ui.cli.input_text('> ')
if not text:
lines.append('')
break
@ -98,11 +51,9 @@ def main() -> None:
_f.write('\n'.join(lines))
# Compress and upload logs
os.chdir(LOG_DIR.parent)
print(os.getcwd())
result = try_and_print.run(
message='Uploading logs...',
function=compress_and_upload,
function=upload_log_dir,
reason='Review',
)
if not result['Failed']:

View file

@ -1,13 +1,13 @@
"""WizardKit: wk module init"""
# vim: sts=2 sw=2 ts=2
import platform
from sys import stderr, version_info
from . import cfg
from . import clone
from . import debug
from . import exe
from . import graph
from . import hw
from . import io
from . import kit
@ -19,10 +19,6 @@ from . import setup
from . import std
from . import ui
if platform.system() != 'Windows':
from wk import graph
from wk import osticket
# Check env
if version_info < (3, 10):

View file

@ -1,36 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIGTzCCBDegAwIBAgIBfDANBgkqhkiG9w0BAQsFADCBsDELMAkGA1UEBhMCVVMx
DzANBgNVBAgTBk9yZWdvbjERMA8GA1UEBxMIUG9ydGxhbmQxHTAbBgNVBAoTFDEy
MDEgQ29tcHV0ZXIgUmVwYWlyMSMwIQYDVQQLExoxMjAxIENlcnRpZmljYXRlIEF1
dGhvcml0eTEVMBMGA1UEAxMMMTIwMSBSb290IENBMSIwIAYJKoZIhvcNAQkBFhNt
YW5hZ2VtZW50QDEyMDEuY29tMB4XDTE4MDgyMDA2MDEwMFoXDTM4MDgyMDA2MDEw
MFowgbAxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZPcmVnb24xETAPBgNVBAcTCFBv
cnRsYW5kMR0wGwYDVQQKExQxMjAxIENvbXB1dGVyIFJlcGFpcjEjMCEGA1UECxMa
MTIwMSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFTATBgNVBAMTDDEyMDEgUm9vdCBD
QTEiMCAGCSqGSIb3DQEJARYTbWFuYWdlbWVudEAxMjAxLmNvbTCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBANGYohJk5/CC/p14R7EpnhdEUF7Wvlnt8yuF
dtuyStlIGkLxPMlj9hQfoLDplQqlKBefTaI3WwrI/Hndso+jStLKgtRWRdyNB34K
AWqT04zXYGicdi3fqaMhEC4SPyX1tRXU2e9kjtIJ21AZx2F40NUjfOMKLVymZgXm
gkG1oA/BSzE8vIidrd/lJPwo0u+EYFa87y+9SHS93Ze1AVoTVqUzSMkjqt+6YIzJ
4XBD7UBvps0Mnd18HMUlXHFXusUL1K921W3wDVcMlNIIA8MJjQk+aVS/1EGSn+81
C+r40x64lYkyh0ZUAHkVXUC/BUfa0SKx1Nfa4mSvtyPnUCb7Dir8MkTDKgopGCok
KmW+VvE2H8AEPCbcctFmhdip19laYxzyDhZ5wiQN6AOg64cWvDf6/uT9hyPvxkj1
ps5vWElryzawTE7h1BI8liMqwsG1Y7cc6D0PABxPsp4iR8pde0oZtpLnEvejRodo
zz3BGvZjq+pHtRMjL+yiDtdAL+K+7/e7gNCQBIGsphahWIOf7TczWVgMNclTNxl3
WZjKkOEs7j+prRTDvffV6H32+Tk5TwgMsfvnY4a37CkJ0L0d1JhWj9wO+gESfg3W
8yvt3hfcj3NOUMJWhJstqlIeX8dj7vVcMhjNvYJxabJmJgk+DNlHe55fXDGJ1CLO
E0EbRTyBAgMBAAGjcjBwMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFM+hXjFx
6BldZFBQW1Pn/Yp3vbw+MAsGA1UdDwQEAwIBBjARBglghkgBhvhCAQEEBAMCAAcw
HgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOC
AgEALWcnu3auMSnSSF/kOiLvJ4RAnHZebGYNcUWM14u1K1/XtTB7AFzQIHX7BcDH
m/z4UEyhl9EdR5Bgf2Szuk+8+LyGqcdAdbPoK+bmcwwL8lufDnlIYBThKIBfU2Xw
vw41972B+HH5r1TZXve1EdJaLyImbxmq5s41oH7djGC+sowtyGuVqP7RBguXBGiJ
At1yfdPWVaxLmE8QFknkIvpgTmELpxasTfvgnQBenA3Ts0Z2hwN4796hLbRzGsb8
4hKWAfQDP0klzXKRRyVeAueXxj/FcNZilYxv15MqMc4qrUiW0hXHluQM1yceNjNZ
SE4Igi1Ap71L4PpgkHIDfZD908UexGGkql+p4EWrpnXUYWTa0sHg1bFKQntgpyFg
86Ug0Q7ZNhImENzeigZknL0ceIdaNUCs7UPrkqpUSJR2yujp1JC3tX1LgKZw8B3J
fQx/8h3zzNuz5dVtr1wUJaUD0nGhMIRBEXb2t4jupEISSTN1pkHPcbNzhAQXjVUA
CJxnnz3jmyGsNCoQf7NWfaN6RSRTWehsC6m7JvPvoU2EZoQkSlNOv4xZuFpEx0u7
MFDtC1cSGPH7YwYXPVc45xAMC6Ni8mvq93oT89XZNHIqE8/T8aPHLwYFgu1b1r/A
L8oMEnG5s8tG3n0DcFoOYsaIzVeP0r7B7e3zKui6DQLuu9E=
-----END CERTIFICATE-----

View file

@ -5,6 +5,7 @@ from . import hw
from . import launchers
from . import log
from . import main
from . import music
from . import net
from . import repairs
from . import setup

View file

@ -6,7 +6,6 @@
TMUX_SIDE_WIDTH = 21
TMUX_LAYOUT = {
'Source': {'height': 2, 'Check': True},
'Ticket': {'height': 2, 'Check': True},
'Started': {'width': TMUX_SIDE_WIDTH, 'Check': True},
'Progress': {'width': TMUX_SIDE_WIDTH, 'Check': True},
}

View file

@ -14,14 +14,11 @@ ATTRIBUTE_COLORS = (
# NOTE: Force 4K read block size for disks >= 3TB
BADBLOCKS_EXTRA_LARGE_DISK = 15 * 1024**4
BADBLOCKS_LARGE_DISK = 3 * 1024**4
BADBLOCKS_MAX_ERRORS = 1
BADBLOCKS_REGEX = re.compile(
r'^Pass completed, (\d+) bad blocks found. .(\d+)/(\d+)/(\d+) errors',
re.IGNORECASE,
)
BADBLOCKS_RESULTS_REGEX = re.compile(
r'^(Checking for bad blocks .read-only test.: ).*\x08+(done|\s+).*?(\x08+)?'
)
BADBLOCKS_RESULTS_REGEX = re.compile(r'^(.*?)\x08.*\x08(.*)')
BADBLOCKS_SKIP_REGEX = re.compile(r'^(Checking|\[)', re.IGNORECASE)
CPU_TEMPS = {
'Cooling Delta': 25,
@ -39,7 +36,6 @@ IO_MINIMUM_TEST_SIZE = 10 * 1024**3
IO_RATE_REGEX = re.compile(
r'(?P<bytes>\d+) bytes.* (?P<seconds>\S+) s(?:,|ecs )',
)
IO_SMALL_DISK = 450 * 1000**3
KEY_NVME = 'nvme_smart_health_information_log'
KEY_SMART = 'ata_smart_attributes'
KNOWN_DISK_ATTRIBUTES = {
@ -86,18 +82,9 @@ NVME_WARNING_KEYS = (
'reliability_degraded',
'volatile_memory_backup_failed',
)
REGEX_BLOCK_GRAPH = re.compile(r'(▁|▂|▃|▄|▅|▆|▇|█)')
REGEX_POWER_ON_TIME = re.compile(
r'^(\d+)([Hh].*|\s+\(\d+\s+\d+\s+\d+\).*)'
)
REGEX_SMART_ATTRIBUTES = re.compile(
r'^\s*(?P<decimal>\d+) / (?P<hex>\w\w): (?P<data>.*)$',
)
REGEX_VOLUME = re.compile(
r'^(?P<dev>.*?) '
r'(?P<result>(APFS|CoreStorage) container|Failed to mount|Mounted on|\S+$)'
r'($| (?P<path>.*) \((?P<details>.*)\))'
)
SMART_SELF_TEST_START_TIMEOUT_IN_SECONDS = 120
SMC_IDS = {
# Sources: https://github.com/beltex/SMCKit/blob/master/SMCKit/SMC.swift
@ -147,16 +134,15 @@ SMC_IDS = {
'TS0C': {'CPU Temp': False, 'Source': 'CPU B DIMM Exit Ambient'},
}
STATUS_COLORS = {
'Done': 'CYAN',
'Passed': 'GREEN',
'Aborted': 'YELLOW',
'N/A': 'YELLOW',
'Skipped': 'YELLOW',
'Unknown': 'YELLOW',
'Working': 'YELLOW',
'Denied': 'RED',
'ERROR': 'RED',
'Failed': 'RED',
'Passed': 'GREEN',
'Aborted': 'YELLOW',
'N/A': 'YELLOW',
'Skipped': 'YELLOW',
'Unknown': 'YELLOW',
'Working': 'YELLOW',
'Denied': 'RED',
'ERROR': 'RED',
'Failed': 'RED',
'TimedOut': 'RED',
}
TEMP_COLORS = {
@ -179,9 +165,8 @@ THRESH_SSD_MIN = 90 * 1024**2
THRESH_SSD_AVG_HIGH = 135 * 1024**2
THRESH_SSD_AVG_LOW = 100 * 1024**2
# VOLUME THRESHOLDS in percent
VOLUME_WARNING_THRESHOLD = 70
VOLUME_FAILURE_THRESHOLD = 85
VOLUME_SIZE_THRESHOLD = 30 # In GB
VOLUME_WARNING_THRESHOLD = 70
VOLUME_FAILURE_THRESHOLD = 85
if __name__ == '__main__':

View file

@ -133,15 +133,6 @@ LAUNCHERS = {
'L_ARGS': '-nodb',
'L_ELEV': 'True',
},
"Fab's Autobackup Pro": {
'L_TYPE': 'Executable',
'L_PATH': 'AutoBackupPro',
'L_ITEM': 'autobackup6pro.exe',
'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd"',
r'reg add HKCU\Software\1201-WizardKit /v FabLastRun /t REG_SZ /d %iso_date% /f >nul 2>&1',
],
},
'FastCopy (as ADMIN)': {
'L_TYPE': 'Executable',
'L_PATH': 'FastCopy',
@ -242,76 +233,40 @@ LAUNCHERS = {
'L_PATH': 'FurMark',
'L_ITEM': 'FurMark.exe',
},
'HWiNFO': {
'L_TYPE': 'Executable',
'L_PATH': 'HWiNFO',
'L_ITEM': 'HWiNFO.exe',
'Extra Code': [
r'for %%a in (32 64) do (',
r' copy /y "%bin%\HWiNFO\general.ini" "%bin%\HWiNFO\HWiNFO%%a.ini"',
r' (echo SensorsOnly=0)>>"%bin%\HWiNFO\HWiNFO%%a.ini"',
r' (echo SummaryOnly=0)>>"%bin%\HWiNFO\HWiNFO%%a.ini"',
r')',
],
},
'HWiNFO (Sensors)': {
'L_TYPE': 'Executable',
'L_PATH': 'HWiNFO',
'L_ITEM': 'HWiNFO.exe',
'Extra Code': [
r'for %%a in (32 64) do (',
r' copy /y "%bin%\HWiNFO\general.ini" "%bin%\HWiNFO\HWiNFO%%a.ini"',
r' (echo SensorsOnly=1)>>"%bin%\HWiNFO\HWiNFO%%a.ini"',
r' (echo SummaryOnly=0)>>"%bin%\HWiNFO\HWiNFO%%a.ini"',
r')',
],
},
'Notepad++': {
'L_TYPE': 'Executable',
'L_PATH': 'notepadplusplus',
'L_ITEM': 'notepadplusplus.exe',
},
'Prime95': {
'L_TYPE': 'Executable',
'L_PATH': 'Prime95',
'L_ITEM': 'prime95.exe',
},
'PuTTY': {
'L_TYPE': 'Executable',
'L_PATH': 'PuTTY',
'L_ITEM': 'PUTTY.EXE',
},
'ShutUp10': {
'L_TYPE': 'Executable',
'L_PATH': 'ShutUp10',
'L_ITEM': 'OOSU10.exe',
},
'ShutUp10 (1201 Minimal Selection)': {
'L_TYPE': 'Executable',
'L_PATH': 'ShutUp10',
'L_ITEM': 'OOSU10.exe',
'L_ARGS': '1201.cfg',
},
'Stress Tests (Classic)': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'launch_stress_tests_classic.py',
'L_ELEV': 'True',
},
'Stress Tests': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'launch_stress_tests.py',
'L_ELEV': 'True',
},
'Windows Repair AIO': {
'L_TYPE': 'Executable',
'L_PATH': 'WinRepairAIO',
'L_ITEM': 'Repair_Windows.exe',
'L_ELEV': 'True',
'Extra Code': [
r'copy /y "%bin%\WinRepairAIO\__empty.ini" "%bin%\WinRepairAIO\settings.ini"',
],
},
'Windows Repair AIO (Fix Associations)': {
'L_TYPE': 'Executable',
'L_PATH': 'WinRepairAIO',
'L_ITEM': 'Repair_Windows.exe',
'L_ELEV': 'True',
'Extra Code': [
r'copy /y "%bin%\WinRepairAIO\__associations.ini" "%bin%\WinRepairAIO\settings.ini"',
],
},
'Windows Repair AIO (Fix Permissions)': {
'L_TYPE': 'Executable',
'L_PATH': 'WinRepairAIO',
'L_ITEM': 'Repair_Windows.exe',
'L_ELEV': 'True',
'Extra Code': [
r'copy /y "%bin%\WinRepairAIO\__permissions.ini" "%bin%\WinRepairAIO\settings.ini"',
],
},
'WinSCP': {
'L_TYPE': 'Executable',
'L_PATH': 'WinSCP',
'L_ITEM': 'WinSCP.exe',
},
'WizTree': {
'L_TYPE': 'Executable',
'L_PATH': 'WizTree',
@ -322,7 +277,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'XMPlay',
'L_ITEM': 'xmplay.exe',
'L_ARGS': r'"%bin%\XMPlay\music.m3u"',
'L_ARGS': r'"%bin%\XMPlay\music.7z"',
},
},
}

View file

@ -8,21 +8,21 @@ NOTE: Non-standard formating is used for BASH/BATCH/PYTHON compatibility
# Features
ENABLED_OPEN_LOGS=False
ENABLED_TICKET_NUMBERS=False
ENABLED_UPLOAD_DATA=True
ENABLED_UPLOAD_DATA=False
# Main Kit
ARCHIVE_PASSWORD='Sorted1201'
KIT_NAME_FULL='1201-WizardKit'
KIT_NAME_SHORT='1201'
SUPPORT_MESSAGE='Please let support know by opening an issue on Gitea'
ARCHIVE_PASSWORD='Abracadabra'
KIT_NAME_FULL='WizardKit'
KIT_NAME_SHORT='WK'
SUPPORT_MESSAGE='Please let 2Shirt know by opening an issue on Gitea'
# Text Formatting
INDENT=4
WIDTH=32
# Live Linux
ROOT_PASSWORD='1201 loves computers!'
TECH_PASSWORD='Sorted1201'
ROOT_PASSWORD='Abracadabra'
TECH_PASSWORD='Abracadabra'
# Time Zones
## See 'timedatectl list-timezones' for valid Linux values

76
scripts/wk/cfg/music.py Normal file
View file

@ -0,0 +1,76 @@
"""WizardKit: Config - Music Sources"""
# vim: sts=2 sw=2 ts=2
MUSIC_MOD = (
('33432', 'ambrozia.xm'),
('33460', 'amigatre.mod'),
('34594', 'CHARIOT.S3M'),
('34596', 'BUTTERFL.XM'),
('34654', 'CTGOBLIN.S3M'),
('35151', 'bananasplit.mod'),
('35280', 'DEADLOCK.XM'),
('38591', 'compo_liam.xm'),
('39987', 'crystald.s3m'),
('40475', 'ELYSIUM.MOD'),
('42146', 'enigma.mod'),
('42519', 'GHOST2.MOD'),
('42560', 'GSLINGER.MOD'),
('42872', 'existing.xm'),
('50427', 'nf-stven.xm'),
('51549', 'overture.mod'),
('54250', 'SATELL.S3M'),
('54313', 'realmk.s3m'),
('55789', 'scrambld.mod'),
('57934', 'spacedeb.mod'),
('59344', 'stardstm.mod'),
('60395', '2ND_PM.S3M'),
('66187', 'external.xm'),
('66343', 'beek-substitutionology.it'),
('67561', 'radix-unreal_superhero.xm'),
('70829', 'inside_out.s3m'),
('83779', 'beyond_music.mod'),
('104208', 'banana_boat.mod'),
('114971', 'tilbury_fair.mod'),
('132563', 'ufo_tune.mod'),
('135906', 'magnetik_girl.xm'),
('140628', 'autumn_in_budapest.xm'),
('143198', 'summer_memories_3.xm'),
('144405', 'hillbilly_billyboy.xm'),
('154795', '4mat_-_eternity.xm'),
('155845', 'bookworm.mo3'),
('155914', 'battleofsteel.xm'),
('158975', '1_channel_moog.it'),
('165495', 'trans.s3m'),
('168513', 'necros_-_introspection.s3m'),
('169628', 'radix_-_feng_shui_schematics.xm'),
('175238', 'unknown48_-_twilight.mod'),
)
MUSIC_SNES = (
'actr',
'crock',
'ct',
'dkc',
'dkq',
'ff6',
'fz',
'loz3',
'mmx',
'ptws',
'scv4',
'sf',
'sf2',
'sgng',
'smk',
'smw',
'yi',
'zamn',
)
MUSIC_SNES_BAD = {
'ct': ['ct-s*', 'ct-v*'],
}
if __name__ == '__main__':
print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -4,41 +4,38 @@
# Servers
BACKUP_SERVERS = {
'Anaconda': {
'Address': 'anaconda.1201.com',
'Share': 'Backups',
'RO-User': 'cx',
'RO-Pass': 'cx',
'RW-User': 'backup',
'RW-Pass': '1201 loves computers!',
},
#'Server One': {
# 'Address': '10.0.0.10',
# 'Share': 'Backups',
# 'RO-User': 'restore',
# 'RO-Pass': 'Abracadabra',
# 'RW-User': 'backup',
# 'RW-Pass': 'Abracadabra',
# },
#'Server Two': {
# 'Address': 'servertwo.example.com',
# 'Share': 'Backups',
# 'RO-User': 'restore',
# 'RO-Pass': 'Abracadabra',
# 'RW-User': 'backup',
# 'RW-Pass': 'Abracadabra',
# },
}
BENCHMARK_SERVER = {
'Name': 'Nextcloud',
'Short Url': 'https://1201.ddns.net/nextcloud/f/14775',
'Url': 'https://1201.ddns.net/nextcloud/public.php/webdav/Benchmarks',
'User': 'LHdPTofNBQNGBbX',
'Pass': '',
}
CRASH_SERVER = {
'Name': 'Nextcloud',
'Url': 'https://1201.ddns.net/nextcloud/public.php/webdav/Issues',
'User': 'fnyzeK64rKxcEL4',
'Pass': '',
'Headers': {'X-Requested-With': 'XMLHttpRequest'},
#'Name': 'CrashServer',
#'Url': '',
#'User': '',
#'Pass': '',
#'Headers': {'X-Requested-With': 'XMLHttpRequest'},
}
SDIO_SERVER = {
'Address': 'anaconda.1201.com',
'Share': 'Snappy',
'Path': '\\',
'RO-User': 'cx',
'RO-Pass': 'cx',
'Address': '',
'Share': '',
'Path': '',
'RO-User': '',
'RO-Pass': '',
}
# Misc
IMGUR_CLIENT_ID='3d1ee1d38707b85'
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -1,17 +0,0 @@
"""Wizard Kit: Config - osTicket"""
# vim: sts=2 sw=2 ts=2
SQL = {
'DB': 'osticket',
'Host': 'osticket.1201.com',
'Port': 3306,
'User': 'wizardkit',
'Pass': 'U9bJnF9eamVkfsVw',
}
STAFF = {
'ID': '23',
'Name': 'Wizard Kit',
}
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -1,7 +1,7 @@
"""WizardKit: Config - Repairs"""
# vim: sts=2 sw=2 ts=2
from wk.cfg.main import KIT_NAME_FULL, KIT_NAME_SHORT
from wk.cfg.main import KIT_NAME_FULL
AUTO_REPAIR_DELAY_IN_SECONDS = 3
AUTO_REPAIR_KEY = fr'Software\{KIT_NAME_FULL}\Auto Repairs'
@ -59,7 +59,7 @@ BLEACH_BIT_CLEANERS = (
'windows_explorer.run',
'windows_explorer.thumbnails',
)
CUSTOM_POWER_PLAN_NAME = f'{KIT_NAME_SHORT} Power Plan'
CUSTOM_POWER_PLAN_NAME = f'{KIT_NAME_FULL} Power Plan'
CUSTOM_POWER_PLAN_DESC = 'Customized for the best experience.'
POWER_PLANS = {
'Balanced': '381b4222-f694-41f0-9685-ff5bb260df2e',

View file

@ -9,9 +9,10 @@ BROWSER_PATHS = {
'Microsoft Edge': 'Microsoft/Edge/Application/msedge.exe',
'Opera': 'Opera/launcher.exe',
}
FAB_TIMEFRAME = 14 # If it's been at least this many days don't prompt for an AV scan
DISABLED_ENTRIES_WINDOWS_11 = {
# Group Name: Option Name
'Install Software': 'Open Shell',
'Configure System': 'Open Shell',
}
LIBREOFFICE_XCU_DATA = '''<?xml version="1.0" encoding="UTF-8"?>
<oor:items xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
@ -28,20 +29,13 @@ REG_CHROME_UBLOCK_ORIGIN = {
)
},
}
REG_ESET_NOD32_SETTINGS = {
'HKCU': {
r'Software\ESET\ESET Security\CurrentVersion\gui\UI_CONFIG': (
('FullScreenMode', 0, 'DWORD'),
('ShowAlertStatus', 0x40000000, 'DWORD'),
('ShowDesktopAlert', 0, 'DWORD'),
('ShowSplash', 0, 'DWORD'),
),
},
REG_WINDOWS_BSOD_MINIDUMPS = {
'HKLM': {
r'Software\ESET\ESET Security\CurrentVersion\Config\Settings\LiveGrid': (
('LiveGridFeedbackEnabled', 1, 'DWORD'),
),
},
# Enable small memory dumps
r'SYSTEM\CurrentControlSet\Control\CrashControl': (
('CrashDumpEnabled', 3, 'DWORD'),
)
}
}
REG_WINDOWS_EXPLORER = {
'HKLM': {
@ -64,12 +58,6 @@ REG_WINDOWS_EXPLORER = {
r'Software\Policies\Microsoft\Windows\DataCollection': (
('AllowTelemetry', 0, 'DWORD'),
),
# Disable Timeline
r'Software\Policies\Microsoft\Windows\System': (
('EnableActivityFeed', 0, 'DWORD'),
('PublishUserActivities', 0, 'DWORD'),
('UploadUserActivities', 0, 'DWORD'),
),
# Disable floating Bing search widget
r'Software\Policies\Microsoft\Edge': (
('WebWidgetAllowed', 0, 'DWORD'),
@ -105,7 +93,6 @@ REG_WINDOWS_EXPLORER = {
),
# Disable search highlights
r'Software\Microsoft\Windows\CurrentVersion\Feeds\DSB': (
('OpenOnHover', 0, 'DWORD'),
('ShowDynamicContent', 0, 'DWORD'),
),
# File Explorer
@ -120,7 +107,7 @@ REG_WINDOWS_EXPLORER = {
),
# Hide Search button / box
r'Software\Microsoft\Windows\CurrentVersion\Search': (
('SearchboxTaskbarMode', 2, 'DWORD'),
('SearchboxTaskbarMode', 1, 'DWORD'),
),
# Disable search highlights from opening on hover
r'Software\Microsoft\Windows\CurrentVersion\SearchSettings': (

View file

@ -9,45 +9,46 @@ DOWNLOAD_FREQUENCY = 7
# Sources
SOURCES = {
# Main
'AVRemover32': 'https://download.eset.com/com/eset/tools/installers/av_remover/latest/avremover_nt32_enu.exe',
'AVRemover64': 'https://download.eset.com/com/eset/tools/installers/av_remover/latest/avremover_nt64_enu.exe',
'AdwCleaner': 'https://adwcleaner.malwarebytes.com/adwcleaner?channel=release',
'AdwCleaner': 'https://downloads.malwarebytes.com/file/adwcleaner',
'Autologon32': 'http://live.sysinternals.com/Autologon.exe',
'Autologon64': 'http://live.sysinternals.com/Autologon64.exe',
'CoreTemp64': 'https://www.alcpu.com/CoreTemp/CoreTemp64.zip',
'ESET_NOD32_AV64': 'https://download.eset.com/com/eset/apps/home/eav/windows/latest/eav_nt64.exe',
'EmsisoftCmd64': 'https://dl.emsisoft.com/EmsisoftCommandlineScanner64.exe',
'Firefox32': 'https://download.mozilla.org/?product=firefox-latest-ssl&os=win&lang=en-US',
'Firefox64': 'https://download.mozilla.org/?product=firefox-latest-ssl&os=win64&lang=en-US',
'HitmanPro32': 'https://dl.surfright.nl/HitmanPro.exe',
'HitmanPro64': 'https://dl.surfright.nl/HitmanPro_x64.exe',
'KVRT': 'https://devbuilds.s.kaspersky-labs.com/devbuilds/KVRT/latest/full/KVRT.exe',
'MBAM': 'https://downloads.malwarebytes.com/file/mb-windows',
'RKill': 'https://download.bleepingcomputer.com/grinler/rkill.exe',
'RegDelNull': 'https://live.sysinternals.com/RegDelNull.exe',
'RegDelNull64': 'https://live.sysinternals.com/RegDelNull64.exe',
# Build Kit
'AIDA64': 'https://download.aida64.com/aida64engineer720.zip',
'Adobe Reader DC': 'https://ardownload2.adobe.com/pub/adobe/acrobat/win/AcrobatDC/2400120615/AcroRdrDCx642400120615_en_US.exe',
'AIDA64': 'https://download.aida64.com/aida64engineer692.zip',
'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',
'Autoruns32': 'http://live.sysinternals.com/Autoruns.exe',
'Autoruns64': 'http://live.sysinternals.com/Autoruns64.exe',
'BleachBit': 'https://download.bleachbit.org/BleachBit-4.4.2-portable.zip',
'BCUninstaller': 'https://github.com/Klocman/Bulk-Crap-Uninstaller/releases/download/v5.7/BCUninstaller_5.7_portable.zip',
'BlueScreenView32': 'http://www.nirsoft.net/utils/bluescreenview.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',
'Everything32': 'https://www.voidtools.com/Everything-1.4.1.1024.x86.zip',
'Everything64': 'https://www.voidtools.com/Everything-1.4.1.1024.x64.zip',
'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',
'FurMark': 'https://geeks3d.com/dl/get/728',
'LibreOffice64': 'https://download.documentfoundation.org/libreoffice/stable/7.6.6/win/x86_64/LibreOffice_7.6.6_Win_x86-64.msi',
'Linux Reader': 'https://www.diskinternals.com/download/Linux_Reader.exe',
'HWiNFO': 'https://www.sac.sk/download/utildiag/hwi_764.zip',
'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.6.2/win/x86_64/LibreOffice_7.6.2_Win_x86-64.msi',
'Macs Fan Control': 'https://www.crystalidea.com/downloads/macsfancontrol_setup.exe',
'Neutron': 'http://keir.net/download/neutron.zip',
'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.191/OpenShellSetup_4_4_191.exe',
'Prime95': 'https://www.mersenne.org/download/software/v30/30.19/p95v3019b13.win64.zip',
'PuTTY': 'https://the.earth.li/~sgtatham/putty/latest/w32/putty.zip',
'SDIO Torrent': 'https://www.glenn.delahoy.com/downloads/sdio/SDIO_Update.torrent',
'ShutUp10': 'https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe',
'Windows Repair AIO': 'http://www.tweaking.com/files/setups/tweaking.com_windows_repair_aio.zip',
'WinSCP': 'https://sourceforge.net/projects/winscp/files/WinSCP/5.19.5/WinSCP-5.19.5-Portable.zip/download',
'WizTree': 'https://diskanalyzer.com/files/wiztree_4_15_portable.zip',
'XMPlay': 'https://support.xmplay.com/files/20/xmplay385.zip?v=47090',
'XMPlay 7z': 'https://support.xmplay.com/files/16/xmp-7z.zip?v=800962',

View file

@ -8,7 +8,6 @@ from wk.cfg.main import KIT_NAME_FULL
SOURCES = {
'Linux': {'Arg': '--linux', 'Type': 'ISO'},
'WinPE': {'Arg': '--winpe', 'Type': 'ISO'},
'ESET SysRescue': {'Arg': '--eset', 'Type': 'IMG'},
'Main Kit': {'Arg': '--main-kit', 'Type': 'KIT'},
'Extra Dir': {'Arg': '--extra-dir', 'Type': 'DIR'},
}
@ -16,44 +15,23 @@ SOURCES = {
# Definitions: Boot entries
BOOT_ENTRIES = {
# Path to check: Comment to remove
'/arch_minimal': 'UFD-MINIMAL',
'/casper': 'UFD-ESET',
'/sources/boot.wim': 'UFD-WINPE',
}
BOOT_FILES = {
# Directory: extension
'/syslinux': 'cfg',
'/boot/grub': 'cfg',
'/EFI/boot': 'conf',
}
IMAGE_BOOT_ENTRIES = {
'El Capitan': 'UFD-MACOS-10.11',
'High Sierra': 'UFD-MACOS-10.13',
'Catalina': 'UFD-MACOS-10.15',
'3S132': 'UFD-ASD-3S132',
'3S138': 'UFD-ASD-3S138',
'3S140': 'UFD-ASD-3S140',
'3S142A': 'UFD-ASD-3S142A',
'3S144': 'UFD-ASD-3S144',
'3S145A': 'UFD-ASD-3S145A',
'3S146': 'UFD-ASD-3S146',
'3S147': 'UFD-ASD-3S147',
'3S148': 'UFD-ASD-3S148',
'3S150': 'UFD-ASD-3S150',
'3S155': 'UFD-ASD-3S155',
'3S156': 'UFD-ASD-3S156',
'3S162': 'UFD-ASD-3S162',
}
# Definitions: Sources and Destinations
## NOTES: Paths are relative to the root of the ISO/UFD
## Sources use rsync's trailing slash syntax
ITEMS = {
'ESET SysRescue': (
('/boot/grub/', '/boot/grub/'),
('/casper/', '/casper/'),
('/EFI/boot/', '/EFI/ESET/'),
),
'Extra Dir': (
('/', '/'),
),
@ -64,14 +42,15 @@ ITEMS = {
('/', f'/{KIT_NAME_FULL}/'),
),
'WinPE': (
('/BOOTMGR', '/'),
('/bootmgr', '/'),
('/bootmgr.efi', '/'),
('/boot/', '/boot/'),
('/efi/boot/', '/EFI/Microsoft/'),
('/efi/microsoft/', '/EFI/Microsoft/'),
('/boot/bcd', '/sources/'),
('/boot/boot.sdi', '/sources/'),
('/BOOTMGR', '/sources/'),
('/en_us', '/'),
('/Boot/', '/boot/'),
('/EFI/Boot/', '/EFI/Microsoft/'),
('/EFI/Microsoft/', '/EFI/Microsoft/'),
('/Boot/BCD', '/sources/'),
('/Boot/boot.sdi', '/sources/'),
('/bootmgr', '/sources/'),
('/sources/boot.wim', '/sources/'),
),
}
@ -95,8 +74,6 @@ ITEMS_FROM_LIVE = {
),
}
ITEMS_HIDDEN = (
# ESET
'casper',
# Linux (all versions)
'arch',
'EFI',
@ -106,8 +83,9 @@ ITEMS_HIDDEN = (
f'{KIT_NAME_FULL}/.cbin',
# WinPE
'boot',
'BOOTMGR',
'bootmgr',
'bootmgr.efi',
'en-us',
'images',
'sources',
)

View file

@ -39,4 +39,5 @@ WINDOWS_BUILDS = {
'10.0.22000': '21H2',
'10.0.22621': '22H2',
'10.0.22631': '23H2',
'10.0.26100': '24H2',
}

View file

@ -41,7 +41,6 @@ class BlockPair():
self.map_data: dict[str, bool | int] = {}
self.map_path: pathlib.Path = pathlib.Path()
self.size: int = source_dev.size
self.stats = {}
self.status: dict[str, float | int | str] = {
'read-skip': 'Pending',
'read-full': 'Pending',
@ -191,25 +190,6 @@ class BlockPair():
# This should never be reached
return False
def reset_progress(self):
"""Reset progress to start fresh recovery."""
self.map_data = {}
self.status = {
'read': 'Pending',
'trim': 'Pending',
'scrape': 'Pending',
}
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 safety_check(self) -> None:
"""Run safety check and abort if necessary."""
# TODO: Expand section to support non-Linux systems
@ -269,7 +249,6 @@ def add_clone_block_pairs(state) -> list[hw_disk.Disk]:
source_sep = get_partition_separator(state.source.path.name)
dest_sep = get_partition_separator(state.destination.path.name)
settings = {}
source_parts = []
# Clone settings
settings = state.load_settings(discard_unused_settings=True)

View file

@ -1,6 +1,7 @@
"""WizardKit: ddrescue TUI"""
# vim: sts=2 sw=2 ts=2
import argparse
import atexit
import datetime
import logging
@ -8,13 +9,10 @@ import os
import pathlib
import subprocess
from typing import Any
from random import randint
import pytz
from docopt import docopt
from wk import cfg, exe, io, log, std
from wk.cfg.ddrescue import DDRESCUE_SPECIFIC_PASS_SETTINGS
from wk.clone import menus
@ -25,24 +23,11 @@ from wk.hw.smart import (
smart_status_ok,
update_smart_details,
)
from wk.ui import ansi, cli, tmux
from wk.ui import ansi, cli
# STATIC VARIABLES
LOG = logging.getLogger(__name__)
DOCSTRING = f'''{cfg.main.KIT_NAME_FULL}: ddrescue TUI
Usage:
ddrescue-tui
ddrescue-tui [options] (clone|image) [<source> [<destination>]]
ddrescue-tui (-h | --help)
Options:
-h --help Show this page
-s --dry-run Print commands to be used instead of running them
--force-local-map Skip mounting shares and save map to local drive
--start-fresh Ignore previous runs and start new recovery
'''
DETECT_DRIVES_NOTICE = '''
This option will force the drive controllers to rescan for devices.
The method used is not 100% reliable and may cause issues. If you see
@ -56,6 +41,42 @@ TIMEZONE = pytz.timezone(cfg.main.LINUX_TIME_ZONE)
# Functions
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='ddrescue-tui',
description=f'{cfg.main.KIT_NAME_FULL}: ddrescue TUI',
)
parser.add_argument('mode', choices=('clone', 'image'), nargs='?')
parser.add_argument('source', nargs='?')
parser.add_argument('destination', nargs='?')
parser.add_argument(
'-s', '--dry-run', action='store_true',
help='Print commands to be used instead of running them',
)
parser.add_argument(
'--force-local-map', action='store_true',
help='Skip mounting shares and save map to local drive',
)
parser.add_argument(
'--start-fresh', action='store_true',
help='Ignore previous runs and start new recovery',
)
args = parser.parse_args()
legacy_args = {
'clone': args.mode == 'clone',
'image': args.mode == 'image',
'<source>': args.source,
'<destination>': args.destination,
'--dry-run': args.dry_run,
'--force-local-map': args.force_local_map,
'--start-fresh': args.start_fresh,
}
return legacy_args
def build_ddrescue_cmd(block_pair, pass_name, settings_menu) -> list[str]:
"""Build ddrescue cmd using passed details, returns list."""
cmd = ['sudo', 'ddrescue']
@ -130,25 +151,13 @@ def check_destination_health(destination) -> str:
result = 'Critical error(s) detected for: {destination.path}'
# Check for minor errors
if not check_attributes(destination, only_blocking=True):
if not check_attributes(destination, only_blocking=False):
result = f'Attribute error(s) detected for: {destination.path}'
# Done
return result
def detect_drives(state):
"""Detect connected drives and check source/dest selections."""
cli.clear_screen()
cli.print_warning(DETECT_DRIVES_NOTICE)
if cli.ask('Are you sure you proceed?'):
cli.print_standard('Forcing controllers to rescan for devices...')
cmd = 'echo "- - -" | sudo tee /sys/class/scsi_host/host*/scan'
exe.run_program(cmd, check=False, shell=True)
if source_or_destination_changed(state):
cli.abort()
def generate_test_map(map_path: pathlib.Path, size: int) -> None:
"""Generate test map with roughly 20% of the space marked as bad."""
chunk = 2*1024**2
@ -189,28 +198,9 @@ def get_ddrescue_settings(settings_menu) -> list:
return settings
def get_stats(output: str | None = None) -> dict[str, Any]:
"""Get stats from ddrescue output, returns dict."""
output = tmux.capture_pane()
stats = {}
temp = []
for line in output[output.find('ipos:'):].splitlines():
temp.extend(line.split(','))
for line in temp:
line = line.strip()
try:
key, value = line.split(':')
stats[key] = value.strip()
except ValueError:
# ignore
pass
return stats
def finalize_recovery(state: State, dry_run: bool = True) -> None:
"""Show recovery finalization options and run selected functions."""
options = (
'Post results to osTicket',
'Relocate Backup GPT',
'Zero-fill Gaps',
'Zero-fill Extra Space',
@ -253,9 +243,6 @@ def finalize_recovery(state: State, dry_run: bool = True) -> None:
if state.mode == 'Image':
details['Disabled'] = True
details['Selected'] = False
if 'osTicket' in name:
# This is last to override the Image section above
details['Selected'] = not state.ost.disabled
menu.add_option(name, details)
# Show menu
@ -275,10 +262,6 @@ def finalize_recovery(state: State, dry_run: bool = True) -> None:
# NOTE: This needs to be run last to avoid corrupting/erasing the backup GPT
relocate_backup_gpt(state, dry_run=dry_run)
# osTicket
if menu.options['Post results to osTicket']['Selected']:
state.post_to_osticket()
def is_missing_source_or_destination(state) -> bool:
"""Check if source or destination dissapeared, returns bool."""
@ -312,7 +295,12 @@ def is_missing_source_or_destination(state) -> bool:
def main() -> None:
"""Main function for ddrescue TUI."""
args = docopt(DOCSTRING)
try:
args = argparse_helper()
except SystemExit:
print('')
cli.pause('Press Enter to exit...')
raise
# Log setup
log_path = log.format_log_path(log_name='main', sub_dir='ddrescue-TUI')
@ -331,9 +319,6 @@ def main() -> None:
# Init
state = State(log_dir=log_path.parent)
if not args['--force-local-map']:
state.ost.select_ticket()
state.update_top_panes()
try:
state.init_recovery(args)
except (FileNotFoundError, std.GenericAbort):
@ -341,7 +326,7 @@ def main() -> None:
cli.abort()
# Show menu
main_menu = menus.main(ost_disabled=state.ost.disabled)
main_menu = menus.main()
settings_menu = menus.settings(state.mode)
while True:
selection = main_menu.advanced_select()
@ -358,18 +343,14 @@ def main() -> None:
# Detect drives
if 'Detect drives' in selection[0]:
detect_drives(state)
# Tech note
if 'tech note' in selection[0]:
note_lines = state.ost.add_note(
'Please enter any additional information about this recovery',
)
state.update_top_panes(note_lines=note_lines)
# Start over
if 'Fresh start' in selection[0]:
state.fresh_start()
cli.clear_screen()
cli.print_warning(DETECT_DRIVES_NOTICE)
if cli.ask('Are you sure you proceed?'):
cli.print_standard('Forcing controllers to rescan for devices...')
cmd = 'echo "- - -" | sudo tee /sys/class/scsi_host/host*/scan'
exe.run_program([cmd], check=False, shell=True)
if source_or_destination_changed(state):
cli.abort()
# Start recovery
if 'Start' in selection:
@ -425,9 +406,6 @@ def relocate_backup_gpt(state: State, dry_run: bool = True) -> None:
if proc.returncode:
cli.print_error('ERROR: Failed to relocate backup GPT.')
LOG.error('sfdisk result: %s, %s', proc.stdout, proc.stderr)
state.notes.add('WARNING: Failed to relocated backup GPT')
else:
state.notes.add('NOTE: Relocated backup GPT to the end of the disk.')
def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None:
@ -497,10 +475,6 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None:
LOG.info('ddrescue cmd: %s', cmd)
return
# Stats
if pass_name not in block_pair.stats:
block_pair.stats[pass_name] = {}
# Start ddrescue and ddrescueview (if enabled)
proc = exe.popen_program(cmd)
if (
@ -529,7 +503,6 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None:
_i += 1
# Update progress
block_pair.stats[pass_name].update(get_stats())
block_pair.update_progress(pass_name)
state.update_progress_pane('Active')
@ -763,17 +736,9 @@ def zero_fill_gaps(
# Re-run ddrescue to zero-fill gaps
proc = exe.run_program(cmd, check=False, pipe=False)
LOG.info('Zero-fill result: %s', proc)
if proc.returncode:
cli.print_error('ERROR: Failed to zero-fill: {block_pair.destination}')
LOG.error('zero-fill error: %s, %s', proc.stdout, proc.stderr)
state.notes.add('WARNING: Failed to zero-fill destination')
else:
msg = (
'NOTE: Zero-filled gaps'
f'{" and extra space on destination." if extend_to_end else "."}'
)
state.notes.add(msg)
if __name__ == '__main__':

View file

@ -29,10 +29,8 @@ if PLATFORM == 'Darwin':
DDRESCUE_SETTINGS['Default']['--odirect'] = {'Selected': False, 'Hidden': True}
MENU_ACTIONS = (
'Start',
'Add tech note',
f'Change settings {ansi.color_string("(experts only)", "YELLOW")}',
f'Detect drives {ansi.color_string("(experts only)", "YELLOW")}',
f'Fresh start {ansi.color_string("(experts only)", "YELLOW")}',
'Quit')
MENU_TOGGLES = {
'Auto continue (if recovery % over threshold)': True,
@ -46,7 +44,7 @@ SETTING_PRESETS = (
# Functions
def main(ost_disabled: bool) -> cli.Menu:
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 = ' '
@ -58,15 +56,6 @@ def main(ost_disabled: bool) -> cli.Menu:
for toggle, selected in MENU_TOGGLES.items():
menu.add_toggle(toggle, {'Selected': selected})
# osTicket actions
if ost_disabled:
menu.actions['Add tech note']['Disabled'] = True
menu.actions['Add tech note']['Hidden'] = True
# TODO: Remove this ugly call
menu.actions[MENU_ACTIONS[2]]['Separator'] = True
else:
menu.actions['Add tech note']['Separator'] = True
# Done
return menu

View file

@ -15,7 +15,7 @@ from typing import Any
import psutil
import pytz
from wk import cfg, debug, exe, io, net, osticket, std
from wk import cfg, debug, exe, io, net, std
from wk.clone import menus
from wk.clone.block_pair import (
BlockPair,
@ -81,10 +81,8 @@ class State():
self.block_pairs: list[BlockPair] = []
self.destination: hw_disk.Disk | pathlib.Path = pathlib.Path('/dev/null')
self.log_dir: pathlib.Path = log_dir
self.ost = osticket.osTicket()
self.progress_out: pathlib.Path = self.log_dir.joinpath('progress.out')
self.mode: str = '?'
self.notes: set = set()
self.source: hw_disk.Disk | None = None
self.working_dir: pathlib.Path | None = None
self.ui: tui.TUI = tui.TUI('Source')
@ -147,7 +145,7 @@ class State():
errors_detected = True
# Check for minor errors
if not check_attributes(self.destination, only_blocking=True):
if not check_attributes(self.destination, only_blocking=False):
cli.print_warning(
f'Attribute error(s) detected for: {self.destination.path}',
)
@ -252,45 +250,18 @@ class State():
if not cli.ask(prompt_msg):
raise std.GenericAbort()
def fresh_start(self) -> None:
"""Clean working dir and reset progress."""
cli.print_error(
'This will reset all progress and create new map file(s).',
)
cli.print_warning(
"Please only proceed if you understand what you're doing!",
)
cli.print_warning(
'NOTE: This will keep the current partition selection(s).',
)
if not cli.ask('Continue?'):
return
# Clean and reset
clean_working_dir(self.working_dir)
for block_pair in self.block_pairs:
block_pair.reset_progress()
self.update_progress_pane('Idle')
def generate_report(self) -> list[str]:
"""Generate report of overall and per block_pair results, returns list."""
report = []
stats_str = (
'... pass: {pass_name} -- '
'non-trimmed: {non-trimmed}, '
'non-scraped: {non-scraped}, '
'bad-sectors: {bad-sector}, '
'slow reads: {slow reads}, '
'run time: {run time}'
)
# Header
report.append(f'{self.mode.title()} Results:')
report.append(f'... Source: {self.source.description}')
report.append(' ')
report.append(f'Source: {self.source.description}')
if self.mode == 'Clone':
report.append(f'... Destination: {self.destination.description}')
report.append(f'Destination: {self.destination.description}')
else:
report.append(f'... Destination: {self.destination}/')
report.append(f'Destination: {self.destination}/')
# Overall
report.append(' ')
@ -303,17 +274,8 @@ class State():
report.append(f'Overall rescued: {percent}, error size: {error_size_str}')
# Block-Pairs
if len(self.block_pairs) == 1:
stats = self.block_pairs[0].stats
if stats:
try:
for _name, _stats in stats.items():
report.append(stats_str.format(pass_name=_name, **_stats))
except KeyError:
# Ignore and omit stats
pass
else:
# Two or more block_pairs
if len(self.block_pairs) > 1:
report.append(' ')
for pair in self.block_pairs:
error_size = pair.get_error_size()
error_size_str = std.bytes_to_string(error_size, decimals=2)
@ -322,26 +284,11 @@ class State():
pair_size = std.bytes_to_string(pair.size, decimals=2)
percent = pair.get_percent_recovered()
percent = format_status_string(percent, width=0)
report.append(' ')
report.append(
f'{pair.source.name} ({pair_size}) '
f'rescued: {percent}, '
f'error size: {error_size_str}'
)
stats = pair.stats
if not stats:
continue
try:
for _name, _stats in stats.items():
report.append(stats_str.format(pass_name=_name, **_stats))
except KeyError:
# Ignore and omit stats
pass
# Notes
if self.notes:
report.append(' ')
report.extend(sorted(self.notes))
# Done
return report
@ -369,7 +316,7 @@ class State():
"""Get total size of all block_pairs in bytes, returns int."""
return sum(pair.size for pair in self.block_pairs)
def init_recovery(self, docopt_args: dict[str, Any]) -> None:
def init_recovery(self, cli_args: dict[str, Any]) -> None:
"""Select source/dest and set env."""
cli.clear_screen()
disk_menu = menus.disks()
@ -377,10 +324,10 @@ class State():
self.ui.set_progress_file(str(self.progress_out))
# Set mode
self.mode = set_mode(docopt_args)
self.mode = set_mode(cli_args)
# Select source
self.source = select_disk_obj('source', disk_menu, docopt_args['<source>'])
self.source = select_disk_obj('source', disk_menu, cli_args['<source>'])
self.update_top_panes()
if self.source.trim:
cli.print_warning('Source device supports TRIM')
@ -393,12 +340,12 @@ class State():
self.destination = select_disk_obj(
'destination',
disk_menu,
docopt_args['<destination>'],
cli_args['<destination>'],
)
self.ui.add_title_pane('Destination', self.destination.name)
elif self.mode == 'Image':
if docopt_args['<destination>']:
self.destination = pathlib.Path(docopt_args['<destination>']).resolve()
if cli_args['<destination>']:
self.destination = pathlib.Path(cli_args['<destination>']).resolve()
else:
self.destination = menus.select_path('Destination')
self.destination.mkdir(parents=True, exist_ok=True)
@ -411,17 +358,10 @@ class State():
self.destination.update_details(skip_children=False)
# Confirmation #1
advanced_selection = False
try:
self.confirm_selections(
prompt_msg='Are these selections correct? (use "No" for advanced selection)',
source_parts=source_parts,
)
except std.GenericAbort:
if cli.ask('Proceed to advanced partition selection?'):
advanced_selection = True
else:
raise
self.confirm_selections(
prompt_msg='Are these selections correct?',
source_parts=source_parts,
)
# Update panes
self.update_progress_pane('Idle')
@ -430,34 +370,18 @@ class State():
self.working_dir = get_working_dir(
self.mode,
self.destination,
force_local=docopt_args['--force-local-map'],
ticket_id=self.ost.ticket_id,
ticket_name=self.ost.ticket_name,
force_local=cli_args['--force-local-map'],
)
# Start fresh if requested
if docopt_args['--start-fresh']:
if cli_args['--start-fresh']:
clean_working_dir(self.working_dir)
# Add block pairs
if advanced_selection:
if self.mode == 'Clone':
source_parts = add_clone_block_pairs(self)
else:
source_parts = add_image_block_pairs(self)
if self.mode == 'Clone':
source_parts = add_clone_block_pairs(self)
else:
if self.mode == 'Clone':
source_parts.append(hw_disk.Disk(self.source.path))
self.add_block_pair(
hw_disk.Disk(self.source.path),
pathlib.Path(self.destination.path),
)
if self.mode == 'Image':
source_parts.append(hw_disk.Disk(self.source.path))
self.add_block_pair(
hw_disk.Disk(self.source.path),
self.destination,
)
source_parts = add_image_block_pairs(self)
# Update SMART data
## TODO: Verify if needed
@ -472,8 +396,7 @@ class State():
# Confirmation #2
self.update_progress_pane('Idle')
if advanced_selection:
self.confirm_selections('Start recovery?', source_parts)
self.confirm_selections('Start recovery?', source_parts)
# Unmount source and/or destination under macOS
if PLATFORM == 'Darwin':
@ -489,10 +412,10 @@ class State():
# Prep destination
if self.mode == 'Clone':
prep_destination(self, source_parts, dry_run=docopt_args['--dry-run'])
prep_destination(self, source_parts, dry_run=cli_args['--dry-run'])
# Safety Checks #2
if not docopt_args['--dry-run']:
if not cli_args['--dry-run']:
for pair in self.block_pairs:
pair.safety_check()
@ -573,29 +496,6 @@ class State():
"""Check if all block_pairs completed pass_name, returns bool."""
return all(p.pass_complete(pass_name) for p in self.block_pairs)
def post_to_osticket(self):
"""Post results to osTicket."""
color = 'Diags'
percent = self.get_percent_recovered()
report = self.generate_report()
report[0] = f'ddrescue-tui {report[0]}'
if percent < 90:
color = 'Diags FAIL'
elif percent < 95:
color = 'Normal'
# Init osTicket if necessary
if not self.ost.ticket_id:
self.ost.init()
self.ost.select_ticket()
# Bail if user changed their mind
if not self.ost.ticket_id:
return
# Post report
self.ost.post_response('\n'.join(report), color=color)
def retry_all_passes(self) -> None:
"""Prep block_pairs for a retry recovery attempt."""
LOG.warning('Updating block_pairs for retry')
@ -712,10 +612,8 @@ class State():
# Write to progress file
self.progress_out.write_text('\n'.join(report), encoding='utf-8', errors='ignore')
def update_top_panes(self, note_lines: list | None = None) -> None:
def update_top_panes(self) -> None:
"""(Re)create top source/destination panes."""
if not note_lines:
note_lines = []
source_exists = True
source_str = ''
dest_exists = True
@ -783,29 +681,6 @@ class State():
dest_str,
)
# Bail if ticket not selected
if not self.ost:
return
# Ticket Details
self.ui.reset_subtitle_pane()
if self.ost.ticket_id and not self.ui.layout['Subtitle']['Panes']:
self.ui.add_subtitle_pane(
ansi.color_string(
[f'#{self.ost.ticket_id}', str(self.ost.ticket_name)],
[None, 'CYAN'],
),
str(self.ost.ticket_subject),
)
# Tech note
note_lines = self.ost.note.replace('...', '').splitlines()
if note_lines:
self.ui.add_subtitle_pane(
ansi.color_string('Tech Note', 'YELLOW'),
' | '.join(note_lines),
)
# Functions
def build_directory_report(path: pathlib.Path) -> list[str]:
@ -926,7 +801,7 @@ def build_object_report(obj) -> list[str]:
return report
def clean_working_dir(working_dir, confirm=False) -> None:
def clean_working_dir(working_dir) -> None:
"""Clean working directory to ensure a fresh recovery session.
NOTE: Data from previous sessions will be preserved
@ -939,9 +814,6 @@ def clean_working_dir(working_dir, confirm=False) -> None:
# Move settings, maps, etc to backup_dir
for entry in os.scandir(working_dir):
if entry.name.endswith(('.dd', '.json', '.map')):
if confirm and entry.name.endswith('.json'):
# Keep JSON settings if using the menu option
continue
new_path = f'{backup_dir}/{entry.name}'
new_path = io.non_clobber_path(new_path)
shutil.move(entry.path, new_path)
@ -1081,22 +953,11 @@ def get_percent_color(percent) -> str:
return color
def get_working_dir(
mode,
destination,
force_local=False,
ticket_id=None,
ticket_name=None,
) -> pathlib.Path:
def get_working_dir(mode, destination, force_local=False) -> pathlib.Path:
"""Get working directory using mode and destination, returns path."""
ticket_id = cli.get_ticket_id()
working_dir = None
# Set ticket ID
if ticket_id is None:
now = datetime.datetime.now(tz=TIMEZONE)
ticket_id = now.strftime('%Y-%m-%dT%H%M_%Z')
ticket_id = ticket_id.replace(' ', '_')
# Use preferred path if possible
if mode == 'Image':
try:
@ -1127,12 +988,6 @@ def get_working_dir(
if mode == 'Clone':
working_dir = working_dir.joinpath(ticket_id)
# Append ticket name if set
if ticket_name:
working_dir = working_dir.with_name(
f'{ticket_id}_{ticket_name.replace(" ", "-")}',
)
# Create directory
working_dir.mkdir(parents=True, exist_ok=True)
os.chdir(working_dir)
@ -1196,12 +1051,12 @@ def select_disk_obj(label:str, disk_menu: cli.Menu, disk_path: str) -> hw_disk.D
raise std.GenericAbort()
def set_mode(docopt_args) -> str:
"""Set mode from docopt_args or user selection, returns str."""
if docopt_args['clone']:
def set_mode(cli_args) -> str:
"""Set mode from cli_args or user selection, returns str."""
if cli_args['clone']:
return 'Clone'
if docopt_args['image']:
if cli_args['image']:
return 'Image'
# Ask user if necessary

View file

@ -81,7 +81,6 @@ def build_cmd_kwargs(
cmd: list[str],
minimized: bool = False,
pipe: bool = True,
priority: bool = False,
shell: bool = False,
**kwargs) -> dict[str, Any]:
"""Build kwargs for use by subprocess functions, returns dict.
@ -120,9 +119,6 @@ def build_cmd_kwargs(
startupinfo.wShowWindow = 6
cmd_kwargs['startupinfo'] = startupinfo
# High priority
if priority:
cmd_kwargs['creationflags'] = subprocess.HIGH_PRIORITY_CLASS
# Pipe output
if pipe:
@ -272,18 +268,6 @@ def run_program(
return proc
def set_proc_priority(name, priority, exact=True):
"""Set process priority by name.
NOTE: priority currently can be only set to NORMAL or HIGH.
"""
if priority not in ('NORMAL', 'HIGH'):
raise RuntimeError('Invalid process priority specified.')
for proc in get_procs(name, exact=exact):
proc.nice(psutil.HIGH_PRIORITY_CLASS)
def start_thread(
function: Callable,
args: Iterable[Any] | None = None,

View file

@ -1,25 +1,13 @@
"""WizardKit: Graph Functions"""
# vim: sts=2 sw=2 ts=2
import base64
import json
import logging
import pathlib
import time
from matplotlib import pyplot
import requests
from wk.cfg.net import BENCHMARK_SERVER, IMGUR_CLIENT_ID
from wk.ui import ansi
# STATIC VARIABLES
LOG = logging.getLogger(__name__)
GRAPH_FIGURE_SIZE = (
9.167, # 660px at 72dpi
4.167, # 300px at 72dpi
)
GRAPH_HORIZONTAL = ('', '', '', '', '', '', '', '')
GRAPH_VERTICAL = (
'', '', '', '',
@ -45,137 +33,6 @@ THRESH_GREAT = 750 * 1024**2
# Functions
def reduce_cpu_temp_list(list_of_temps, factor=5, start=30) -> list[float]:
"""Reduce temperature list by averaging adjacent values.
NOTE: This only averages values after the amount defined by start.
NOTE 2: If the last group is less than the factor it is simply dropped.
"""
new_list = list_of_temps[:start]
list_of_temps = list_of_temps[start:]
while list_of_temps:
group = list_of_temps[:factor]
list_of_temps = list_of_temps[factor:]
if len(group) < factor:
continue
new_list.append(sum(group)/factor)
return new_list
def export_cpu_graph(cpu_description, log_dir, sensor_history):
"""Exports PNG graph using matplotlib."""
lines = {}
offset_labels = []
out_path = pathlib.Path(f'{log_dir}/cpu_tests.png')
run_averages = {}
# Safety check
if not sensor_history:
raise RuntimeError('No sensor_data available.')
# Prep
offset = 0
for run_label, sensor_data in sensor_history:
all_run_temps = []
offset_labels.append((offset, run_label))
run_length = 0
for adapter in sensor_data.get('CPUTemps', {}).values():
for source, data in adapter.items():
y_values = data['Temps']
if run_label in ('Sysbench', 'Prime95'):
y_values = reduce_cpu_temp_list(y_values)
all_run_temps.extend(y_values)
run_length = max(run_length, len(y_values))
if source not in lines:
lines[source] = []
lines[source].extend(y_values)
try:
run_averages[run_label] = {
'Start': offset,
'End': offset+run_length,
'Temp': sum(all_run_temps) / len(all_run_temps),
}
except ZeroDivisionError:
# Ignore
pass
offset += run_length
# Build graph
_, ax = pyplot.subplots(
dpi=72,
figsize=list(x*2 for x in GRAPH_FIGURE_SIZE),
layout='constrained',
)
ax.set_title(cpu_description)
ax.set_xticks([])
for label, data in lines.items():
ax.plot(data, label=label)
#prev_label = 'Idle' # Always skip Idle
prev_label = ''
for offset, label in offset_labels:
color = 'grey'
if label == prev_label:
continue
if label == 'Sysbench':
color = 'orange'
if label == 'Prime95':
color = 'red'
if label == 'Cooldown':
color = 'blue'
label = ''
ax.axvline(x=offset, color=color, label=label, linestyle='--')
#ax.axvline(x=offset, color=color)
prev_label = label
for run_label, data in run_averages.items():
if run_label not in ('Prime95', 'Sysbench'):
continue
ax.axhline(
y = data['Temp'],
color = 'orange' if run_label == 'Sysbench' else 'red',
label = f'{run_label} (Avg)',
linestyle = ':',
)
ax.legend()
pyplot.savefig(out_path)
# Done
return out_path
def export_io_graph(disk, log_dir, read_rates):
"""Exports PNG graph using matplotlib."""
# Safety check
if not read_rates:
raise RuntimeError(f'No read rates for {disk.path}')
# Prep
max_rate = max(read_rates) / (1024**2)
max_rate = max(800, max_rate)
out_path = pathlib.Path(f'{log_dir}/{disk.path.name}_iobenchmark.png')
plot_data = ([], [])
# Prep data for graph
for i, rate in enumerate(read_rates):
plot_data[0].append((i+1) / len(read_rates) * 100) # Step
plot_data[1].append(int(rate / (1024**2))) # Data
# Build graph
_, ax = pyplot.subplots(
dpi=72,
figsize=GRAPH_FIGURE_SIZE,
layout='constrained',
)
ax.set_title('I/O Benchmark')
ax.plot(*plot_data, label=disk.description.replace('_', ' '))
ax.legend()
pyplot.savefig(out_path)
# Done
return out_path
def generate_horizontal_graph(
rate_list: list[float],
graph_width: int = 40,
@ -259,76 +116,6 @@ def merge_rates(
return merged_rates
def upload_to_imgur(image_path):
"""Upload image to Imgur and return image url as str."""
image_data = None
image_link = None
# Bail early
if not image_path:
raise RuntimeError(f'Invalid image path: {image_path}')
# Read image file and convert to base64 then convert to str
with open(image_path, 'rb') as _f:
image_data = base64.b64encode(_f.read()).decode()
# POST image
url = 'https://api.imgur.com/3/image'
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
payload = (
f'--{boundary}\r\nContent-Disposition: form-data; '
f'name="image"\r\n\r\n{image_data}\r\n--{boundary}--'
)
headers = {
'content-type': f'multipart/form-data; boundary={boundary}',
'Authorization': f'Client-ID {IMGUR_CLIENT_ID}',
}
response = requests.request(
'POST',
url,
data=payload,
headers=headers,
timeout=60,
)
# Return image link
if response.ok:
json_data = json.loads(response.text)
image_link = json_data['data']['link']
return image_link
def upload_to_nextcloud(image_path, ticket_number, dev_name):
"""Upload image to Nextcloud server and return folder url as str."""
image_data = None
ticket_range = f'{ticket_number[:3]}00-{ticket_number[:3]}99'
# Bail early
if not image_path:
raise RuntimeError(f'Invalid image path: {image_path}')
# Read image file and convert to base64
with open(image_path, 'rb') as _f:
image_data = _f.read()
# PUT image
url = (
f'{BENCHMARK_SERVER["Url"]}/'
f'{ticket_range}/{ticket_number}_iobenchmark'
f'_{dev_name}_{time.strftime("%Y-%m-%d_%H%M_%z")}.png'
)
requests.put(
url,
data=image_data,
auth=(BENCHMARK_SERVER['User'], BENCHMARK_SERVER['Pass']),
headers={'X-Requested-With': 'XMLHttpRequest'},
timeout=60,
)
# Return folder link
return BENCHMARK_SERVER['Short Url']
def vertical_graph_line(percent: float, rate: float, scale: int = 32) -> str:
"""Build colored graph string using thresholds, returns str."""
color_bar = None

View file

@ -7,11 +7,9 @@ from . import diags
from . import disk
from . import keyboard
from . import network
from . import osticket
from . import screensavers
from . import sensors
from . import smart
from . import surface_scan
from . import system
from . import test
from . import volumes

View file

@ -2,10 +2,10 @@
# vim: sts=2 sw=2 ts=2
import logging
import platform
from subprocess import PIPE, STDOUT
from wk import graph
from wk.cfg.hw import (
IO_ALT_TEST_SIZE_FACTOR,
IO_BLOCK_SIZE,
@ -24,9 +24,6 @@ from wk.exe import run_program
from wk.std import PLATFORM
from wk.ui import ansi
if platform.system() != 'Windows':
from wk import graph
# STATIC VARIABLES
LOG = logging.getLogger(__name__)
@ -100,7 +97,7 @@ def calc_io_dd_values(dev_size, test_mode=False) -> dict[str, int]:
}
def check_io_results(state, test_obj, rate_list, graph_width) -> None:
def check_io_results(test_obj, rate_list, graph_width) -> None:
"""Check I/O restuls and generate report using rate_list."""
avg_read = sum(rate_list) / len(rate_list)
min_read = min(rate_list)
@ -144,36 +141,8 @@ def check_io_results(state, test_obj, rate_list, graph_width) -> None:
else:
test_obj.set_status('Unknown')
# Export and upload graphs
export_and_upload_graphs(state, test_obj, rate_list)
def export_and_upload_graphs(state, test_obj, rate_list):
"""Export and upload graphs."""
image_path = None
try:
image_path = graph.export_io_graph(test_obj.dev, state.log_dir, rate_list)
except RuntimeError as err:
# Failed to export PNG, skip uploads below
LOG.error('Failed to export graph: %s', err)
test_obj.report.append('Failed to export graph')
return
# Upload PNG
if not state.ost.disabled and state.ost.ticket_id:
try:
imgur_url = graph.upload_to_imgur(image_path)
nextcloud_url = graph.upload_to_nextcloud(
image_path, state.ost.ticket_id, test_obj.dev.path.name)
test_obj.report.append(f'Imgur: {imgur_url}')
test_obj.report.append(f'Nextcloud: {nextcloud_url}')
except Exception as err:
LOG.error('%s', err)
LOG.error('Failed to upload graph')
test_obj.report.append('Failed to upload graph')
def run_io_test(state, test_obj, log_path, test_mode=False) -> None:
def run_io_test(test_obj, log_path, test_mode=False) -> None:
"""Run I/O benchmark and handle exceptions."""
dev_path = test_obj.dev.path
if PLATFORM == 'Darwin':
@ -242,7 +211,7 @@ def run_io_test(state, test_obj, log_path, test_mode=False) -> None:
offset += dd_values['Read Blocks'] + skip
# Check results
check_io_results(state, test_obj, read_rates, IO_GRAPH_WIDTH)
check_io_results(test_obj, read_rates, IO_GRAPH_WIDTH)

View file

@ -2,7 +2,6 @@
# vim: sts=2 sw=2 ts=2
import logging
import platform
import re
import subprocess
@ -14,13 +13,6 @@ from wk.os.mac import set_fans as macos_set_fans
from wk.std import PLATFORM
from wk.ui import ansi
if platform.system() != 'Windows':
from wk.graph import (
export_cpu_graph,
upload_to_imgur,
upload_to_nextcloud,
)
# STATIC VARIABLES
LOG = logging.getLogger(__name__)
@ -28,9 +20,8 @@ SysbenchType = tuple[subprocess.Popen, TextIO]
# Functions
def check_cooling_results(state, test_object) -> None:
def check_cooling_results(sensors, test_object) -> None:
"""Check cooling result via sensor data."""
sensors = state.sensors
idle_temp = sensors.get_cpu_temp('Idle')
cooldown_temp = sensors.get_cpu_temp('Cooldown')
max_temp = sensors.get_cpu_temp('Max')
@ -93,37 +84,6 @@ def check_cooling_results(state, test_object) -> None:
*report_labels, only_cpu=True, include_avg_for=average_labels):
test_object.report.append(f' {line}')
# Export and upload graph
export_and_upload_graphs(state, test_object)
def export_and_upload_graphs(state, test_object):
"""Export and upload graphs."""
image_path = None
try:
image_path = export_cpu_graph(
cpu_description = state.system.cpu_description,
log_dir = state.log_dir,
sensor_history = state.sensors.history,
)
except RuntimeError as err:
# Failed to export PNG, skip uploads below
LOG.error('Failed to export graph: %s', err)
test_object.report.append('Failed to export graph')
return
# Upload PNG
if not state.ost.disabled and state.ost.ticket_id:
try:
imgur_url = upload_to_imgur(image_path)
nextcloud_url = upload_to_nextcloud(image_path, state.ost.ticket_id, 'cpu')
test_object.report.append(f'Imgur: {imgur_url}')
test_object.report.append(f'Nextcloud: {nextcloud_url}')
except Exception as err:
LOG.error('%s', err)
LOG.error('Failed to upload graph')
test_object.report.append('Failed to upload graph')
def check_mprime_results(test_obj, working_dir) -> None:
"""Check mprime log files and update test_obj."""

View file

@ -1,26 +1,22 @@
"""WizardKit: Hardware diagnostics"""
# vim: sts=2 sw=2 ts=2
import argparse
import atexit
import logging
import os
import pathlib
import platform
import subprocess
from docopt import docopt
from wk import cfg, debug, exe, log, osticket, std
from wk.cfg.hw import CPU_TEST_MINUTES, STATUS_COLORS
from wk import cfg, debug, exe, log, std
from wk.cfg.hw import STATUS_COLORS
from wk.hw import benchmark as hw_benchmark
from wk.hw import cpu as hw_cpu
from wk.hw import disk as hw_disk
from wk.hw import osticket as hw_osticket
from wk.hw import sensors as hw_sensors
from wk.hw import smart as hw_smart
from wk.hw import surface_scan as hw_surface_scan
from wk.hw import system as hw_system
from wk.hw import volumes as hw_volumes
from wk.hw.audio import audio_test
from wk.hw.keyboard import keyboard_test
from wk.hw.network import network_test
@ -31,27 +27,10 @@ from wk.ui import ansi, cli, tui
# STATIC VARIABLES
DOCSTRING = f'''{cfg.main.KIT_NAME_FULL}: Hardware Diagnostics
Usage:
hw-diags [options]
hw-diags (-h | --help)
Options:
-c --cli Force CLI mode
-h --help Show this page
-q --quick Skip menu and perform a quick check
-t --test-mode Run diags in test mode
'''
LOG = logging.getLogger(__name__)
IO_SIZE_SKIP_NAME = (
'Skip USB Benchmarks '
f'(< {std.bytes_to_string(cfg.hw.IO_SMALL_DISK, use_binary=False)})'
)
TEST_GROUPS = {
# Also used to build the menu options
## NOTE: This needs to be above MENU_SETS
'System Info': 'post_system_info',
'CPU (Sysbench)': 'cpu_test_sysbench',
'CPU (Prime95)': 'cpu_test_mprime',
'CPU (Cooling)': 'cpu_test_cooling',
@ -59,7 +38,6 @@ TEST_GROUPS = {
'Disk Self-Test': 'disk_self_test',
'Disk Surface Scan': 'disk_surface_scan',
'Disk I/O Benchmark': 'disk_io_benchmark',
'Disk Utilization': 'disk_volume_utilization',
}
MENU_ACTIONS = (
'Audio Test',
@ -71,13 +49,7 @@ MENU_ACTIONS = (
MENU_ACTIONS_SECRET = (
'Matrix',
'Tubes',
'?Secrets',
)
MENU_OPTIONS_SECRET = {
'Ignore SMART errors': False,
'Override CPU Testing Time (minutes)': CPU_TEST_MINUTES,
'Override Surface Scan Error Limit': 1,
}
MENU_OPTIONS_QUICK = ('Disk Attributes',)
MENU_SETS = {
'Full Diagnostic': (*TEST_GROUPS,),
@ -87,16 +59,12 @@ MENU_SETS = {
'Disk Self-Test',
'Disk Surface Scan',
'Disk I/O Benchmark',
'Disk Utilization',
),
'Disk Diagnostic (Quick)': ('Disk Attributes', 'Disk Utilization'),
'Disk Diagnostic (Quick)': ('Disk Attributes',),
}
MENU_TOGGLES = (
'osTicket Integration',
'osTicket Tech Note',
IO_SIZE_SKIP_NAME,
'Skip USB Benchmarks',
)
NUM_DISK_TESTS = len([s for s in TEST_GROUPS if s.startswith('Disk')])
PLATFORM = std.PLATFORM
# Classes
@ -105,9 +73,7 @@ class State():
def __init__(self, test_mode=False):
self.disks: list[hw_disk.Disk] = []
self.log_dir: pathlib.Path | None = None
self.ost = osticket.osTicket()
self.progress_file: pathlib.Path | None = None
self.secret_menu = build_secret_menu()
self.sensors: hw_sensors.Sensors = hw_sensors.Sensors()
self.system: hw_system.System | None = None
self.test_groups: list[TestGroup] = []
@ -148,11 +114,6 @@ class State():
self.disks.clear()
self.sensors = hw_sensors.Sensors()
self.test_groups.clear()
self.ui.remove_all_subtitle_panes()
# osTicket
self.ost.init()
self.ost.disabled = not menu.toggles['osTicket Integration']['Selected']
# Set log
self.log_dir = log.format_log_path(
@ -187,17 +148,6 @@ class State():
# Only add selected options
continue
if 'System' in name:
test = Test(dev=self.system, label=name, name=name)
self.test_groups.insert(
0,
TestGroup(
name=name,
function=globals()[TEST_GROUPS[name]],
test_objects=[test],
)
)
if 'CPU' in name:
self.system.tests.append(
Test(dev=self.system, label=name[5:-1], name=name),
@ -215,11 +165,8 @@ class State():
# Group CPU tests
if self.system.tests:
index = 0
if self.test_groups and self.test_groups[0].name.startswith('System'):
index = 1
self.test_groups.insert(
index,
0,
TestGroup(
name='CPU & Cooling',
function=run_cpu_tests,
@ -255,16 +202,6 @@ class State():
for test in disk.tests:
_f.write(f'\n{test.name}:\n')
_f.write('\n'.join(debug.generate_object_report(test, indent=1)))
if platform.system() == 'Darwin':
cmd = [(
f'sudo gpt -r show "{disk.path}"'
f' >> {debug_dir}/gpt_{disk.path.name}.info'
)]
exe.run_program(cmd, check=False, shell=True)
# osTicket
with open(f'{debug_dir}/osTicket.report', 'a', encoding='utf-8') as _f:
_f.write('\n'.join(debug.generate_object_report(self.ost)))
# SMC
if os.path.exists('/.wk-live-macos'):
@ -314,6 +251,36 @@ class State():
# Functions
def argparse_helper() -> dict[str, bool]:
"""Helper function to setup and return args, returns dict.
NOTE: A dict is used to match the legacy code.
"""
parser = argparse.ArgumentParser(
prog='hw-diags',
description=f'{cfg.main.KIT_NAME_FULL}: Hardware Diagnostics',
)
parser.add_argument(
'-c', '--cli', action='store_true',
help='Force CLI mode',
)
parser.add_argument(
'-q', '--quick', action='store_true',
help='Skip menu and perform a quick check',
)
parser.add_argument(
'-t', '--test-mode', action='store_true',
help='Run diags in test mode',
)
args = parser.parse_args()
legacy_args = {
'--cli': args.cli,
'--quick': args.quick,
'--test-mode': args.test_mode,
}
return legacy_args
def build_menu(cli_mode=False, quick_mode=False) -> cli.Menu:
"""Build main menu, returns wk.ui.cli.Menu."""
menu = cli.Menu(title='')
@ -331,9 +298,6 @@ def build_menu(cli_mode=False, quick_mode=False) -> cli.Menu:
menu.add_set(name, {'Targets': targets})
menu.actions['Start']['Separator'] = True
# osTicket
menu.toggles['osTicket Tech Note']['Selected'] = False
# Update default selections for quick mode if necessary
if quick_mode:
for name, details in menu.options.items():
@ -345,7 +309,6 @@ def build_menu(cli_mode=False, quick_mode=False) -> cli.Menu:
menu.options['CPU (Sysbench)']['Selected'] = False
menu.options['CPU (Prime95)']['Selected'] = False
menu.options['CPU (Cooling)']['Selected'] = False
menu.options['System Info']['Selected'] = False
# Add CLI actions if necessary
if cli_mode or 'DISPLAY' not in os.environ:
@ -371,24 +334,6 @@ def build_menu(cli_mode=False, quick_mode=False) -> cli.Menu:
return menu
def build_secret_menu() -> cli.Menu:
title_text = [
ansi.color_string(('Expert Settings', "(It's a secret to everyone!)"), ('ORANGE', None)),
' ',
ansi.color_string(
[' !!', 'These settings can cause', 'MAJOR DAMAGE', 'to systems'],
[None, 'YELLOW', 'RED', 'YELLOW'],
),
' !! Please read the manual before making changes',
' ',
]
menu = cli.Menu(title='\n'.join(title_text))
for option, value in MENU_OPTIONS_SECRET.items():
menu.add_option(option, {'Selected': False, 'Value': value})
menu.add_action('Main Menu')
return menu
def cpu_tests_init(state: State) -> None:
"""Initialize CPU tests."""
sensors_out = pathlib.Path(f'{state.log_dir}/sensors.out')
@ -432,7 +377,7 @@ def cpu_test_cooling(state: State, test_object, test_mode=False) -> None:
if test_object.disabled:
return
hw_cpu.check_cooling_results(state, test_object)
hw_cpu.check_cooling_results(state.sensors, test_object)
state.update_progress_file()
@ -445,11 +390,6 @@ def cpu_test_mprime(state: State, test_object, test_mode=False) -> None:
test_minutes = cfg.hw.CPU_TEST_MINUTES
if test_mode:
test_minutes = cfg.hw.TEST_MODE_CPU_LIMIT
if state.secret_menu.options.get(
'Override CPU Testing Time (minutes)')['Selected']:
test_minutes = int(
state.secret_menu.options.get('Override CPU Testing Time (minutes)')['Value'],
)
# Bail early
if test_object.disabled:
@ -485,7 +425,6 @@ def cpu_test_mprime(state: State, test_object, test_mode=False) -> None:
aborted = True
# Stop Prime95
state.sensors.save_average_temps(temp_label='Cooldown', seconds=5)
hw_cpu.stop_mprime(proc)
# Get cooldown temp
@ -526,11 +465,6 @@ def cpu_test_sysbench(state: State, test_object, test_mode=False) -> None:
test_minutes = cfg.hw.CPU_TEST_MINUTES
if test_mode:
test_minutes = cfg.hw.TEST_MODE_CPU_LIMIT
if state.secret_menu.options.get(
'Override CPU Testing Time (minutes)')['Selected']:
test_minutes = int(
state.secret_menu.options.get('Override CPU Testing Time (minutes)')['Value'],
)
# Bail early
if test_object.disabled:
@ -562,7 +496,6 @@ def cpu_test_sysbench(state: State, test_object, test_mode=False) -> None:
LOG.error('Failed to find sysbench process', exc_info=True)
except KeyboardInterrupt:
aborted = True
state.sensors.save_average_temps(temp_label='Cooldown', seconds=5)
hw_cpu.stop_sysbench(proc, filehandle)
# Get cooldown temp
@ -591,11 +524,9 @@ def cpu_test_sysbench(state: State, test_object, test_mode=False) -> None:
# 0 == Completed w/out issue
# -2 == Stopped with INT signal
# -15 == Stopped with TERM signal
test_object.failed = True
test_object.set_status('Failed')
test_object.report.append(f' Failed with return code: {proc.returncode}')
else:
test_object.passed = True
test_object.set_status('Passed')
test_object.report.append(' Completed without issue.')
state.update_progress_file()
@ -634,18 +565,13 @@ def disk_io_benchmark(
)
state.ui.set_current_pane_height(10)
for test in test_objects:
if (
skip_usb
and test.dev.bus == 'USB'
and test.dev.size < cfg.hw.IO_SMALL_DISK
):
test.set_status('Skipped')
test.disabled = True
if test.disabled:
# Skip
continue
# Start benchmark
for test in test_objects:
if test.disabled:
# Skip USB devices if requested
if skip_usb and test.dev.bus == 'USB':
test.set_status('Skipped')
continue
# Start benchmark
@ -662,7 +588,7 @@ def disk_io_benchmark(
)
state.update_progress_file()
try:
hw_benchmark.run_io_test(state, test, test_log, test_mode=test_mode)
hw_benchmark.run_io_test(test, test_log, test_mode=test_mode)
except KeyboardInterrupt:
aborted = True
except (subprocess.CalledProcessError, TypeError, ValueError) as err:
@ -787,12 +713,7 @@ def disk_surface_scan(state: State, test_objects, test_mode=False) -> None:
"""Read-only disk surface scan using badblocks."""
LOG.info('Disk Surface Scan (badblocks)')
aborted = False
max_errors = 1
threads = []
if state.secret_menu.options.get(
'Override Surface Scan Error Limit')['Selected']:
max_errors = state.secret_menu.options.get(
'Override Surface Scan Error Limit')['Value']
# Update panes
state.update_title_text(
@ -809,7 +730,7 @@ def disk_surface_scan(state: State, test_objects, test_mode=False) -> None:
# Start thread
test_log = f'{state.log_dir}/{test.dev.path.name}_badblocks.log'
threads.append(exe.start_thread(
hw_surface_scan.run_scan, args=(test, test_log, test_mode, max_errors),
hw_surface_scan.run_scan, args=(test, test_log, test_mode),
))
# Show progress
@ -842,20 +763,14 @@ def disk_surface_scan(state: State, test_objects, test_mode=False) -> None:
raise std.GenericAbort('Aborted')
def disk_volume_utilization(state, test_objects, test_mode=False) -> None:
"""Check disk for full volumes."""
_ = test_mode
LOG.info('Disk Utilization')
for test in test_objects:
hw_volumes.check_volume_utilization(test)
# Done
state.update_progress_file()
def main() -> None:
"""Main function for hardware diagnostics."""
args = docopt(DOCSTRING)
try:
args = argparse_helper()
except SystemExit:
print('')
cli.pause('Press Enter to exit...')
raise
log.update_log_path(dest_name='Hardware-Diagnostics', timestamp=True)
# Safety check
@ -869,7 +784,6 @@ def main() -> None:
# Quick Mode
if args['--quick']:
menu.toggles['osTicket Integration']['Selected'] = False
run_diags(state, menu, quick_mode=True, test_mode=args['--test-mode'])
return
@ -901,11 +815,9 @@ def main() -> None:
state.ui.update_clock()
# Secrets
if '?Secrets' in selection:
state.secret_menu.settings_select()
if 'Matrix' in selection:
screensaver('matrix')
if 'Tubes' in selection:
elif 'Tubes' in selection:
# Tubes ≈≈ Pipes?
screensaver('pipes')
@ -927,33 +839,6 @@ def main() -> None:
state.update_title_text('Main Menu')
def post_system_info(state, test_objects, test_mode=False) -> None:
"""Post system info to osTicket."""
_ = test_mode
# Bail early
if state.ost.disabled:
return
# Build report
report = state.system.generate_full_report()
if state.disks:
report.append('\n[Disks]')
for disk in state.disks:
report.append(f'... {disk.description}')
# Post to osTicket
state.ost.post_response('\n'.join(report))
# Update test object(s)
for test_object in test_objects:
test_object.passed = True
test_object.set_status('Done')
# Done
state.update_progress_file()
def print_countdown(proc, seconds) -> None:
"""Print countdown to screen while proc is alive."""
seconds = int(seconds)
@ -991,22 +876,8 @@ def run_cpu_tests(state: State, test_objects, test_mode=False) -> None:
cpu_tests_end(state)
state.update_progress_file()
# Post results to osTicket
if not state.ost.disabled:
failed = any(test.failed for test in state.system.tests)
cli.print_info('Posting results to osTicket...')
state.ost.post_response(
hw_osticket.build_report(state.system, 'CPU'),
color='Diags FAIL' if failed else 'Diags',
)
def run_diags(
state: State,
menu: cli.Menu,
quick_mode: bool = False,
test_mode: bool = False,
) -> None:
def run_diags(state: State, menu, quick_mode=False, test_mode=False) -> None:
"""Run selected diagnostics."""
aborted = False
atexit.register(state.save_debug_reports)
@ -1018,32 +889,6 @@ def run_diags(
cli.pause()
return
# osTicket
if not state.ost.disabled:
# Select Ticket
state.ost.select_ticket()
# Update top_text
if state.ost.ticket_id:
state.ui.add_subtitle_pane(
cli.color_string(
[f'#{state.ost.ticket_id}', str(state.ost.ticket_name)],
[None, 'CYAN'],
),
str(state.ost.ticket_subject),
)
# Add note
if (state.ost.ticket_id
and menu.toggles['osTicket Tech Note']['Selected']):
note_lines = state.ost.add_note()
if note_lines:
state.ui.add_subtitle_pane(
cli.color_string('Tech Note', 'YELLOW'),
' | '.join(note_lines),
)
# Run tests
for group in state.test_groups:
@ -1051,7 +896,7 @@ def run_diags(
function = group.function
args = [group.test_objects]
if group.name == 'Disk I/O Benchmark':
args.append(menu.toggles[IO_SIZE_SKIP_NAME]['Selected'])
args.append(menu.toggles['Skip USB Benchmarks']['Selected'])
state.ui.clear_current_pane()
try:
function(state, *args, test_mode=test_mode)
@ -1073,17 +918,10 @@ def run_diags(
if test.status == 'Pending':
test.set_status('Aborted')
# Post disk results
hw_osticket.post_disk_results(state, NUM_DISK_TESTS)
# Show results
show_results(state)
# Update checkboxes
hw_osticket.update_checkboxes(state, NUM_DISK_TESTS)
# Done
state.ui.remove_all_subtitle_panes()
state.save_debug_reports()
atexit.unregister(state.save_debug_reports)
if quick_mode:
@ -1114,7 +952,7 @@ def show_results(state: State) -> None:
]
if cpu_tests_enabled:
cli.print_success('CPU:')
cli.print_report(state.system.generate_cpu_ram_report())
cli.print_report(state.system.generate_report())
cli.print_standard(' ')
# Disk Tests

View file

@ -1,216 +0,0 @@
"""WizardKit: osTicket hardware diagnostic functions"""
# vim: sts=2 sw=2 ts=2
import logging
import re
from wk import osticket
from wk.cfg.hw import (
REGEX_BLOCK_GRAPH,
REGEX_SMART_ATTRIBUTES,
)
from wk.hw import smart as hw_smart
from wk.ui import cli
# STATIC VARIABLES
LOG = logging.getLogger(__name__)
# Functions
def build_report(dev, dev_type, num_disk_tests=None):
"""Build report for posting to osTicket, returns str."""
report = []
# Combined result
if dev_type == 'CPU' or len(dev.tests) == num_disk_tests:
# Build list of failed tests (if any)
failed_tests = [t.name for t in dev.tests if t.failed]
failed_tests = [name.replace('Disk ', '') for name in failed_tests]
if len(failed_tests) > 2:
failed_tests = f'{", ".join(failed_tests[:-1])}, & {failed_tests[-1]}'
else:
failed_tests = ' & '.join(failed_tests)
# Get overall result
result = 'UNKNOWN'
if any(t.failed for t in dev.tests):
result = 'FAILED'
elif all(t.passed for t in dev.tests):
result = 'PASSED'
# Add to report
report.append(
f'{dev_type} hardware diagnostic tests: {result}'
f'{" ("+failed_tests+")" if failed_tests else ""}'
)
report.append('')
# Description
if hasattr(dev, 'cpu_description'):
report.append(dev.cpu_description)
else:
report.append(dev.description)
if hasattr(dev, 'ram_total'):
if len(dev.ram_dimms) == 1 and 'justTotalRAM' in dev.ram_dimms[0]:
report.append(f'{dev.ram_total} (Total - no DIMM info available)')
else:
report.append(f'{dev.ram_total} ({", ".join(dev.ram_dimms)})')
if hasattr(dev, 'serial') and dev.serial:
report.append(f'Serial Number: {dev.serial}')
report.append('')
# Notes
if hasattr(dev, 'notes') and dev.notes:
report.append('Notes')
report.extend([f'... {note}' for note in dev.notes])
report.append('')
# Tests
for test in dev.tests:
report.append(f'{test.name} ({test.status})')
# Report
if test.name == 'Disk Attributes' and dev.attributes:
report.extend(
convert_report(
hw_smart.generate_attribute_report(dev),
start_index=0,
),
)
else:
report.extend(convert_report(test.report, start_index=1))
# I/O graph upload report
report.extend(getattr(test, 'upload_report', []))
# Spacer
report.append('')
# Remove last line if empty
if not report[-1].strip():
report.pop(-1)
# Done
return cli.strip_colors('\n'.join(report))
def convert_report(original_report, start_index):
"""Convert report to an osTicket compatible type, returns list."""
report = []
# Convert report
for line in original_report[start_index:]:
# Remove colors and leading spaces
line = cli.strip_colors(line)
line = re.sub(r'^\s+', '', line)
# Disk I/O Benchmark
if REGEX_BLOCK_GRAPH.search(line):
line = REGEX_BLOCK_GRAPH.sub('', line)
line = line.strip()
# SMART attributes
match = REGEX_SMART_ATTRIBUTES.search(line)
if match:
# Switch decimal and hex labels
_dec = f'{match.group("decimal"):>3}'
_dec = osticket.pad_with_dots(_dec)
_hex = match.group('hex')
_data = match.group('data')
line = f'{_hex}/{_dec}: {_data}'
line = line.replace('failed', 'FAILED')
# Skip empty lines
if not line.strip():
continue
# Fix inner spacing
for spacing in re.findall(r'\s\s+', line):
new_padding = osticket.pad_with_dots(spacing)
new_padding += ' '
line = line.replace(spacing, new_padding)
# Indent line
line = f'... {line}'
# Add to (converted) report
report.append(line)
# Done
return report
def post_disk_results(state, num_disk_tests):
"""Post disk test results for all disks."""
disk_tests = []
for group in state.test_groups:
if group.name.startswith('Disk'):
disk_tests.extend(group.test_objects)
# Bail if no disk tests were run
if not disk_tests or state.ost.disabled:
return
# Post disk results
cli.print_info('Posting results to osTicket...')
for disk in state.disks:
state.ost.post_response(
build_report(disk, 'Disk', num_disk_tests),
color='Diags FAIL' if any(t.failed for t in disk.tests) else 'Diags',
)
def update_checkboxes(state, num_disk_tests):
"""Update osTicket checkboxes after confirmation."""
cpu_tests = []
disk_tests = []
num_disk_tests_run = len(state.test_groups)
# Build list of tests
for group in state.test_groups:
if group.name.startswith('CPU'):
cpu_tests.extend(group.test_objects)
num_disk_tests_run -= 1
elif group.name.startswith('Disk'):
disk_tests.extend(group.test_objects)
elif group.name.startswith('System'):
num_disk_tests_run -= 1
# Bail if osTicket integration disabled
if state.ost.disabled:
return
# Bail if values not confirmed
if not cli.ask('Update osTicket checkboxes using the data above?'):
return
# CPU max temp and pass/fail
if cpu_tests:
state.ost.set_cpu_max_temp(state.sensors.get_cpu_temp('Max'))
if any(t.failed for t in cpu_tests):
state.ost.set_flag_failed('CPU')
elif all(t.passed for t in cpu_tests):
state.ost.set_flag_passed('CPU')
# Check results for all disks
if state.disks:
all_disks_passed = True
for disk in state.disks:
if any(t.failed for t in disk.tests):
# Mark failed disk in osTicket and stop checking results
all_disks_passed = False
state.ost.set_flag_failed('Disk')
break
if not all(t.passed for t in disk.tests):
all_disks_passed = False
break
# All disks passed
if all_disks_passed and num_disk_tests_run == num_disk_tests:
# Only mark as passed if a full disk diagnostic passed
state.ost.set_flag_passed('Disk')
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -10,7 +10,7 @@ from wk.cfg.hw import (
BADBLOCKS_LARGE_DISK,
BADBLOCKS_REGEX,
BADBLOCKS_RESULTS_REGEX,
#BADBLOCKS_SKIP_REGEX,
BADBLOCKS_SKIP_REGEX,
TEST_MODE_BADBLOCKS_LIMIT,
)
from wk.exe import run_program
@ -25,52 +25,34 @@ LOG = logging.getLogger(__name__)
# Functions
def check_surface_scan_results(test_obj, log_path) -> None:
"""Check results and set test status."""
report = []
report_color = None
# Read result
with open(log_path, 'r', encoding='utf-8') as _f:
for line in _f.readlines():
line = ansi.strip_colors(line.strip())
if not line:
if not line or BADBLOCKS_SKIP_REGEX.match(line):
# Skip
continue
# Clean line by removing backspaces/etc
match = BADBLOCKS_RESULTS_REGEX.match(line)
if match:
if match.group(2) == ' done':
line = BADBLOCKS_RESULTS_REGEX.sub(r'\1done', line)
else:
line = BADBLOCKS_RESULTS_REGEX.sub(r'\1\2', line)
line = line.replace(r'\x08', '')
line = line.strip()
line = BADBLOCKS_RESULTS_REGEX.sub(r'\1 \2', line)
# Add to report
report.append(line)
match = BADBLOCKS_REGEX.search(line)
if match:
if all(s == '0' for s in match.groups()):
test_obj.passed = True
test_obj.report.append(f' {line}')
test_obj.set_status('Passed')
else:
test_obj.failed = True
test_obj.report.append(f' {ansi.color_string(line, "YELLOW")}')
test_obj.set_status('Failed')
# Set report color and save to test_obj
if test_obj.failed:
report_color = 'RED'
elif not test_obj.passed:
report_color = 'YELLOW'
for line in report:
test_obj.report.append(f' {ansi.color_string(line, report_color)}')
# Handle undefined result status
else:
test_obj.report.append(f' {ansi.color_string(line, "YELLOW")}')
if not (test_obj.passed or test_obj.failed):
test_obj.set_status('Unknown')
def run_scan(test_obj, log_path, test_mode=False, max_errors=1) -> None:
def run_scan(test_obj, log_path, test_mode=False) -> None:
"""Run surface scan and handle exceptions."""
block_size = '1024'
dev = test_obj.dev
@ -89,17 +71,8 @@ def run_scan(test_obj, log_path, test_mode=False, max_errors=1) -> None:
or dev.size >= BADBLOCKS_LARGE_DISK):
block_size = '4096'
# Max errors
if int(max_errors) <= 0:
max_errors = ''
else:
max_errors = f'-e{max_errors}'
# Start scan
cmd = ['sudo', 'badblocks', '-sv', '-b', block_size]
if max_errors:
cmd.append(max_errors)
cmd.append(dev_path)
cmd = ['sudo', 'badblocks', '-sv', '-b', block_size, '-e', '1', dev_path]
if test_mode:
# Only test a limited scope instead of the whole device
cmd.append(TEST_MODE_BADBLOCKS_LIMIT)

View file

@ -2,7 +2,6 @@
# vim: sts=2 sw=2 ts=2
import logging
import os
import plistlib
import re
@ -18,7 +17,6 @@ from wk.ui import ansi
# STATIC VARIABLES
LOG = logging.getLogger(__name__)
EVERYMAC_URL = 'https://everymac.com/ultimate-mac-lookup/?search_keywords='
@dataclass(slots=True)
@ -35,7 +33,7 @@ class System:
self.set_cpu_description()
self.get_ram_details()
def generate_cpu_ram_report(self) -> list[str]:
def generate_report(self) -> list[str]:
"""Generate CPU & RAM report, returns list."""
report = []
report.append(ansi.color_string('Device', 'BLUE'))
@ -51,38 +49,6 @@ class System:
return report
def generate_full_report(self) -> list[str]:
"""Generate full report, returns list."""
report = ['[System]']
report.extend([f'... {line}' for line in self.get_system_info()])
report.append('\n[Motherboard]')
report.extend([f'... {line}' for line in self.get_mobo_info()])
report.append('\n[BIOS]')
report.extend([f'... {line}' for line in self.get_bios_info()])
report.append('\n[CPU]')
report.append(f'... {self.cpu_description}')
report.append('\n[RAM]')
report.append(f'... {self.ram_total} ({", ".join(self.ram_dimms)})')
report.append('\n[GPU]')
report.extend([f'... {line}' for line in self.get_gpu_info()])
return report
def get_bios_info(self) -> list[str]:
"""Get BIOS details, returns list."""
report = []
# Bail early
if PLATFORM != 'Linux':
# Only Linux is supported ATM
return report
# Get details
report.append(f'Version: {get_dmi_info_linux("bios_version")}')
report.append(f'Released: {get_dmi_info_linux("bios_date")}')
# Done
return report
def get_cpu_details(self) -> None:
"""Get CPU details using OS specific methods."""
cmd = ['lscpu', '--json']
@ -102,78 +68,6 @@ class System:
continue
self.raw_details[_field] = _data
def get_gpu_info(self) -> list[str]:
"""Get GPU details, returns list."""
report = []
# Bail early
if PLATFORM != 'Linux':
# Only Linux is supported ATM
return report
# Get PCI details
proc = run_program(['lspci'])
for line in proc.stdout.splitlines():
if 'VGA' not in line:
continue
line = re.sub('^.*:', '', line)
line = re.sub('Integrated Graphics Controller.*', 'iGPU', line)
line = line.replace('Advanced Micro Devices, Inc.', 'AMD')
line = line.replace('Intel Corporation', 'Intel')
line = line.replace('Generation Core Processor Family', 'Gen')
report.append(f'{line.strip()}')
# Get GLX info
if 'DISPLAY' in os.environ or 'WAYLAND_DISPLAY' in os.environ:
proc = run_program(['glxinfo'])
for line in proc.stdout.splitlines():
if 'OpenGL renderer' in line:
line = re.sub('^.*:', '', line)
report.append(line.strip())
break
# Done
return report
def get_mobo_info(self) -> list[str]:
"""Get motherboard details, returns list."""
report = []
# Bail early
if PLATFORM != 'Linux':
# Only Linux is supported ATM
return report
# Get details
report.append(f'Vendor: {get_dmi_info_linux("board_vendor")}')
report.append(f'Name: {get_dmi_info_linux("board_name")}')
report.append(f'Version: {get_dmi_info_linux("board_version")}')
report.append(f'Serial: {get_dmi_info_linux("board_serial")}')
# Done
return report
def get_system_info(self) -> list[str]:
"""Get system details, returns list."""
report = []
# Bail early
if PLATFORM != 'Linux':
# Only Linux is supported ATM
return report
# Get details
vendor = get_dmi_info_linux("sys_vendor")
serial = get_dmi_info_linux("product_serial")
report.append(f'Vendor: {vendor}')
report.append(f'Name: {get_dmi_info_linux("product_name")}')
report.append(f'Serial: {serial}')
if 'apple' in vendor.lower():
report.append(f'{EVERYMAC_URL}{serial}')
# Done
return report
def get_ram_details(self) -> None:
"""Get RAM details using OS specific methods."""
if PLATFORM == 'Darwin':
@ -211,16 +105,6 @@ class System:
self.cpu_description = re.sub(r'\s+', ' ', proc.stdout.strip())
def get_dmi_info_linux(value) -> str:
"""Get DMI info, returns str."""
dmi_path = '/sys/devices/virtual/dmi/id'
cmd = ['sudo', 'cat', f'{dmi_path}/{value}']
proc = run_program(cmd, check=False)
if proc.returncode:
return '[???]'
return proc.stdout.strip()
def get_ram_list_linux() -> list[list]:
"""Get RAM list using dmidecode."""
cmd = ['sudo', 'dmidecode', '--type', 'memory']

View file

@ -1,123 +0,0 @@
"""WizardKit: Volume functions"""
# vim: sts=2 sw=2 ts=2
import logging
import re
from wk import os as wk_os
from wk.cfg.hw import (
VOLUME_FAILURE_THRESHOLD,
VOLUME_WARNING_THRESHOLD,
VOLUME_SIZE_THRESHOLD,
)
from wk.std import PLATFORM, bytes_to_string
from wk.ui.ansi import color_string
# STATIC VARIABLES
LOG = logging.getLogger(__name__)
# Functions
def add_dev_line(test_obj, details) -> None:
"""Add device line to test report."""
if details is test_obj.dev:
details = details.raw_details
filesystem = details['fstype']
report_line = re.sub(r"^/dev/", " ", details['name'])
label = details['label']
if label:
label = f', "{color_string(label, "CYAN")}"'
report_line += f' ({filesystem}{label if label else ""})'
# Bail early
if not filesystem:
# Skip devices without a filesystem
return
# Get sizes
used = -1
percent_used = -1
size = details['size']
if PLATFORM == 'Darwin':
size = int(details.get('TotalSize', -1))
free = int(details.get('FreeSpace', 0))
used = size - free
elif PLATFORM == 'Linux':
free = details.get('fsavail', 0)
used = details.get('fsused', -1)
if free is None:
free = 0
if used is None:
used = -1
percent_used = (used / size) * 100
# Report Bitlocker
if filesystem == 'BitLocker':
test_obj.report.append(f'{report_line} {bytes_to_string(size)}')
# Handle unsupported devices
if not details['mountpoint']:
# Under Linux the volume needs to be mounted to get used space
used = -1
# Check for failures
if (used > 0
and percent_used >= VOLUME_FAILURE_THRESHOLD
and size >= VOLUME_SIZE_THRESHOLD * 1024**3):
test_obj.failed = True
# Build and color size_line if needed
color = None
if test_obj.failed:
color = 'RED'
elif percent_used >= VOLUME_WARNING_THRESHOLD:
color = 'YELLOW'
size_line = f'{bytes_to_string(size)}'
if used > 0:
size_line += f' ({bytes_to_string(used)} used, {percent_used:0.0f}% full)'
size_line = color_string(size_line, str(color))
# Done
test_obj.report.append(f'{report_line} {size_line}')
def check_volume_utilization(test_obj) -> None:
"""Check volume utilization using OS specific methods."""
dev = test_obj.dev
# Mount all volumes (read only if possible)
mount_all_volumes(test_obj.dev)
dev.update_details(skip_children=False)
# Build report
test_obj.report.append(color_string('Disk Utilization', 'BLUE'))
for _d in (dev, *dev.children):
add_dev_line(test_obj, _d)
# Update test object
if test_obj.failed:
test_obj.dev.add_note('Full volume(s) detected', color='YELLOW')
test_obj.passed = False
test_obj.set_status('Failed')
else:
test_obj.passed = True
test_obj.set_status('Passed')
def mount_all_volumes(dev) -> None:
"""Mount all volumes for dev using."""
if PLATFORM == 'Darwin':
# NOTE: Disabled due to a bug they should already be mounted by macOS
#wk_os.mac.mount_disk(device_path=dev.path)
pass
elif PLATFORM == 'Linux':
wk_os.linux.mount_volumes(
device_path=dev.path,
read_write=False,
scan_corestorage=not any(t.failed for t in dev.tests),
)
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -43,7 +43,7 @@ def case_insensitive_search(
path = pathlib.Path(path).resolve()
given_path = path.joinpath(item)
real_path = None
regex = fr'^{item}'
regex = fr'^{item}$'
# Quick check
if given_path.exists():

View file

@ -11,6 +11,7 @@ import re
from wk.cfg.launchers import LAUNCHERS
from wk.cfg.main import ARCHIVE_PASSWORD, KIT_NAME_FULL
from wk.cfg.music import MUSIC_MOD, MUSIC_SNES, MUSIC_SNES_BAD
from wk.cfg.sources import SOURCES
from wk.exe import popen_program, run_program, wait_for_procs
from wk.io import copy_file, delete_item, recursive_copy, rename_item
@ -122,16 +123,9 @@ def download_aida64() -> None:
def download_autoruns() -> None:
"""Download Autoruns."""
out_path = BIN_DIR.joinpath(f'Sysinternals/Autoruns64.exe')
download_file(out_path, SOURCES['Autoruns64'])
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')
delete_item(BIN_DIR.joinpath('BCUninstaller/win-x86'))
for item in ('Autoruns32', 'Autoruns64'):
out_path = BIN_DIR.joinpath(f'Sysinternals/{item}.exe')
download_file(out_path, SOURCES[item])
def download_bleachbit() -> None:
@ -151,6 +145,9 @@ def download_bleachbit() -> None:
def download_bluescreenview() -> None:
"""Download BlueScreenView."""
archive_32 = download_to_temp(
'bluescreenview32.zip', SOURCES['BlueScreenView32'],
)
archive_64 = download_to_temp(
'bluescreenview64.zip', SOURCES['BlueScreenView64'],
)
@ -160,21 +157,11 @@ def download_bluescreenview() -> None:
out_path.joinpath('BlueScreenView.exe'),
out_path.joinpath('BlueScreenView64.exe'),
)
extract_archive(archive_32, out_path)
delete_from_temp('bluescreenview32.zip')
delete_from_temp('bluescreenview64.zip')
def download_coretemp():
"""Download Core Temp."""
archive_64 = download_to_temp('coretemp64.zip', SOURCES['CoreTemp64'])
out_path = BIN_DIR.joinpath('CoreTemp')
extract_archive(archive_64, out_path, 'Core Temp.exe')
rename_item(
out_path.joinpath('Core Temp.exe'),
out_path.joinpath('CoreTemp64.exe'),
)
delete_from_temp('coretemp64.zip')
def download_ddu() -> None:
"""Download Display Driver Uninstaller."""
archive = download_to_temp('DDU.exe', SOURCES['DDU'])
@ -191,6 +178,13 @@ def download_ddu() -> None:
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."""
archive = download_to_temp('erunt.zip', SOURCES['ERUNT'])
@ -200,6 +194,7 @@ def download_erunt() -> None:
def download_everything() -> None:
"""Download Everything."""
archive_32 = download_to_temp('everything32.zip', SOURCES['Everything32'])
archive_64 = download_to_temp('everything64.zip', SOURCES['Everything64'])
out_path = BIN_DIR.joinpath('Everything')
extract_archive(archive_64, out_path, 'Everything.exe')
@ -207,6 +202,8 @@ def download_everything() -> None:
out_path.joinpath('Everything.exe'),
out_path.joinpath('Everything64.exe'),
)
extract_archive(archive_32, out_path)
delete_from_temp('everything32.zip')
delete_from_temp('everything64.zip')
@ -214,11 +211,14 @@ def download_fastcopy() -> None:
"""Download FastCopy."""
installer = download_to_temp('FastCopyInstaller.exe', SOURCES['FastCopy'])
out_path = BIN_DIR.joinpath('FastCopy')
run_program([installer, '/NOSUBDIR', f'/DIR={out_path}', '/EXTRACT64'])
tmp_path = TMP_DIR.joinpath('FastCopy64')
run_program([installer, '/NOSUBDIR', f'/DIR={out_path}', '/EXTRACT32'])
run_program([installer, '/NOSUBDIR', f'/DIR={tmp_path}', '/EXTRACT64'])
rename_item(
out_path.joinpath('FastCopy.exe'),
tmp_path.joinpath('FastCopy.exe'),
out_path.joinpath('FastCopy64.exe'),
)
delete_from_temp('FastCopy64')
delete_from_temp('FastCopyInstaller.exe')
delete_item(BIN_DIR.joinpath('FastCopy/setup.exe'))
@ -243,23 +243,11 @@ def download_furmark() -> None:
delete_from_temp('FurMarkInstall')
def download_libreoffice() -> None:
"""Download LibreOffice."""
out_path = INSTALLERS_DIR.joinpath(f'LibreOffice64.msi')
download_file(
out_path,
SOURCES[f'LibreOffice64'],
referer='https://www.libreoffice.org/download/download-libreoffice/',
)
ui.sleep(1)
def download_linux_reader():
"""Download Linux Reader."""
installer = download_to_temp('LinuxReader.exe', SOURCES['Linux Reader'])
out_path = BIN_DIR.joinpath('LinuxReader')
extract_archive(installer, out_path, '-x!*PLUGINSDIR', '-x!Uninstall*')
delete_from_temp('LinuxReader.exe')
def download_hwinfo() -> None:
"""Download HWiNFO."""
archive = download_to_temp('HWiNFO.zip', SOURCES['HWiNFO'])
extract_to_bin(archive, 'HWiNFO')
delete_from_temp('HWiNFO.zip')
def download_macs_fan_control() -> None:
@ -268,6 +256,18 @@ def download_macs_fan_control() -> None:
download_file(out_path, SOURCES['Macs Fan Control'])
def download_libreoffice() -> None:
"""Download LibreOffice."""
for arch in 32, 64:
out_path = INSTALLERS_DIR.joinpath(f'LibreOffice{arch}.msi')
download_file(
out_path,
SOURCES[f'LibreOffice{arch}'],
referer='https://www.libreoffice.org/download/download-libreoffice/',
)
ui.sleep(1)
def download_neutron() -> None:
"""Download Neutron."""
archive = download_to_temp('neutron.zip', SOURCES['Neutron'])
@ -295,13 +295,6 @@ def download_openshell() -> None:
download_file(out_path, SOURCES[name[:-4]])
def download_prime95():
"""Download Prime95."""
archive = download_to_temp('prime95.zip', SOURCES['Prime95'])
extract_to_bin(archive, 'Prime95')
delete_from_temp('prime95.zip')
def download_putty() -> None:
"""Download PuTTY."""
archive = download_to_temp('putty.zip', SOURCES['PuTTY'])
@ -309,12 +302,6 @@ def download_putty() -> None:
delete_from_temp('putty.zip')
def download_shutup10():
"""Download O&O ShutUp10."""
out_path = BIN_DIR.joinpath('ShutUp10/OOSU10.exe')
download_file(out_path, SOURCES['ShutUp10'])
def download_snappy_driver_installer_origin() -> None:
"""Download Snappy Driver Installer Origin."""
archive = download_to_temp('aria2.zip', SOURCES['Aria2'])
@ -385,29 +372,6 @@ def download_snappy_driver_installer_origin() -> None:
delete_from_temp('fake.7z')
def download_windows_repair_aio():
"""Download Windows Repair AIO."""
archive = download_to_temp('winrepairaio.zip', SOURCES['Windows Repair AIO'])
out_path = BIN_DIR.joinpath('WinRepairAIO')
tmp_path = TMP_DIR.joinpath('WinRepairAIO')
extract_archive(archive, tmp_path)
for item in tmp_path.joinpath('Tweaking.com - Windows Repair').iterdir():
try:
rename_item(item, out_path.joinpath(item.name))
except FileExistsError:
# Ignore and use our defaults
pass
delete_from_temp('WinRepairAIO')
delete_from_temp('winrepairaio.zip')
def download_winscp():
"""Download WinSCP."""
archive = download_to_temp('winscp.zip', SOURCES['WinSCP'])
extract_to_bin(archive, 'WinSCP')
delete_from_temp('winscp.zip')
def download_wiztree() -> None:
"""Download WizTree."""
archive = download_to_temp('wiztree.zip', SOURCES['WizTree'])
@ -443,6 +407,57 @@ def download_xmplay() -> None:
delete_from_temp('xmp-rar.zip')
delete_from_temp('Innocuous.zip')
def download_xmplay_music() -> None:
"""Download XMPlay Music."""
music_tmp = TMP_DIR.joinpath('music')
music_tmp.mkdir(exist_ok=True)
current_dir = os.getcwd()
os.chdir(music_tmp)
url_mod = 'https://api.modarchive.org/downloads.php'
url_rsn = 'http://snesmusic.org/v2/download.php'
# Download music
for song_id, song_name in MUSIC_MOD:
download_file(
music_tmp.joinpath(f'MOD/{song_name}'),
f'{url_mod}?moduleid={song_id}#{song_name}',
)
for game in MUSIC_SNES:
download_file(
music_tmp.joinpath(f'SNES/{game}.rsn'),
f'{url_rsn}?spcNow={game}',
)
# Extract SNES archives
for item in music_tmp.joinpath('SNES').iterdir():
cmd = [
SEVEN_ZIP,
'x', item, f'-oSNES\\{item.stem}',
'-bso0', '-bse0', '-bsp0',
]
run_program(cmd)
delete_item(item)
# Remove bad songs
for game, globs in MUSIC_SNES_BAD.items():
for glob in globs:
for item in music_tmp.joinpath(f'SNES/{game}').glob(glob):
delete_item(item)
# Compress music
cmd = [
SEVEN_ZIP,
'a', '-t7z', '-mx=9',
'-bso0', '-bse0', '-bsp0',
BIN_DIR.joinpath('XMPlay/music.7z'),
'MOD', 'SNES',
]
run_program(cmd)
os.chdir(current_dir)
# Cleanup
delete_from_temp('music')
# "Main" Function
def build_kit() -> None:
@ -467,27 +482,23 @@ def build_kit() -> None:
try_print.run('Autoruns...', download_autoruns)
try_print.run('BleachBit...', download_bleachbit)
try_print.run('BlueScreenView...', download_bluescreenview)
try_print.run('CoreTemp...', download_coretemp)
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('FastCopy...', download_fastcopy)
try_print.run('FurMark...', download_furmark)
try_print.run('HWiNFO...', download_hwinfo)
try_print.run('LibreOffice...', download_libreoffice)
try_print.run('Linux Reader...', download_linux_reader)
try_print.run('Macs Fan Control...', download_macs_fan_control)
try_print.run('Neutron...', download_neutron)
try_print.run('Notepad++...', download_notepad_plus_plus)
try_print.run('O&O ShutUp10...', download_shutup10)
try_print.run('OpenShell...', download_openshell)
try_print.run('Prime95...', download_prime95)
try_print.run('PuTTY...', download_putty)
try_print.run('WinSCP...', download_winscp)
try_print.run('Windows Repair AIO...', download_windows_repair_aio)
try_print.run('Snappy Driver Installer...', download_snappy_driver_installer_origin)
try_print.run('WizTree...', download_wiztree)
try_print.run('XMPlay...', download_xmplay)
try_print.run('Snappy Driver Installer...', download_snappy_driver_installer_origin)
try_print.run('XMPlay Music...', download_xmplay_music)
# Pause
print('', flush=True)

View file

@ -6,18 +6,14 @@ import pathlib
import platform
from datetime import datetime, timedelta
from subprocess import CalledProcessError, CompletedProcess, Popen
from subprocess import CompletedProcess, Popen
import requests
from wk.cfg.main import ARCHIVE_PASSWORD
from wk.cfg.net import SDIO_SERVER
from wk.cfg.sources import DOWNLOAD_FREQUENCY, SOURCES
from wk.exe import popen_program, run_program
from wk.io import get_path_obj
from wk.net import mount_network_share
from wk.std import GenericError, sleep
from wk.ui.cli import abort, ask, print_error
# STATIC VARIABLES
@ -29,14 +25,6 @@ HEADERS = {
'Gecko/20100101 Firefox/97.0'
),
}
MOUNT_EXCEPTIONS = (RuntimeError, CalledProcessError)
SDIO_REMOTE_PATH = get_path_obj(
(
fr'\\{SDIO_SERVER["Address"]}\{SDIO_SERVER["Share"]}\{SDIO_SERVER["Path"]}'
fr'\SDIO{"64" if ARCH == "64" else ""}.exe'
),
resolve=False,
)
# "GLOBAL" VARIABLES
@ -215,52 +203,6 @@ def get_tool_path(folder, name, check=True, suffix=None) -> pathlib.Path:
return tool_path
def get_sdio_path(interactive: bool) -> pathlib.Path:
"""Try to mount SDIO server."""
use_network = False
sdio_path = get_tool_path("SDIO", "SDIO")
def _mount_server() -> CompletedProcess:
if interactive:
print('Connecting to server... (Press CTRL+c to use local copy)')
return mount_network_share(SDIO_SERVER, read_write=False)
def _try_again(error_msg: str) -> bool:
"""Ask to try again or quit."""
print_error(error_msg)
if ask(' Try again?'):
return True
if not ask(' Use local version?'):
abort()
return False
# Bail early
if not SDIO_SERVER['Address']:
return sdio_path
# Main loop
while True:
try:
proc = _mount_server()
except KeyboardInterrupt:
break
except MOUNT_EXCEPTIONS as err:
if not (interactive and _try_again(f' ERROR: {err}')):
break
else:
if proc.returncode == 0:
# Network copy available
use_network = True
break
# Failed to mount
if not (interactive and _try_again(' Failed to mount server')):
break
# Done
if use_network:
sdio_path = SDIO_REMOTE_PATH
return sdio_path
def run_tool(
folder, name, *run_args,
cbin=False, cwd=False, download=False, popen=False,

View file

@ -1,6 +1,7 @@
"""WizardKit: UFD Functions"""
# vim: sts=2 sw=2 ts=2
import argparse
import logging
import math
import os
@ -9,8 +10,6 @@ import re
import shutil
from subprocess import CalledProcessError
from docopt import docopt
from wk import io, log
from wk.cfg.main import KIT_NAME_FULL, KIT_NAME_SHORT
from wk.cfg.ufd import (
@ -29,32 +28,6 @@ from wk.ui import cli as ui
# STATIC VARIABLES
DOCSTRING = '''WizardKit: Build UFD
Usage:
build-ufd [options] --ufd-device PATH
[--linux PATH]
[--main-kit PATH]
[--winpe PATH]
[--eset 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
-s PATH, --eset 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__)
EXTRA_IMAGES_LIST = '/mnt/UFD/arch/extra_images.list'
MIB = 1024 ** 2
@ -63,6 +36,54 @@ UFD_LABEL = f'{KIT_NAME_SHORT}_UFD'
# Functions
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."""
cmd = [
@ -95,7 +116,12 @@ def apply_image(part_path, image_path, hide_macos_boot=True) -> None:
def build_ufd() -> None:
"""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']:
log.enable_debug_mode()
if args['--update'] and args['EXTRA_IMAGES']:
@ -149,6 +175,13 @@ def build_ufd() -> None:
dev_path=ufd_dev,
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)
# Mount UFD
@ -352,7 +385,7 @@ def create_table(dev_path, use_mbr=False, images=None) -> None:
for part, real in zip(part_sizes, images):
end = start + real
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
@ -438,6 +471,17 @@ def hide_items(ufd_dev_first_partition, items) -> None:
run_program(cmd, shell=True, check=False)
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)."""
cmd = [
@ -571,10 +615,7 @@ def update_boot_entries(ufd_dev, images=None) -> None:
'sed',
'--in-place',
'--regexp-extended',
(
f's/___+/{uuids[0]}/; '
f's#by-label/(eSysRescueLiveCD|{ISO_LABEL}|{UFD_LABEL})#by-uuid/{uuids[0]}#'
),
f's/___+/{uuids[0]}/',
*configs,
]
run_program(cmd)

View file

@ -21,7 +21,7 @@ if os.name == 'nt':
)
else:
# Example: "/home/tech/Logs"
DEFAULT_LOG_DIR = pathlib.Path('~/Logs').expanduser().resolve()
DEFAULT_LOG_DIR = f'{os.path.expanduser("~")}/Logs'
DEFAULT_LOG_NAME = cfg.main.KIT_NAME_FULL

View file

@ -88,15 +88,11 @@ def mount_backup_shares(read_write: bool = False) -> list[str]:
continue
# Mount share
try:
proc = mount_network_share(details, mount_point, read_write=read_write)
if proc.returncode:
report.append(f'Failed to Mount {mount_str}')
else:
report.append(f'Mounted {mount_str}')
except RuntimeError:
# Assuming we're not connected to a network?
proc = mount_network_share(details, mount_point, read_write=read_write)
if proc.returncode:
report.append(f'Failed to Mount {mount_str}')
else:
report.append(f'Mounted {mount_str}')
# Done
return report

View file

@ -2,13 +2,9 @@
# vim: sts=2 sw=2 ts=2
import logging
import plistlib
import re
from wk import std
from wk.exe import run_program
from wk.hw.disk import Disk
from wk.ui import cli
# STATIC VARIABLES
@ -36,189 +32,6 @@ def decode_smc_bytes(text) -> int:
return result
def get_apfs_volumes(device_path):
"""Get APFS volumes contained in device_path, returns list."""
volumes = []
containers = []
# Get APFS details
cmd = ['diskutil', 'apfs', 'list', '-plist']
proc = 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
LOG.error('Failed to get diskutil apfs list for %s', device_path)
# Find container(s) relating to device_path
for container in plist_data['Containers']:
if container['DesignatedPhysicalStore'] == device_path:
containers.append(container)
# Add volumes
for container in containers:
for volume in container['Volumes']:
mount_volume(volume['DeviceIdentifier'])
volumes.append(Disk(f'/dev/{volume["DeviceIdentifier"]}'))
# Done
return volumes
def get_core_storage_volumes(device_path):
"""Get CoreStorage volumes contained in device_path, returns list."""
disks = []
volumes = []
# Get coreStorage details
cmd = ['diskutil', 'corestorage', 'list', '-plist']
proc = 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
LOG.error('failed to get diskutil corestorage list for %s', device_path)
# Find related virtual disks
for l_vg in plist_data['CoreStorageLogicalVolumeGroups']:
related = False
# Compare parent physical volumes against device_path
for p_v in l_vg['CoreStoragePhysicalVolumes']:
uuid = p_v['CoreStorageUUID']
cmd = ['diskutil', 'coreStorage', 'info', '-plist', uuid]
proc = run_program(cmd, check=False, encoding=None, errors=None)
try:
plist_data = plistlib.loads(proc.stdout)
except (TypeError, ValueError):
LOG.error('failed to get diskutil corestorage info for %s', uuid)
continue
if plist_data['DeviceIdentifier'] == device_path:
related = True
break
# Move on if no related p_v was found
if not related:
continue
# Add logical disks to list
for l_vf in l_vg['CoreStorageLogicalVolumeFamilies']:
for l_v in l_vf['CoreStorageLogicalVolumes']:
uuid = l_v['CoreStorageUUID']
cmd = ['diskutil', 'coreStorage', 'info', '-plist', uuid]
proc = run_program(cmd, check=False, encoding=None, errors=None)
try:
plist_data = plistlib.loads(proc.stdout)
except (TypeError, ValueError):
LOG.error('failed to get diskutil corestorage info for %s', uuid)
continue
disks.append(plist_data['DeviceIdentifier'])
# Get volumes from logical disks
for disk in disks:
cmd = ['diskutil', 'list', '-plist', f'/dev/{disk}']
proc = run_program(cmd, check=False, encoding=None, errors=None)
try:
plist_data = plistlib.loads(proc.stdout)
disk_data = plist_data['AllDisksAndPartitions'][0]
except (TypeError, ValueError):
LOG.error('Failed to get diskutil list for %s', disk)
continue
if 'Partitions' in disk_data:
for part in disk_data['Partitions']:
mount_volume(part['DeviceIdentifier'])
volumes.append(Disk(f'/dev/{part["DeviceIdentifier"]}'))
elif 'VolumeName' in disk_data:
mount_volume(disk_data['DeviceIdentifier'])
volumes.append(Disk(f'/dev/{disk_data["DeviceIdentifier"]}'))
# Done
return volumes
def mount_volume(volume_path, read_only=True):
"""Try to mount a volume, returns subprocess.CompletedProcess."""
cmd = ['sudo', 'diskutil', 'mount']
if read_only:
cmd.append('readOnly')
cmd.append(volume_path)
proc = run_program(cmd, check=False)
# Done
return proc
def mount_disk(device_path=None):
"""Mount all detected volumes, returns list.
NOTE: If device_path is specified then only volumes
under that path will be mounted.
"""
report = []
volumes = []
# Get device details
cmd = [
'diskutil',
'list',
'-plist',
]
if device_path:
cmd.append(device_path)
proc = 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
LOG.error('Failed to get diskutil list for %s', device_path)
return report
# Mount and add volumes
for part in plist_data['AllDisksAndPartitions'][0]['Partitions']:
proc = mount_volume(part['DeviceIdentifier'])
# Add volume
volumes.append(Disk(f'/dev/{part["DeviceIdentifier"]}'))
# Add sub-volumes
if proc.returncode:
if part['Content'] == 'Apple_CoreStorage':
volumes.extend(get_core_storage_volumes(part['DeviceIdentifier']))
elif part['Content'] == 'Apple_APFS':
volumes.extend(get_apfs_volumes(part['DeviceIdentifier']))
# Build report from volume list
for vol in volumes:
result = f'{vol.details["name"]:<20}'
# Containers
if vol.details['Content'] in ('Apple_APFS', 'Apple_CoreStorage'):
result += f'{vol.details["Content"].replace("Apple_", "")} container'
report.append(cli.color_string(result, 'BLUE'))
continue
# Unknown partitions
if not vol.details['mountpoint']:
result += 'Failed to mount'
report.append(cli.color_string(result, 'RED'))
continue
# Volumes
result += f'{"Mounted on "+vol.details.get("mountpoint", "?"):<40}'
size = vol.details.get('TotalSize', -1)
free = vol.details.get('FreeSpace', -1)
used = size - free
result = (
f'{result} ({vol.details.get("fstype", "Unknown FS")+",":<5} '
f'{std.bytes_to_string(used, decimals=1):>9} used, '
f'{std.bytes_to_string(free, decimals=1):>9} free) '
)
report.append(result)
# Done
return report
def set_fans(mode) -> None:
"""Set fans to auto or max."""
if mode == 'auto':

View file

@ -6,7 +6,6 @@ import logging
import os
import pathlib
import platform
import re
from contextlib import suppress
from typing import Any
@ -28,7 +27,6 @@ from wk.cfg.windows_builds import (
)
from wk.exe import get_json_from_command, run_program, wait_for_procs
from wk.kit.tools import find_kit_dir
from wk.osticket import osTicket
from wk.std import (
GenericError,
GenericWarning,
@ -75,9 +73,6 @@ KNOWN_HIVE_NAMES = {
RAM_OK = 5.5 * 1024**3 # ~6 GiB assuming a bit of shared memory
RAM_WARNING = 3.5 * 1024**3 # ~4 GiB assuming a bit of shared memory
REG_MSISERVER = r'HKLM\SYSTEM\CurrentControlSet\Control\SafeBoot\Network\MSIServer'
REGEX_4K_ALIGNMENT = re.compile(
r'^(?P<description>.*?)\s+(?P<size>\d+)\s+(?P<offset>\d+)',
)
SLMGR = pathlib.Path(f'{os.environ.get("SYSTEMROOT")}/System32/slmgr.vbs')
SYSTEMDRIVE = os.environ.get('SYSTEMDRIVE')
@ -170,29 +165,23 @@ def set_timezone(zone) -> None:
# Info Functions
def check_4k_alignment(show_alert=False) -> list[str]:
"""Check if all partitions are 4K aligned, returns list."""
cmd = ['WMIC', 'partition', 'get', 'Caption,Size,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
proc = run_program(cmd)
for line in proc.stdout.splitlines():
line = line.strip()
if not line or not line.startswith('Disk'):
continue
match = REGEX_4K_ALIGNMENT.match(line)
if not match:
LOG.error('Failed to parse partition info for: %s', line)
continue
if int(match.group('offset')) % 4096 != 0:
report.append(
ansi.color_string(
f'{match.group("description")}'
f' ({bytes_to_string(match.group("size"), decimals=1)})'
,
'RED'
)
)
for part in json_data:
if part['StartingOffset'] % 4096 != 0:
report.append(
ansi.color_string(
f'{part["Name"]}'
f' ({bytes_to_string(part["Size"], decimals=1)})'
,
'RED'
)
)
# Show alert
if show_alert:
@ -204,109 +193,74 @@ def check_4k_alignment(show_alert=False) -> list[str]:
0,
ansi.color_string('One or more partitions not 4K aligned', 'YELLOW'),
)
report.sort()
return report
def defender_is_disabled():
"""Check if Windows Defender is enabled, returns bool."""
reg_key = r'Software\Microsoft\Windows Defender'
disabled = reg_read_value('HKLM', reg_key, 'DisableAntiSpyware')
disabled = disabled or reg_read_value('HKLM', reg_key, 'DisableAntiVirus')
return bool(disabled)
def export_bitlocker_info() -> None:
"""Get Bitlocker info and save to either the base directory of the kit or osTicket."""
script_path = find_kit_dir('Scripts').joinpath('export_bitlocker.ps1')
cmd = ['PowerShell', '-ExecutionPolicy', 'Bypass', '-File', script_path]
file_name = ''
output = []
# Include OS info
if os.environ.get('SYSTEMDRIVE', 'C:').upper() == 'X:':
# Assuming this is true
output.append('[Check run under Win10XPE]')
else:
output.append(get_os_name())
output.append('')
# Get Bitlocker info
proc = run_program(cmd, check=False)
if proc.returncode or not proc.stdout.strip():
output.append('Error: Failed to export Bitlocker info\n')
output.extend(proc.stdout.splitlines())
# Show info
print('\n'.join(output), '\n\n')
# Save to osTicket
ost = osTicket()
ost.init()
ost.select_ticket()
if not ost.disabled:
ost.post_response('\n'.join(output))
result = 'OK'
if ost.disabled or ost.errors:
result = 'Unknown'
print(
ansi.color_string(
['\nPost info to osTicket... ', result],
[None, 'GREEN' if result == 'OK' else 'YELLOW'],
)
)
# Save to file
if ost.ticket_name:
file_name = f'{ost.ticket_id}_{ost.ticket_name.replace(" ", "-")}'
if not file_name:
file_name = ui.input_text(prompt_msg='Enter filename: ', allow_empty=False)
file_path = pathlib.Path(f'../../../Bitlocker_{file_name}.txt').resolve()
with open(file_path, 'a', encoding='utf-8') as _f:
_f.write('\n'.join(output))
# Done
ui.pause('\nPress Enter to exit...')
def get_installed_antivirus() -> list[str]:
"""Get list of installed antivirus programs, returns list."""
cmd = [
'WMIC', r'/namespace:\\root\SecurityCenter2',
'path', 'AntivirusProduct',
'get', 'displayName', '/value',
"""Get Bitlocker info and save to the base directory of the kit."""
commands = [
['manage-bde', '-status', SYSTEMDRIVE],
['manage-bde', '-protectors', '-get', SYSTEMDRIVE],
]
products = []
report = []
# Get list of products
proc = run_program(cmd)
for line in proc.stdout.splitlines():
line = line.strip()
if '=' in line:
products.append(line.split('=')[1])
# Get filename
file_name = ui.input_text(prompt_msg='Enter filename')
file_path = pathlib.Path(f'../../Bitlocker_{file_name}.txt').resolve()
# Save info
with open(file_path, 'a', encoding='utf-8') as _f:
for cmd in commands:
proc = run_program(cmd, check=False)
_f.write(f'{proc.stdout}\n\n')
def get_installed_antivirus() -> dict[str, dict]:
"""Get installed antivirus products and their status, returns dict."""
script_path = find_kit_dir('Scripts').joinpath('check_av.ps1')
cmd = ['PowerShell', '-ExecutionPolicy', 'Bypass', '-File', script_path]
json_data = get_json_from_command(cmd)
products = {}
# Check state and build dict
for p in json_data:
name = p['displayName']
state = p['productState']
enabled = ((state>>8) & 0x11) in (0x10, 0x11) # middle two hex digits
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
for product in sorted(products):
cmd = [
'WMIC', r'/namespace:\\root\SecurityCenter2',
'path', 'AntivirusProduct',
'where', f'displayName="{product}"',
'get', 'productState', '/value',
]
proc = run_program(cmd)
state = proc.stdout.split('=')[1]
state = hex(int(state))
if str(state)[3:5] not in ['10', '11']:
report.append(ansi.color_string(f'[Disabled] {product}', 'YELLOW'))
for name, details in products.items():
if details['Enabled']:
if details['Outdated']:
products_active.append(ansi.color_string(f'{name} [OUTDATED]', 'YELLOW'))
else:
products_active.append(name)
else:
report.append(product)
# Disabled
products_inactive.append(ansi.color_string(f'[Disabled] {name}', 'YELLOW'))
# Final check
if not report:
report.append(ansi.color_string('No products detected', 'RED'))
if not (products_active or products_inactive):
products_inactive.append(ansi.color_string('No products detected', 'RED'))
# Done
return report
products_active.sort()
products_inactive.sort()
return products_active + products_inactive
def get_installed_ram(as_list=False, raise_exceptions=False) -> list | str:

View file

@ -1,431 +0,0 @@
"""WizardKit: osTicket Functions"""
# vim: sts=2 sw=2 ts=2
import atexit
import logging
import pathlib
import time
import mariadb
from wk.cfg.hw import TESTSTATION_FILE
from wk.cfg.osticket import SQL, STAFF
from wk.ui import ansi, cli
# STATIC_VARIABLES
LOG = logging.getLogger(__name__)
FLAG_CODES = {
'Pass': 1,
'Fail': 2,
}
FLAG_CPU = 'zTemps'
FLAG_DISK = 'zHDTune'
FLAG_MAX_TEMP = 'zMaxTemp'
RESPONSE_COLOR_CODES = {
'Normal': '0',
'Contact': '1',
'Diags': '2',
'Diags FAIL': '3',
'Money': '4',
}
TABLE_RESPONSE = 'ost_ticket_response'
TABLE_TICKET = 'ost_ticket'
# Classes
class osTicket():
"""Class to track osTicket data and functions."""
def __init__(self):
self.db_connection = None
self.db_cursor = None
self.disabled: bool = False
self.errors: bool = False
self.note: str = ''
self.ticket_id: int | None = None
self.ticket_name: str | None = None
self.ticket_subject: str | None = None
# Ensure connection is closed atexit
atexit.register(self._disconnect)
def _connect(self, silent=True):
"""Establish connection to osTicket."""
if self.disabled:
return
# Connect to database
try:
self.db_connection = mariadb.connect(
host=SQL['Host'],
port=SQL['Port'],
database=SQL['DB'],
user=SQL['User'],
password=SQL['Pass'],
connect_timeout=5,
)
self.db_cursor = self.db_connection.cursor()
except mariadb.Error:
# Assuming network issue or bad creds
pass
# Raise exception if necessary
if self.db_cursor is None:
LOG.error('Failed to connect to osTicket database')
if silent:
# Don't raise exceptions, just disable ost
self.disabled = True
self.errors = True
else:
raise RuntimeError('Failed to connect to osTicket database')
def _disconnect(self):
"""Close osTicket connection."""
for db_obj in (self.db_cursor, self.db_connection):
if db_obj:
try:
db_obj.close()
except mariadb.Error:
# Ignore errors since vars will be reset below
pass
# Reset db objects
self.db_cursor = None
self.db_connection = None
def _get_flag(self, flag_name):
"""Get flag for self.ticket_id from osTicket, returns str."""
flag_value = None
self._verify_ticket_id()
# Build SQL cmd
sql_cmd = (
f"SELECT `{flag_name}` FROM `{SQL['DB']}`.`{TABLE_TICKET}` "
f"WHERE `{TABLE_TICKET}`.`ticket_id` = {self.ticket_id};"
)
# Run SQL cmd
try:
self.db_cursor.execute(sql_cmd)
for s in self.db_cursor:
flag_value = s[0]
except mariadb.Error as err_msg:
cli.print_error(err_msg)
self.errors = True
# Done
return str(flag_value)
def _get_ticket_field(self, ticket_id, field_name):
"""Get field for ticket_id from osTicket, returns str."""
field_data = None
# Build SQL cmd
sql_cmd = (
f"SELECT {field_name} FROM `{SQL['DB']}`.`{TABLE_TICKET}` "
f"WHERE `{TABLE_TICKET}`.`ticket_id` = {ticket_id};"
)
# Lookup data
# NOTE: If multiple entries are found it will return the last
try:
self.db_cursor.execute(sql_cmd)
for result in self.db_cursor:
field_data = result[0]
except mariadb.Error as err_msg:
# Show error and return None
cli.print_error(err_msg)
# Done
return field_data
def _set_flag(self, flag_name, flag_value):
"""Set flag_name to flag_value for ticket_id in osTicket.
NOTE: This will overwrite any existing value.
"""
self._verify_ticket_id()
sql_cmd = (
f"UPDATE `{SQL['DB']}`.`{TABLE_TICKET}` "
f"SET `{flag_name}` = '{flag_value}' "
f"WHERE `{TABLE_TICKET}`.`ticket_id` = {self.ticket_id};"
)
# Run SQL cmd
try:
self.db_cursor.execute(sql_cmd)
except mariadb.Error as err_msg:
cli.print_error(err_msg)
self.errors = True
def _verify_ticket_id(self):
"""Verify that ticket_id has been set."""
if not self.ticket_id:
LOG.error('Ticket ID not set')
raise RuntimeError('Ticket ID not set')
def add_note(self, prompt: str = 'Add note') -> list[str]:
"""Add note to be included in osTicket replies."""
lines = []
if not prompt:
prompt = 'Please enter any additional information for this ticket'
# Instructions
cli.print_standard(prompt)
cli.print_info(' (End note with an empty line)')
cli.print_standard(' ')
# Get note
while True:
text = cli.input_text('> ', allow_empty=True)
if not text:
break
lines.append(text.strip())
# Save note
if lines:
self.note = lines[0]
for line in lines[1:]:
self.note += f'\n...{line}'
else:
self.note = ''
# Done
return lines
def init(self):
"""Revert to defaults."""
self._disconnect()
self.disabled = False
self.errors = False
self.note = None
self.ticket_id = None
self.ticket_name = None
def post_response(self, response, color='Normal'):
"""Post a reply to a ticket in osTicket."""
cur_date_time = time.strftime('%Y-%m-%d %H:%M:%S')
lines = []
test_station = get_test_station_name()
self._connect(silent=True)
self._verify_ticket_id()
# Bail if disabled
if self.disabled:
return
# Format response
if test_station:
lines.append(f'[Test-Station: {test_station}]')
lines.append(f'[Report for ticket #{self.ticket_id} {self.ticket_name}]')
if self.note:
lines.append(f'[Note] {self.note}\n')
lines.append(str(response))
response = '\n'.join(lines)
response = ansi.strip_colors(response)
response = response.replace("`", "'").replace("'", "\\'")
# Build SQL cmd
sql_cmd = (
f"INSERT INTO `{SQL['DB']}`.`{TABLE_RESPONSE}` "
f"(ticket_id, staff_id, staff_name, response, created, code) "
f"VALUES ("
f" '{self.ticket_id}',"
f" '{STAFF['ID']}',"
f" '{STAFF['Name']}',"
f" '{response}',"
f" '{cur_date_time}',"
f" '{RESPONSE_COLOR_CODES.get(color, 'Normal')}'"
f");"
)
# Run SQL cmd
try:
self.db_cursor.execute(sql_cmd)
except mariadb.Error:
self.errors = True
# Update ticket last repsonse field
sql_cmd = (
f"UPDATE `{SQL['DB']}`.`{TABLE_TICKET}`"
f"SET `lastresponse` = '{cur_date_time}' "
f"WHERE `{TABLE_TICKET}`.`ticket_id` = {self.ticket_id};"
)
try:
self.db_cursor.execute(sql_cmd)
except mariadb.Error:
self.errors = True
# Done
self._disconnect()
def select_ticket(self):
"""Set ticket number and name from osTicket DB."""
cli.print_standard('Connecting to osTicket...')
# Bail if disabled
if self.disabled:
return
# Connect
while True:
try:
self._connect(silent=False)
except (mariadb.Error, RuntimeError):
cli.print_warning('Failed to connect to osTicket')
if not cli.ask('Try again?'):
cli.print_standard('Integration disabled for this session')
self.disabled = True
return
else:
# Connection successful
break
# Main loop
while self.ticket_id is None:
cli.print_standard(' ')
_id = cli.input_text(
'Enter ticket number (or leave blank to disable): ', allow_empty=True,
)
_id = _id.strip()
# Nothing entered
if not _id:
print(' ')
if cli.ask('Disable osTicket integration for this session?'):
self.disabled = True
break
# Invalid ID entered
if not _id.isnumeric():
continue
# Valid ID entered, lookup name
_name = self._get_ticket_field(_id, 'name')
# Invalid ticket selected
if _name is None:
cli.print_error(f'Ticket #{_id} not found')
continue
# Valid ticket selected, lookup subject
_subject = self._get_ticket_field(_id, 'subject')
# Verify selection
cli.print_colored(
['You have selected ticket', f'#{_id}', _name],
[None, 'BLUE', None],
)
cli.print_colored(f' {_subject}', 'CYAN')
cli.print_standard(' ')
if cli.ask('Is this correct?'):
self.ticket_id = _id
self.ticket_name = _name
self.ticket_subject = _subject
# Done
self._disconnect()
def set_cpu_max_temp(self, temp):
"""Set CPU max temp in osTicket for ticket_id.
NOTE: This will not replace a higher temp value.
"""
LOG.info('Setting max CPU temp to %s', temp)
self._connect(silent=True)
# Bail if disabled
if self.disabled:
return
# Compare to current temp
current_temp = self._get_flag(FLAG_MAX_TEMP)
if str(current_temp).isnumeric() and int(current_temp) > temp:
cli.print_warning('Not replacing higher temp in osTicket')
self._disconnect()
return
# Update temp
self._set_flag(FLAG_MAX_TEMP, int(temp))
# Done
self._disconnect()
def set_flag_failed(self, flag_name):
"""Set flag as failed in osTicket for ticket_id."""
LOG.warning('Setting osTicket %s checkbox to FAILED', flag_name)
real_flag_name = FLAG_CPU if flag_name == 'CPU' else FLAG_DISK
self._connect(silent=True)
# Bail if disabled
if self.disabled:
return
# Set flag
self._set_flag(real_flag_name, FLAG_CODES['Fail'])
# Done
self._disconnect()
def set_flag_passed(self, flag_name):
"""Set flag as passed in osTicket for ticket_id.
NOTE: This will not overwrite a failed status.
"""
real_flag_name = FLAG_CPU if flag_name == 'CPU' else FLAG_DISK
self._connect(silent=True)
# Bail if disabled
if self.disabled:
return
# Bail if flag checkbox set as FAILED
if self._get_flag(real_flag_name) == str(FLAG_CODES['Fail']):
cli.print_warning(
f'Not replacing osTicket {flag_name} checkbox FAILED value',
)
self._disconnect()
return
# Current value != to FAILED, updating checkbox
LOG.info('Setting osTicket %s checkbox to PASSED', flag_name)
self._set_flag(real_flag_name, FLAG_CODES['Pass'])
# Done
self._disconnect()
# Functions
def get_test_station_name():
"""Get test station name, returns str."""
hostname_file = pathlib.Path(TESTSTATION_FILE)
# Bail early
if not hostname_file.exists():
return ''
if not hostname_file.read_text(encoding='utf-8'):
return ''
# Done
return hostname_file.read_text(encoding='utf-8').splitlines()[0]
def pad_with_dots(text, pad_right=False):
"""Replace space padding with dots, returns str.
NOTE: This is a dumb hack to better align text in osTicket.
"""
text = str(text)
text = text.replace(' ', '..')
if '.' in text:
if pad_right:
text = f'{text}.'
else:
text = f'.{text}'
# Done
return text
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -30,7 +30,6 @@ from wk.exe import (
get_procs,
run_program,
popen_program,
set_proc_priority,
wait_for_procs,
)
from wk.io import (
@ -40,22 +39,14 @@ from wk.io import (
non_clobber_path,
rename_item,
)
from wk.kit.tools import (
download_tool,
extract_archive,
get_tool_path,
run_tool,
)
from wk.kit.tools import (download_tool, get_tool_path, run_tool)
from wk.log import (
format_log_path,
get_root_logger_path,
update_log_path,
)
from wk.os.win import (
ARCH,
OS_VERSION,
defender_is_disabled,
show_alert_box,
get_timezone,
set_timezone,
reg_delete_value,
@ -94,16 +85,7 @@ PROGRAMFILES_32 = os.environ.get(
'PROGRAMFILES', r'C:\Program Files (x86)',
),
)
MBAM_EXE_PATH = 'Malwarebytes/Anti-Malware/mbam.exe'
MBAM_PRESERVE_MARKER = 'Preserve-MBAM.marker'
MBAM_UNINSTALL_KEY = (
r'Software\Microsoft\Windows\CurrentVersion\Uninstall'
r'\{35065F43-4BB2-439A-BFF7-0F1014F2E0CD}_is1'
)
MS_ANTIVIRUS_ENABLED = 0x1000
PROGRAMDATA = os.environ.get('{ALLUSERSPROFILE}', r'C:\ProgramData')
SYSTEMDRIVE = os.environ.get('SYSTEMDRIVE', 'C:')
EMSISOFT_INSTALL_PATH = get_path_obj(f'{SYSTEMDRIVE}/EmsisoftCmd')
WHITELIST = '\n'.join((
str(CONEMU_EXE),
fr'{PROGRAMFILES_32}\TeamViewer\TeamViewer.exe',
@ -189,17 +171,22 @@ def build_menus(base_menus, title, presets) -> dict[str, ui.Menu]:
return menus
def end_session(menus: dict[str, ui.Menu]) -> None:
"""End Auto Repairs session."""
# Add last run marker
reg_set_value(
'HKCU',
fr'Software\{KIT_NAME_FULL}',
'AutoRepairsLastRun',
time.strftime('%Y-%m-%d'),
'SZ',
)
def update_scheduled_task() -> None:
"""Create (or update) scheduled task to start repairs."""
cmd = [
'schtasks', '/create', '/f',
'/sc', 'ONLOGON',
'/tn', f'{KIT_NAME_FULL}-AutoRepairs',
'/rl', 'HIGHEST',
'/tr', fr'C:\Windows\System32\cmd.exe "/C {sys.executable} {sys.argv[0]}"',
]
if IN_CONEMU:
cmd[-1] = f'{CONEMU_EXE} -run {sys.executable} {sys.argv[0]}'
run_program(cmd)
def end_session() -> None:
"""End Auto Repairs session."""
# Remove logon task
cmd = [
'schtasks', '/delete', '/f',
@ -231,18 +218,10 @@ def end_session(menus: dict[str, ui.Menu]) -> None:
except FileNotFoundError:
LOG.error('Ending repair session but session not started.')
try:
reg_delete_value('HKCU', AUTO_REPAIR_KEY, 'Kill Explorer')
except FileNotFoundError:
# Ignore if not set
pass
for group in menus:
try:
cmd = ['reg', 'delete', fr'HKCU\{AUTO_REPAIR_KEY}\{group}', '/f']
run_program(cmd)
except CalledProcessError:
LOG.error(
'Failed to remove Auto Repairs session settings for group %s', group,
)
cmd = ['reg', 'delete', fr'HKCU\{AUTO_REPAIR_KEY}', '/f']
run_program(cmd)
except CalledProcessError:
LOG.error('Failed to remove Auto Repairs session settings')
def get_entry_settings(group, name) -> dict[str, Any]:
@ -269,15 +248,11 @@ def init(menus, presets) -> None:
session_started = is_session_started()
# Check if autologon is needed
if not session_started:
if is_autologon_enabled():
LOG.warning('Skipping Autologon to preserve current settings.')
menus['Options'].options['Use Autologon']['Disabled'] = True
menus['Options'].options['Use Autologon']['Selected'] = False
reg_set_value('HKCU', AUTO_REPAIR_KEY, 'Use Autologon', 0, 'DWORD')
else:
# Deselect Autologon by default
menus['Options'].options['Use Autologon']['Selected'] = False
if not session_started and is_autologon_enabled():
LOG.warning('Skipping Autologon to preserve current settings.')
menus['Options'].options['Use Autologon']['Disabled'] = True
menus['Options'].options['Use Autologon']['Selected'] = False
reg_set_value('HKCU', AUTO_REPAIR_KEY, 'Use Autologon', 0, 'DWORD')
save_selection_settings(menus)
# Start new session
@ -299,7 +274,6 @@ def init_run(options) -> None:
update_scheduled_task()
if options['Kill Explorer']['Selected']:
atexit.register(start_explorer)
reg_set_value('HKCU', AUTO_REPAIR_KEY, 'Kill Explorer', 1, 'DWORD')
TRY_PRINT.run('Killing Explorer...', kill_explorer, msg_good='DONE')
if options['Use Autologon']['Selected'] and not is_autologon_enabled():
TRY_PRINT.run(
@ -344,6 +318,7 @@ def init_session(options) -> None:
download=True, msg_good='DONE',
)
print('')
reboot(30)
def is_autologon_enabled() -> bool:
@ -440,7 +415,7 @@ def run_auto_repairs(base_menus, presets) -> None:
show_main_menu(base_menus, menus, presets, title)
except SystemExit:
if ui.ask('End session?'):
end_session(menus)
end_session()
raise
# Start or resume repairs
@ -465,7 +440,7 @@ def run_auto_repairs(base_menus, presets) -> None:
ui.abort()
# Done
end_session(menus)
end_session()
ui.print_info('Done')
ui.pause('Press Enter to exit...')
@ -635,20 +610,6 @@ def update_main_menu(menus) -> None:
menus['Main'].options[name]['Display Name'] = display_name
def update_scheduled_task():
"""Create (or update) scheduled task to start repairs."""
cmd = [
'schtasks', '/create', '/f',
'/sc', 'ONLOGON',
'/tn', f'{KIT_NAME_FULL}-AutoRepairs',
'/rl', 'HIGHEST',
'/tr', fr'C:\Windows\System32\cmd.exe "/C {sys.executable} {sys.argv[0]}"',
]
if IN_CONEMU:
cmd[-1] = f'{CONEMU_EXE} -run {sys.executable} {sys.argv[0]}'
run_program(cmd)
# Auto Repairs: Wrapper Functions
def auto_adwcleaner(group, name) -> None:
"""Run AdwCleaner scan.
@ -720,28 +681,6 @@ def auto_chkdsk(group, name) -> None:
reboot()
def auto_disable_defender(group, name):
"""Open Windows Defender Threat Settings."""
result = TRY_PRINT.run(
'Open Windows Defender settings...',
open_defender_settings,
disable=True,
msg_good='Done',
)
save_settings(group, name, result=result)
def auto_enable_defender(group, name):
"""Open Windows Defender Threat Settings."""
result = TRY_PRINT.run(
'Open Windows Defender settings...',
open_defender_settings,
enable=True,
msg_good='Done',
)
save_settings(group, name, result=result)
def auto_disable_pending_renames(group, name) -> None:
"""Disable pending renames."""
result = TRY_PRINT.run(
@ -775,21 +714,6 @@ def auto_dism(group, name) -> None:
reboot()
def auto_emsisoft_cmd_run(group, name):
"""Run EmisoftCmd."""
TRY_PRINT.run('EmsisoftCmd (Install)...', install_emsisoft_cmd)
TRY_PRINT.run('EmsisoftCmd (Update)...', update_emsisoft_cmd)
result = TRY_PRINT.run('EmsisoftCmd (Scan)...', run_emsisoft_cmd_scan)
TRY_PRINT.run('EmsisoftCmd (Uninstall Service)...', delete_emsisoft_cmd_service)
save_settings(group, name, result=result)
def auto_emsisoft_cmd_uninstall(group, name):
"""Uninstall EmsisoftCmd."""
result = TRY_PRINT.run('EmsisoftCmd (Uninstall)...', uninstall_emsisoft_cmd)
save_settings(group, name, result=result)
def auto_enable_regback(group, name) -> None:
"""Enable RegBack."""
result = TRY_PRINT.run(
@ -800,14 +724,6 @@ def auto_enable_regback(group, name) -> None:
save_settings(group, name, result=result)
def auto_fix_file_associations(group, name):
"""Run Fix File Associations scan."""
result = TRY_PRINT.run(
'Fix File Associations...', fix_file_associations, msg_good='DONE',
)
save_settings(group, name, result=result)
def auto_hitmanpro(group, name) -> None:
"""Run HitmanPro scan."""
result = TRY_PRINT.run('HitmanPro...', run_hitmanpro, msg_good='DONE')
@ -820,29 +736,6 @@ def auto_kvrt(group, name) -> None:
save_settings(group, name, result=result)
def auto_mbam_install(group, name):
"""Install Malwarebytes."""
result = TRY_PRINT.run('Malwarebytes (Install)...', install_mbam)
save_settings(group, name, result=result)
def auto_mbam_run(group, name):
"""Run Malwarebytes.
save_settings() is called first since MBAM may kill this script.
"""
save_settings(group, name, done=True, failed=False, message='DONE')
result = TRY_PRINT.run('Malwarebytes (Run)...', run_mbam, msg_good='DONE')
ui.pause('Press Enter to continue...')
save_settings(group, name, result=result)
def auto_mbam_uninstall(group, name):
"""Uninstall Malwarebytes."""
result = TRY_PRINT.run('Malwarebytes (Uninstall)...', uninstall_mbam)
save_settings(group, name, result=result)
def auto_microsoft_defender(group, name) -> None:
"""Run Microsoft Defender scan."""
result = TRY_PRINT.run(
@ -862,7 +755,6 @@ def auto_remove_power_plan(group, name) -> None:
"""Remove custom power plan and set to Balanced."""
result = TRY_PRINT.run(
'Remove Custom Power Plan...', remove_custom_power_plan,
high_performance=True,
)
save_settings(group, name, result=result)
@ -1091,61 +983,11 @@ def backup_registry() -> None:
run_tool('ERUNT', 'ERUNT', backup_path, 'sysreg', 'curuser', 'otherusers')
def delete_emsisoft_cmd_service():
"""Delete EmsisoftCmd service."""
try:
stop_service('epp')
except GenericError:
pass
# Delete service
run_program(['sc', 'delete', 'epp'], check=False)
def delete_registry_null_keys() -> None:
"""Delete registry keys with embedded null characters."""
run_tool('RegDelNull', 'RegDelNull', '-s', '-y', download=True)
def fix_file_associations():
"""Fix file associations via registry edits."""
winaio_path = get_tool_path('WinRepairAIO', 'Repair_Windows').parent
winaio_path = winaio_path.joinpath(
f'Files/regfiles/file_associations/{OS_VERSION}',
)
for item in winaio_path.iterdir():
if item.suffix.lower() != '.reg':
continue
cmd = ['reg', 'import', str(item), f'/reg:{ARCH}']
run_program(cmd, check=False)
def install_emsisoft_cmd():
"""Install EmsisoftCmd."""
download_tool('EmsisoftCmd', 'EmsisoftCmd')
installer = get_tool_path('EmsisoftCmd', 'EmsisoftCmd')
extract_archive(installer, EMSISOFT_INSTALL_PATH, '-aos')
def install_mbam():
"""Install Malwarebytes."""
marker = set_local_storage_path('.', MBAM_PRESERVE_MARKER)
marker.unlink(missing_ok=True)
# Check for current installation
for path in ('ProgramW6432', 'PROGRAMFILES', 'PROGRAMFILES(X86)'):
if os.path.exists(f'{os.environ.get(path, "")}/{MBAM_EXE_PATH}'):
LOG.info('Previous Malwarebytes installation detected.')
marker.touch()
break
# Install / Upgrade
proc = run_tool(
'MBAM', 'MBAM', '/VERYSILENT', '/NORESTART', download=True, popen=True,
)
proc.wait()
def log_kvrt_results(log_path, report_path) -> None:
"""Parse KVRT report and log results in plain text."""
log_text = ''
@ -1216,41 +1058,6 @@ def run_bleachbit(cleaners, preview=True) -> None:
)
def run_emsisoft_cmd_scan():
"""Run EmsisoftCmd scan."""
log_path = format_log_path(
log_name='EmsisoftCmd', timestamp=True, tool=True,
)
log_path.parent.mkdir(parents=True, exist_ok=True)
quarantine_path = set_quarantine_path('EmsisoftCmd')
quarantine_path.mkdir(parents=True, exist_ok=True)
whitelist_path = log_path.with_suffix('.wl')
# Create whitelist
whitelist_path.write_text(WHITELIST, encoding='utf-8')
# Run Scan
cmd = [
f'{EMSISOFT_INSTALL_PATH}/a2cmd.exe',
fr'/files={PROGRAMDATA},{SYSTEMDRIVE}\Users',
'/archive', '/memory', '/ntfs', '/pup', '/traces',
f'/log={log_path}',
f'/quarantine={quarantine_path}',
f'/whitelist={whitelist_path}',
]
if IN_CONEMU:
cmd.extend(['-new_console:nb', '-new_console:s33V'])
run_program(cmd, check=False, pipe=False)
sleep(5)
set_proc_priority('a2cmd.exe', 'HIGH')
wait_for_procs('a2cmd.exe')
else:
proc = popen_program(cmd, priority=True)
sleep(5)
set_proc_priority('a2cmd.exe', 'HIGH')
proc.wait()
def run_bcuninstaller() -> None:
"""Run BCUninstaller."""
run_tool('BCUninstaller', 'BCUninstaller')
@ -1262,11 +1069,7 @@ def run_hitmanpro() -> None:
log_path = log_path.with_suffix('.xml')
log_path.parent.mkdir(parents=True, exist_ok=True)
cmd_args = ['/scanonly', f'/log={log_path}']
proc = run_tool(
'HitmanPro', 'HitmanPro', *cmd_args,
download=True, popen=True, priority=True,
)
proc.wait()
run_tool('HitmanPro', 'HitmanPro', *cmd_args, download=True)
def run_kvrt() -> None:
@ -1300,41 +1103,16 @@ def run_kvrt() -> None:
_f.write(f'"{kvrt_path}" {" ".join(cmd_args)}\n')
cmd = ('cmd', '/c', tmp_file, '-new_console:nb', '-new_console:s33V')
run_program(cmd, check=False)
sleep(5)
set_proc_priority('KVRT', 'HIGH', exact=False)
sleep(1)
wait_for_procs('KVRT.exe')
log_kvrt_results(log_path, report_path)
return
# Run in background
proc = run_tool(
'KVRT', 'KVRT', *cmd_args,
download=True, popen=True, priority=True,
)
sleep(5)
set_proc_priority('KVRT', 'HIGH', exact=False)
proc.wait()
run_tool('KVRT', 'KVRT', *cmd_args, download=True)
log_kvrt_results(log_path, report_path)
def run_mbam():
"""Run Malwarebytes."""
exe_path = None
# Get EXE path
for path in ('ProgramW6432', 'PROGRAMFILES'):
test_path = get_path_obj(f'{os.environ.get(path, "")}/{MBAM_EXE_PATH}')
if test_path.exists():
exe_path = str(test_path)
# Bail if MBAM not found
if not exe_path:
raise FileNotFoundError('MBAM not found')
# Run
run_program(exe_path, check=False)
def run_microsoft_defender(full=True) -> None:
"""Run Microsoft Defender scan."""
reg_key = r'Software\Microsoft\Windows Defender'
@ -1351,7 +1129,8 @@ def run_microsoft_defender(full=True) -> None:
# Get MS Defender status
## NOTE: disabled may be set to an int instead of bool
## This is fine because we're just checking if it's enabled.
disabled = defender_is_disabled()
disabled = bool(reg_read_value('HKLM', reg_key, 'DisableAntiSpyware'))
disabled = disabled or reg_read_value('HKLM', reg_key, 'DisableAntiVirus')
try:
passive_mode = reg_read_value('HKLM', reg_key, 'PassiveMode') == 2
except FileNotFoundError:
@ -1393,38 +1172,6 @@ def run_rkill() -> None:
run_tool('RKill', 'RKill', *cmd_args, download=True)
def uninstall_emsisoft_cmd():
"""Uninstall EmsisoftCmd."""
delete_folder(EMSISOFT_INSTALL_PATH, force=True, ignore_errors=True)
def uninstall_mbam():
"""Uninstall Malwarebytes."""
marker = set_local_storage_path('.', MBAM_PRESERVE_MARKER)
if marker.exists():
marker.unlink()
raise GenericWarning('Leaving existing MBAM installation in place.')
# Uninstall
install_path = reg_read_value('HKLM', MBAM_UNINSTALL_KEY, 'InstallLocation')
cmd = [
fr'{install_path}\mbuns.exe', '/Uninstall', '/VERYSILENT', '/NORESTART',
]
run_program(cmd)
def update_emsisoft_cmd():
"""Update EmsisoftCmd."""
cmd = [f'{EMSISOFT_INSTALL_PATH}/a2cmd.exe', '/update']
if IN_CONEMU:
cmd.extend(['-new_console:nb', '-new_console:s33V'])
run_program(cmd, check=False, pipe=False)
sleep(1)
wait_for_procs('a2cmd.exe')
else:
run_program(cmd, check=False)
# OS Built-in Functions
def create_custom_power_plan(enable_sleep=True, keep_display_on=False) -> None:
"""Create new power plan and set as active."""
@ -1546,63 +1293,6 @@ def kill_explorer() -> None:
run_program(cmd, check=False)
def open_defender_settings(disable=False, enable=False):
"""Open Windows Defender Threat Settings."""
enabled = None
# Check Registry if Defender is disabled
if defender_is_disabled():
raise GenericError('Defender is disabled.')
# Check WMIC if Defender is active
cmd = [
'WMIC', r'/namespace:\\root\SecurityCenter2',
'path', 'AntivirusProduct',
'where', 'displayName="Windows Defender"',
'get', 'productState', '/value',
]
try:
proc = run_program(cmd)
status = proc.stdout.split('=')[1]
enabled = bool(int(status) & MS_ANTIVIRUS_ENABLED)
except Exception:
# Unknown result, just show the prompt
pass
# Set prompt message
message = 'Please adjust Windows Defender settings as appropriate.'
if disable:
if enabled is False:
# Already disabled, just bail
return
message = 'Please disable realtime Windows Defender scanning.'
elif enable:
if enabled:
# Already enabled, just bail
return
message = 'Please enable realtime Windows Defender scanning.'
message += '\nPress OK to continue repairs.'
# Check Kill Explorer setting
cmd = ['start', '', 'windowsdefender://threatsettings']
kill_explorer_proc = False
try:
kill_explorer_proc = reg_read_value(
'HKCU', AUTO_REPAIR_KEY, 'Kill Explorer',
)
except FileNotFoundError:
# Ignore if not set
pass
if kill_explorer_proc:
# Explorer is needed to open the settings window, relaunch for now
start_explorer()
sleep(2)
run_program(cmd, check=False, shell=True)
show_alert_box(message=message, title='Windows Defender: Threat Settings')
if kill_explorer_proc:
kill_explorer()
def reboot(timeout=10) -> None:
"""Reboot the system."""
atexit.unregister(start_explorer)
@ -1804,11 +1494,6 @@ def run_sfc_scan() -> None:
raise OSError
def run_uninstallview() -> None:
"""Run UninstallView."""
run_tool('UninstallView', 'UninstallView')
def set_system_restore_size(size=8) -> None:
"""Set System Restore size."""
cmd = [

View file

@ -2,13 +2,11 @@
# vim: sts=2 sw=2 ts=2
import configparser
from datetime import datetime, timedelta
import logging
import json
import os
import re
import sys
import webbrowser
from typing import Any
@ -16,23 +14,21 @@ from wk.cfg.main import KIT_NAME_FULL
from wk.cfg.setup import (
BROWSER_PATHS,
DISABLED_ENTRIES_WINDOWS_11,
FAB_TIMEFRAME,
LIBREOFFICE_XCU_DATA,
REG_CHROME_UBLOCK_ORIGIN,
REG_ESET_NOD32_SETTINGS,
REG_OPEN_SHELL_LOW_POWER_IDLE,
REG_OPEN_SHELL_SETTINGS,
REG_WINDOWS_EXPLORER,
REG_OPEN_SHELL_SETTINGS,
REG_OPEN_SHELL_LOW_POWER_IDLE,
REG_WINDOWS_BSOD_MINIDUMPS,
UBLOCK_ORIGIN_URLS,
)
from wk.exe import get_procs, 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.kit.tools import (
ARCH,
extract_archive,
extract_tool,
find_kit_dir,
get_sdio_path,
get_tool_path,
run_tool,
)
@ -41,7 +37,6 @@ from wk.os.win import (
OS_VERSION,
activate_with_bios,
check_4k_alignment,
get_installed_antivirus,
get_installed_ram,
get_os_activation,
get_os_name,
@ -50,7 +45,7 @@ from wk.os.win import (
get_volume_usage,
is_activated,
is_secure_boot_enabled,
reg_read_value,
list_installed_antivirus,
reg_set_value,
reg_write_settings,
stop_service,
@ -166,44 +161,6 @@ def build_menus(base_menus, title, presets) -> dict[str, ui.Menu]:
return menus
def check_if_auto_repairs_are_needed():
"""Check if Auto Repairs are needed based on last Fab run."""
try:
last_run = reg_read_value(
'HKCU', fr'Software\{KIT_NAME_FULL}', 'FabLastRun',
)
except FileNotFoundError:
# Assuming it isn't needed
return
fab_last_run = datetime.strptime(last_run, '%Y-%m-%d')
# Check if Auto Repairs was run after Fab
auto_repairs_last_run = None
try:
last_run = reg_read_value(
'HKCU', fr'Software\{KIT_NAME_FULL}', 'AutoRepairsLastRun',
)
auto_repairs_last_run = datetime.strptime(last_run, '%Y-%m-%d')
except FileNotFoundError:
# Assuming it wasn't
pass
# Check date(s) and prompt tech if necessary
if datetime.now() - fab_last_run < timedelta(days=FAB_TIMEFRAME):
if auto_repairs_last_run and auto_repairs_last_run >= fab_last_run:
# Auto Repairs shouldn't be needed
return
ui.print_warning('Fab was recently run, Auto Repairs may be needed.')
selection = ui.choice(
'Continue with Auto Setup, run Auto Repairs, or Quit?', ['C', 'R', 'Q'],
)
if selection == 'Q':
ui.abort()
if selection == 'R':
auto_repairs_script = find_kit_dir('Scripts').joinpath('auto_repairs.py')
os.execv(sys.executable, ['python', auto_repairs_script])
def check_os_and_set_menu_title(title) -> str:
"""Check OS version and update title for menus, returns str."""
color = None
@ -232,9 +189,6 @@ def check_os_and_set_menu_title(title) -> str:
def load_preset(menus, presets, title, enable_menu_exit=True) -> None:
"""Load menu settings from preset and ask selection question(s)."""
msp = False
# Menu exit entry
if not enable_menu_exit:
MENU_PRESETS.actions['Main Menu'].update({'Disabled':True, 'Hidden':True})
@ -259,36 +213,8 @@ def load_preset(menus, presets, title, enable_menu_exit=True) -> None:
ui.clear_screen()
ui.print_standard(f'{title}')
print('')
if selection[0] == 'Default':
# OpenShell
if OS_VERSION != 11 and ui.ask('Install OpenShell?'):
menus['Install Software'].options['Open-Shell']['Selected'] = True
menus['Configure System'].options['Open-Shell']['Selected'] = True
# LibreOffice
if ui.ask('Install LibreOffice?'):
menus['Install Software'].options['LibreOffice']['Selected'] = True
# Hiberboot & Hibernation
print('')
msg = 'Disable Fast Startup and enable Hibernation? (Recommended for SSDs)'
if ui.ask(msg):
for option in ('Disable Fast Startup', 'Enable Hibernation'):
menus['Configure System'].options[option]['Selected'] = True
# Apply ITS settings?
msp = ui.ask('Is this an ITS system?')
if msp:
option = 'Apply ITS Settings'
menus['Configure System'].options[option]['Selected'] = True
# ESET NOD32 AV
print('')
if msp or ui.ask('Install ESET NOD32 AV?'):
option = 'ESET NOD32 AV'
if msp or ui.ask(' For VIP?'):
option = f'{option} (MSP)'
menus['Install Software'].options[option]['Selected'] = True
if selection[0] == 'Default' and ui.ask('Install LibreOffice?'):
menus['Install Software'].options['LibreOffice']['Selected'] = True
# Re-enable Main Menu action if disabled
MENU_PRESETS.actions['Main Menu'].update({'Disabled':False, 'Hidden':False})
@ -313,9 +239,6 @@ def run_auto_setup(base_menus, presets) -> None:
# Check OS and update title for menus
title = check_os_and_set_menu_title(title)
# Check if AV scan needs to be run
check_if_auto_repairs_are_needed()
# Generate menus
menus = build_menus(base_menus, title, presets)
@ -449,11 +372,6 @@ def auto_enable_regback() -> None:
)
def auto_apply_its_settings():
"""Apply ITS settings."""
TRY_PRINT.run('Apply ITS settings...', apply_its_settings)
def auto_system_restore_enable() -> None:
"""Enable System Restore."""
cmd = [
@ -487,6 +405,7 @@ def auto_activate_windows() -> None:
def auto_config_browsers() -> None:
"""Configure Browsers."""
prompt = ' Press Enter to continue...'
TRY_PRINT.run('Chrome Notifications...', disable_chrome_notifications)
TRY_PRINT.run(
'uBlock Origin...', enable_ublock_origin, msg_good='STARTED',
)
@ -510,49 +429,16 @@ def auto_config_open_shell() -> None:
TRY_PRINT.run('Open Shell...', config_open_shell)
def auto_disable_chrome_notifications():
"""Disable Chrome notifications.
NOTE: This can cause Chrome Sync to be re-authenticated."""
TRY_PRINT.run('Disable Chrome Notifications...', disable_chrome_notifications)
def auto_disable_fast_startup():
"""Disable fast startup (i.e. Hiberboot)."""
TRY_PRINT.run(
'Disable Fast Startup...', reg_set_value, 'HKLM',
r'SYSTEM\CurrentControlSet\Control\Session Manager\Power',
'HiberbootEnabled', 0, 'DWORD',
)
def auto_disable_password_expiration() -> None:
"""Disable password expiration for all users."""
TRY_PRINT.run('Disable password expiration...', disable_password_expiration)
def auto_enable_hibernation():
"""Enable Hibernation."""
TRY_PRINT.run(
'Enable Hibernation...', run_program, ['powercfg', '/hibernate', 'on'],
)
def auto_export_aida64_report() -> None:
"""Export AIDA64 reports."""
TRY_PRINT.run('AIDA64 Report...', export_aida64_report)
def auto_install_eset_nod32_av():
"""Install ESET NOD32 AV."""
TRY_PRINT.run('ESET NOD32...', install_eset_nod32_av)
def auto_install_eset_nod32_av_msp():
"""Install ESET NOD32 AV with MSP settings."""
TRY_PRINT.run('ESET NOD32 (MSP)...', install_eset_nod32_av, msp=True)
def auto_install_firefox() -> None:
"""Install Firefox."""
TRY_PRINT.run('Firefox...', install_firefox)
@ -564,10 +450,7 @@ def auto_install_libreoffice() -> None:
NOTE: It is assumed that auto_install_vcredists() will be run
before this function to satisfy the vcredist=False usage.
"""
TRY_PRINT.run(
'LibreOffice...', install_libreoffice,
use_mso_formats=True, vcredist=False,
)
TRY_PRINT.run('LibreOffice...', install_libreoffice, vcredist=False)
def auto_install_open_shell() -> None:
@ -600,9 +483,9 @@ def auto_open_device_manager() -> None:
TRY_PRINT.run('Device Manager...', open_device_manager)
def auto_open_mic_and_webcam_tests():
"""Open Webcam Tests."""
TRY_PRINT.run('Webcam Tests...', open_mic_and_webcam_tests)
def auto_open_hwinfo_sensors() -> None:
"""Open HWiNFO Sensors."""
TRY_PRINT.run('HWiNFO Sensors...', open_hwinfo_sensors)
def auto_open_microsoft_store_updates() -> None:
@ -638,7 +521,7 @@ def auto_show_4k_alignment_check() -> None:
def auto_show_installed_antivirus() -> None:
"""Display installed antivirus."""
TRY_PRINT.run('Virus Protection...', get_installed_antivirus)
TRY_PRINT.run('Virus Protection...', list_installed_antivirus)
def auto_show_installed_ram() -> None:
@ -670,11 +553,6 @@ def auto_show_storage_status() -> None:
TRY_PRINT.run('Storage Status...', get_storage_status)
def auto_shutup_10():
"""Disable Windows telemetry using O&O ShutUp 10."""
TRY_PRINT.run('Disable Telemetry...', run_shutup_10)
def auto_windows_temp_fix() -> None:
"""Restore default ACLs for Windows\\Temp."""
TRY_PRINT.run(r'Windows\Temp fix...', fix_windows_temp)
@ -685,12 +563,7 @@ def config_explorer() -> None:
"""Configure Windows Explorer and restart the process."""
reg_write_settings(REG_WINDOWS_EXPLORER)
kill_procs('explorer.exe', force=True)
popen_program([fr'{SYSTEMDRIVE}\Windows\explorer.exe'])
sleep(3)
for _ in range(30):
if get_procs('explorer.exe'):
break
sleep(1)
popen_program(['explorer.exe'])
def config_open_shell() -> None:
@ -757,14 +630,14 @@ def disable_chrome_notifications() -> None:
def disable_password_expiration() -> None:
"""Disable password expiration for all users."""
cmd = ['wmic', 'UserAccount', 'set', 'PasswordExpires=False']
script_path = find_kit_dir('Scripts').joinpath('disable_password_expiration.ps1')
cmd = ['PowerShell', '-ExecutionPolicy', 'Bypass', '-File', script_path]
run_program(cmd)
def enable_bsod_minidumps() -> None:
"""Enable saving minidumps during BSoDs."""
cmd = ['wmic', 'RECOVEROS', 'set', 'DebugInfoType', '=', '3']
run_program(cmd)
reg_write_settings(REG_WINDOWS_BSOD_MINIDUMPS)
def enable_ublock_origin() -> None:
@ -809,20 +682,6 @@ def fix_windows_temp() -> None:
# Install Functions
def install_eset_nod32_av(msp=False):
"""Install ESET NOD32 Antivirus."""
config_file = f'eset-config{"-msp" if msp else ""}.xml'
reg_write_settings(REG_ESET_NOD32_SETTINGS)
run_tool(
'ESET', 'ESET_NOD32_AV',
'--silent', '--accepteula',
'--msi-property', 'PRODUCTTYPE=eav',
'PRODUCT_LANG=1033', 'PRODUCT_LANG_CODE=en-US',
f'ADMINCFG="{config_file}"',
cwd=True, download=True,
)
def install_firefox() -> None:
"""Install Firefox.
@ -979,11 +838,6 @@ def uninstall_firefox() -> None:
# Misc Functions
def apply_its_settings():
"""Apply ITS settings."""
create_custom_power_plan(enable_sleep=False)
def check_secure_boot_status() -> None:
"""Check Secure Boot status."""
is_secure_boot_enabled(raise_exceptions=True, show_alert=True)
@ -1071,11 +925,20 @@ def open_device_manager() -> None:
popen_program(['mmc', 'devmgmt.msc'])
def open_mic_and_webcam_tests():
"""Open Webcam Tests."""
webbrowser.open('https://mictests.com/')
sleep(1)
webbrowser.open('http://webcamtests.com/')
def open_hwinfo_sensors() -> None:
"""Open HWiNFO sensors."""
hwinfo_path = get_tool_path('HWiNFO', 'HWiNFO')
base_config = hwinfo_path.with_name('general.ini')
# Write new config to disk
with open(hwinfo_path.with_suffix('.ini'), 'w', encoding='utf-8') as _f:
_f.write(
f'{base_config.read_text(encoding="utf-8")}\n'
'SensorsOnly=1\nSummaryOnly=0\n'
)
# Open HWiNFO
run_tool('HWiNFO', 'HWiNFO', popen=True)
def open_microsoft_store_updates() -> None:
@ -1091,10 +954,7 @@ def open_snappy_driver_installer_origin() -> None:
stop_service(svc)
if any([get_service_status(s) != 'stopped' for s in appid_services]):
raise GenericWarning('Failed to stop AppID services')
sdio_path = get_sdio_path(interactive=False)
log_dir = format_log_path(tool=True).parent
cmd = [sdio_path, '-log_dir', log_dir]
popen_program(cmd, cwd=sdio_path.parent, pipe=True)
run_tool('SDIO', 'SDIO', cwd=True, pipe=True, popen=True)
def open_windows_activation() -> None:
@ -1110,12 +970,7 @@ def open_windows_updates() -> None:
def open_xmplay() -> None:
"""Open XMPlay."""
sleep(2)
run_tool('XMPlay', 'XMPlay', 'music.m3u', cwd=True, popen=True)
def run_shutup_10():
"""Disable Windows telemetry using O&O ShutUp 10."""
run_tool('ShutUp10', 'OOSU10', '1201.cfg', '/quiet', cwd=True)
run_tool('XMPlay', 'XMPlay', 'music.7z', cwd=True, popen=True)
if __name__ == '__main__':

View file

@ -67,7 +67,7 @@ def fix_layout(
# Calculate constraints
avail_horizontal, avail_vertical = get_window_size()
avail_vertical -= layout['Current'].get('height', 0)
for group in ('Title', 'Subtitle', 'Info'):
for group in ('Title', 'Info'):
if not layout[group]['Panes']:
continue
avail_vertical -= layout[group].get('height', 0) + 1
@ -97,7 +97,7 @@ def fix_layout(
)
for group, data in layout.items():
num_panes = len(data['Panes'])
if num_panes < 2 or group not in ('Title', 'Subtitle', 'Info'):
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']:

View file

@ -19,7 +19,6 @@ 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},
'Subtitle': {'Panes': [], 'height': TMUX_TITLE_HEIGHT},
'Info': {'Panes': []},
'Current': {'Panes': [environ.get('TMUX_PANE', None)]},
'Workers': {'Panes': []},
@ -116,29 +115,6 @@ class TUI():
# Add pane
self.layout['Title']['Panes'].append(tmux.split_window(**tmux_args))
def add_subtitle_pane(self, line1: str, line2: str) -> None:
"""Add pane to subtitle row."""
lines = [line1, line2]
tmux_args = {
'behind': True,
'lines': TMUX_TITLE_HEIGHT,
'target_id': None,
'text': '\n'.join(lines),
'vertical': True,
}
if self.layout['Subtitle']['Panes']:
tmux_args.update({
'behind': False,
'percent': 50,
'target_id': self.layout['Subtitle']['Panes'][-1],
'text': '\n'.join(lines),
'vertical': False,
})
tmux_args.pop('lines')
# Add pane
self.layout['Subtitle']['Panes'].append(tmux.split_window(**tmux_args))
def add_worker_pane(
self,
lines: int | None = None,
@ -245,12 +221,6 @@ class TUI():
self.layout['Info']['Panes'].clear()
tmux.kill_pane(*panes)
def remove_all_subtitle_panes(self) -> None:
"""Remove all subtitle panes and update layout."""
panes = self.layout['Subtitle']['Panes'].copy()
self.layout['Subtitle']['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)
@ -272,13 +242,6 @@ class TUI():
self.layout['Title']['Panes'] = panes[:1]
self.set_title(line1, line2, colors)
def reset_subtitle_pane(self) -> None:
"""Remove all extra subtitle panes and update layout."""
panes = self.layout['Subtitle']['Panes'].copy()
if len(panes) > 1:
tmux.kill_pane(*panes[1:])
self.layout['Subtitle']['Panes'] = panes[:1]
def set_current_pane_height(self, height: int) -> None:
"""Set current pane height and update layout."""
self.layout['Current']['height'] = height
@ -360,7 +323,7 @@ def fix_layout(layout, forced: bool = False) -> None:
pass
# Update "group" panes widths
for group in ('Title', 'Subtitle', 'Info'):
for group in ('Title', 'Info'):
num_panes = len(layout[group]['Panes'])
if num_panes <= 1:
continue

View file

@ -84,7 +84,7 @@ function copy_live_env() {
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.jpg" "$PROFILE_DIR/airootfs/usr/share/WizardKit/syslinux/syslinux.jpg"
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/"
@ -128,7 +128,8 @@ function update_live_env() {
label="${KIT_NAME_SHORT}_LINUX"
# Boot config
cp "$ROOT_DIR/images/Syslinux.jpg" "$PROFILE_DIR/syslinux/splash.jpg"
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
sed -i -r "s/KIT_NAME_SHORT/$KIT_NAME_SHORT/" "$PROFILE_DIR/profiledef.sh"
@ -196,7 +197,7 @@ function update_live_env() {
# Wallpaper
mkdir -p "$PROFILE_DIR/airootfs/usr/share/wallpaper"
cp "$ROOT_DIR/images/Linux.jpg" "$PROFILE_DIR/airootfs/usr/share/wallpaper/burned.in"
cp "$ROOT_DIR/images/Linux.png" "$PROFILE_DIR/airootfs/usr/share/wallpaper/burned.in"
# udevil
mkdir -p "$PROFILE_DIR/airootfs/media"

View file

@ -195,10 +195,10 @@ ln -s /Volumes/RAM_Disk/Logs "${WK_PATH}/var/root/Logs"
cp -a "${LINUX_DIR}/profile_base/airootfs/etc/skel/.tmux.conf" "${WK_PATH}/etc/tmux.conf"
rsync -aS /usr/bin/{env,killall} "${WK_PATH}/usr/bin"/
rsync -aS "${MACOS_DIR}/live-macos-startup" "${ROOT_DIR}/scripts/" "${WK_PATH}/usr/local/bin"/
if [[ -f "${ROOT_DIR}/images/macOS-${OS_VERSION:0:5}.jpg" ]]; then
cp -a "${ROOT_DIR}/images/macOS-${OS_VERSION:0:5}.jpg" "${WK_PATH}/usr/local/wallpaper.jpg"
if [[ -f "${ROOT_DIR}/images/macOS-${OS_VERSION:0:5}.png" ]]; then
cp -a "${ROOT_DIR}/images/macOS-${OS_VERSION:0:5}.png" "${WK_PATH}/usr/local/wallpaper.png"
else
cp -a "${ROOT_DIR}/images/macOS.jpg" "${WK_PATH}/usr/local/wallpaper.jpg"
cp -a "${ROOT_DIR}/images/macOS.png" "${WK_PATH}/usr/local/wallpaper.png"
fi
# Unbless

View file

@ -30,7 +30,7 @@ rem robocopy /e windows/cbin %OUT_DIR%\.cbin
mkdir %OUT_DIR%\.cbin
copy ..\LICENSE.txt %OUT_DIR%\LICENSE.txt
copy ..\README.md %OUT_DIR%\README.md
copy ..\images\ConEmu.jpg %OUT_DIR%\.bin\ConEmu\
copy ..\images\ConEmu.png %OUT_DIR%\.bin\ConEmu\
attrib +h %OUT_DIR%\.bin >nul 2>&1
attrib +h %OUT_DIR%\.cbin >nul 2>&1

View file

@ -1,7 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAgEAhF5u/wn04dSDI1mg6HlbDhmM3orJtu2kZdOTBd+/kvCxdC4h+AuwRSWjbIhGi9s8R3iq64gC6TJ2dX4skJ5fMeYqHMZieGgfXderRiPgMURCDWONLZCWyowBun5qGaXkYQo5VbLUNGXua9+RMXaxoJSCOhxq/6CzdleD6pkNNgL5a1ZarD3wLCNRsS50OmyGytXsYTClAY+F9lTd4VXjKuZpjqGfhA5Xz09Rad3Me2Tsd07cdO3LxDNlr969Q/hEwfZ/g+ePaIO0Z/zFHIO4J6Mnt8POTU1fM99tgqUQHWZRP2A+9OYx7nApdA4IFWVUhNfsVkrgcgYdCLD8U16Cdfwm1i1RiBhlYBfzA+EDjD6cbegaaMCKhsCZMZKQB4LyJTDgjvCrk1Q7dE0Nc1Nq8qD/BMbZkIKQcQH8xrX1hONayUMdOPnpDpi6IP2NIYFTbdJD70JQ8ru+gIDRQJR6g0AwnjMoJYNRKgAtWlcbKQ8YQW/FNREtyUhh8tquCyFbofGUiJxmTbWki7u7VJxRLNSnp+NQhPNT4FUbWy1vPrJi0l3MxuXdG3nZ0Brnggn72tnGcAUOmRTPubNlkhFStqcCM+tAVTHeLwHobdMewKQMWGPt6UXLtEJINqW9nCh1SAOA3cjsZ3ugXLRQQc82rEgTfTp2RtuN+NSf/vgoDbk= 1201 PuTTY Key
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDGZqPA7/T3zw8YipngM6vKpxX+IMpwqm2jad+NcJWxC0Hh09oVUalgbO/eWRloQlNgxHTosdIkSp++xnyeEHI3vL0pbko/c0OJbs6vEsHp8ljzt61HKKsscVADCI/ogGwHkPIHENgbiXkBPutEBLYFPtMcJQw6GBIkagW+dTyqEw/8c1cDmTq7AM74tHqgrpYjJy35Nne0K+zGIeqzTJdtg3fkRCN9JH24c3VbYoeIpMlgfxxgRG44DC/o0BRoMr3wWiS/sTrutlluB03vaQWe+4o8p789wq+fDqKhBTisH96RQ1Y1D5eZcf0DngBJtnCLWPG+z6YP0DFW5DYeQRoYNneRkR10RUqN0Eufok/UMNnxWcW+TztTxO5JSucXt0MYpLsWO1U7B22MR1W7oVlbznUrVENSQGRqyOS4PdqmI4faF0vEKis68S7NaXg88zwBr612HwFBianXvvxhkyEKv8ECbeXSvHcIajepdzbGLwaussDQiNv8vyX/8o3K7Si/ktdgIK4UzfaMq7G0vX1KEvuKAfMPYn4z0zuc/ahxTTfvHqfO+cnYgH8Pnzi1DGziRveeKIFT4OKOP5C3wHntsfv1CStK/LXhH3j4PNRuet0Fm5+jUJ22DQqlXtFiwFz4E+Bt+67exhm4Out4rAUqCzjDW5+mw0WU2P7u01cT+Q== admin@anaconda.1201.com
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDaJkYcxi7eSpRE7i0n2N2q4CHebPqxYc5eLQ2mDbrLpAIRUSdVjqe8RiVLJ5LKejkGMnWbaiDZ59cLWJhDTfqZFCRexVQkA9v8MVHUWINzxqfJFOnz7dKXP0PmggDbuKMIUkY65KtOLC2Cnqs3epmysQxrSf33W3IqPwL2XjJQXRbFLd4UG1bSZtBAAF3C/i7hBzb2iQPIpr1v1rCrW6gVhrgyoN7ZTfMqcKHnTPsUn95Qtyp6NUYKh4ctrPmTcTHFfwmgQiAYQ6jMcH8bNBf1XDmJuEIwaILYVzzrWOXlx84rqaEUdeRbvd4S/G9FcRt9eO3o+jD4hupdyvIDCttP3ct6yeWQVotreZE/5OVwfxr0BNXPBmTiyF2c6cRrMlXpQMoRQyjnD5Eua0VEUJzj2ows5APGmciJudDw4rXoRLOZRX/nDuXqgNJJ6q+MgRx3hor/etbV2mXcmSMdoI8DuybFzHuEb9GRwYhLxzDdDe+zmevx0rl01W8EJImqgrTwMblADbM4VHpC974d5w2s9uoXJg1NY4UVkSjJdK0De0kyGh8hK2pqTqCBh1OznB43Yla5PwM+05JIMySVRuivvZ1+tBh8ZURzIbFFIrdgNiZIooI4hA/m8qz4CTWIxKWstXR08eOufIbjV+R6VJjEssvLmgYw93rUJ4WbQNbaiw== twoshirt@anaconda.1201.com
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKjMOQl/re3sTBUFeNEYOt2DsZLEeQiFfzu1tFlMUGcLnpjpA/Zhp0qpBBVhFS9j8p7s1838v+HS5dN1yLhWjjdM7EbzIwczJTe83D52GPwOisVWyL6cdB++bynl6LQRGe/dZu19uO1nX3sVU8gime4KLMasihCWw4XATQC1fFB6CnTu62ps3jO1n1MnjsX4odjC/Und5xD39trGU5aO4l17Zns+OBCLwsERofvkb02XZaqmcAXie+/o3jS43dANJXhtsqk0dRiNONccsnshFP90ujR8mPQU1h15fppwI/mRJ4GwNkA7XMcHghj/KqOIptSf0w8bBBQ7RTJATG0nPw45BJHu1bHZEbECpHBbM6TIIKgYVm7cJs54kf0yN66UWzDOfzqjL8Mo/Gmu/bHKhCg+gEQg4xFKbBtB8adMCrj8q9JZ2x8a9l9wcUKpSN8DM4SWUP4F4uXe0cAHhrMIAOfCFcX1lBsilNxidE5fZUGW3WJJ3+WUkeWnfifEBDZpSV0BCWjHKz1R/tQeJfBS8sjjGSheIJZBxLYc+6IUdL77Zjy2byzQ7m4PfaMDI3CxXhkhEeC4AnCn6/quRLFuChrvRYLC9UDrrmMMB4xo4SGsWKnM83orD1UbtbsqfJaHLBGF3WgII77uDvmUnwZJmaHCYM4N97lkoqNgC4VAEd4Q== twoshirt@dinraal.2shirt.work
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDa9lUItn9ooNu2FrOUMHytoQlAtH0x+tcszmt0ZNZN6DU6H87bFtZcB3gaanU4Kr3mIaJBUSjgbafJHiiYXvbzt+Gx7iQxAsXQkOfzArKHu9WV5OIHBATtONI3pokF3crlHJVR7hjY1lvP07c+F/Oygt0Hjd2RcslxPjqSVBRTl6z8Yu7r/BbkNiZRJfuJW/hNg+tGYRFHM6uy0qZ3fOwUmQ+MqCXm3V9K0A/e8dNyHEkW2v4CuAW8QP7gjA4nT/jrkJAA98firwSJdMgpZIFSuzf48D/AhDGuE55W5m/ItpMjVO7oDvqrEygyMO1ugxrIVqj7EM3HtArOtsMDueyCcTd5tWs/e80Y7/E4iB4bQQGo9ifA9oPqywAfETqvO0VUtjO9cMt7/Xnhc9qLxwKstr9UX9ORFm8hiaq9jVPkbwpmBIdfBpk4YwbY/1ObRQmOgsIuq5otPMrw17ymrJCagxk74BAOiMoUte5fhBezMl0ZlsBA9yWw63PHchWCyXQ0o195A3e04Api2FYx2er3mA28vhj8WkXwsuF1C2nypztn0GlmVoDKK5Z//slefLWAYq+V7AyGW3x2zYi4WpBiyM+FmG4ABVw5alxJjkvsoC1Ho7kKjQAdARhy+HsveSYLPPaSsnFIexpTjPlNlSquZG2rXxWLquo40CMMbRdJbw== twoshirt@farosh
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCh+EGFqMfJmfOQvfqfv/ilDL80krdZNdBvJ8xFWYMRy8z4z1Ih7BlCfkGObyxKLIYJgkPPhN3xLWWpN78ME7YeeUF1Ly84oIJABvK9CuZPDcUwDhpLoBli/qxoF/YyLufqtECKaQpDWt+iB1522XcVUerRFMGX3brE+b49gmXSuCC0lPijqcWV9KZVNKj9EStgY5b4DTjINOoiHIHBFsov37wt/+qJn7th+EDqcPndIcTQ31VbcGqEWtfNQWPGVmH898Q2XLvCbN+seBK9ZmttcXzWlS70qUr4xHrZCnegwMnBKPQ0sVo7NtC5DQHrN4I4NyvY0fr/o16nIphfaNZgy/CDo1WauBjJlbQ9MRcFVSPhL3LeyiBtBwC6Nr/qR+6Xjf/BV0ybw8MjXukZxKfW9xJsL631Xrk1/oYJnNKL5IyhIsVFRtfwcB4YfPEZbrdBxrse0Ypchgvpl5srTQfEL5ZTf/ozWCUST2Bf+dDMYrdqudg5zhPU6prneaEzlBxiLqGwBId9bzIqqHD+fE4a90oOX/WpZW0PFApnXGkPahaTPHGOAMfM+DmXdr2e5emdRZp+wgZU1EXwUzKVwE4fnj6HhuUlAt18LBZ/WLHMDGryuKemW0z9X9CUhzgkAmzgRCJ09ZXLYcwuyLwv9HnMQUa6RlWK69s3T/m+Y8fzKQ== twoshirt@mipha.2shirt.work
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC7MHLxoozeHOmFpjIGaXi8m0LuHZb4eUjX6x50XUlP81oVHbl4iAqw/N4r4j+Lg+lga7xcTeBMNWsa/tGxYkAfF8TP8M0S8XEMOR04r9iA4V6wQ8nGhv99y24NZMJPfiseNjDEwoPYPP5+zpCNfvxeMXxz3EhLFwzSp1dz+f1tBcDd8OtxDAGPn1cIM4Ms9gmIv3/HxOinW4IHxEbsZUWtKGPumsuIYwObfBYZPK0WtK23LPYqtReLYbzQmsf5SPpJAx20mwtZm8Y+LBXAKuxWEKzlF4l/ubqjTfMff5t+8FPEyCgRwm8VcMqxA1b04Pkcuq08AuooJuLKHOJ+aSyDWGXrSf3RlUAx8GafYOpZrcMEhQ0Q1a0XqZNTUXrf13qVPBqnKV8asOXrYIP56Z3zp8qoHasT8t5PbYrwvtV7weW/X61cQf/nn1B/BJbJr/rrOOxdqRCsH44OlElrhjwHsuzQbMc4U4wwoazlMmfNKsKUaoBmLPDTfmMBwnCuBrngNY9JsSB778fqwxsyLSfDTcIDW2kGuQ4P/4u5cKNw/q8Zo15Ghw2327kyTK9e4EGPCpBk+s5832FWS0uJLV269R5aZ5dYzYU13djfVOw2ypZTdr55nTlIH/31iSmJS0PBKyRLa2a7YxErt37W3f2U1eUt+j91LeaP5cMKHkWAsw== twoshirt@naydra.2shirt.work
#Put SSH keys here

View file

@ -1,3 +1,3 @@
# Put known WiFi networks in the form:
1201computersXI:::wifipassword
1201computersXI-5g:::wifipassword
#SSID-5G:::Password
#SSID-2G:::hunter2

View file

@ -1,12 +1,14 @@
aic94xx-firmware
ddrescueview-bin
hardinfo-gtk3
hfsprogs
iwgtk
memtest86-efi
mprime-bin
opensuperclone-git
pipes.sh
python-mariadb-connector
python-smbus2
smartmontools-svn
ttf-font-awesome-4
udevil
wd719x-firmware
wimboot-bin

View file

@ -1,5 +1,4 @@
adobe-source-han-sans-otc-fonts
adobe-source-sans-fonts
aic94xx-firmware
alsa-utils
amd-ucode
antiword
@ -9,9 +8,12 @@ arch-install-scripts
base
bc
bind
bluez
bluez-utils
bolt
btrfs-progs
cbatticon
chntpw
cmatrix
colordiff
conky
@ -23,7 +25,6 @@ ddrescueview-bin
device-mapper
diffutils
dkms
dmde
dmidecode
dmraid
dos2unix
@ -38,12 +39,16 @@ f2fs-tools
fatresize
feh
ffmpeg
firefox
foot-terminfo
gnome-keyring
gnu-netcat
gparted
gpicview
gptfdisk
grub
gsmartcontrol
hardinfo-gtk3
hexedit
hfsprogs
htop
@ -65,14 +70,12 @@ libxft
linux
linux-firmware
linux-firmware-marvell
linux-headers
lm_sensors
lsscsi
lvm2
lzip
man-db
man-pages
mariadb-clients
mdadm
mediainfo
memtest86-efi
@ -89,35 +92,31 @@ nano
nbd
ncdu
ndisc6
netsurf
nfs-utils
nmap
noto-fonts
noto-fonts-cjk
ntfs-3g
numlockx
nvme-cli
open-iscsi
openbox
openssh
opensuperclone-git
otf-font-awesome-4
p7zip
papirus-icon-theme
parted
pcmanfm
perl
picom
pipes.sh
pv
python
python-docopt
python-mariadb-connector
python-matplotlib
python-packaging
python-prompt_toolkit
python-psutil
python-pytz
python-requests
qemu-guest-agent
ranger
refind
reiserfsprogs
rfkill
@ -127,10 +126,10 @@ rxvt-unicode
rxvt-unicode-terminfo
sdparm
smartmontools-svn
smbclient
sof-firmware
speedtest-cli
spice-vdagent
squashfs-tools
st
sudo
sysbench
@ -141,6 +140,7 @@ systemd-sysvcompat
terminus-font
testdisk
texinfo
thunar
tigervnc
tint2
tk
@ -161,9 +161,12 @@ usb_modeswitch
usbmuxd
usbutils
util-linux
veracrypt
vim
virtualbox-guest-utils
volumeicon
wd719x-firmware
wezterm-terminfo
which
wimboot-bin
wimlib
@ -172,6 +175,7 @@ xarchiver
xf86-input-libinput
xf86-video-amdgpu
xf86-video-fbdev
xf86-video-nouveau
xf86-video-qxl
xf86-video-vesa
xfsprogs

View file

@ -1,27 +0,0 @@
pkgname=dmde
pkgver=4.0.6.806
pkgrel=1
pkgdesc="DMDE is a powerful tool for data searching, editing, and recovery on disks"
arch=('x86_64')
url="https://dmde.com/"
license=('custom')
makedepends=(unzip)
replaces=($pkgname)
source=("dmde-mine.ini")
source_x86_64=("https://dmde.com/download/dmde-4-0-6-806-lin64-gui.zip")
sha512sums=('1f11bee45672f507c4401031811273e8d6b103358bd0b2bec1cd6ecca1782ec23c0af3f81c23c5fcedd4380169fdea0eab60909ce59e17053029608ba324586a')
sha512sums_x86_64=('b2d20d13ced8780baa46a435c772af16b7b16b38c7daf65d2edc55f3560bb2ab417390ffd55cea6f424d4237ff911baa5547a45a9c9e279689fb1fd8fb82fc99')
package() {
cd $srcdir
mkdir -p ./usr/share/dmde/ $pkgdir/usr/bin/ $pkgdir/usr/share/applications/ $pkgdir/usr/share/pixmaps/
unzip ./dmde-4-0-6-806-lin64-gui.zip -d $srcdir/usr/share/dmde/
cp $srcdir/dmde-mine.ini $srcdir/usr/share/dmde/dmde.ini
cp -r usr $pkgdir
chmod +x $pkgdir/usr/share/dmde/dmde
chmod +x $pkgdir/usr/share/dmde/dmde-su
ln -sr /usr/share/dmde/dmde $pkgdir/usr/bin/dmde
ln -sr /usr/share/dmde/dmde-su $pkgdir/usr/bin/dmde-su
}

View file

@ -1,482 +0,0 @@
;
;dmde.ini - DMDE for Linux Initialization File
;
setupsign=5e440968
lickey=
activatekey[0]=
portablekey=
activatedev=
lngfile=\en
;interface language file
loadlngfiles=%appres%/locals/*.lng
;load language files
loadcptables=%appres%/locals/*.tbl
;load code page tables for Unicode-Char,Translit,Upcase conversions
editortemplates=%appres%/template.txt
;editor template file(s) - wildcards are possible
[CONSOLE]
autosizemaxy=
autosizemaxx=
[GUI]
mainwndpos=
;x,y,cx,cy
dialogfontsize=
;dialogfont=
;scale percents:
dialogwidth=100
dialogheight=100
;editorfont=
editorfontsize=
;editorfontbold=0
;previewfiles=*.bmp;*.gif;*.ico;*.jfif;*.jpe;*.jpeg;*.jpg;*.png;*.tif;*.tiff
[COMMON]
mainwndsplit=-1
;usecodepage=1252
;manually select used code page (CP)
;oemcodepage=850
;manually select source OEM CP for DOS names
applylngcodepage=1
;=1: use codepages from the lng file
translitenable=1
;=1: transliterate symbols outside current code page
viewtranslit=0
;=1: transliterate symbols on the screen
rtlapply=0
;=1: use rtl ui for right-to-left languages
rtldismiss=0
;=1: turn off rtl string mirroring if terminal already supports it
shellopen=xdg-open
stephints=0
;=1: activate step-by-step hints
;=0: deactivate step-by-step hints
popuphints=
;=1: activate popup hints (mouse over)
;=0: deactivate popup hints (mouse over)
kilobyte=1000
;kilobyte=1024
displayCHS=0
editorhexcolsnum=16
;editorhexcolsnum=N - maximum columns number for hex editor
;editorhexcolsnum=0: auto
editorhexgroupsize=8
startup=p
;startup=p - open physical drives at startup
;startupimage=filename - open disk image
;startupdev=devicestring - open device at startup
;startupraid=raid.ini - load RAID from "raid.ini" at startup
;startupcopy=copy.ini - load "Copy sectors" from "copy.ini" at startup
readonly=0
;=1 - globally disable writing to devices
devopen_editmode=0
;=1 - default edit mode when open using dialog box
devload_editmode=0
;=1 - default edit mode when load from log-file
devapply_ioparamsopt=0
;=1 - option to change i/o params when applying changes
devapply_editmodeopt=0
;=1 - option to set edit mode when applying changes
partapply_ioparamsopt=1
;=1 - option to change i/o params when applying partitioning changes
partapply_editmodeopt=1
;=1 - option to set edit mode when applying partitioning changes
queryclosedevice=1
;query when closing device
showpartitions=1
;show partitions when open device
;editmode=1
;enable disk editor edit mode by default
;Redefine default device enumeration:
;Linux device enumeration by FindFile:
;enumdevs0=0,"/dev/fd*",0,"",""
;enumdevs1=0,"/dev/sd?",0,"",""
;enumdevs2=0,"/dev/hd?",0,"",""
;enumdevs3=0,"/dev/ub?",0,"",""
;enumdevs4=0,"/dev/md*",0,"",""
;enumdevs5=0,"/dev/scd*",0,"",""
;include /dev/disks/by-id & /dev/disks/path enumeration:
;enumdevs_byid=1
;devusbdetectex=
ckeventtick=1
;ckeventtick=0 check event every time (slow down operations)
;ckeventtick=1 check event every 55ms only
mousehandler=1
;mousehandler=0 - no mouse
;mousehandler=1 - use mouse
inet=2
;1:read hosts file and access DNS servers
;2:use getaddrinfo function for internet connections
[IO]
;deviopopupontry=3
;additional tries num. before displaying i/o dialog box when errors are auto ignored
deviopopupdelay=2000
;additional ms delay before displaying i/o dialog box when errors are auto ignored
queryioerrors=1
querydrivenotready=1
retries=1
;retries=N
; read/write retries number for sector with errors (N=0..999)
; if N=0 read block only once (the rest of the block after error is zeroed)
seekretries=1
;seekretries=N - read/write auto retries number if sector not found (N=0..64)
deviojump=0
;deviojump=N
;jump over N sectors after I/O error
deviojumpreturn=0
;deviojumpreturn=1 - reverse read after jump
;deviojumpreturn=0 - do not reverse read
devioskipfiller=0x50494B53
;fill skipped sectors with hex values
deviobadfiller=0x20444142
;deviobadfiller=0x20444142 - fill bad sectors with hex values
;deviobadfiller= - do not fill bads
buffer=131072
;buffer=N - disk data transfer block size (N=4096..1048576)
dblbuffer=2097152
;dblbuffer=N -
;(N=4096..16777216)
diskcache=16777216
;diskcache=N - disk cache size
;(N=4096..33554432)
;diskmaxmodsize=209715200
;max size for pending modifications
devreset_chkserial=1
;check device serial number when resetting handle
devreset_trynewdevs=1
;devreset_trynewdevs=0 ;never try different device names
;devreset_trynewdevs=1 ;try different names when needed and serial can be checked
;devreset_trynewdevs=2 ;always try different names (not recommended)
devlistresetonerror=0
;devlistresetonerror=1 ;Update the list of devices on a device i/o error
vhd_writeenabled=0
;vhd_writeenabled=1 ;enable partial write support for virtual disk image files (vhd/vmdk/etc.)
[DR]
maxrecoverdepth=1024
maxfilerenames=16
maxdirrenames=16
splitfilesize=0
[FullScan]
showvolumesnum=2048
[FAT]
fatdirentryaccept=1
;FAT directory entry validation level:
;fatdirentryaccept=0 - accept reliable entries within reliable sectors only
;fatdirentryaccept=1 - accept any entries within reliable sectors
;fatdirentryaccept=2 - accept reliable entries within any sector
;fatdirentryaccept=3 - accept any entry within any sector
fatinvdirtotree=0
;fatinvdirtotree=0 - add FAT invalid directories to file panel only
;fatinvdirtotree=1 - insert FAT invalid directories into tree also
fatfoundtoroot=1
;fatfoundtoroot=0 - add found root subdirs to a common directory list
;fatfoundtoroot=1 - insert found root subdirectories into $Root
volumeseachblock=4194304
;blocksize for FAT/NTFS volume search
fatmaxvolumes=1024
[NTFSSearch]
ntfsmaxvolumes=1024
ntfsmaxprocstarts=1024
ntfsmaxmftruns=0xffffff
ntfsmaxprocmftruns=32768
ntfsmaxindxrecs=0
;ntfsthoroughsearch=0 - may skip small inserted MFT fragments
;ntfsthoroughsearch=1 - more thorough NTFS Search
ntfsthoroughsearch=1
;ntfsmftshift=0 - process sector aligned MFT records only
;ntfsmftshift=N - process MFT shifted by multiple of N bytes (shifted, traced, etc.)
ntfsmftshift=1
;ntfsrestrunlen=N - small MFT runs (N or less records) to be partially processed only
ntfsrestrunlen=4
[IFACE]
filecachesize=32768
;max. number of cached file panel items
maxtreechilditems=2048
;max child items number displayed in tree (GUI)
charclustermap=]xR./=><<!x|[
;console UI:
dlgframe=1
dlglistframe=0
;console chars:
charraidbtns=+,30,x,^,v,*
charmarks=<>x*\u25BC\x20\x20
charbtnshadow=\x20\x20\x20
charwframe=\u250C\u2500\u2510\u2502\u2502\u2514\u2500\u2518
charframe=\u250C\u2500\u2510\u2502\u2502\u2514\u2500\u2518
charvscroll=\u25B2\u25BC\u2592\u25A0
charhscroll=<>\u2592\u25A0
chartree=\u251C\u2514
charprogress=\u2588\u2592
; remove "#" to use color scheme
editorcolors#=\ ;color scheme
1F,\ ;0 Default
1B,\ ;1 Caption
0F,\ ;2 Focused
70,\ ;3 Selected block
0F,\ ;4 Selected object
0E,\ ;5 Modified
1A,\ ;6 Read Error
1A,\ ;7 Title
1C,\ ;8 Invalid Value
03,\ ;9 selected input
07,\ ;10 grayed
1B,\ ;11 selected not focused
1A,\ ;12 Caption Raid Disk #1
17,\ ;13 Caption Raid Disk #2
1C ;14 Invalid Input
editorcolors#=\ ;gray scheme
8F,\ ;0 Default
8B,\ ;1 Caption
0F,\ ;2 Focused
70,\ ;3 Selected block
0F,\ ;4 Selected object
0E,\ ;5 Modified
8A,\ ;6 Read Error
8A,\ ;7 Title
8C,\ ;8 Invalid Value
03,\ ;9 selected input
07,\ ;10 grayed
8B,\ ;11 selected not focused
81,\ ;12 Caption Raid Disk #1
82,\ ;13 Caption Raid Disk #2
8C ;14 Invalid Input
editorcolors#=\ ;blue scheme
1B,\ ;0 Default
1E,\ ;1 Caption
3F,\ ;2 Focused
30,\ ;3 Selected block
0F,\ ;4 Selected object
0E,\ ;5 Modified
1A,\ ;6 Read Error
1A,\ ;7 Title
1C,\ ;8 Invalid Value
3F,\ ;9 selected input
07,\ ;10 grayed
1F,\ ;11 selected not focused
1A,\ ;12 Caption Raid Disk #1
17,\ ;13 Caption Raid Disk #2
1C ;14 Invalid Input
;console text attributes:
; xxxx:
; x... - text background color for shortcut
; .x.. - text color for shortcut
; ..x. - text background color
; ...x - text color
; 0=Black 1=Blue 2=Green 3=Cyan 4=Red 5=Magenta 6=Brown 7=ltGray
; 8=dkGray 9=ltBlue A=ltGreen B=ltCyan C=ltRed D=ltMagenta E=Yellow F=White
; remove "#" to use color scheme
colors#=\ ;color scheme
3E30,\ ;menubox
3E30,\ ;menubar
0E0E,\ ;highlighted menu item
3838,\ ;disabled menu item
2E20,\ ;button
2828,\ ;disabled button
2E2F,\ ;focused button
2E2B,\ ;default button
0030,\ ;list item
002F,\ ;focused list item
003F,\ ;selected list item
003E,\ ;marked list item
002E,\ ;marked focused list item
002F,\ ;active dialog caption
002F,\ ;not active dialog caption
001F,\ ;input
003F,\ ;input selection
0078,\ ;disabled input
00FE,\ ;modified input
0013,\ ;scrollbar
3E30,\ ;cluster
3F3F,\ ;cluster highlighted
7878,\ ;cluster disabled
001F,\ ;active window frame
0017,\ ;not active window frame
001F,\ ;active window title
0017,\ ;not active window title
001E,\ ;column title
1E1F,\ ;item in window
002F,\ ;focused item in window
001A,\ ;selected item in window
001E,\ ;marked item in window
003E,\ ;focused marked item in window
3E30,\ ;status line
7E70,\ ;dialog box text
3F3B,\ ;dialog column header
1F1E,\ ;window column header
0071,\ ;group/tab frame color
1E1F,\ ;window button
1818,\ ;window disabled button
2E2F,\ ;window focused button
1E1A,\ ;window default button
1E1F,\ ;window cluster
1E1F,\ ;window cluster highlighted
1818 ;window cluster disabled
colors#=\ ;gray scheme
7470,\ ;menubox
F4F0,\ ;menubar
040F,\ ;highlighted menu item
7878,\ ;disabled menu item
F4F0,\ ;button
F7F7,\ ;disabled button
F4F0,\ ;focused button
F4F0,\ ;default button
008F,\ ;list item
000F,\ ;focused list item
008B,\ ;selected list item
008E,\ ;marked list item
000E,\ ;marked focused list item
00F0,\ ;active dialog caption
00F8,\ ;not active dialog caption
008F,\ ;input
0080,\ ;input selection
0078,\ ;disabled input
008E,\ ;modified input
008F,\ ;scrollbar
7470,\ ;cluster
7470,\ ;cluster highlighted
7878,\ ;cluster disabled
008F,\ ;active window frame
008F,\ ;not active window frame
008F,\ ;active window title
008F,\ ;not active window title
008B,\ ;column title
8B8F,\ ;item in window
000F,\ ;focused item in window
008B,\ ;selected item in window
008E,\ ;marked item in window
000E,\ ;focused marked item in window
0070,\ ;status line
7470,\ ;dialog box text
8F8B,\ ;dialog column header
8F8B,\ ;window column header
0070,\ ;group/tab frame color
8E8F,\ ;window button
8787,\ ;window disabled button
0E0F,\ ;window focused button
8E8B,\ ;window default button
8E8F,\ ;window cluster
8E8F,\ ;window cluster highlighted
8787 ;window cluster disabled
colors#=\ ;blue scheme
3F30,\ ;menubox
3F30,\ ;menubar
0F0F,\ ;highlighted menu item
3838,\ ;disabled menu item
7470,\ ;button
7878,\ ;disabled button
3F3F,\ ;focused button
3F3F,\ ;default button
001B,\ ;list item
003F,\ ;focused list item
001F,\ ;selected list item
001E,\ ;marked list item
003E,\ ;marked focused list item
003F,\ ;active dialog caption
003F,\ ;not active dialog caption
001F,\ ;input
003F,\ ;input selection
0078,\ ;disabled input
00FE,\ ;modified input
001F,\ ;scrollbar
7470,\ ;cluster
7470,\ ;cluster highlighted
7878,\ ;cluster disabled
001B,\ ;active window frame
001B,\ ;not active window frame
001F,\ ;active window title
001B,\ ;not active window title
001E,\ ;column title
1E1B,\ ;item in window
003F,\ ;focused item in window
001F,\ ;selected item in window
001E,\ ;marked item in window
003E,\ ;focused marked item in window
0030,\ ;status line
7470,\ ;dialog box text
1B1E,\ ;dialog column header
1F1E,\ ;window column header
0073,\ ;group/tab frame color
1E1B,\ ;window button
1818,\ ;window disabled button
3F3F,\ ;window focused button
1E1F,\ ;window default button
1E1B,\ ;window cluster
1E1B,\ ;window cluster highlighted
1818 ;window cluster disabled

View file

@ -47,7 +47,7 @@ prepare() {
patch --directory="$_sourcedir" <"$srcdir/$(basename ${file})"
fi
done
sed 's/Liberation Mono/Hack/;s/float alpha = 0.8;/float alpha = 0.55;/' "$_sourcedir/config.def.h" > "$_sourcedir/config.h"
sed 's/Liberation Mono/Hack/;s/float alpha = 0.8;/float alpha = 0.85;/' "$_sourcedir/config.def.h" > "$_sourcedir/config.h"
# This package provides a mechanism to provide a custom config.h. Multiple
# configuration states are determined by the presence of two files in

View file

@ -15,18 +15,21 @@ syslinux
tigervnc
wpa_supplicant
# hardinfo-gtk3 / opensuperclone-git
cmake
# iwgtk
gtk4
meson
qrencode
scdoc
# python-mariadb-connector
mariadb-libs
# smartmontools-svn
subversion
# udevil
gettext
intltool
# wd719x-firmware
lha

View file

@ -1,3 +0,0 @@
0.0 0 0
0
LOCAL

View file

@ -1,36 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIGTzCCBDegAwIBAgIBfDANBgkqhkiG9w0BAQsFADCBsDELMAkGA1UEBhMCVVMx
DzANBgNVBAgTBk9yZWdvbjERMA8GA1UEBxMIUG9ydGxhbmQxHTAbBgNVBAoTFDEy
MDEgQ29tcHV0ZXIgUmVwYWlyMSMwIQYDVQQLExoxMjAxIENlcnRpZmljYXRlIEF1
dGhvcml0eTEVMBMGA1UEAxMMMTIwMSBSb290IENBMSIwIAYJKoZIhvcNAQkBFhNt
YW5hZ2VtZW50QDEyMDEuY29tMB4XDTE4MDgyMDA2MDEwMFoXDTM4MDgyMDA2MDEw
MFowgbAxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZPcmVnb24xETAPBgNVBAcTCFBv
cnRsYW5kMR0wGwYDVQQKExQxMjAxIENvbXB1dGVyIFJlcGFpcjEjMCEGA1UECxMa
MTIwMSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFTATBgNVBAMTDDEyMDEgUm9vdCBD
QTEiMCAGCSqGSIb3DQEJARYTbWFuYWdlbWVudEAxMjAxLmNvbTCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBANGYohJk5/CC/p14R7EpnhdEUF7Wvlnt8yuF
dtuyStlIGkLxPMlj9hQfoLDplQqlKBefTaI3WwrI/Hndso+jStLKgtRWRdyNB34K
AWqT04zXYGicdi3fqaMhEC4SPyX1tRXU2e9kjtIJ21AZx2F40NUjfOMKLVymZgXm
gkG1oA/BSzE8vIidrd/lJPwo0u+EYFa87y+9SHS93Ze1AVoTVqUzSMkjqt+6YIzJ
4XBD7UBvps0Mnd18HMUlXHFXusUL1K921W3wDVcMlNIIA8MJjQk+aVS/1EGSn+81
C+r40x64lYkyh0ZUAHkVXUC/BUfa0SKx1Nfa4mSvtyPnUCb7Dir8MkTDKgopGCok
KmW+VvE2H8AEPCbcctFmhdip19laYxzyDhZ5wiQN6AOg64cWvDf6/uT9hyPvxkj1
ps5vWElryzawTE7h1BI8liMqwsG1Y7cc6D0PABxPsp4iR8pde0oZtpLnEvejRodo
zz3BGvZjq+pHtRMjL+yiDtdAL+K+7/e7gNCQBIGsphahWIOf7TczWVgMNclTNxl3
WZjKkOEs7j+prRTDvffV6H32+Tk5TwgMsfvnY4a37CkJ0L0d1JhWj9wO+gESfg3W
8yvt3hfcj3NOUMJWhJstqlIeX8dj7vVcMhjNvYJxabJmJgk+DNlHe55fXDGJ1CLO
E0EbRTyBAgMBAAGjcjBwMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFM+hXjFx
6BldZFBQW1Pn/Yp3vbw+MAsGA1UdDwQEAwIBBjARBglghkgBhvhCAQEEBAMCAAcw
HgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOC
AgEALWcnu3auMSnSSF/kOiLvJ4RAnHZebGYNcUWM14u1K1/XtTB7AFzQIHX7BcDH
m/z4UEyhl9EdR5Bgf2Szuk+8+LyGqcdAdbPoK+bmcwwL8lufDnlIYBThKIBfU2Xw
vw41972B+HH5r1TZXve1EdJaLyImbxmq5s41oH7djGC+sowtyGuVqP7RBguXBGiJ
At1yfdPWVaxLmE8QFknkIvpgTmELpxasTfvgnQBenA3Ts0Z2hwN4796hLbRzGsb8
4hKWAfQDP0klzXKRRyVeAueXxj/FcNZilYxv15MqMc4qrUiW0hXHluQM1yceNjNZ
SE4Igi1Ap71L4PpgkHIDfZD908UexGGkql+p4EWrpnXUYWTa0sHg1bFKQntgpyFg
86Ug0Q7ZNhImENzeigZknL0ceIdaNUCs7UPrkqpUSJR2yujp1JC3tX1LgKZw8B3J
fQx/8h3zzNuz5dVtr1wUJaUD0nGhMIRBEXb2t4jupEISSTN1pkHPcbNzhAQXjVUA
CJxnnz3jmyGsNCoQf7NWfaN6RSRTWehsC6m7JvPvoU2EZoQkSlNOv4xZuFpEx0u7
MFDtC1cSGPH7YwYXPVc45xAMC6Ni8mvq93oT89XZNHIqE8/T8aPHLwYFgu1b1r/A
L8oMEnG5s8tG3n0DcFoOYsaIzVeP0r7B7e3zKui6DQLuu9E=
-----END CERTIFICATE-----

View file

@ -30,7 +30,6 @@ alias rmdirs='find -depth -mindepth 1 -type d -exec rmdir "{}" --ignore-fail-on-
alias rs='rsync -avhPS --stats --exclude-from="$HOME/.rsync_exclusions"'
alias rsz='rsync -avhzPS --stats --exclude-from="$HOME/.rsync_exclusions"'
alias sdu='sudo du -sch --apparent-size'
alias set_lp8550_slope='sudo set_lp8550_slope.py'
alias srmdirs='sudo find -depth -mindepth 1 -type d -exec rmdir "{}" --ignore-fail-on-non-empty \;'
alias srs='sudo rsync -avhPS --stats --exclude-from="$HOME/.rsync_exclusions"'
alias srsz='sudo rsync -avhzPS --stats --exclude-from="$HOME/.rsync_exclusions"'

View file

@ -1,4 +0,0 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by fontconfig.
# For information about cache directory tags, see:
# http://www.brynosaurus.com/cachedir/

Some files were not shown because too many files have changed in this diff Show more