You are not logged in.
@HobbitJack is using a /bin/sh shebang, presumably to avoid the bash bloat, so they should probably read the "Case Conditional Construct" section of https://pubs.opengroup.org/onlinepubs/9 … hap02.html instead.
Bash == /bin/sh in Arch but most other distributions aren't so foolish.
Jin, Jîyan, Azadî
Offline
In this case it was mostly because this was bodged together several times (this is version 2 lol). Also something of force of Python habit, but this isn't Python. Next time I update it I'll want to merge the argument checking into a case statement, but if I recall I was just adding those ad hoc so whatever came out is what we got here.
I am at least aware of case statements, don't worry! =D
slides massive shell script monstrosities full of bloated case-esac used for data analysis under a rock
Last edited by HobbitJack (2024-12-12 09:06:58)
Astrophysicist and programmer. I like simple things.
Offline
A seasonally-appropriate script that uses "find" to scan through FLAC files, find any with "Christmas" or "Xmas" in the file name and asks if the GENRE tag should add "Christmas" as a GENRE if it is absent.
#! /bin/bash
if [[ $# -eq 0 ]]; then
/usr/bin/find . -regextype egrep -regex '.*(Xmas|Christmas).*\.flac' -exec $0 {} \;
else
GENRE=$(/usr/bin/metaflac --show-tag=GENRE $1)
if [[ ! $(echo $GENRE | /usr/bin/grep "Christmas") ]]; then
echo "$1 does not contain Christmas in GENRE"
read -p "Would you like to add it? [y/n]" answer
if [[ $answer == y ]]; then
NEW_GENRE="$GENRE; Christmas"
NEW_GENRE=$(echo $NEW_GENRE | sed 's/GENRE=//')
echo "New GENRE is $NEW_GENRE"
/usr/bin/metaflac --preserve-modtime --remove-tag=GENRE --set-tag="GENRE=$NEW_GENRE" $1
/usr/bin/metaflac --show-tag=TITLE --show-tag=GENRE $1
fi
fi
fi
Offline
Another little script from me, this one to manage my ever-growing collection of 'noteson${thing}.txt'.
#!/usr/bin/sh
case $1 in
''|'-h'|'--help'|'help')
echo 'Usage: note COMMAND|NOTE'
echo 'manage notes'
echo
echo "Notes are saved at '~/.local/share/note/'."
echo
echo 'COMMAND:'
echo ' cat NOTE print the contents of NOTE to STDOUT'
echo ' ls list all notes'
echo ' reinit delete all notes and recreate note directory'
echo ' rm NOTE delete NOTE'
echo ' help print this help'
echo ' version print version and license information'
echo
echo 'NOTE:'
echo " Open NOTE in '`basename ${EDITOR:-vi}`'"
exit
;;
'-v'|'--version'|'version')
echo 'note v2.1.3'
echo 'Copyright (C) 2024 Jack Renton Uteg.'
echo 'License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.'
echo 'This is free software: you are free to change and redistribute it.'
echo 'There is NO WARRANTY, to the extent permitted by law.'
echo
echo 'Written by Jack Renton Uteg.'
exit
;;
'cat')
if [ -z "$2" ]
then
echo "note cat: no note specified"
exit 1
fi
cat ~/.local/share/note/$2
;;
'ls')
ls -1 ~/.local/share/note/
;;
'reinit')
rm -rf ~/.local/share/note/
mkdir -p ~/.local/share/note/
;;
'rm')
if [ -z "$2" ]
then
echo "note rm: no note specified"
exit 1
fi
rm ~/.local/share/note/$2
;;
*)
if [ -z "$1" ]
then
echo "note: no note specified"
exit 1
fi
${EDITOR:-vi} ~/.local/share/note/$1
esac
Exceptionally minimal, since all this is a basic wrapper for a few regular commands that you can otherwise do with a tad bit more typing. Likely not of interest to anyone but me -- but I at least did use 'case' this time
Astrophysicist and programmer. I like simple things.
Offline
echo "note rm: no note specified"
cd ~/.local/share/note/
select note in *; do echo "$note" && break; done
Or you look into autocompletion
You also likely want to quote the parameters everywhere, eg. you're testing "$2" but attempt to delete the unquoted $2
Offline
I *might* look into autocomplete, but a select interface is interactive and exactly not the kind of thing I want to deal with. I'd rather just be told I typed it wrong.
Fixed the quoting oversight, though.
Astrophysicist and programmer. I like simple things.
Offline
If you are like me and always check for updates directly after boot with "yay", then you may want this automation too:
This small script creates and enables a systemd service. The service checks for updates and installs them if available with yay.
#!/bin/bash
# Prompt for the username
read -p "Enter the username: " USERNAME
SERVICE_FILE="yay-update.service"
SERVICE_PATH="/etc/systemd/system/${SERVICE_FILE}"
# Create the service file
cat <<EOF > "${SERVICE_PATH}"
[Unit]
Description=Yay System Update by user
After=network-online.target
[Service]
Type=oneshot
User=${USERNAME}
ExecStart=/bin/sh -c "sleep 5 && /usr/bin/yay --noconfirm --sudoloop"
[Install]
WantedBy=multi-user.target
EOF
# Enable and start the service
systemctl enable "${SERVICE_FILE}"
systemctl start "${SERVICE_FILE}"
echo "Service ${SERVICE_FILE} has been installed, enabled, and started for the user: ${USERNAME}."
If you get an error like "can't resolve host...", you have do change "sleep 5" to let's say "8" or "sleep 10".
It depends on how fast your internet connection is available. For my connection over cable It must be "5" or more.
The script must be executed as root of course.
Check the status of the new service with "systemctl status yay-update.service".
Ich weiß, dass ich nichts weiß !
Offline
If you get an error like "can't resolve host...", you have do change "sleep 5" to let's say "8" or "sleep 10".
https://bbs.archlinux.org/viewtopic.php … 1#p2090691
The script must be executed as root of course.
system services/timers run as root.
PSA: unattended updates (leaving alone with yay) are not a very good idea, you want to log that somewhere and present yourself that log after the session started.
Offline
This Script will update with "yay" and informs the user about the progress with desktop notifications.
After that it completely cleans the Pacman / Yay cache and use logrotate on pacman.log file.
Every action is logged in /tmp/yay_update.log
You can start this script everytime you boot with a systemd service.
Here is my /usr/local/bin/yay_update.sh file.
Don't forget to make it executable (chmod +x yay_update.sh)
Maybe you have to adjust "sleep 5". My cable connection is ready in 5 seconds.
#!/bin/bash
# File: /usr/local/bin/yay-update.sh
LOG="/tmp/yay_update.log"
sleep 5 && ID=$(notify-send -p -t 1000 -u normal -a Yay --hint=string:sound-name:service-login 'Yay Update' 'System update started ...')
# Trim log file to prevent infinite growth
tail -n 100 $LOG > /tmp/yay_update.tmp && mv /tmp/yay_update.tmp $LOG
echo "$(date): Starting system update..." >> $LOG
# Perform system update
if yay --noconfirm --sudoloop --noprogressbar --needed 2>&1 | tee -a $LOG; then
if grep -q "Error" /tmp/yay_update.log; then
beep -f 2000
notify-send -r $ID -t 5000 -u critical -a Yay -i /usr/share/icons/Adwaita/symbolic/status/computer-fail-symbolic.svg --hint=string:sound-name:dialog-error 'Yay Update' 'Error during update! See /tmp/yay_update.log'
exit 1
fi
beep -f 100
else
beep -f 2000
notify-send -r $ID -t 5000 -u critical -a Yay -i /usr/share/icons/Adwaita/symbolic/status/computer-fail-symbolic.svg --hint=string:sound-name:dialog-error 'Yay Update' 'Error during update! See /tmp/yay_update.log'
exit 1
fi
# Check if no update was necessary
if grep -q "there is nothing to do" $LOG; then
beep -f 500
notify-send -r $ID -t 1000 -u normal -a Yay -i /usr/share/icons/Adwaita/symbolic/categories/emoji-body-symbolic.svg --hint=string:sound-name:dialog-information 'Yay Update' 'No updates available!'
echo "No cache cleanup and logrotate needed, as no update was performed!" >> $LOG
exit 0
fi
# Clean cache
echo "- Cleaning Yay and Pacman cache ..." >> $LOG
yay -Sc --noconfirm 2>&1 | tee -a $LOG
sudo rm -rf /var/cache/pacman/pkg/ 2>&1 | tee -a $LOG
echo "Cache completely cleaned." >> $LOG
# Apply logrotate to pacman.log
echo "- Starting logrotate for /var/log/pacman.log" >> $LOG
if ! sudo logrotate -f -v /etc/logrotate.d/pacman 2>&1 | tee -a $LOG; then
beep -f 2000
notify-send -t 5000 -u critical -a Yay --hint=string:sound-name:dialog-error 'Yay Update' 'Error during logrotate!'
fi
echo "Done! Everything completed." >> $LOG
beep -f 500
notify-send -r $ID -t 1000 -u normal -a Yay -i /usr/share/icons/Adwaita/symbolic/categories/emoji-body-symbolic.svg --hint=string:sound-name:complete 'Yay Update' 'New updates installed!'
Now everything is logged in /tmp/yay_update.log
This script works very well under Gnome desktop/Wayland.
Here ist my yay-update.service file /etc/systemd/system/yay-update.service (Gnome/Wayland):
Don't forget to replace "User=lennart" with your username.
[Unit]
Description=Yay System Update by user
After=network-online.target
Wants=network-online.target
[Service]
TimeoutStartSec=infinity
Type=oneshot
User=lennart
Environment=DISPLAY=:0 WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000
ExecStart=/usr/local/bin/yay-update.sh
[Install]
WantedBy=multi-user.target
Here is the configuration file for logrotate: /etc/logrotate.d/pacman:
/var/log/pacman.log {
weekly
rotate 4
compress
missingok
notifempty
create 644 root root
}
Install the service with:
sudo systemctl enable yay-update.service
Test the service with:
sudo systemctl start yay-update.service
As I wrote before, It works very well with Gnome / Wayland. For other systems, you have to adjust it.
btw. "beep", "logrotate" and "notify-send" must be installed
yay -S beep logrotate notify-send
Regards,
EISENFELD
Last edited by EISENFELD (2025-02-16 11:19:33)
Ich weiß, dass ich nichts weiß !
Offline
As discussed in the linked thread, the network-online.target typically does not do what you'd expect.
Wait until you've an icmp response from a relevant peer.
yay -Scc --noconfirm | tee -a /tmp/yay_update.log
This will drop stderr, otoh you're probably not interested in a copy of stdout?
… >> /tmp/yay_update.log 2>&1
Offline
I think "yay --noconfirm --sudoloop --noprogressbar --needed 2>&1 | tee -a $LOG" is the best solution.
I updated the code in my post.
Ich weiß, dass ich nichts weiß !
Offline
yay --noconfirm --sudoloop --noprogressbar --needed 2>&1 | tee -a $LOG
It might be worth pointing out that the following IIRC, would be an alternative equivalent.
yay --noconfirm --sudoloop --noprogressbar --needed |& tee -a $LOG
I commonly use '|&' to pipe both 'stdout' and 'stderr'.
Scripts I Use : https://github.com/Cody-Learner
grep -m1 'model name' /proc/cpuinfo : AMD Ryzen 7 8745HS w/ Radeon 780M Graphics
grep -m1 'model name' /proc/cpuinfo : Intel(R) N95
grep -m1 'model name' /proc/cpuinfo : AMD Ryzen 5 PRO 2400GE w/ Radeon Vega Graphics
Offline
If someone is interested. Here is the final (so far) version with install script:
It works very well on my system, I didn't test it on others. There are also some nice screenshots:
https://github.com/lennart1978/Yay-autoupdate
Last edited by EISENFELD (2025-02-25 05:38:34)
Ich weiß, dass ich nichts weiß !
Offline
Just got a Rival SteelSeries 3 mouse with leds on it to be used on an always on PC.
To take care of leds, this one turns them off after 15miin inactivity and back on as soon as an input is registered; going to make a simple systemd service out of it:
while true ; do
if timeout 900 inotifywait /dev/input/event* ; then
c1="08080b" ; c2=$c1 ;
rivalcfg --light-effect steady --logo-color $c1 --strip-top-color $c2 --strip-middle-color $c2 --strip-bottom-color $c2
else
c1="000" ; c2=$c1 ;
rivalcfg --light-effect steady --logo-color $c1 --strip-top-color $c2 --strip-middle-color $c2 --strip-bottom-color $c2
fi
done
Now if I could find a scriptable way to turn off leds on the K70 core rgb, it would be great.
-edit-
seems i can leverage openlinkhub for that.
Last edited by kokoko3k (2025-03-06 12:35:41)
Help me to improve ssh-rdp !
Retroarch User? Try my koko-aio shader !
Offline
Since there seem to be ongoing hurdles with receiving distro news, this prints the ones for the current and previous month in a console-friendly way.
#!/bin/sh
export LC_ALL=C
THIS_MONTH="$(date +'%b %Y')"
LAST_MONTH="$(date +'%b %Y' -d -1month)"
curl -sL 'https://archlinux.org/feeds/news/' | tr '\n' ' ' | \
xmlstarlet sel -T -t -m "//rss/channel/item[contains(string(pubDate), '$LAST_MONTH') or contains(string(pubDate), '$THIS_MONTH')]" \
-o $'\n\e[33m' -v pubDate -o $'\n\e[0;1m' -v title -o $'\e[0m' -v description -o $'\n\n────────────────\n' |\
sed $'s/<p>/\\n\\n/g; s/<h.>/\\n\\n\e[1;34m/g; s%</\(h.\|b\|em\|strong\|pre\|code\)>%\e[0m%g;
s/<li>/\\n· /g;
s/<\(code\|pre\)>/\e[1;32m/g;
s/<\(strong\|em\)>/\e[1m/g;
s/>/>/g; s/</</g; s/<[^>]*>//g' | fold -sw 100
Offline
How have you handled the concatenation of multiple video clips in your workflow?
Simple task right?! My goal was to develop a simple shell script ensuring lossless quality by leveraging FFmpeg's stream copy feature (`-c copy`). This approach would avoid re-encoding and preserves the original quality of the videos. However, this seemingly simple task has plagued me with a consistent problem that I cannot seem to remedy. The problem, of course, is the handling of video clips that vary in container, codec, size, resolution, fps, etc. I have been at this for more than a year with countless revisions and no satisfactory result. All suggestions and improvements are welcome! I will share the first iteration I began with to start:
Ffx_v1:
#!/bin/sh -e
# Author: 4ndr0666
# ================= // FFX version 1 //
## Description: Losslessly concatenate video files.
# --------------------------------------------
usage() {
>&2 printf 'Usage: %s [files]\n' "${0##*/}"
exit 1
}
[ "$1" ] || usage
for i in "$@" ; do
[ -f "$i" ] || usage
done
tmp=input.txt
trap 'rm "$tmp" 2>/dev/null ||:' EXIT INT TERM
:>"$tmp"
for i in "$@" ; do
printf 'file %s\n' "$i" >>"$tmp"
done
exec ffmpeg \
-safe 0 \
-f concat \
-i "$tmp" \
-c copy \
output."${1##*.}"
Issues Impeding Goal:
All input videos must have the same codec, resolution, and frame rate to ensure seamless concatenation without re-encoding.
Transcoding is typically required to "normalize" videos with different codecs or formats before concatenation.
- 4ndr0666
Offline
he problem, of course, is the handling of video clips that vary in container, codec, size, resolution, fps, etc.
https://mkvtoolnix.download/doc/mkvmerg … le_linking
Actual concatenation isn't a thing here - you'll have to re-encode to align the sources no matter what.
Also with shells for sane adults you don't need any temporary file for the source list, https://trac.ffmpeg.org/wiki/Concatenat … einputfile
Offline
he problem, of course, is the handling of video clips that vary in container, codec, size, resolution, fps, etc.
https://mkvtoolnix.download/doc/mkvmerg … le_linking
Actual concatenation isn't a thing here - you'll have to re-encode to align the sources no matter what.Also with shells for sane adults you don't need any temporary file for the source list, https://trac.ffmpeg.org/wiki/Concatenat … einputfile
Badass! Thanks for sharing your knowledge! To follow up with an update, this is what I wound up with:
#!/bin/sh
# Author: 4ndr0666
set -euo
# ===================== // MERGE //
## Description: Merges video files using ffmpeg.
# Handles temporary files securely with mktemp and trap.
# Attempts stream copy before re-encoding.
# Uses `qp 0` for lossless quality.
# --------------------------------------------------------
# === Dependencies ===
command -v ffmpeg >/dev/null 2>&1 || {
echo "Error: ffmpeg not found in PATH." >&2
exit 1
}
command -v realpath >/dev/null 2>&1 || {
echo "Error: realpath command not found." >&2
exit 1
}
# === Help ===
show_help() {
echo "Usage: $0 [OPTIONS] file1.mp4 file2.mp4 [...]" >&2
echo "Options:"
echo " -o FILE Set output filename."
echo " -q Use fast preset (crf=15) instead of lossless fallback."
echo " -h, --help Show this help message and exit."
echo "Default output filename: merged_$(date +%Y%m%d_%H%M%S).mp4" >&2
}
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
MERGE_DIR="$XDG_CACHE_HOME/ffx"
mkdir -p "$MERGE_DIR"
TS=$(date +%Y%m%d_%H%M%S)
DEFAULT_OUT_NAME="merged_${TS}.mp4"
OUT_NAME="$DEFAULT_OUT_NAME"
FAST_MODE=0
# === Args ===
while [ $# -gt 0 ]; do
case "$1" in
-o)
shift
[ -z "${1:-}" ] && {
echo "Error: -o requires a filename." >&2
show_help
exit 1
}
OUT_NAME="$1"
shift
;;
-q)
FAST_MODE=1
shift
;;
-h | --help)
show_help
exit 0
;;
--)
shift
break
;;
-*)
echo "Error: Unknown option '$1'" >&2
show_help
exit 1
;;
*) break ;;
esac
done
[ "$#" -lt 2 ] && {
echo "Error: At least two input files required." >&2
show_help
exit 1
}
[ -f "$OUT_NAME" ] && {
echo "Error: '$OUT_NAME' exists. Refusing to overwrite." >&2
exit 1
}
# === TMP & TRAP ===
LIST_FILE=$(mktemp "$MERGE_DIR/merge_list.XXXXXX.txt") || {
echo "Error: Could not create list file." >&2
exit 1
}
cleanup() { rm -f "$LIST_FILE"; }
trap cleanup EXIT INT TERM
for f in "$@"; do
if [ -f "$f" ] && [ -r "$f" ]; then
abs_path=$(realpath "$f") || {
echo "Warning: Could not resolve '$f'." >&2
continue
}
esc=$(printf "%s" "$abs_path" | sed "s/'/''/g")
printf "file '%s'\n" "$esc" >>"$LIST_FILE" || {
echo "Error writing to list." >&2
exit 1
}
else
echo "Warning: Skipping '$f'." >&2
fi
done
[ ! -s "$LIST_FILE" ] && {
echo "Error: No valid input files." >&2
exit 1
}
# === FFMPEG stream copy ===
echo "Attempting stream copy to '$OUT_NAME'..." >&2
if ffmpeg -hide_banner -loglevel warning -f concat -safe 0 -i "$LIST_FILE" -c copy "$OUT_NAME"; then
echo "✅ Stream copy successful: $OUT_NAME" >&2
exit 0
else
echo "⚠️ Stream copy failed. Re-encoding..." >&2
fi
# === FFMPEG Re-encode ===
if [ "$FAST_MODE" -eq 1 ]; then
echo "Using fast preset with crf=15." >&2
VOPTS="-c:v libx264 -crf 15 -preset fast"
else
echo "Using lossless encode with qp=0." >&2
VOPTS="-c:v libx264 -qp 0 -preset veryslow -pix_fmt yuv420p"
fi
if ffmpeg -hide_banner -loglevel warning -f concat -safe 0 -i "$LIST_FILE" $VOPTS -c:a flac "$OUT_NAME"; then
echo "✅ Re-encoded merge complete: $OUT_NAME" >&2
exit 0
else
echo "❌ Re-encode failed." >&2
exit 1
fi
Last edited by 4ndr0666 (2025-05-30 04:55:01)
Offline
Hardware check, that i use to preview whats working and what not, as post install. (Python Script)
### FAST HARDWARE CHECK-SCRIPT ###
### AUTOR: DBM ###
#!/usr/bin/env python3
import subprocess
import sys
import time
import os
import re
import platform
import json
import argparse
from datetime import datetime
import socket
# ANSI colors and styles
class Colors:
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
CYAN = '\033[96m'
MAGENTA = '\033[95m'
RESET = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
# Symbols for status indicators
SYMBOLS = {
"OK": "✓",
"WARN": "⚠",
"FAIL": "✗",
"INFO": "ℹ"
}
def print_status(name, status, message="", indent=0):
color = {
"OK": Colors.GREEN,
"WARN": Colors.YELLOW,
"FAIL": Colors.RED,
"INFO": Colors.CYAN
}.get(status, Colors.RESET)
indent_str = " " * indent
print(f"{indent_str}{Colors.BOLD}{name:20}{Colors.RESET} [{color}{SYMBOLS.get(status, '?')}{Colors.RESET}] {color}{message}{Colors.RESET}")
def run_cmd(cmd, ignore_errors=False, timeout=10):
try:
output = subprocess.check_output(
cmd,
shell=True,
stderr=subprocess.STDOUT,
text=True,
timeout=timeout
)
return output.strip()
except subprocess.CalledProcessError as e:
if not ignore_errors:
return f"Command failed: {e.output.strip()}"
return ""
except subprocess.TimeoutExpired:
return "Command timed out"
except Exception as e:
return f"Error: {str(e)}"
def get_system_info():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}System Information{Colors.RESET}")
# Basic system info
distro = run_cmd("cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '\"'", ignore_errors=True)
hostname = socket.gethostname()
kernel = run_cmd("uname -r")
arch = platform.machine()
uptime = run_cmd("uptime -p")
print_status("OS", "INFO", distro)
print_status("Hostname", "INFO", hostname)
print_status("Kernel", "INFO", f"{kernel} ({arch})")
print_status("Uptime", "INFO", uptime)
def check_cpu():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}CPU Information{Colors.RESET}")
# Basic CPU info
cpuinfo = run_cmd("grep 'model name' /proc/cpuinfo | head -1")
if cpuinfo:
cpu_name = cpuinfo.split(':',1)[1].strip()
print_status("Model", "OK", cpu_name)
else:
print_status("Model", "FAIL", "No CPU info detected")
# CPU cores/threads
cores = run_cmd("grep -c '^processor' /proc/cpuinfo")
physical_cores = run_cmd("lscpu | grep 'Core(s) per socket' | awk '{print $4}'")
sockets = run_cmd("lscpu | grep 'Socket(s)' | awk '{print $2}'")
print_status("Cores/Threads", "OK", f"{cores} threads, {physical_cores} cores/socket, {sockets} sockets")
# CPU flags
flags = run_cmd("grep 'flags' /proc/cpuinfo | head -1 | cut -d':' -f2")
if flags:
important_flags = ["avx", "avx2", "aes", "sse4", "vmx", "svm"]
present_flags = [f for f in important_flags if f in flags.lower()]
print_status("Important Flags", "OK", ", ".join(present_flags) if present_flags else "None")
# CPU vulnerabilities
vulnerabilities = run_cmd("grep . /sys/devices/system/cpu/vulnerabilities/*")
if vulnerabilities:
vuln_lines = vulnerabilities.splitlines()
for line in vuln_lines:
vuln = line.split(":")[-1].strip()
status = "WARN" if vuln not in ["Not affected", "Mitigation: PTI"] else "OK"
print_status(os.path.basename(line.split(":")[0]), status, vuln)
def check_gpu():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}GPU Information{Colors.RESET}")
# PCI devices
lspci_out = run_cmd("lspci -nn | grep -i 'vga\\|3d\\|2d'")
if not lspci_out:
print_status("GPU", "FAIL", "No GPU detected")
return
gpus = lspci_out.splitlines()
for i, gpu in enumerate(gpus, 1):
print_status(f"GPU {i}", "OK", gpu.strip())
# OpenGL info
glx = run_cmd("glxinfo -B | grep -E 'OpenGL vendor|OpenGL renderer|OpenGL version'", ignore_errors=True)
if glx:
for line in glx.splitlines():
key, val = line.split(":", 1)
print_status(key.strip(), "OK", val.strip(), indent=4)
else:
print_status("OpenGL", "WARN", "No OpenGL info available", indent=4)
# Vulkan info
vulkan = run_cmd("vulkaninfo --summary 2>/dev/null | grep -A3 '^GPU id:'", ignore_errors=True)
if vulkan:
print_status("Vulkan", "OK", "Available", indent=4)
for line in vulkan.splitlines():
if ":" in line:
key, val = line.split(":", 1)
print_status(key.strip(), "OK", val.strip(), indent=8)
else:
print_status("Vulkan", "WARN", "Not available or no Vulkan devices", indent=4)
def check_memory():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}Memory Information{Colors.RESET}")
# RAM info
meminfo = run_cmd("free -h")
if meminfo:
lines = meminfo.splitlines()
if len(lines) >= 2:
mem_line = lines[1].split()
total, used, free, shared, buff_cache, available = mem_line[1:7]
print_status("RAM", "OK", f"Total: {total}, Used: {used}, Free: {free}, Available: {available}")
# Swap info
swap = run_cmd("swapon --show=NAME,SIZE,USED,PRIO --noheadings")
if swap:
swap_lines = swap.splitlines()
for i, line in enumerate(swap_lines, 1):
parts = line.split()
if len(parts) >= 3:
print_status(f"Swap {i}", "OK", f"Size: {parts[1]}, Used: {parts[2]}")
else:
print_status("Swap", "WARN", "No swap configured")
# DIMM info
dimm = run_cmd("sudo dmidecode --type memory", ignore_errors=True)
if dimm and "No such file or directory" not in dimm:
dimm_count = dimm.count("Memory Device")
speed = re.search(r"Speed: (\d+ MHz)", dimm)
max_speed = re.search(r"Maximum Speed: (\d+ MHz)", dimm)
print_status("DIMMs", "OK", f"{dimm_count} memory devices detected")
if speed:
print_status("Speed", "OK", speed.group(1), indent=4)
if max_speed:
print_status("Max Speed", "OK", max_speed.group(1), indent=4)
else:
print_status("DIMM Info", "WARN", "No DIMM info (try as root)")
def check_disks():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}Storage Information{Colors.RESET}")
# Disk devices
disks = run_cmd("lsblk -d -o NAME,MODEL,SIZE,TYPE,ROTA,RO | grep disk")
if not disks:
print_status("Disks", "FAIL", "No disks detected")
return
disk_list = disks.splitlines()
for disk in disk_list:
parts = disk.split()
name = parts[0]
model = " ".join(parts[1:-3]) if len(parts) > 3 else "Unknown"
size = parts[-3]
is_ssd = parts[-2] == "0"
ro = parts[-1] == "1"
disk_type = "SSD" if is_ssd else "HDD"
status = "WARN" if ro else "OK"
print_status(f"Disk {name}", status, f"{model} ({size}, {disk_type})")
# SMART status
smart = run_cmd(f"sudo smartctl -H /dev/{name}", ignore_errors=True)
if "PASSED" in smart:
print_status("SMART", "OK", "Healthy", indent=4)
elif "FAILED" in smart:
print_status("SMART", "FAIL", "Failed", indent=4)
else:
print_status("SMART", "WARN", "Unknown", indent=4)
# Filesystems
mounts = run_cmd("df -hT | grep -v tmpfs | grep -v udev")
if mounts:
print(f"\n{Colors.BOLD}Filesystems:{Colors.RESET}")
print(mounts)
# Test disk speed (only on / or /home)
test_disk_speed()
def test_disk_speed():
test_locations = ["/", "/home", "/tmp"]
valid_locations = [loc for loc in test_locations if os.path.exists(loc)]
if not valid_locations:
print_status("Disk Speed", "WARN", "No suitable location for testing")
return
test_dir = valid_locations[0]
test_file = os.path.join(test_dir, "hwtest_disk_testfile.bin")
size_mb = 100
try:
# Write speed test
start_write = time.time()
with open(test_file, "wb") as f:
for _ in range(size_mb):
f.write(os.urandom(1024 * 1024))
f.flush()
os.fsync(f.fileno())
end_write = time.time()
write_time = end_write - start_write
write_speed = size_mb / write_time if write_time > 0 else 0
# Read speed test
start_read = time.time()
with open(test_file, "rb") as f:
while f.read(1024 * 1024):
pass
end_read = time.time()
read_time = end_read - start_read
read_speed = size_mb / read_time if read_time > 0 else 0
os.remove(test_file)
status = "OK" if read_speed > 200 and write_speed > 100 else ("WARN" if read_speed > 50 else "FAIL")
print_status("Disk Speed", status,
f"Read: {read_speed:.1f} MB/s, Write: {write_speed:.1f} MB/s (tested on {test_dir})")
except Exception as e:
print_status("Disk Speed", "FAIL", f"Error: {str(e)}")
if os.path.exists(test_file):
try:
os.remove(test_file)
except:
pass
def check_network():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}Network Information{Colors.RESET}")
# Interfaces
interfaces = run_cmd("ip -brief a")
if interfaces:
print(f"{Colors.BOLD}Network Interfaces:{Colors.RESET}")
for line in interfaces.splitlines():
parts = line.split()
if len(parts) >= 3:
print(f" {parts[0]:10} {parts[1]:8} {parts[2]}")
else:
print_status("Interfaces", "FAIL", "No network interfaces detected")
# Connectivity
ping = run_cmd("ping -c 3 8.8.8.8", ignore_errors=True)
if "3 packets transmitted, 3 received" in ping:
print_status("Connectivity", "OK", "Internet reachable")
else:
print_status("Connectivity", "WARN", "Internet not reachable")
# DNS
try:
socket.gethostbyname("google.com")
print_status("DNS", "OK", "DNS resolution working")
except:
print_status("DNS", "FAIL", "DNS resolution failed")
# Speed test (optional)
if "--full" in sys.argv:
print_status("Speed Test", "INFO", "Running... (this may take a while)")
speed = run_cmd("curl -s https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py | python3 -", ignore_errors=True)
if speed:
print(f"\n{Colors.BOLD}Speed Test Results:{Colors.RESET}")
print(speed)
def check_usb():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}USB Devices{Colors.RESET}")
out = run_cmd("lsusb -v", ignore_errors=True) or run_cmd("lsusb")
if out:
devices = out.split("\n\n")
print_status("USB Devices", "OK", f"{len(devices)} devices detected")
for i, dev in enumerate(devices[:5], 1): # Limit to first 5 devices for brevity
lines = dev.splitlines()
if not lines:
continue
# First line is usually the device info
bus_device = lines[0].strip()
print_status(f"Device {i}", "INFO", bus_device, indent=4)
# Try to extract more info
for line in lines[1:]:
if "idVendor" in line or "idProduct" in line or "iProduct" in line:
print(f"{' ' * 8}{line.strip()}")
else:
print_status("USB Devices", "FAIL", "No USB devices detected")
def check_temperatures():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}Temperatures{Colors.RESET}")
temps = run_cmd("sensors -j", ignore_errors=True) or run_cmd("sensors", ignore_errors=True)
if not temps:
print_status("Sensors", "WARN", "No temperature sensors found")
return
try:
# Try to parse as JSON first
data = json.loads(temps)
for chip, values in data.items():
if not isinstance(values, dict):
continue
print_status(chip, "INFO", "", indent=0)
for key, val in values.items():
if isinstance(val, dict):
for subkey, subval in val.items():
if "input" in subkey and isinstance(subval, (int, float)):
print_status(f"{key} {subkey}", "OK", f"{subval:.1f}°C", indent=4)
elif isinstance(val, (int, float)) and "temp" in key.lower():
print_status(key, "OK", f"{val:.1f}°C", indent=4)
except json.JSONDecodeError:
# Fall back to text parsing
lines = temps.splitlines()
for line in lines:
if "°C" in line or "°F" in line:
print(line.strip())
def check_power():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}Power Information{Colors.RESET}")
# Battery info
battery = run_cmd("upower -e | grep battery", ignore_errors=True)
if battery:
battery = battery.strip()
info = run_cmd(f"upower -i {battery}")
if info:
# Extract relevant info
state = re.search(r'state:\s*(\w+)', info, re.I)
percentage = re.search(r'percentage:\s*(\d+%)', info, re.I)
time_to = re.search(r'time to (empty|full):\s*([\d\.]+ \w+)', info, re.I)
if state and percentage:
status = "OK" if state.group(1).lower() != "discharging" else "WARN"
print_status("Battery", status,
f"{percentage.group(1)}, {state.group(1).capitalize()}" +
(f", {time_to.group(2)} to {time_to.group(1)}" if time_to else ""))
else:
print_status("Battery", "INFO", "No battery detected (desktop system?)")
# Power profile
profile = run_cmd("cat /sys/firmware/acpi/platform_profile", ignore_errors=True)
if profile:
print_status("Power Profile", "OK", profile.strip().capitalize())
# CPU frequencies
freqs = run_cmd("cpupower frequency-info | grep 'current CPU frequency'", ignore_errors=True)
if freqs:
freq = freqs.split(":")[1].strip()
print_status("CPU Freq", "OK", freq)
def check_hardware_acceleration():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}Hardware Acceleration{Colors.RESET}")
# VAAPI
vaapi = run_cmd("vainfo | grep -i 'vainfo: VA-API version'", ignore_errors=True)
if vaapi:
print_status("VA-API", "OK", "Supported")
else:
print_status("VA-API", "WARN", "Not available")
# VDPAU
vdpau = run_cmd("vdpauinfo | grep -i 'Information string'", ignore_errors=True)
if vdpau:
print_status("VDPAU", "OK", "Supported")
else:
print_status("VDPAU", "WARN", "Not available")
# NVDEC/NVENC
nvdec = run_cmd("nvidia-smi -q | grep 'NVDEC'", ignore_errors=True)
if nvdec:
print_status("NVDEC", "OK", "Supported")
nvenc = run_cmd("nvidia-smi -q | grep 'NVENC'", ignore_errors=True)
if nvenc:
print_status("NVENC", "OK", "Supported")
def check_security():
print(f"\n{Colors.BOLD}{Colors.UNDERLINE}Security Status{Colors.RESET}")
# Secure Boot
secure_boot = run_cmd("bootctl status | grep 'Secure Boot'", ignore_errors=True)
if secure_boot:
status = "OK" if "enabled" in secure_boot.lower() else "WARN"
print_status("Secure Boot", status, secure_boot.split(":")[1].strip())
else:
print_status("Secure Boot", "WARN", "Status unknown")
# Firewall
firewall = run_cmd("sudo firewall-cmd --state 2>/dev/null", ignore_errors=True) or \
run_cmd("sudo ufw status | grep 'Status'", ignore_errors=True)
if firewall:
status = "OK" if "running" in firewall.lower() or "active" in firewall.lower() else "WARN"
print_status("Firewall", status, firewall.strip())
else:
print_status("Firewall", "WARN", "No active firewall detected")
# SELinux/AppArmor
selinux = run_cmd("getenforce 2>/dev/null", ignore_errors=True)
if selinux:
status = "OK" if "Enforcing" in selinux else "WARN"
print_status("SELinux", status, selinux.strip())
apparmor = run_cmd("aa-status 2>/dev/null | grep 'apparmor module is loaded'", ignore_errors=True)
if apparmor:
print_status("AppArmor", "OK", "Loaded")
def generate_report():
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"hardware_report_{timestamp}.txt"
print(f"\n{Colors.BOLD}Generating report to {filename}...{Colors.RESET}")
# Redirect stdout to file
original_stdout = sys.stdout
with open(filename, "w") as f:
sys.stdout = f
main(quiet=True)
sys.stdout = original_stdout
print_status("Report", "OK", f"Saved to {filename}")
def main(quiet=False):
if not quiet:
print(f"\n{Colors.BOLD}{Colors.BLUE}=== Comprehensive Hardware Diagnostic Tool ==={Colors.RESET}")
print(f"{Colors.BOLD}Running on {platform.system()} {platform.release()}{Colors.RESET}\n")
get_system_info()
check_cpu()
check_gpu()
check_memory()
check_disks()
check_network()
check_usb()
check_temperatures()
check_power()
check_hardware_acceleration()
check_security()
if not quiet:
print(f"\n{Colors.BOLD}{Colors.GREEN}Diagnostic complete!{Colors.RESET}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Comprehensive hardware diagnostic tool")
parser.add_argument("--full", action="store_true", help="Run extended tests (including network speed test)")
parser.add_argument("--report", action="store_true", help="Generate a detailed report file")
args = parser.parse_args()
if args.report:
generate_report()
else:
main()
Offline