Merge branch 'datarec' into dev
This commit is contained in:
commit
bb631b39e9
11 changed files with 1476 additions and 30 deletions
43
.bin/Scripts/ddrescue-tui
Executable file
43
.bin/Scripts/ddrescue-tui
Executable file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
## Wizard Kit: ddrescue TUI Launcher
|
||||||
|
|
||||||
|
SESSION_NAME="ddrescue-tui"
|
||||||
|
WINDOW_NAME="GNU ddrescue TUI"
|
||||||
|
MENU="ddrescue-tui-menu"
|
||||||
|
|
||||||
|
function ask() {
|
||||||
|
while :; do
|
||||||
|
read -p "$1 " -r answer
|
||||||
|
if echo "$answer" | egrep -iq '^(y|yes|sure)$'; then
|
||||||
|
return 0
|
||||||
|
elif echo "$answer" | egrep -iq '^(n|no|nope)$'; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo "$0:" "$@" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for running session
|
||||||
|
if tmux list-session | grep -q "$SESSION_NAME"; then
|
||||||
|
echo "WARNING: tmux session $SESSION_NAME already exists."
|
||||||
|
echo ""
|
||||||
|
if ask "Kill current session?"; then
|
||||||
|
tmux kill-session -t "$SESSION_NAME" || \
|
||||||
|
die "Failed to kill session: $SESSION_NAME"
|
||||||
|
else
|
||||||
|
echo "Aborted."
|
||||||
|
echo ""
|
||||||
|
echo -n "Press Enter to exit... "
|
||||||
|
read -r
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start session
|
||||||
|
tmux new-session -s "$SESSION_NAME" -n "$WINDOW_NAME" "$MENU" $*
|
||||||
|
|
||||||
63
.bin/Scripts/ddrescue-tui-menu
Executable file
63
.bin/Scripts/ddrescue-tui-menu
Executable file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/bin/python3
|
||||||
|
#
|
||||||
|
## Wizard Kit: TUI for ddrescue cloning and imaging
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Init
|
||||||
|
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
|
||||||
|
from functions.ddrescue import *
|
||||||
|
from functions.hw_diags import *
|
||||||
|
init_global_vars()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
# Prep
|
||||||
|
clear_screen()
|
||||||
|
args = list(sys.argv)
|
||||||
|
run_mode = ''
|
||||||
|
source_path = None
|
||||||
|
dest_path = None
|
||||||
|
|
||||||
|
# Parse args
|
||||||
|
try:
|
||||||
|
script_name = os.path.basename(args.pop(0))
|
||||||
|
run_mode = str(args.pop(0)).lower()
|
||||||
|
source_path = args.pop(0)
|
||||||
|
dest_path = args.pop(0)
|
||||||
|
except IndexError:
|
||||||
|
# We'll set the missing paths later
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Show usage
|
||||||
|
if re.search(r'-*(h|help|\?)', str(sys.argv), re.IGNORECASE):
|
||||||
|
show_usage(script_name)
|
||||||
|
exit_script()
|
||||||
|
|
||||||
|
# Start cloning/imaging
|
||||||
|
if run_mode in ('clone', 'image'):
|
||||||
|
menu_ddrescue(source_path, dest_path, run_mode)
|
||||||
|
else:
|
||||||
|
if not re.search(r'^-*(h|help\?)', run_mode, re.IGNORECASE):
|
||||||
|
print_error('Invalid mode.')
|
||||||
|
|
||||||
|
# Done
|
||||||
|
print_standard('\nDone.')
|
||||||
|
pause("Press Enter to exit...")
|
||||||
|
exit_script()
|
||||||
|
except GenericAbort:
|
||||||
|
abort()
|
||||||
|
except GenericError as ge:
|
||||||
|
msg = 'Generic Error'
|
||||||
|
if str(ge):
|
||||||
|
msg = str(ge)
|
||||||
|
print_error(msg)
|
||||||
|
abort()
|
||||||
|
except SystemExit:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
major_exception()
|
||||||
|
|
||||||
|
# vim: sts=4 sw=4 ts=4
|
||||||
39
.bin/Scripts/ddrescue-tui-smart-display
Executable file
39
.bin/Scripts/ddrescue-tui-smart-display
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/bin/python3
|
||||||
|
#
|
||||||
|
## Wizard Kit: SMART attributes display for ddrescue TUI
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Init
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
sys.path.append(os.getcwd())
|
||||||
|
from functions.hw_diags import *
|
||||||
|
#init_global_vars()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
# Prep
|
||||||
|
clear_screen()
|
||||||
|
dev_path = sys.argv[1]
|
||||||
|
devs = scan_disks(True, dev_path)
|
||||||
|
|
||||||
|
# Warn if SMART unavailable
|
||||||
|
if dev_path not in devs:
|
||||||
|
print_error('SMART data not available')
|
||||||
|
exit_script()
|
||||||
|
|
||||||
|
# Initial screen
|
||||||
|
dev = devs[dev_path]
|
||||||
|
clear_screen()
|
||||||
|
show_disk_details(dev, only_attributes=True)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
exit_script()
|
||||||
|
except SystemExit:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
major_exception()
|
||||||
|
|
||||||
|
# vim: sts=4 sw=4 ts=4
|
||||||
12
.bin/Scripts/echo-and-hold
Executable file
12
.bin/Scripts/echo-and-hold
Executable file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
## Wizard Kit: "echo" text to screen and "hold" by waiting for user input
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
echo "Usage: $(basename "$0") \"text\""
|
||||||
|
echo " e.g. $(basename "$0") \"Some text to show\""
|
||||||
|
}
|
||||||
|
|
||||||
|
echo -en "$@" && read -r __dont_care
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
|
@ -197,6 +197,30 @@ def extract_item(item, filter='', silent=False):
|
||||||
if not silent:
|
if not silent:
|
||||||
print_warning('WARNING: Errors encountered while exctracting data')
|
print_warning('WARNING: Errors encountered while exctracting data')
|
||||||
|
|
||||||
|
def get_process(name=None):
|
||||||
|
"""Get process by name, returns psutil.Process obj."""
|
||||||
|
proc = None
|
||||||
|
if not name:
|
||||||
|
raise GenericError
|
||||||
|
|
||||||
|
for p in psutil.process_iter():
|
||||||
|
try:
|
||||||
|
if p.name() == name:
|
||||||
|
proc = p
|
||||||
|
except psutil._exceptions.NoSuchProcess:
|
||||||
|
# Process finished during iteration? Going to ignore
|
||||||
|
pass
|
||||||
|
return proc
|
||||||
|
|
||||||
|
def get_simple_string(prompt='Enter string'):
|
||||||
|
"""Get string from user (minimal allowed character set) and return as str."""
|
||||||
|
simple_string = None
|
||||||
|
while simple_string is None:
|
||||||
|
_input = input('{}: '.format(prompt))
|
||||||
|
if re.match(r"^(\w|-| |\.|')+$", _input, re.ASCII):
|
||||||
|
simple_string = _input.strip()
|
||||||
|
return simple_string
|
||||||
|
|
||||||
def get_ticket_number():
|
def get_ticket_number():
|
||||||
"""Get TicketNumber from user, save in LogDir, and return as str."""
|
"""Get TicketNumber from user, save in LogDir, and return as str."""
|
||||||
if not ENABLED_TICKET_NUMBERS:
|
if not ENABLED_TICKET_NUMBERS:
|
||||||
|
|
@ -213,15 +237,6 @@ def get_ticket_number():
|
||||||
f.write(ticket_number)
|
f.write(ticket_number)
|
||||||
return ticket_number
|
return ticket_number
|
||||||
|
|
||||||
def get_simple_string(prompt='Enter string'):
|
|
||||||
"""Get string from user (only alphanumeric/space chars) and return as str."""
|
|
||||||
simple_string = None
|
|
||||||
while simple_string is None:
|
|
||||||
_input = input('{}: '.format(prompt))
|
|
||||||
if re.match(r'^(\w|-| )+$', _input, re.ASCII):
|
|
||||||
simple_string = _input.strip()
|
|
||||||
return simple_string
|
|
||||||
|
|
||||||
def human_readable_size(size, decimals=0):
|
def human_readable_size(size, decimals=0):
|
||||||
"""Convert size in bytes to a human-readable format and return a str."""
|
"""Convert size in bytes to a human-readable format and return a str."""
|
||||||
# Prep string formatting
|
# Prep string formatting
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ def get_mounted_volumes():
|
||||||
mounted_volumes.extend(item.get('children', []))
|
mounted_volumes.extend(item.get('children', []))
|
||||||
return {item['source']: item for item in mounted_volumes}
|
return {item['source']: item for item in mounted_volumes}
|
||||||
|
|
||||||
def mount_all_volumes():
|
def mount_volumes(all_devices=True, device_path=None, read_write=False):
|
||||||
"""Mount all detected filesystems."""
|
"""Mount all detected filesystems."""
|
||||||
report = {}
|
report = {}
|
||||||
|
|
||||||
|
|
@ -195,6 +195,9 @@ def mount_all_volumes():
|
||||||
cmd = [
|
cmd = [
|
||||||
'lsblk', '-J', '-p',
|
'lsblk', '-J', '-p',
|
||||||
'-o', 'NAME,FSTYPE,LABEL,UUID,PARTTYPE,TYPE,SIZE']
|
'-o', 'NAME,FSTYPE,LABEL,UUID,PARTTYPE,TYPE,SIZE']
|
||||||
|
if not all_devices and device_path:
|
||||||
|
# Only mount volumes for specific device
|
||||||
|
cmd.append(device_path)
|
||||||
result = run_program(cmd)
|
result = run_program(cmd)
|
||||||
json_data = json.loads(result.stdout.decode())
|
json_data = json.loads(result.stdout.decode())
|
||||||
devs = json_data.get('blockdevices', [])
|
devs = json_data.get('blockdevices', [])
|
||||||
|
|
@ -233,8 +236,11 @@ def mount_all_volumes():
|
||||||
vol_data['show_data']['warning'] = True
|
vol_data['show_data']['warning'] = True
|
||||||
else:
|
else:
|
||||||
# Mount volume
|
# Mount volume
|
||||||
|
cmd = ['udevil', 'mount',
|
||||||
|
'-o', 'rw' if read_write else 'ro',
|
||||||
|
vol_path]
|
||||||
try:
|
try:
|
||||||
run_program(['udevil', 'mount', '-o', 'ro', vol_path])
|
run_program(cmd)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
vol_data['show_data']['data'] = 'Failed to mount'
|
vol_data['show_data']['data'] = 'Failed to mount'
|
||||||
vol_data['show_data']['error'] = True
|
vol_data['show_data']['error'] = True
|
||||||
|
|
@ -242,11 +248,16 @@ def mount_all_volumes():
|
||||||
mounted_volumes = get_mounted_volumes()
|
mounted_volumes = get_mounted_volumes()
|
||||||
|
|
||||||
# Format pretty result string
|
# Format pretty result string
|
||||||
if vol_data['show_data']['data'] != 'Failed to mount':
|
if vol_data['show_data']['data'] == 'Failed to mount':
|
||||||
|
vol_data['mount_point'] = None
|
||||||
|
else:
|
||||||
size_used = human_readable_size(
|
size_used = human_readable_size(
|
||||||
mounted_volumes[vol_path]['used'])
|
mounted_volumes[vol_path]['used'])
|
||||||
size_avail = human_readable_size(
|
size_avail = human_readable_size(
|
||||||
mounted_volumes[vol_path]['avail'])
|
mounted_volumes[vol_path]['avail'])
|
||||||
|
vol_data['size_avail'] = size_avail
|
||||||
|
vol_data['size_used'] = size_used
|
||||||
|
vol_data['mount_point'] = mounted_volumes[vol_path]['target']
|
||||||
vol_data['show_data']['data'] = 'Mounted on {}'.format(
|
vol_data['show_data']['data'] = 'Mounted on {}'.format(
|
||||||
mounted_volumes[vol_path]['target'])
|
mounted_volumes[vol_path]['target'])
|
||||||
vol_data['show_data']['data'] = '{:40} ({} used, {} free)'.format(
|
vol_data['show_data']['data'] = '{:40} ({} used, {} free)'.format(
|
||||||
|
|
|
||||||
1233
.bin/Scripts/functions/ddrescue.py
Normal file
1233
.bin/Scripts/functions/ddrescue.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,7 @@
|
||||||
# Wizard Kit: Functions - HW Diagnostics
|
# Wizard Kit: Functions - HW Diagnostics
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
from functions.common import *
|
from functions.common import *
|
||||||
|
|
||||||
|
|
@ -56,7 +57,9 @@ def get_read_rate(s):
|
||||||
|
|
||||||
def get_smart_details(dev):
|
def get_smart_details(dev):
|
||||||
"""Get SMART data for dev if possible, returns dict."""
|
"""Get SMART data for dev if possible, returns dict."""
|
||||||
cmd = 'sudo smartctl --all --json /dev/{}'.format(dev).split()
|
cmd = 'sudo smartctl --all --json {}{}'.format(
|
||||||
|
'' if '/dev/' in dev else '/dev/',
|
||||||
|
dev).split()
|
||||||
result = run_program(cmd, check=False)
|
result = run_program(cmd, check=False)
|
||||||
try:
|
try:
|
||||||
return json.loads(result.stdout.decode())
|
return json.loads(result.stdout.decode())
|
||||||
|
|
@ -509,12 +512,17 @@ def run_tests(tests):
|
||||||
global_vars['LogFile']))
|
global_vars['LogFile']))
|
||||||
pause('Press Enter to exit...')
|
pause('Press Enter to exit...')
|
||||||
|
|
||||||
def scan_disks():
|
def scan_disks(full_paths=False, only_path=None):
|
||||||
"""Scan for disks eligible for hardware testing."""
|
"""Scan for disks eligible for hardware testing."""
|
||||||
clear_screen()
|
clear_screen()
|
||||||
|
|
||||||
# Get eligible disk list
|
# Get eligible disk list
|
||||||
result = run_program(['lsblk', '-J', '-O'])
|
cmd = ['lsblk', '-J', '-O']
|
||||||
|
if full_paths:
|
||||||
|
cmd.append('-p')
|
||||||
|
if only_path:
|
||||||
|
cmd.append(only_path)
|
||||||
|
result = run_program(cmd)
|
||||||
json_data = json.loads(result.stdout.decode())
|
json_data = json.loads(result.stdout.decode())
|
||||||
devs = {}
|
devs = {}
|
||||||
for d in json_data.get('blockdevices', []):
|
for d in json_data.get('blockdevices', []):
|
||||||
|
|
@ -536,13 +544,18 @@ def scan_disks():
|
||||||
for dev, data in devs.items():
|
for dev, data in devs.items():
|
||||||
# Get SMART attributes
|
# Get SMART attributes
|
||||||
run_program(
|
run_program(
|
||||||
cmd = 'sudo smartctl -s on /dev/{}'.format(dev).split(),
|
cmd = 'sudo smartctl -s on {}{}'.format(
|
||||||
|
'' if full_paths else '/dev/',
|
||||||
|
dev).split(),
|
||||||
check = False)
|
check = False)
|
||||||
data['smartctl'] = get_smart_details(dev)
|
data['smartctl'] = get_smart_details(dev)
|
||||||
|
|
||||||
# Get NVMe attributes
|
# Get NVMe attributes
|
||||||
if data['lsblk']['tran'] == 'nvme':
|
if data['lsblk']['tran'] == 'nvme':
|
||||||
cmd = 'sudo nvme smart-log /dev/{} -o json'.format(dev).split()
|
cmd = 'sudo nvme smart-log /dev/{} -o json'.format(dev).split()
|
||||||
|
cmd = 'sudo nvme smart-log {}{} -o json'.format(
|
||||||
|
'' if full_paths else '/dev/',
|
||||||
|
dev).split()
|
||||||
result = run_program(cmd, check=False)
|
result = run_program(cmd, check=False)
|
||||||
try:
|
try:
|
||||||
data['nvme-cli'] = json.loads(result.stdout.decode())
|
data['nvme-cli'] = json.loads(result.stdout.decode())
|
||||||
|
|
@ -588,19 +601,23 @@ def scan_disks():
|
||||||
TESTS['NVMe/SMART']['Devices'] = devs
|
TESTS['NVMe/SMART']['Devices'] = devs
|
||||||
TESTS['badblocks']['Devices'] = devs
|
TESTS['badblocks']['Devices'] = devs
|
||||||
TESTS['iobenchmark']['Devices'] = devs
|
TESTS['iobenchmark']['Devices'] = devs
|
||||||
|
return devs
|
||||||
|
|
||||||
def show_disk_details(dev):
|
def show_disk_details(dev, only_attributes=False):
|
||||||
"""Display disk details."""
|
"""Display disk details."""
|
||||||
dev_name = dev['lsblk']['name']
|
dev_name = dev['lsblk']['name']
|
||||||
# Device description
|
if not only_attributes:
|
||||||
print_info('Device: /dev/{}'.format(dev['lsblk']['name']))
|
# Device description
|
||||||
print_standard(' {:>4} ({}) {} {}'.format(
|
print_info('Device: {}{}'.format(
|
||||||
str(dev['lsblk'].get('size', '???b')).strip(),
|
'' if '/dev/' in dev['lsblk']['name'] else '/dev/',
|
||||||
str(dev['lsblk'].get('tran', '???')).strip().upper().replace(
|
dev['lsblk']['name']))
|
||||||
'NVME', 'NVMe'),
|
print_standard(' {:>4} ({}) {} {}'.format(
|
||||||
str(dev['lsblk'].get('model', 'Unknown Model')).strip(),
|
str(dev['lsblk'].get('size', '???b')).strip(),
|
||||||
str(dev['lsblk'].get('serial', 'Unknown Serial')).strip(),
|
str(dev['lsblk'].get('tran', '???')).strip().upper().replace(
|
||||||
))
|
'NVME', 'NVMe'),
|
||||||
|
str(dev['lsblk'].get('model', 'Unknown Model')).strip(),
|
||||||
|
str(dev['lsblk'].get('serial', 'Unknown Serial')).strip(),
|
||||||
|
))
|
||||||
|
|
||||||
# Warnings
|
# Warnings
|
||||||
if dev.get('NVMe Disk', False):
|
if dev.get('NVMe Disk', False):
|
||||||
|
|
@ -615,7 +632,12 @@ def show_disk_details(dev):
|
||||||
|
|
||||||
# Attributes
|
# Attributes
|
||||||
if dev.get('NVMe Disk', False):
|
if dev.get('NVMe Disk', False):
|
||||||
print_info('Attributes:')
|
if only_attributes:
|
||||||
|
print_info('SMART Attributes:', end='')
|
||||||
|
print_warning(' Updated: {}'.format(
|
||||||
|
time.strftime('%Y-%m-%d %H:%M %Z')))
|
||||||
|
else:
|
||||||
|
print_info('Attributes:')
|
||||||
for attrib, threshold in sorted(ATTRIBUTES['NVMe'].items()):
|
for attrib, threshold in sorted(ATTRIBUTES['NVMe'].items()):
|
||||||
if attrib in dev['nvme-cli']:
|
if attrib in dev['nvme-cli']:
|
||||||
print_standard(
|
print_standard(
|
||||||
|
|
@ -636,7 +658,12 @@ def show_disk_details(dev):
|
||||||
print_success(raw_str, timestamp=False)
|
print_success(raw_str, timestamp=False)
|
||||||
elif dev['smartctl'].get('ata_smart_attributes', None):
|
elif dev['smartctl'].get('ata_smart_attributes', None):
|
||||||
# SMART attributes
|
# SMART attributes
|
||||||
print_info('Attributes:')
|
if only_attributes:
|
||||||
|
print_info('SMART Attributes:', end='')
|
||||||
|
print_warning(' Updated: {}'.format(
|
||||||
|
time.strftime('%Y-%m-%d %H:%M %Z')))
|
||||||
|
else:
|
||||||
|
print_info('Attributes:')
|
||||||
s_table = dev['smartctl'].get('ata_smart_attributes', {}).get(
|
s_table = dev['smartctl'].get('ata_smart_attributes', {}).get(
|
||||||
'table', {})
|
'table', {})
|
||||||
s_table = {a.get('id', 'Unknown'): a for a in s_table}
|
s_table = {a.get('id', 'Unknown'): a for a in s_table}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ die () {
|
||||||
|
|
||||||
# Check for running session
|
# Check for running session
|
||||||
if tmux list-session | grep -q "$SESSION_NAME"; then
|
if tmux list-session | grep -q "$SESSION_NAME"; then
|
||||||
echo "WARNING: hw-diags tmux session already exists."
|
echo "WARNING: tmux session $SESSION_NAME already exists."
|
||||||
echo ""
|
echo ""
|
||||||
if ask "Kill current session?"; then
|
if ask "Kill current session?"; then
|
||||||
tmux kill-session -t "$SESSION_NAME" || \
|
tmux kill-session -t "$SESSION_NAME" || \
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ if __name__ == '__main__':
|
||||||
print_standard('{}: Volume mount tool'.format(KIT_NAME_FULL))
|
print_standard('{}: Volume mount tool'.format(KIT_NAME_FULL))
|
||||||
|
|
||||||
# Mount volumes
|
# Mount volumes
|
||||||
report = mount_all_volumes()
|
report = mount_volumes(all_devices=True)
|
||||||
|
|
||||||
# Print report
|
# Print report
|
||||||
print_info('\nResults')
|
print_info('\nResults')
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ alias 7z5='7z a -t7z -mx=5'
|
||||||
alias 7z7='7z a -t7z -mx=7'
|
alias 7z7='7z a -t7z -mx=7'
|
||||||
alias 7z9='7z a -t7z -mx=9'
|
alias 7z9='7z a -t7z -mx=9'
|
||||||
alias df='pydf'
|
alias df='pydf'
|
||||||
|
alias ddrescue='sudo ddrescue --ask --min-read-rate=64k -vvvv'
|
||||||
alias diff='colordiff -ur'
|
alias diff='colordiff -ur'
|
||||||
alias du='du -sch --apparent-size'
|
alias du='du -sch --apparent-size'
|
||||||
alias fix-perms='find -type d -exec chmod 755 "{}" \; && find -type f -exec chmod 644 "{}" \;'
|
alias fix-perms='find -type d -exec chmod 755 "{}" \; && find -type f -exec chmod 644 "{}" \;'
|
||||||
|
|
@ -36,3 +37,5 @@ alias srsz='sudo rsync -avhzPS --stats --exclude-from="$HOME/.rsync_exclusions"'
|
||||||
alias testdisk='sudo testdisk'
|
alias testdisk='sudo testdisk'
|
||||||
alias umount='sudo umount'
|
alias umount='sudo umount'
|
||||||
alias unmount='sudo umount'
|
alias unmount='sudo umount'
|
||||||
|
alias wkclone='sudo ddrescue-tui clone'
|
||||||
|
alias wkimage='sudo ddrescue-tui image'
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue