chore: fix shell check issues

This commit is contained in:
Krzysztof kuhy Rudnicki 2025-11-06 19:39:04 +01:00
parent f11ce4002f
commit 6d78878b99
16 changed files with 1374 additions and 1099 deletions

View File

@ -40,3 +40,4 @@ This repo automates Linux desktop bootstrap, hardening, and i3 setup. Its pri
- Follow the sudo re-exec + idempotent install pattern from `setup_periodic_system.sh` and `hosts/guard/setup_hosts_guard.sh`.
- Add new periodic behaviors as templates under `scripts/system-maintenance/bin` and `.../systemd`, then extend `setup_periodic_system.sh` to install/enable them.
- Extend package policy by updating `scripts/digital_wellbeing/pacman/pacman_blocked_keywords.txt` or by adding `check_for_<pkg>` + `prompt_for_<pkg>_challenge` blocks in the wrapper.
- Run `scripts/meta/shell_check.sh` to detect things to fix before committing.

View File

@ -18,7 +18,7 @@ timestamp() { date '+%Y-%m-%d %H:%M:%S%z'; }
log() {
local msg="$1"
echo "[$(timestamp)] $msg"
if [[ -w "$(dirname "$LOG_FILE")" ]] || [[ ! -e "$LOG_FILE" && -w /var/log ]]; then
if [[ -w "$(dirname "$LOG_FILE")" ]] || [[ ! -e $LOG_FILE && -w /var/log ]]; then
echo "[$(timestamp)] $msg" >> "$LOG_FILE" || true
fi
}
@ -44,7 +44,17 @@ detect_distro() {
list_input_nodes() {
print_header "Input device nodes"
ls -l /dev/input/by-id 2>/dev/null | sed -n '1,120p' || true
if [[ -d /dev/input/by-id ]]; then
# Robust listing with proper handling of special characters
local count=0
while IFS= read -r -d '' f; do
stat -c '%A %a %U:%G %N' "$f" 2> /dev/null || true
count=$((count + 1))
[[ $count -ge 120 ]] && break
done < <(find /dev/input/by-id -maxdepth 1 -mindepth 1 -print0 2> /dev/null)
else
echo "/dev/input/by-id not present"
fi
echo
if compgen -G "/dev/input/*js*" > /dev/null; then
ls -l /dev/input/js* || true
@ -57,7 +67,10 @@ list_input_nodes() {
show_lsusb() {
print_header "USB devices (filtered)"
if command -v lsusb > /dev/null 2>&1; then
lsusb | grep -Ei 'microsoft|xbox|045e:' || { echo "No Microsoft/Xbox device found via lsusb."; true; }
lsusb | grep -Ei 'microsoft|xbox|045e:' || {
echo "No Microsoft/Xbox device found via lsusb."
true
}
else
echo "lsusb not found (usbutils). Install usbutils for richer diagnostics."
fi
@ -91,7 +104,7 @@ check_permissions() {
print_header "Permissions on event/joystick nodes"
local any=0
for path in /dev/input/by-id/*-event-joystick /dev/input/js*; do
if [[ -e "$path" ]]; then
if [[ -e $path ]]; then
any=1
printf '%s -> ' "$path"
local dev
@ -159,7 +172,7 @@ main() {
if compgen -G "/dev/input/by-id/*-event-joystick" > /dev/null; then
local found_label=0
for f in /dev/input/by-id/*-event-joystick; do
[[ -e "$f" ]] || continue
[[ -e $f ]] || continue
if printf '%s' "$(basename "$f")" | grep -Eqi 'xbox|microsoft|controller|wireless'; then
found_label=1
break
@ -181,4 +194,3 @@ main() {
}
main "$@"

View File

@ -285,8 +285,24 @@ run_linters() {
local cbi_out="$TMPDIR/checkbashisms.txt"
local cbi_status=0
if is_cmd checkbashisms; then
# Only run checkbashisms on scripts that are intended for /bin/sh (or unspecified),
# skip explicit bash/zsh scripts to avoid false positives.
local -a CBI_FILES
CBI_FILES=()
for f in "${FILES[@]}"; do
local first
first=$(head -n 1 -- "$f" 2> /dev/null || true)
if [[ $first =~ bash || $first =~ zsh ]]; then
continue
fi
CBI_FILES+=("$f")
done
if [[ ${#CBI_FILES[@]} -gt 0 ]]; then
# checkbashisms exits 0 if OK, 1 if issues, other codes for tool warnings
checkbashisms "${FILES[@]}" > "$cbi_out" 2>&1
checkbashisms "${CBI_FILES[@]}" > "$cbi_out" 2>&1
else
: > "$cbi_out"
fi
cbi_status=$?
if [[ $cbi_status -eq 1 ]]; then
issues=$((issues + 1))

View File

@ -78,40 +78,68 @@ if [[ $# -lt 1 ]]; then
exit 1
fi
INPUT_PATH="$1"; shift || true
INPUT_PATH="$1"
shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
-O | --out-dir)
OUT_DIR="$2"; shift 2;;
OUT_DIR="$2"
shift 2
;;
-e | --ext)
OUT_EXT="$2"; shift 2;;
OUT_EXT="$2"
shift 2
;;
-m | --model)
RN_MODEL="$2"; shift 2;;
RN_MODEL="$2"
shift 2
;;
--no-ml)
NO_ML=true; shift;;
NO_ML=true
shift
;;
--preset)
PRESET="$2"; shift 2;;
PRESET="$2"
shift 2
;;
-j | --jobs)
JOBS="$2"; shift 2;;
JOBS="$2"
shift 2
;;
-f | --force)
FORCE=true; shift;;
FORCE=true
shift
;;
-q | --quiet)
QUIET=true; shift;;
QUIET=true
shift
;;
--lowpass)
LOWPASS="$2"; shift 2;;
LOWPASS="$2"
shift 2
;;
--suffix)
SUFFIX="$2"; shift 2;;
SUFFIX="$2"
shift 2
;;
--no-advanced | --compat)
NO_ADVANCED=true; shift;;
NO_ADVANCED=true
shift
;;
--allow-fallback)
REQUIRE_ML=false; shift;;
REQUIRE_ML=false
shift
;;
-h | --help)
print_usage; exit 0;;
print_usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
print_usage
exit 1;;
exit 1
;;
esac
done
@ -119,7 +147,7 @@ require_cmd ffmpeg
# Resolve FFmpeg binary (env override -> local build -> system)
FFMPEG_BIN=${FFMPEG_BIN:-}
if [[ -z "${FFMPEG_BIN}" ]]; then
if [[ -z ${FFMPEG_BIN} ]]; then
if [[ -x "$SCRIPT_DIR/ffmpeg-build/FFmpeg/ffmpeg" ]]; then
FFMPEG_BIN="$SCRIPT_DIR/ffmpeg-build/FFmpeg/ffmpeg"
else
@ -127,7 +155,7 @@ if [[ -z "${FFMPEG_BIN}" ]]; then
fi
fi
if ! command -v "$FFMPEG_BIN" >/dev/null 2>&1 && [[ ! -x "$FFMPEG_BIN" ]]; then
if ! command -v "$FFMPEG_BIN" > /dev/null 2>&1 && [[ ! -x $FFMPEG_BIN ]]; then
echo "Error: FFmpeg binary not found: $FFMPEG_BIN" >&2
exit 1
fi
@ -167,9 +195,9 @@ fi
# Try to auto-discover an RNNoise model if none provided
find_default_rn_model() {
local candidate=""
# local candidate reserved for future selection logic
# Allow env variable override
if [[ -n "${RNNOISE_MODEL:-}" && -f "${RNNOISE_MODEL}" ]]; then
if [[ -n ${RNNOISE_MODEL:-} && -f ${RNNOISE_MODEL} ]]; then
echo "${RNNOISE_MODEL}"
return 0
fi
@ -184,11 +212,11 @@ find_default_rn_model() {
# Prefer '.rnnn' models (rnnoise-nu style) over legacy '.nn'
local exts=("rnnn" "nn" "model")
for d in "${dirs[@]}"; do
if [[ -d "$d" ]]; then
if [[ -d $d ]]; then
for ext in "${exts[@]}"; do
# Pick the first matching model file
for f in "$d"/*."$ext"; do
if [[ -f "$f" ]]; then
if [[ -f $f ]]; then
echo "$f"
return 0
fi
@ -208,7 +236,7 @@ if [[ $NO_ML == false ]]; then
fi
else
# arnndn available; require an external model
if [[ -n "$RN_MODEL" && -f "$RN_MODEL" ]]; then
if [[ -n $RN_MODEL && -f $RN_MODEL ]]; then
:
else
if model_path=$(find_default_rn_model); then
@ -222,7 +250,7 @@ if [[ $NO_ML == false ]]; then
fi
fi
fi
if [[ -z "$RN_MODEL" ]]; then
if [[ -z $RN_MODEL ]]; then
echo "Error: RNNoise model required but not found. Automatic download failed." >&2
echo "Hint: Set RN_URL to a reachable model URL and run Bash/get_rnnoise_model.sh, or supply -m /path/to/model.nn." >&2
exit 10
@ -263,7 +291,7 @@ build_filters() {
fi
# Optional low-pass to shave hiss; keep disabled unless requested
if [[ -n "$LOWPASS" ]]; then
if [[ -n $LOWPASS ]]; then
filters+=("lowpass=f=${LOWPASS}")
fi
@ -291,7 +319,8 @@ build_filters() {
filters+=("aresample=16000")
filters+=("aformat=channel_layouts=mono:sample_fmts=s16")
local IFS=","; echo "${filters[*]}"
local IFS=","
echo "${filters[*]}"
}
make_out_path_for_file() {
@ -300,7 +329,7 @@ make_out_path_for_file() {
base=$(basename -- "$in_file")
base="${base%.*}"
local out_base="${base}${SUFFIX}.${OUT_EXT}"
if [[ -n "$OUT_DIR" ]]; then
if [[ -n $OUT_DIR ]]; then
mkdir -p -- "$OUT_DIR"
echo "$OUT_DIR/$out_base"
else
@ -317,14 +346,14 @@ process_one() {
# Choose codec based on extension
local codec=(-c:a pcm_s16le)
if [[ "$OUT_EXT" == "flac" ]]; then
if [[ $OUT_EXT == "flac" ]]; then
codec=(-c:a flac)
fi
local af
af=$(build_filters)
if [[ -f "$out_file" && $FORCE == false ]]; then
if [[ -f $out_file && $FORCE == false ]]; then
echo "Skip (exists): $out_file"
return 0
fi
@ -335,7 +364,7 @@ process_one() {
# Concurrency helpers (bash >= 5 supports wait -n; fallback to sequential if not)
supports_wait_n=false
if [[ -n "${BASH_VERSINFO:-}" && ${BASH_VERSINFO[0]} -ge 5 ]]; then
if [[ -n ${BASH_VERSINFO:-} && ${BASH_VERSINFO[0]} -ge 5 ]]; then
supports_wait_n=true
fi
@ -353,7 +382,7 @@ run_dir() {
local running=0
for f in "${files[@]}"; do
if [[ "$JOBS" -le 1 || $supports_wait_n == false ]]; then
if [[ $JOBS -le 1 || $supports_wait_n == false ]]; then
process_one "$f"
else
process_one "$f" &
@ -373,13 +402,13 @@ run_dir() {
main() {
# Sanity checks and notices
if [[ -n "$RN_MODEL" && $use_arnndn == false && $NO_ML == false ]]; then
if [[ -n $RN_MODEL && $use_arnndn == false && $NO_ML == false ]]; then
echo "Note: arnndn filter not available in your ffmpeg or model missing — using afftdn." >&2
fi
if [[ -f "$INPUT_PATH" ]]; then
if [[ -f $INPUT_PATH ]]; then
process_one "$INPUT_PATH"
elif [[ -d "$INPUT_PATH" ]]; then
elif [[ -d $INPUT_PATH ]]; then
run_dir "$INPUT_PATH"
else
echo "Error: Input path not found: $INPUT_PATH" >&2

View File

@ -46,10 +46,10 @@ convert_video() {
CONVERTED_SIZE=$(stat -c%s "$output_file")
# Print out details
echo "Original size: $(numfmt --to=iec $ORIGINAL_SIZE)"
echo "Original size: $(numfmt --to=iec "$ORIGINAL_SIZE")"
echo "Video length: $DURATION seconds"
echo "Target size: $TARGET_SIZE"
echo "Converted size: $(numfmt --to=iec $CONVERTED_SIZE)"
echo "Converted size: $(numfmt --to=iec "$CONVERTED_SIZE")"
echo "Target bitrate: ${TARGET_BITRATE}kbps"
}
@ -62,7 +62,7 @@ move_video() {
ORIGINAL_SIZE=$(stat -c%s "$input_file")
# Check if video is below target size and in desired format
if [[ "$ORIGINAL_SIZE" -le "$TARGET_SIZE_BYTES" && "${input_file##*.}" == "$TARGET_EXT" ]]; then
if [[ $ORIGINAL_SIZE -le $TARGET_SIZE_BYTES && ${input_file##*.} == "$TARGET_EXT" ]]; then
mv "$input_file" "$output_file"
echo "Moved $input_file to $output_file"
else

View File

@ -1,7 +1,7 @@
#!/bin/bash
# Get the list of directories in the current script directory
directories=($(find . -maxdepth 1 -type d ! -name .))
mapfile -t directories < <(find . -maxdepth 1 -type d ! -name . -printf '%f\n')
# Check if there is exactly one directory
if [ ${#directories[@]} -ne 1 ]; then
@ -13,12 +13,12 @@ fi
folder_name=${directories[0]}
random_string() {
local length=$1
tr -dc 'a-zA-Z0-9!@#$%^&*()_+{}|:<>?~' < /dev/urandom | head -c $length
local length="$1"
tr -dc 'a-zA-Z0-9!@#$%^&*()_+{}|:<>?~' < /dev/urandom | head -c "$length"
}
# Number of copies to create (default 100)
num_copies=${1:-100}
num_copies="${1:-100}"
# Create the specified number of copies
for ((i = 1; i <= num_copies; i++)); do

View File

@ -15,9 +15,9 @@ downloaded_size=0
# Calculate total number of files and total size to download
for file in *.txt; do
while IFS= read -r url; do
if [[ -n "$url" ]]; then
if [[ -n $url ]]; then
total_files=$((total_files + 1))
size=$(wget --spider "$url" 2>&1 | grep Length | awk '{print $2}')
size=$(wget --spider "$url" 2>&1 | awk '/Length/ {print $2}')
total_size=$((total_size + size))
fi
done < "$file"
@ -27,11 +27,11 @@ done
for file in *.txt; do
echo "Processing $file..."
while IFS= read -r url; do
if [[ -n "$url" ]]; then
if [[ -n $url ]]; then
{
wget -q --show-progress "$url"
downloaded_files=$((downloaded_files + 1))
size=$(wget --spider "$url" 2>&1 | grep Length | awk '{print $2}')
size=$(wget --spider "$url" 2>&1 | awk '/Length/ {print $2}')
downloaded_size=$((downloaded_size + size))
remaining_files=$((total_files - downloaded_files))
remaining_size=$((total_size - downloaded_size))

View File

@ -18,7 +18,11 @@
set -euo pipefail
IFS=$'\n\t'
GREEN="\033[1;32m"; YELLOW="\033[1;33m"; RED="\033[1;31m"; BLUE="\033[1;34m"; NC="\033[0m"
GREEN="\033[1;32m"
YELLOW="\033[1;33m"
RED="\033[1;31m"
BLUE="\033[1;34m"
NC="\033[0m"
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
log_ok() { echo -e "${GREEN}[ OK ]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
@ -46,11 +50,27 @@ EOF
while [[ $# -gt 0 ]]; do
case "$1" in
--policy) DO_POLICY=true; shift ;;
--set-default) SET_DEFAULT=true; shift ;;
--restart) DO_RESTART=true; shift ;;
-h|--help) usage; exit 0 ;;
*) log_error "Unknown argument: $1"; usage; exit 1 ;;
--policy)
DO_POLICY=true
shift
;;
--set-default)
SET_DEFAULT=true
shift
;;
--restart)
DO_RESTART=true
shift
;;
-h | --help)
usage
exit 0
;;
*)
log_error "Unknown argument: $1"
usage
exit 1
;;
esac
done
@ -90,7 +110,7 @@ JSON
log_ok "Policy written: $policy_file"
wrote_any=true
done
if [[ "$wrote_any" != true ]]; then
if [[ $wrote_any != true ]]; then
log_warn "Policy may not have been written. No candidate directories processed."
fi
}
@ -121,7 +141,8 @@ restart_thorium() {
pkill -9 -f 'unityhub-bin' 2> /dev/null || true
# Start Thorium detached if available
if command -v thorium-browser > /dev/null 2>&1; then
nohup thorium-browser >/dev/null 2>&1 & disown || true
nohup thorium-browser > /dev/null 2>&1 &
disown || true
fi
log_ok "Thorium restart attempted."
}

View File

@ -23,7 +23,11 @@ set -euo pipefail
IFS=$'\n\t'
SCRIPT_NAME="$(basename "$0")"
GREEN="\033[1;32m"; YELLOW="\033[1;33m"; RED="\033[1;31m"; BLUE="\033[1;34m"; NC="\033[0m"
GREEN="\033[1;32m"
YELLOW="\033[1;33m"
RED="\033[1;31m"
BLUE="\033[1;34m"
NC="\033[0m"
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
log_ok() { echo -e "${GREEN}[ OK ]${NC} $*"; }
@ -49,10 +53,23 @@ RUN_TEST=false
while [[ $# -gt 0 ]]; do
case "$1" in
-y|--yes) AUTO_INSTALL=true; shift ;;
--test) RUN_TEST=true; shift ;;
-h|--help) usage; exit 0 ;;
*) log_error "Unknown argument: $1"; usage; exit 1 ;;
-y | --yes)
AUTO_INSTALL=true
shift
;;
--test)
RUN_TEST=true
shift
;;
-h | --help)
usage
exit 0
;;
*)
log_error "Unknown argument: $1"
usage
exit 1
;;
esac
done
@ -64,7 +81,7 @@ require_cmd() {
ensure_deps_arch() {
# Best-effort install for Arch-based systems
if [[ "$AUTO_INSTALL" != true ]]; then
if [[ $AUTO_INSTALL != true ]]; then
log_warn "Skipping package installation (use -y to auto-install)."
return 0
fi
@ -128,16 +145,16 @@ detect_unityhub() {
)
local found_exec=""
for d in "${search_dirs[@]}"; do
[[ -d "$d" ]] || continue
[[ -d $d ]] || continue
# prefer official naming when present
local f
for f in "$d"/*.desktop; do
[[ -e "$f" ]] || continue
if grep -qiE '^(Name|Comment)=.*Unity Hub' "$f" 2>/dev/null || \
[[ -e $f ]] || continue
if grep -qiE '^(Name|Comment)=.*Unity Hub' "$f" 2> /dev/null ||
grep -qiE 'Exec=.*unityhub' "$f" 2> /dev/null; then
local exec_line
exec_line="$(grep -iE '^Exec=' "$f" | head -n1 | sed 's/^Exec=//')"
if [[ -n "$exec_line" ]]; then
if [[ -n $exec_line ]]; then
found_exec="$exec_line"
break 2
fi
@ -145,14 +162,14 @@ detect_unityhub() {
done
done
if [[ -n "$found_exec" ]]; then
if [[ -n $found_exec ]]; then
# Normalize: ensure %U present
if [[ "$found_exec" != *"%U"* && "$found_exec" != *"%u"* ]]; then
if [[ $found_exec != *"%U"* && $found_exec != *"%u"* ]]; then
found_exec+=" %U"
fi
if [[ "$found_exec" == flatpak* ]]; then
if [[ $found_exec == flatpak* ]]; then
install_type="FLATPAK"
elif [[ "$found_exec" == *AppImage* || "$found_exec" == *appimage* ]]; then
elif [[ $found_exec == *AppImage* || $found_exec == *appimage* ]]; then
install_type="APPIMAGE"
else
install_type="NATIVE"
@ -170,7 +187,7 @@ detect_unityhub() {
local ai
for ai in "${ai_candidates[@]}"; do
for p in $ai; do
if [[ -f "$p" && -x "$p" ]]; then
if [[ -f $p && -x $p ]]; then
install_type="APPIMAGE"
exec_cmd="$p %U"
echo "$install_type|$exec_cmd"
@ -224,12 +241,13 @@ register_mime_handler() {
}
verify_registration() {
local expected="$(basename "$1")"
local cur1="$(xdg-mime query default x-scheme-handler/unityhub 2>/dev/null || true)"
local cur2="$(xdg-mime query default x-scheme-handler/unity 2>/dev/null || true)"
local expected cur1 cur2
expected="$(basename "$1")"
cur1="$(xdg-mime query default x-scheme-handler/unityhub 2> /dev/null || true)"
cur2="$(xdg-mime query default x-scheme-handler/unity 2> /dev/null || true)"
log_info "Current handler (unityhub): ${cur1:-<none>}"
log_info "Current handler (unity): ${cur2:-<none>}"
if [[ "$cur1" == "$expected" ]]; then
if [[ $cur1 == "$expected" ]]; then
log_ok "unityhub scheme correctly set to $expected"
else
log_warn "unityhub scheme not set to $expected (currently: ${cur1:-none})."
@ -237,7 +255,7 @@ verify_registration() {
}
maybe_test_open() {
if [[ "$RUN_TEST" == true ]]; then
if [[ $RUN_TEST == true ]]; then
log_info "Opening test link: unityhub://v1/editor-signin"
if command -v xdg-open > /dev/null 2>&1; then
xdg-open 'unityhub://v1/editor-signin' > /dev/null 2>&1 || true
@ -257,7 +275,7 @@ main() {
log_info "Detecting Unity Hub installation..."
IFS='|' read -r install_type exec_cmd < <(detect_unityhub)
log_info "Detected type: $install_type"
if [[ -z "${exec_cmd:-}" ]]; then
if [[ -z ${exec_cmd:-} ]]; then
log_warn "Could not find Unity Hub executable automatically."
log_warn "- If using Flatpak: install with 'flatpak install flathub com.unity.UnityHub'"
log_warn "- If native (AUR): ensure 'unityhub' is in PATH"
@ -283,7 +301,7 @@ NOTE
maybe_test_open
log_ok "Done. If login still fails, check the Hub's logs and share the outputs of:\n which unityhub || true\n flatpak info com.unity.UnityHub 2>/dev/null | sed -n '1,5p' || true\n xdg-mime query default x-scheme-handler/unityhub\n grep -R "x-scheme-handler/unityhub" ~/.local/share/applications /usr/share/applications 2>/dev/null | head -n 10"
log_ok "Done. If login still fails, check the Hub's logs and share the outputs of:\n which unityhub || true\n flatpak info com.unity.UnityHub 2>/dev/null | sed -n '1,5p' || true\n xdg-mime query default x-scheme-handler/unityhub\n grep -R \"x-scheme-handler/unityhub\" ~/.local/share/applications /usr/share/applications 2>/dev/null | head -n 10"
}
main "$@"

View File

@ -7,53 +7,60 @@ random_number() {
# Function to generate random string with non-computer-friendly characters
random_string() {
local length=$1
tr -dc 'a-zA-Z0-9!@#$%^&*()_+{}|:<>?~' < /dev/urandom | head -c $length
local length="$1"
tr -dc 'a-zA-Z0-9!@#$%^&*()_+{}|:<>?~' < /dev/urandom | head -c "$length"
}
# Function to calculate total number of folders to be created
calculate_total_folders() {
local depth=$1
local depth="$1"
local total=0
if [ "$depth" -le 10 ]; then
local num_subfolders=$(random_number 1 50)
local num_subfolders
num_subfolders=$(random_number 1 50)
total=$((num_subfolders + total))
for ((i = 1; i <= num_subfolders; i++)); do
total=$((total + $(calculate_total_folders $((depth + 1)))))
done
fi
echo $total
echo "$total"
}
# Function to create folders and files recursively
create_structure() {
local current_depth=$1
local parent_dir=$2
local start_time=$3
local current_depth="$1"
local parent_dir="$2"
local start_time="$3"
if [ "$current_depth" -le 10 ]; then
local num_subfolders=$(random_number 1 50)
local num_subfolders
num_subfolders=$(random_number 1 50)
echo "Creating $num_subfolders subfolders at depth $current_depth"
for ((i = 1; i <= num_subfolders; i++)); do
local subfolder="$parent_dir/$(random_string 255)"
local subfolder
subfolder="$parent_dir/$(random_string 255)"
mkdir -p "$subfolder"
((generated_folders++))
# Display progress
local elapsed_time=$(( $(date +%s) - start_time ))
local estimated_total_time=$(( elapsed_time * total_folders / generated_folders ))
local remaining_time=$(( estimated_total_time - elapsed_time ))
local elapsed_time
elapsed_time=$(($(date +%s) - start_time))
local estimated_total_time
estimated_total_time=$((elapsed_time * total_folders / generated_folders))
local remaining_time
remaining_time=$((estimated_total_time - elapsed_time))
echo "Generated: $generated_folders/$total_folders folders. Estimated time left: $remaining_time seconds."
# Create random number of empty files
local num_files=$(random_number 10 100)
local num_files
num_files=$(random_number 10 100)
echo "Creating $num_files files"
for ((j = 1; j <= num_files; j++)); do
touch "$subfolder/$(random_string 255)"
done
# Recursively create subfolders
create_structure $((current_depth + 1)) "$subfolder" $start_time
create_structure $((current_depth + 1)) "$subfolder" "$start_time"
done
fi
}
@ -62,12 +69,14 @@ create_structure() {
main_folder="/home/k.rudnicki@aiclearing.com/testsAndMisc/Bash/main_folder"
mkdir -p "$main_folder"
# Calculate total folders to be created
# Calculate total folders to be created (best-effort). If calculation is expensive, you can uncomment.
# total_folders=$(calculate_total_folders 1)
# Fallback when not precomputed: estimate grows as we generate
total_folders=${total_folders:-0}
generated_folders=0
echo "Total folders to be generated: $total_folders"
echo "Total folders to be generated: ${total_folders:-unknown}"
# Start creating structure from the main folder
start_time=$(date +%s)
create_structure 1 "$main_folder" $start_time
create_structure 1 "$main_folder" "$start_time"

View File

@ -24,8 +24,14 @@ has_cmd() { command -v "$1" >/dev/null 2>&1; }
YES=false
while [[ $# -gt 0 ]]; do
case "$1" in
-y|--yes) YES=true; shift;;
*) echo "Unknown option: $1" >&2; exit 2;;
-y | --yes)
YES=true
shift
;;
*)
echo "Unknown option: $1" >&2
exit 2
;;
esac
done
@ -35,7 +41,7 @@ RN_TARGET_NAME=${RN_TARGET_NAME:-"rnnoise_model.rnnn"}
mkdir -p "$RN_TARGET_DIR"
dest="$RN_TARGET_DIR/$RN_TARGET_NAME"
if [[ -f "$dest" ]]; then
if [[ -f $dest ]]; then
echo "Model already exists at: $dest"
exit 0
fi
@ -53,7 +59,7 @@ if ! has_cmd curl && ! has_cmd wget; then
fi
# Priority 1: explicit URL
if [[ -n "${RN_URL:-}" ]]; then
if [[ -n ${RN_URL:-} ]]; then
echo "Downloading RNNoise model from RN_URL: $RN_URL" >&2
tmp=$(mktemp)
if has_cmd curl; then
@ -61,7 +67,7 @@ if [[ -n "${RN_URL:-}" ]]; then
else
wget -qO "$tmp" "$RN_URL"
fi
if [[ -s "$tmp" ]]; then
if [[ -s $tmp ]]; then
mv "$tmp" "$dest"
echo "Saved RNNoise model to: $dest"
exit 0
@ -83,7 +89,7 @@ for u in "${NU_URLS[@]}"; do
tmp=$(mktemp)
if has_cmd curl; then
if curl -fsSL "$u" -o "$tmp"; then
if [[ -s "$tmp" ]]; then
if [[ -s $tmp ]]; then
mv "$tmp" "$dest"
echo "Saved RNNoise model to: $dest" >&2
exit 0
@ -91,7 +97,7 @@ for u in "${NU_URLS[@]}"; do
fi
else
if wget -qO "$tmp" "$u"; then
if [[ -s "$tmp" ]]; then
if [[ -s $tmp ]]; then
mv "$tmp" "$dest"
echo "Saved RNNoise model to: $dest" >&2
exit 0
@ -110,7 +116,7 @@ for u in "${RNNDN_URLS[@]}"; do
tmp=$(mktemp)
if has_cmd curl; then
if curl -fsSL "$u" -o "$tmp"; then
if [[ -s "$tmp" ]]; then
if [[ -s $tmp ]]; then
mv "$tmp" "$dest"
echo "Saved RNNoise model to: $dest" >&2
exit 0
@ -118,7 +124,7 @@ for u in "${RNNDN_URLS[@]}"; do
fi
else
if wget -qO "$tmp" "$u"; then
if [[ -s "$tmp" ]]; then
if [[ -s $tmp ]]; then
mv "$tmp" "$dest"
echo "Saved RNNoise model to: $dest" >&2
exit 0

View File

@ -89,12 +89,12 @@ main() {
fi
fi
# Dependencies
if [[ "$distro" == "ubuntu" || "$distro" == "debian" ]]; then
if [[ $distro == "ubuntu" || $distro == "debian" ]]; then
sudo apt update
sudo apt install -y git build-essential yasm nasm pkg-config libx264-dev libx265-dev libvpx-dev libopus-dev libfdk-aac-dev libmp3lame-dev libvorbis-dev libass-dev libfreetype6-dev libgnutls28-dev libaom-dev libdav1d-dev libxvidcore-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libxcb-shape0-dev libdrm-dev libvulkan-dev libva-dev libvdpau-dev librtmp-dev libunistring-dev libgnutls28-dev libchromaprint-dev libbluray-dev librubberband-dev libspeex-dev libsoxr-dev libvmaf-dev libzimg-dev libsvtav1-dev libtheora-dev libwebp-dev libopenal-dev libjack-jackd2-dev libpulse-dev librnnoise-dev
elif [[ "$distro" == "arch" || "$distro" == "manjaro" || "$distro" == "endeavouros" ]]; then
elif [[ $distro == "arch" || $distro == "manjaro" || $distro == "endeavouros" ]]; then
sudo pacman -Syu --needed base-devel yasm nasm pkgconf rnnoise
elif [[ "$distro" == "fedora" ]]; then
elif [[ $distro == "fedora" ]]; then
sudo dnf install -y git make gcc yasm nasm pkgconf-pkg-config rnnoise-devel libX11-devel libXext-devel libXfixes-devel libXv-devel libXrandr-devel libXi-devel libXtst-devel libXinerama-devel freetype-devel fontconfig-devel libass-devel libvpx-devel libaom-devel libdav1d-devel zimg-devel rubberband-devel soxr-devel libvorbis-devel opus-devel lame-devel
else
echo "Note: please ensure rnnoise development headers are installed (pkg-config rnnoise)." >&2

View File

@ -26,7 +26,7 @@ require_command() {
local package_hint="${2:-}"
if ! command -v "$cmd" > /dev/null 2>&1; then
if [[ -n "$package_hint" ]]; then
if [[ -n $package_hint ]]; then
error "Missing command '$cmd'. Try installing the package: $package_hint"
else
error "Missing command '$cmd'."
@ -62,10 +62,10 @@ install_uv() {
curl -LsSf https://astral.sh/uv/install.sh | sh
local local_bin="$HOME/.local/bin"
if [[ ":$PATH:" != *":$local_bin:"* ]]; then
if [[ :$PATH: != *":$local_bin:"* ]]; then
warn "Adding $local_bin to PATH in ~/.profile and ~/.zshrc. Open a new shell to apply."
printf '\nexport PATH="$HOME/.local/bin:$PATH"\n' >> "$HOME/.profile"
printf '\nexport PATH="$HOME/.local/bin:$PATH"\n' >> "$HOME/.zshrc"
printf "\nexport PATH=\"\$HOME/.local/bin:\$PATH\"\n" >> "$HOME/.profile"
printf "\nexport PATH=\"\$HOME/.local/bin:\$PATH\"\n" >> "$HOME/.zshrc"
fi
}
@ -116,7 +116,7 @@ sync_unity_mcp_repo() {
fi
done
if [[ -z "$server_subdir" ]]; then
if [[ -z $server_subdir ]]; then
error "UnityMcpServer src directory not found. Checked candidates: ${candidates[*]}"
error "Repository layout may have changed. Inspect $repo_dir for the new server location."
exit 1
@ -133,14 +133,14 @@ configure_vscode_mcp() {
local mcp_config="$mcp_config_dir/mcp.json"
local tmp
if [[ ! -d "$server_src" ]]; then
if [[ ! -d $server_src ]]; then
error "Server source directory $server_src is missing."
exit 1
fi
mkdir -p "$mcp_config_dir"
if [[ ! -f "$mcp_config" ]]; then
if [[ ! -f $mcp_config ]]; then
info "Creating new VS Code MCP configuration at $mcp_config"
echo '{}' > "$mcp_config"
else
@ -169,9 +169,11 @@ configure_vscode_mcp() {
}
verify_python_version() {
require_command python "python"
local version
version="$(python - <<'PY'
version="$(
python - << 'PY'
import sys
print("%d.%d.%d" % sys.version_info[:3])
PY

View File

@ -44,9 +44,19 @@ DEBUG=0
# Colors
if [[ -t 1 && ${NO_COLOR} -eq 0 ]]; then
GREEN="\e[32m"; YELLOW="\e[33m"; RED="\e[31m"; BLUE="\e[34m"; BOLD="\e[1m"; RESET="\e[0m"
GREEN="\e[32m"
YELLOW="\e[33m"
RED="\e[31m"
BLUE="\e[34m"
BOLD="\e[1m"
RESET="\e[0m"
else
GREEN=""; YELLOW=""; RED=""; BLUE=""; BOLD=""; RESET=""
GREEN=""
YELLOW=""
RED=""
BLUE=""
BOLD=""
RESET=""
fi
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
@ -118,43 +128,124 @@ gen_api_key() {
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || { err "Required command '$1' not found"; return 1; }
command -v "$1" > /dev/null 2>&1 || {
err "Required command '$1' not found"
return 1
}
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--image) IMAGE="$2"; shift 2;;
--tag) TAG="$2"; shift 2;;
--port) PORT="$2"; shift 2;;
--host) HOST="$2"; shift 2;;
--data-dir) DATA_DIR="$2"; CACHE_DIR="${DATA_DIR}/cache"; shift 2;;
--cache-dir) CACHE_DIR="$2"; shift 2;;
--no-docker-install) DOCKER_INSTALL=0; shift;;
--keep-alive) KEEP_ALIVE=1; shift;;
--) shift; RUN_COMMAND=("$@"); break;;
--api-key) API_KEY="$2"; GENERATE_API_KEY=0; shift 2;;
--generate-api-key) GENERATE_API_KEY=1; shift;;
--disable-api-key) DISABLE_API_KEY=1; shift;;
--preload-langs) PRELOAD_LANGS="$2"; shift 2;;
--env) EXTRA_ENV+=("$2"); shift 2;;
--pull-only) PULL_ONLY=1; shift;;
--uninstall) UNINSTALL=1; shift;;
--purge) UNINSTALL=1; KEEP_DATA=0; shift;;
--keep-data) KEEP_DATA=1; shift;;
--health-timeout) HEALTH_TIMEOUT="$2"; shift 2;;
--no-color) NO_COLOR=1; shift;;
--debug) DEBUG=1; shift;;
-h|--help) usage; exit 0;;
-v|--version) echo "${VERSION}"; exit 0;;
*) err "Unknown argument: $1"; usage; exit 1;;
--image)
IMAGE="$2"
shift 2
;;
--tag)
TAG="$2"
shift 2
;;
--port)
PORT="$2"
shift 2
;;
--host)
HOST="$2"
shift 2
;;
--data-dir)
DATA_DIR="$2"
CACHE_DIR="${DATA_DIR}/cache"
shift 2
;;
--cache-dir)
CACHE_DIR="$2"
shift 2
;;
--no-docker-install)
DOCKER_INSTALL=0
shift
;;
--keep-alive)
KEEP_ALIVE=1
shift
;;
--)
shift
RUN_COMMAND=("$@")
break
;;
--api-key)
API_KEY="$2"
GENERATE_API_KEY=0
shift 2
;;
--generate-api-key)
GENERATE_API_KEY=1
shift
;;
--disable-api-key)
DISABLE_API_KEY=1
shift
;;
--preload-langs)
PRELOAD_LANGS="$2"
shift 2
;;
--env)
EXTRA_ENV+=("$2")
shift 2
;;
--pull-only)
PULL_ONLY=1
shift
;;
--uninstall)
UNINSTALL=1
shift
;;
--purge)
UNINSTALL=1
KEEP_DATA=0
shift
;;
--keep-data)
KEEP_DATA=1
shift
;;
--health-timeout)
HEALTH_TIMEOUT="$2"
shift 2
;;
--no-color)
NO_COLOR=1
shift
;;
--debug)
DEBUG=1
shift
;;
-h | --help)
usage
exit 0
;;
-v | --version)
echo "${VERSION}"
exit 0
;;
*)
err "Unknown argument: $1"
usage
exit 1
;;
esac
done
}
ensure_root() {
if [[ $EUID -ne 0 ]]; then
err "This script must run as root (or via sudo)."; exit 1
err "This script must run as root (or via sudo)."
exit 1
fi
}
@ -164,22 +255,33 @@ install_docker() {
return 0
fi
if [[ ${DOCKER_INSTALL} -eq 0 ]]; then
err "Docker is not installed and --no-docker-install specified."; exit 1
err "Docker is not installed and --no-docker-install specified."
exit 1
fi
log "Installing Docker..."
if command -v apt-get > /dev/null 2>&1; then
apt-get update -y
apt-get install -y ca-certificates curl gnupg
install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
curl -fsSL "https://download.docker.com/linux/$(
. /etc/os-release
echo "$ID"
)/gpg" | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(. /etc/os-release; echo "$VERSION_CODENAME") stable" \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$(
. /etc/os-release
echo "$ID"
) $(
. /etc/os-release
echo "$VERSION_CODENAME"
) stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update -y
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
else
err "Unsupported package manager. Please install Docker manually."; exit 1
err "Unsupported package manager. Please install Docker manually."
exit 1
fi
# Attempt to start docker daemon if dockerd exists and systemctl available; otherwise rely on user
if command -v systemctl > /dev/null 2>&1; then
@ -218,7 +320,7 @@ write_env_file() {
if [[ -d $d ]]; then
CUR_UID=$(stat -c %u "$d" 2> /dev/null || echo -1)
if [[ ${CUR_UID} -ne ${CONTAINER_UID} ]]; then
chown ${CONTAINER_UID}:${CONTAINER_GID} "$d" 2>/dev/null || warn "Unable to chown $d to ${CONTAINER_UID}:${CONTAINER_GID}"
chown "${CONTAINER_UID}":"${CONTAINER_GID}" "$d" 2> /dev/null || warn "Unable to chown $d to ${CONTAINER_UID}:${CONTAINER_GID}"
fi
fi
done
@ -236,16 +338,19 @@ write_env_file() {
API_KEY_LINE="LT_API_KEYS=${API_KEY}"
fi
{ echo "# LibreTranslate environment file"; echo "# Generated $(date -u +%Y-%m-%dT%H:%M:%SZ)"; echo "${API_KEY_LINE}";
[[ -n ${PRELOAD_LANGS} ]] && echo "LT_PRELOAD_LANGS=${PRELOAD_LANGS}";
for kv in "${EXTRA_ENV[@]:-}"; do echo "$kv"; done; } > "${ENV_FILE}.tmp"
{
echo "# LibreTranslate environment file"
echo "# Generated $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "${API_KEY_LINE}"
[[ -n ${PRELOAD_LANGS} ]] && echo "LT_PRELOAD_LANGS=${PRELOAD_LANGS}"
for kv in "${EXTRA_ENV[@]:-}"; do echo "$kv"; done
} > "${ENV_FILE}.tmp"
mv "${ENV_FILE}.tmp" "${ENV_FILE}"
chmod 600 "${ENV_FILE}"
success "Environment file written: ${ENV_FILE}"
}
start_container_ephemeral() {
log "Starting ephemeral container..."
docker rm -f "${SERVICE_NAME}" > /dev/null 2>&1 || true
docker run -d --name "${SERVICE_NAME}" \
--env-file "${ENV_FILE}" \
@ -253,7 +358,7 @@ start_container_ephemeral() {
-v "${CACHE_DIR}:/app/cache" \
-p "${PORT}:${PORT}" \
"${IMAGE}:${TAG}" \
--host 0.0.0.0 --port ${PORT}
--host 0.0.0.0 --port "${PORT}"
success "Container started (ephemeral)"
echo
echo "Endpoint (pending readiness): http://$(hostname -I | awk '{print $1}'):${PORT}"
@ -261,7 +366,8 @@ start_container_ephemeral() {
}
health_check() {
local start=$(date +%s)
local start
start=$(date +%s)
local url="http://127.0.0.1:${PORT}/languages"
local attempt=0
while true; do
@ -378,4 +484,3 @@ main() {
}
main "$@"

View File

@ -1,10 +1,9 @@
#!/bin/bash
process_table_schema() {
while IFS=$'\t' read -r column_name _ data_type _; do
# Print the column name and data type
echo -e "$column_name\t$data_type"
printf '%s\t%s\n' "$column_name" "$data_type"
done < "$1"
}

View File

@ -34,11 +34,26 @@ log() {
}
detect_pkg_mgr() {
if command -v apt-get >/dev/null 2>&1; then echo apt; return; fi
if command -v dnf >/dev/null 2>&1; then echo dnf; return; fi
if command -v yum >/dev/null 2>&1; then echo yum; return; fi
if command -v pacman >/dev/null 2>&1; then echo pacman; return; fi
if command -v zypper >/dev/null 2>&1; then echo zypper; return; fi
if command -v apt-get > /dev/null 2>&1; then
echo apt
return
fi
if command -v dnf > /dev/null 2>&1; then
echo dnf
return
fi
if command -v yum > /dev/null 2>&1; then
echo yum
return
fi
if command -v pacman > /dev/null 2>&1; then
echo pacman
return
fi
if command -v zypper > /dev/null 2>&1; then
echo zypper
return
fi
echo none
}
@ -56,8 +71,8 @@ has_libcublas12() {
# venv-provided NVIDIA CUDA libs
if [[ -x "$VENV_DIR/bin/python" ]]; then
local pyver
pyver="$($VENV_DIR/bin/python -c 'import sys;print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || true)"
if [[ -n "$pyver" ]]; then
pyver="$("$VENV_DIR"/bin/python -c 'import sys;print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2> /dev/null || true)"
if [[ -n $pyver ]]; then
for d in "$VENV_DIR/lib/python$pyver/site-packages/nvidia/cublas/lib" \
"$VENV_DIR/lib/python$pyver/site-packages/nvidia/cudnn/lib" \
"$VENV_DIR/lib/python$pyver/site-packages/nvidia/cuda_runtime/lib"; do
@ -69,7 +84,8 @@ has_libcublas12() {
}
ensure_cuda_runtime() {
local mgr; mgr="$(detect_pkg_mgr)"
local mgr
mgr="$(detect_pkg_mgr)"
if [[ $OFFLINE -eq 1 ]]; then
if has_libcublas12; then return 0; fi
echo "CUDA runtime (libcublas.so.12) not found and offline mode is enabled. Install CUDA 12 runtime or rerun with --online." >&2
@ -85,14 +101,18 @@ ensure_cuda_runtime() {
set +e
case "$mgr" in
pacman)
sudo pacman -Sy --noconfirm cuda cudnn || true ;;
sudo pacman -Sy --noconfirm cuda cudnn || true
;;
apt)
sudo apt-get update -y || true
sudo apt-get install -y nvidia-cuda-toolkit || true ;;
sudo apt-get install -y nvidia-cuda-toolkit || true
;;
dnf | yum)
sudo "$mgr" install -y cuda cudnn || true ;;
sudo "$mgr" install -y cuda cudnn || true
;;
zypper)
sudo zypper install -y cuda cudnn || true ;;
sudo zypper install -y cuda cudnn || true
;;
*) log "Unknown package manager; cannot install CUDA automatically." ;;
esac
set -e
@ -112,7 +132,7 @@ install_system_deps() {
# If diarization requested and online, we may also try to ensure libsndfile
local need_libsndfile=0
if [[ "${FW_DIARIZE:-}" == "1" ]]; then
if [[ ${FW_DIARIZE:-} == "1" ]]; then
# Heuristic: check common library file
if [[ ! -e /usr/lib/x86_64-linux-gnu/libsndfile.so && ! -e /usr/lib/libsndfile.so && ! -e /usr/lib64/libsndfile.so ]]; then
need_libsndfile=1
@ -129,7 +149,8 @@ install_system_deps() {
exit 5
fi
local mgr; mgr="$(detect_pkg_mgr)"
local mgr
mgr="$(detect_pkg_mgr)"
log "Detected package manager: $mgr (installing missing: $([[ $need_ffmpeg -eq 1 ]] && echo ffmpeg)$([[ $need_espeak -eq 1 ]] && echo espeak-ng)$([[ $need_libsndfile -eq 1 ]] && echo libsndfile))"
if ! command -v sudo > /dev/null 2>&1; then
@ -152,39 +173,45 @@ install_system_deps() {
# If that failed, try libsndfile2 (newer distros)
sudo apt-get install -y libsndfile2 || true
fi
sudo apt-get install -y "${pkgs[@]}" || log "apt-get install failed; continuing" ;;
sudo apt-get install -y "${pkgs[@]}" || log "apt-get install failed; continuing"
;;
dnf)
pkgs=(python3-venv python3-pip)
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile)
sudo dnf install -y "${pkgs[@]}" || log "dnf install failed; continuing" ;;
sudo dnf install -y "${pkgs[@]}" || log "dnf install failed; continuing"
;;
yum)
pkgs=(python3-venv python3-pip)
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile)
sudo yum install -y "${pkgs[@]}" || log "yum install failed; continuing" ;;
sudo yum install -y "${pkgs[@]}" || log "yum install failed; continuing"
;;
pacman)
pkgs=(python-virtualenv python-pip)
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile)
sudo pacman -Sy --noconfirm "${pkgs[@]}" || log "pacman install failed; continuing" ;;
sudo pacman -Sy --noconfirm "${pkgs[@]}" || log "pacman install failed; continuing"
;;
zypper)
pkgs=(python311-virtualenv python311-pip)
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile1)
sudo zypper install -y "${pkgs[@]}" || log "zypper install failed; continuing" ;;
sudo zypper install -y "${pkgs[@]}" || log "zypper install failed; continuing"
;;
*)
log "Unknown package manager; please ensure ffmpeg and espeak-ng are installed." ;;
log "Unknown package manager; please ensure ffmpeg and espeak-ng are installed."
;;
esac
set -e
}
setup_venv() {
if [[ ! -d "$VENV_DIR" ]]; then
if [[ ! -d $VENV_DIR ]]; then
log "Creating venv at $VENV_DIR"
python3 -m venv "$VENV_DIR"
fi
@ -208,7 +235,7 @@ install_python_deps() {
exit 7
fi
# If diarization requested offline, check for its deps too (warn-only)
if [[ "${FW_DIARIZE:-}" == "1" ]]; then
if [[ ${FW_DIARIZE:-} == "1" ]]; then
python - << 'PY' || true
try:
import soundfile, speechbrain, torch # noqa: F401
@ -218,25 +245,25 @@ PY
fi
return 0
fi
if [[ "$has_nvidia_flag" -eq 1 ]]; then
if [[ $has_nvidia_flag -eq 1 ]]; then
# If ctranslate2 is not installed, attempt CUDA-enabled wheel (quiet, with fallback)
if ! "$VENV_DIR/bin/python" -c 'import ctranslate2' > /dev/null 2>&1; then
log "Installing CUDA-enabled CTranslate2 (cu12 wheel)"
python -m pip install -q --retries 1 --upgrade "ctranslate2<5,>=4.0" --extra-index-url https://download.opennmt.net/ctranslate2/cu12 || \
python -m pip install -q --retries 1 --upgrade "ctranslate2<5,>=4.0" --extra-index-url https://download.opennmt.net/ctranslate2/cu12 ||
log "Warning: could not reach cu12 wheel index; will proceed with available ctranslate2"
fi
# Ensure NVIDIA CUDA 12 runtime libs are available inside the venv
python -m pip install -q --retries 1 --upgrade nvidia-cublas-cu12 nvidia-cuda-runtime-cu12 nvidia-cudnn-cu12 || \
python -m pip install -q --retries 1 --upgrade nvidia-cublas-cu12 nvidia-cuda-runtime-cu12 nvidia-cudnn-cu12 ||
log "Warning: failed to install NVIDIA cu12 runtime libs via pip"
fi
python -m pip install -q --retries 1 --upgrade faster-whisper ffmpeg-python
# If diarization requested and online, install its Python deps best-effort
if [[ "${FW_DIARIZE:-}" == "1" ]]; then
python -m pip install -q --retries 1 --upgrade soundfile speechbrain || \
if [[ ${FW_DIARIZE:-} == "1" ]]; then
python -m pip install -q --retries 1 --upgrade soundfile speechbrain ||
log "Warning: failed to install soundfile/speechbrain"
# Torch and torchaudio CPU wheels (force to avoid mismatched CUDA builds)
python -m pip install -q --retries 1 --upgrade --force-reinstall --index-url https://download.pytorch.org/whl/cpu torch torchaudio || \
python -m pip install -q --retries 1 --upgrade --force-reinstall --index-url https://download.pytorch.org/whl/cpu torch torchaudio ||
log "Warning: failed to install torch/torchaudio CPU wheels"
fi
python - << 'PY'
@ -246,7 +273,7 @@ PY
}
ensure_runner() {
if [[ ! -f "$PY_RUNNER" ]]; then
if [[ ! -f $PY_RUNNER ]]; then
echo "Runner not found: $PY_RUNNER" >&2
exit 3
fi
@ -260,17 +287,17 @@ generate_test_audio() {
espeak-ng -w "$tmpwav" "This is a quick test of faster whisper transcription." > /dev/null 2>&1 || true
fi
# If espeak-ng failed or not present, try espeak
if [[ ! -s "$tmpwav" ]] && command -v espeak >/dev/null 2>&1; then
if [[ ! -s $tmpwav ]] && command -v espeak > /dev/null 2>&1; then
log "espeak-ng unavailable or failed; trying espeak -> $tmpwav" >&2
espeak -w "$tmpwav" "This is a quick test of faster whisper transcription." > /dev/null 2>&1 || true
fi
# Fallback: generate tone via Python stdlib (no external deps)
if [[ ! -s "$tmpwav" ]]; then
if [[ ! -s $tmpwav ]]; then
log "Generating 3s 1kHz WAV via Python stdlib -> $tmpwav" >&2
python3 -c 'import sys,wave,math,array;outfile=sys.argv[1];fr=16000;dur=3;freq=1000.0;ampl=0.3;n=fr*dur;data=array.array("h",[int(max(-1.0,min(1.0,ampl*math.sin(2*math.pi*freq*(i/fr))))*32767) for i in range(n)]);wf=wave.open(outfile,"w");wf.setnchannels(1);wf.setsampwidth(2);wf.setframerate(fr);wf.writeframes(data.tobytes());wf.close()' "$tmpwav" || true
fi
# Final fallback: tone via ffmpeg
if [[ ! -s "$tmpwav" ]]; then
if [[ ! -s $tmpwav ]]; then
log "Creating a 3s sine tone WAV via ffmpeg -> $tmpwav" >&2
ffmpeg -f lavfi -i sine=frequency=1000:duration=3 -ar 16000 -ac 1 -f wav -y "$tmpwav" > /dev/null 2>&1 || true
fi
@ -306,18 +333,46 @@ main() {
INPUT_FILE=""
# Parse args
PARSED=$(getopt -o m:l:o:h -l online,prepare-model:,model-dir: -- "$@") || { usage; exit 2; }
PARSED=$(getopt -o m:l:o:h -l online,prepare-model:,model-dir: -- "$@") || {
usage
exit 2
}
eval set -- "$PARSED"
while true; do
case "$1" in
-m) MODEL="$2"; shift 2;;
-l) LANGUAGE="$2"; shift 2;;
-o) OUTDIR="$2"; shift 2;;
-h) usage; exit 0;;
--online) OFFLINE=0; shift;;
--prepare-model) PREPARE_MODEL="$2"; OFFLINE=0; shift 2;;
--model-dir) MODEL_DIR="$2"; shift 2;;
--) shift; break;;
-m)
MODEL="$2"
shift 2
;;
-l)
LANGUAGE="$2"
shift 2
;;
-o)
OUTDIR="$2"
shift 2
;;
-h)
usage
exit 0
;;
--online)
OFFLINE=0
shift
;;
--prepare-model)
PREPARE_MODEL="$2"
OFFLINE=0
shift 2
;;
--model-dir)
MODEL_DIR="$2"
shift 2
;;
--)
shift
break
;;
*) break ;;
esac
done
@ -332,7 +387,7 @@ main() {
setup_venv
# If asked to prepare a model, do that and exit
if [[ -n "$PREPARE_MODEL" ]]; then
if [[ -n $PREPARE_MODEL ]]; then
if [[ $OFFLINE -eq 1 ]]; then
echo "--prepare-model requires network; rerun with --online." >&2
exit 2
@ -354,27 +409,27 @@ main() {
ensure_runner
local input="$INPUT_FILE"
if [[ -z "$input" ]]; then
if [[ -z $input ]]; then
input="$(generate_test_audio)"
if [[ ! -s "$input" ]]; then
if [[ ! -s $input ]]; then
echo "Failed to generate test audio. Please provide an audio file." >&2
exit 4
fi
fi
if [[ ! -f "$input" ]]; then
if [[ ! -f $input ]]; then
echo "Input file not found: $input" >&2
exit 2
fi
local args=("$input" "--model" "$MODEL")
[[ -n "$LANGUAGE" ]] && args+=("--language" "$LANGUAGE")
[[ -n "$OUTDIR" ]] && args+=("--outdir" "$OUTDIR")
[[ -n $LANGUAGE ]] && args+=("--language" "$LANGUAGE")
[[ -n $OUTDIR ]] && args+=("--outdir" "$OUTDIR")
# Pass diarization via env if requested
if [[ "${FW_DIARIZE:-}" == "1" ]]; then
if [[ ${FW_DIARIZE:-} == "1" ]]; then
args+=("--diarize")
if [[ -n "${FW_NUM_SPEAKERS:-}" ]]; then
if [[ -n ${FW_NUM_SPEAKERS:-} ]]; then
args+=("--num-speakers" "${FW_NUM_SPEAKERS}")
fi
fi
@ -386,8 +441,8 @@ main() {
# Include system and possible venv-provided CUDA libs
local pyver venv_cuda_paths=""
if [[ -x "$VENV_DIR/bin/python" ]]; then
pyver="$($VENV_DIR/bin/python -c 'import sys;print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || true)"
if [[ -n "$pyver" ]]; then
pyver="$("$VENV_DIR"/bin/python -c 'import sys;print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2> /dev/null || true)"
if [[ -n $pyver ]]; then
venv_cuda_paths="$VENV_DIR/lib/python$pyver/site-packages/nvidia/cublas/lib:$VENV_DIR/lib/python$pyver/site-packages/nvidia/cudnn/lib:$VENV_DIR/lib/python$pyver/site-packages/nvidia/cuda_runtime/lib"
fi
fi
@ -395,7 +450,10 @@ main() {
export PATH="${PATH}:${CUDA_HOME}/bin"
# shellcheck disable=SC1091
source "$VENV_DIR/bin/activate"
python -c 'from faster_whisper import WhisperModel; WhisperModel("tiny", device="cuda", compute_type="float16"); print("[PY] CUDA test init succeeded.")' || { echo "CUDA environment check failed. Aborting as requested." >&2; exit 6; }
python -c 'from faster_whisper import WhisperModel; WhisperModel("tiny", device="cuda", compute_type="float16"); print("[PY] CUDA test init succeeded.")' || {
echo "CUDA environment check failed. Aborting as requested." >&2
exit 6
}
args+=("--device" "cuda")
fi
@ -411,15 +469,15 @@ main() {
# Offline: prefer local directory if present; otherwise use cache without network
if [[ $OFFLINE -eq 1 ]]; then
local local_model_path=""
if [[ -d "$MODEL" ]]; then
if [[ -d $MODEL ]]; then
local_model_path="$MODEL"
elif [[ -d "$MODEL_DIR/$MODEL" ]]; then
local_model_path="$MODEL_DIR/$MODEL"
fi
if [[ -n "$local_model_path" ]]; then
if [[ -n $local_model_path ]]; then
args=("$input" "--model" "$local_model_path")
[[ -n "$LANGUAGE" ]] && args+=("--language" "$LANGUAGE")
[[ -n "$OUTDIR" ]] && args+=("--outdir" "$OUTDIR")
[[ -n $LANGUAGE ]] && args+=("--language" "$LANGUAGE")
[[ -n $OUTDIR ]] && args+=("--outdir" "$OUTDIR")
fi
fi
python "$PY_RUNNER" "${args[@]}"
@ -427,4 +485,3 @@ main() {
}
main "$@"