fix: resolve all shellcheck errors

- Replace 'A && B || C' patterns with proper if-then-else statements (SC2015)
- Add check_for_virtualbox function to invoke prompt_for_virtualbox_challenge (SC2317)
- Fix install_launcher function to escape variable in heredoc (SC2119/SC2120)
- Apply shfmt formatting to ensure consistent style

Fixes 7 SC2015 violations, 1 SC2317 violation, and 1 SC2119/SC2120 pair.
All 79 shell files now pass shellcheck without errors.
This commit is contained in:
Krzysztof kuhy Rudnicki 2025-11-16 21:17:08 +01:00
parent 8e0a720499
commit 03bd36e41d
7 changed files with 1666 additions and 1627 deletions

View File

@ -26,7 +26,7 @@ AMD_ENABLE_SI_CIK=${AMD_ENABLE_SI_CIK:-auto}
AMD_SKIP_INITRAMFS=${AMD_SKIP_INITRAMFS:-0}
AMD_VERBOSE=${AMD_VERBOSE:-0}
vlog() { [ "$AMD_VERBOSE" = 1 ] && echo "[amd] $*" || true; }
vlog() { if [ "$AMD_VERBOSE" = 1 ]; then echo "[amd] $*"; fi; }
info() { echo "[amd] $*"; }
warn() { echo "[amd][warn] $*" >&2; }
@ -64,7 +64,7 @@ _build_aur_pkg() {
cd "$HOME/aur"
if [ ! -d "$pkg" ]; then git clone "$url"; else (cd "$pkg" && git fetch -q --all && git reset -q --hard origin/HEAD || git pull --ff-only || true); fi
cd "$pkg"
rm -f -- *.pkg.tar.* 2> /dev/null || true
rm -f -- *.pkg.tar.* 2>/dev/null || true
yes | makepkg -s -c -C --noconfirm --needed
local built=(*.pkg.tar.zst)
yes | sudo pacman -U --noconfirm "${built[@]}"
@ -72,8 +72,8 @@ _build_aur_pkg() {
_install_repo_or_aur() {
local pkg="$1"
if pacman -Si "$pkg" > /dev/null 2>&1; then
if pacman -Qi "$pkg" > /dev/null 2>&1; then
if pacman -Si "$pkg" >/dev/null 2>&1; then
if pacman -Qi "$pkg" >/dev/null 2>&1; then
vlog "$pkg already installed"
else
yes | sudo pacman -Sy --noconfirm "$pkg"
@ -115,8 +115,8 @@ for n in "${CIK_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_CIK=1 && b
if [ "$AMD_ENABLE_SI_CIK" = "1" ] || { [ "$AMD_ENABLE_SI_CIK" = "auto" ] && { [ $IS_SI = 1 ] || [ $IS_CIK = 1 ]; }; }; then
info "Configuring amdgpu for SI/CIK (IS_SI=$IS_SI IS_CIK=$IS_CIK)"
TMP_CONF=$(mktemp)
printf 'options amdgpu si_support=1\noptions amdgpu cik_support=1\n' > "$TMP_CONF"
printf 'options radeon si_support=0\noptions radeon cik_support=0\n' >> "$TMP_CONF"
printf 'options amdgpu si_support=1\noptions amdgpu cik_support=1\n' >"$TMP_CONF"
printf 'options radeon si_support=0\noptions radeon cik_support=0\n' >>"$TMP_CONF"
sudo mkdir -p /etc/modprobe.d
sudo cp "$TMP_CONF" /etc/modprobe.d/10-amdgpu-si-cik.conf
rm -f "$TMP_CONF"
@ -142,7 +142,7 @@ else
fi
# Check active kernel driver
KDRV=$(lspci -k -d ::0300 2> /dev/null | awk '/Kernel driver in use:/ {print $5; exit}')
KDRV=$(lspci -k -d ::0300 2>/dev/null | awk '/Kernel driver in use:/ {print $5; exit}')
[ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'amdgpu|radeon' | head -n1 | awk '{print $1}')
info "Kernel driver in use: ${KDRV:-unknown}"

View File

@ -26,7 +26,7 @@ INTEL_ENABLE_GUC=${INTEL_ENABLE_GUC:-}
INTEL_SKIP_INITRAMFS=${INTEL_SKIP_INITRAMFS:-0}
INTEL_VERBOSE=${INTEL_VERBOSE:-1}
vlog() { [ "$INTEL_VERBOSE" = 1 ] && echo "[intel] $*" || true; }
vlog() { if [ "$INTEL_VERBOSE" = 1 ]; then echo "[intel] $*"; fi; }
info() { echo "[intel] $*"; }
warn() { echo "[intel][warn] $*" >&2; }
@ -44,10 +44,10 @@ fi
install_pkg() {
local pkg="$1"
if pacman -Qi "$pkg" > /dev/null 2>&1; then
if pacman -Qi "$pkg" >/dev/null 2>&1; then
vlog "$pkg already installed"
else
if pacman -Si "$pkg" > /dev/null 2>&1; then
if pacman -Si "$pkg" >/dev/null 2>&1; then
yes | sudo pacman -Sy --noconfirm "$pkg"
else
warn "Package $pkg not found in repos (not handling AUR here)"
@ -89,7 +89,7 @@ if [ -n "$INTEL_ENABLE_GUC" ]; then
else
info "Configuring enable_guc=$INTEL_ENABLE_GUC"
sudo mkdir -p /etc/modprobe.d
echo "options i915 enable_guc=$INTEL_ENABLE_GUC" | sudo tee /etc/modprobe.d/i915-guc.conf > /dev/null
echo "options i915 enable_guc=$INTEL_ENABLE_GUC" | sudo tee /etc/modprobe.d/i915-guc.conf >/dev/null
if [ "$INTEL_SKIP_INITRAMFS" != 1 ] && [ -f /etc/mkinitcpio.conf ]; then
info "Regenerating initramfs (mkinitcpio -P) for GuC/HuC change"
sudo mkinitcpio -P || warn "mkinitcpio failed; continue manually"
@ -100,7 +100,7 @@ if [ -n "$INTEL_ENABLE_GUC" ]; then
fi
# Report kernel driver
KDRV=$(lspci -k -d ::0300 2> /dev/null | awk '/Kernel driver in use:/ {print $5; exit}')
KDRV=$(lspci -k -d ::0300 2>/dev/null | awk '/Kernel driver in use:/ {print $5; exit}')
[ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'i915|xe' | head -n1 | awk '{print $1}')
info "Kernel driver in use: ${KDRV:-unknown}"

View File

@ -9,23 +9,23 @@ LOGTAG=hosts-guard-hook
stop_units_if_present() {
local units=(hosts-bind-mount.service hosts-guard.path)
for u in "${units[@]}"; do
if command -v systemctl > /dev/null 2>&1; then
if systemctl list-unit-files 2> /dev/null | grep -q "^$u"; then
systemctl stop "$u" > /dev/null 2>&1 || true
if command -v systemctl >/dev/null 2>&1; then
if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then
systemctl stop "$u" >/dev/null 2>&1 || true
fi
fi
done
}
is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2> /dev/null | grep -qw ro; }
is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro; }
mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2> /dev/null || echo 0; }
mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; }
cleanup_mount_stacks() {
local i=0
# Unmount bind layers until /etc/hosts is no longer a mountpoint
if command -v mountpoint > /dev/null 2>&1; then
if command -v mountpoint >/dev/null 2>&1; then
while mountpoint -q "$TARGET"; do
umount -l "$TARGET" > /dev/null 2>&1 || break
umount -l "$TARGET" >/dev/null 2>&1 || break
i=$((i + 1))
((i > 20)) && break
done
@ -34,7 +34,7 @@ cleanup_mount_stacks() {
local cnt
cnt=$(mount_layers_count)
while ((cnt > 1)); do
umount -l "$TARGET" > /dev/null 2>&1 || break
umount -l "$TARGET" >/dev/null 2>&1 || break
i=$((i + 1))
((i > 20)) && break
cnt=$(mount_layers_count)
@ -43,23 +43,27 @@ cleanup_mount_stacks() {
}
# Drop protective attributes if present
if command -v lsattr > /dev/null 2>&1; then
attrs=$(lsattr -d "$TARGET" 2> /dev/null || true)
echo "$attrs" | grep -q " i " && chattr -i "$TARGET" > /dev/null 2>&1 || true
echo "$attrs" | grep -q " a " && chattr -a "$TARGET" > /dev/null 2>&1 || true
if command -v lsattr >/dev/null 2>&1; then
attrs=$(lsattr -d "$TARGET" 2>/dev/null || true)
if echo "$attrs" | grep -q " i "; then
chattr -i "$TARGET" >/dev/null 2>&1 || true
fi
if echo "$attrs" | grep -q " a "; then
chattr -a "$TARGET" >/dev/null 2>&1 || true
fi
fi
stop_units_if_present
logger -t "$LOGTAG" "pre: unlocking /etc/hosts (starting)"
echo "$(date -Is) pre-unlock" >> /run/hosts-guard-hook.log 2> /dev/null || true
echo "$(date -Is) pre-unlock" >>/run/hosts-guard-hook.log 2>/dev/null || true
# Always collapse any existing layers; we'll operate on the plain file
cleanup_mount_stacks
# If someone managed a ro single-layer mount, ensure rw by remounting or collapsing again
if is_ro_mount; then
mount -o remount,rw "$TARGET" > /dev/null 2>&1 || cleanup_mount_stacks
mount -o remount,rw "$TARGET" >/dev/null 2>&1 || cleanup_mount_stacks
fi
logger -t "$LOGTAG" "pre: unlocking /etc/hosts (done)"

View File

@ -84,7 +84,7 @@ post_relock_hosts() {
# Ensure periodic system services (timer/monitor) are set up; if not, trigger setup
ensure_periodic_maintenance() {
# Only proceed if systemd/systemctl is available
if ! command -v systemctl > /dev/null 2>&1; then
if ! command -v systemctl >/dev/null 2>&1; then
return 0
fi
@ -214,10 +214,10 @@ check_and_handle_db_lock() {
# Determine which processes actually have the lock open
local -a holders=()
if command -v fuser > /dev/null 2>&1; then
mapfile -t holders < <(fuser "$lock_file" 2> /dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true)
elif command -v lsof > /dev/null 2>&1; then
mapfile -t holders < <(lsof -t "$lock_file" 2> /dev/null | grep -E '^[0-9]+$' || true)
if command -v fuser >/dev/null 2>&1; then
mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true)
elif command -v lsof >/dev/null 2>&1; then
mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true)
else
holders=()
fi
@ -237,8 +237,8 @@ check_and_handle_db_lock() {
local gui_holder=0
for pid in "${holders[@]}"; do
local comm args lower
comm=$(ps -p "$pid" -o comm= 2> /dev/null || true)
args=$(ps -p "$pid" -o args= 2> /dev/null || true)
comm=$(ps -p "$pid" -o comm= 2>/dev/null || true)
args=$(ps -p "$pid" -o args= 2>/dev/null || true)
lower="${comm,,} ${args,,}"
if [[ $lower == *" pacman"* || $lower == pacman* || $lower == *"/pacman "* || $lower == *" pamac"* ]]; then
pac_holder=1
@ -254,21 +254,21 @@ check_and_handle_db_lock() {
if [[ $gui_holder -eq 1 ]]; then
echo -e "${YELLOW}A background software updater is holding the pacman lock. Attempting to stop it...${NC}" >&2
if command -v systemctl > /dev/null 2>&1; then
systemctl --quiet stop packagekit.service 2> /dev/null || true
systemctl --quiet stop packagekit 2> /dev/null || true
if command -v systemctl >/dev/null 2>&1; then
systemctl --quiet stop packagekit.service 2>/dev/null || true
systemctl --quiet stop packagekit 2>/dev/null || true
fi
pkill -x packagekitd 2> /dev/null || true
pkill -f gnome-software 2> /dev/null || true
pkill -f discover 2> /dev/null || true
pkill -x packagekitd 2>/dev/null || true
pkill -f gnome-software 2>/dev/null || true
pkill -f discover 2>/dev/null || true
sleep 1
# Re-check holders
holders=()
if command -v fuser > /dev/null 2>&1; then
mapfile -t holders < <(fuser "$lock_file" 2> /dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true)
elif command -v lsof > /dev/null 2>&1; then
mapfile -t holders < <(lsof -t "$lock_file" 2> /dev/null | grep -E '^[0-9]+$' || true)
if command -v fuser >/dev/null 2>&1; then
mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true)
elif command -v lsof >/dev/null 2>&1; then
mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true)
fi
if [[ ${#holders[@]} -gt 0 ]]; then
echo -e "${RED}Cannot free the pacman lock; another process still holds it. Try again later.${NC}" >&2
@ -279,7 +279,7 @@ check_and_handle_db_lock() {
# Decide whether to remove the lock
local now epoch age
if epoch=$(stat -c %Y "$lock_file" 2> /dev/null); then
if epoch=$(stat -c %Y "$lock_file" 2>/dev/null); then
now=$(date +%s)
age=$((now - epoch))
else
@ -317,7 +317,7 @@ check_and_handle_db_lock() {
function remove_installed_blocked_packages() {
# args not used; kept for future policy extension
# List installed package names
mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2> /dev/null)
mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2>/dev/null)
local to_remove=()
for name in "${installed_names[@]}"; do
if is_blocked_package_name "$name"; then
@ -508,8 +508,8 @@ function prompt_for_steam_challenge() {
read_status=$?
# Kill the timer display
kill "$display_pid" 2> /dev/null
wait "$display_pid" 2> /dev/null
kill "$display_pid" 2>/dev/null
wait "$display_pid" 2>/dev/null
echo # Add a newline after the timer
# Check if read timed out
@ -536,8 +536,30 @@ function prompt_for_steam_challenge() {
fi
}
function check_for_virtualbox() {
# List of VirtualBox-related packages
local vbox_packages=("virtualbox" "virtualbox-host-modules-arch" "virtualbox-guest-iso" "virtualbox-ext-oracle")
# Check if the command is an installation command
if [[ $1 == "-S" || $1 == "-Sy" || $1 == "-Syu" || $1 == "-Syyu" || $1 == "-U" ]]; then
# Check all arguments
for arg in "$@"; do
# Strip repository prefix if present
local package_name="${arg##*/}"
# Check if argument matches any VirtualBox package
for package in "${vbox_packages[@]}"; do
if [[ $arg == "$package" || $arg == *"/$package-"* || $arg == *"/$package/"* ||
$arg == *"/$package" || $package_name == "$package" ]]; then
return 0 # VirtualBox package found
fi
done
done
fi
return 1 # No VirtualBox package found
}
# Function to prompt for solving a word unscrambling challenge (for virtualbox - always active)
# shellcheck disable=SC2329 # Invoked dynamically when matching VirtualBox packages
function prompt_for_virtualbox_challenge() {
echo -e "${YELLOW}WARNING: You are trying to install VirtualBox.${NC}"
echo -e "${YELLOW}VirtualBox challenge will begin shortly...${NC}"
@ -629,8 +651,8 @@ function prompt_for_virtualbox_challenge() {
read_status=$?
# Kill the timer display
kill "$display_pid" 2> /dev/null
wait "$display_pid" 2> /dev/null
kill "$display_pid" 2>/dev/null
wait "$display_pid" 2>/dev/null
echo # Add a newline after the timer
# Check if read timed out
@ -681,6 +703,13 @@ if check_for_steam "$@"; then
fi
fi
# Check for VirtualBox (challenge-eligible package)
if check_for_virtualbox "$@"; then
if ! prompt_for_virtualbox_challenge; then
exit 1
fi
fi
# Display operation
display_operation "$1"

View File

@ -37,7 +37,7 @@ fail() {
}
usage() {
cat << EOF
cat <<EOF
Usage: $SCRIPT_NAME [options]
Options:
@ -90,14 +90,14 @@ done
REPO_DIR="$INSTALL_ROOT/unreal-mcp"
# ---------- Dependencies ----------
require_cmd() { command -v "$1" > /dev/null 2>&1; }
require_cmd() { command -v "$1" >/dev/null 2>&1; }
ensure_packages_arch() {
# Install with pacman using sudo when needed; keep idempotent with --needed
local pkgs=(git jq uv python rsync)
local to_install=()
for p in "${pkgs[@]}"; do
if ! pacman -Qi "$p" > /dev/null 2>&1; then
if ! pacman -Qi "$p" >/dev/null 2>&1; then
to_install+=("$p")
fi
done
@ -173,10 +173,10 @@ install_launcher() {
local python_dir="$REPO_DIR/Python"
local launcher="$bin_dir/unreal-mcp-server"
mkdir -p "$bin_dir"
cat > "$launcher" << EOF
cat >"$launcher" <<EOF
#!/bin/bash
set -euo pipefail
exec uv --directory "$python_dir" run unreal_mcp_server.py "${1:-}" < /dev/null
exec uv --directory "$python_dir" run unreal_mcp_server.py "\${1:-}" < /dev/null
EOF
chmod +x "$launcher"
if [[ $EUID -eq 0 ]]; then chown "$ACTUAL_USER:$ACTUAL_USER" "$launcher"; fi
@ -199,7 +199,7 @@ configure_continue() {
local tmp_file
tmp_file="$(mktemp)"
if [[ ! -f $cont_cfg ]]; then
cat > "$tmp_file" << JSON
cat >"$tmp_file" <<JSON
{
"mcpServers": {
"unrealMCP": {
@ -221,7 +221,7 @@ JSON
command: "uv",
args: ["--directory", $dir, "run", "unreal_mcp_server.py"]
}
' "$cont_cfg" > "$tmp_file" && mv "$tmp_file" "$cont_cfg"
' "$cont_cfg" >"$tmp_file" && mv "$tmp_file" "$cont_cfg"
fi
if [[ $EUID -eq 0 ]]; then chown "$ACTUAL_USER:$ACTUAL_USER" "$cont_cfg"; fi
@ -247,12 +247,12 @@ configure_vscode_user_mcp() {
local candidates=(code code-insiders codium)
local found_any=false
for cli in "${candidates[@]}"; do
if ! command -v "$cli" > /dev/null 2>&1; then
if ! command -v "$cli" >/dev/null 2>&1; then
continue
fi
found_any=true
log "Registering MCP server in VS Code user profile via: $cli --add-mcp"
if "$cli" --add-mcp "$json" > "/tmp/${cli}-add-mcp.log" 2>&1; then
if "$cli" --add-mcp "$json" >"/tmp/${cli}-add-mcp.log" 2>&1; then
log "[$cli] user profile: unrealMCP added/updated"
else
sed -n '1,200p' "/tmp/${cli}-add-mcp.log" || true
@ -281,7 +281,7 @@ configure_vscode_user_mcp() {
local name
for name in "${unreal_profiles[@]}"; do
log "[$cli] Adding unrealMCP to profile: $name"
if "$cli" --profile "$name" --add-mcp "$json" > "/tmp/${cli}-add-mcp-${name// /_}.log" 2>&1; then
if "$cli" --profile "$name" --add-mcp "$json" >"/tmp/${cli}-add-mcp-${name// /_}.log" 2>&1; then
log "[$cli] profile '$name': unrealMCP added/updated"
else
sed -n '1,200p' "/tmp/${cli}-add-mcp-${name// /_}.log" || true
@ -307,7 +307,7 @@ install_plugin_into_project() {
local upath="$PROJECT_UPROJECT"
if [[ -d $upath ]]; then
# Resolve .uproject in the provided directory
mapfile -t _uprojects < <(find "$upath" -maxdepth 1 -type f -name "*.uproject" 2> /dev/null || true)
mapfile -t _uprojects < <(find "$upath" -maxdepth 1 -type f -name "*.uproject" 2>/dev/null || true)
if [[ ${#_uprojects[@]} -eq 0 ]]; then
fail "--project directory '$upath' contains no .uproject files"
elif [[ ${#_uprojects[@]} -gt 1 ]]; then
@ -349,7 +349,7 @@ print_summary() {
if [[ -n $RESOLVED_PROJECT_DIR ]]; then
plugin_dest="$RESOLVED_PROJECT_DIR/Plugins/UnrealMCP"
fi
cat << EOF
cat <<EOF
============================================
Unreal MCP setup complete
============================================

View File

@ -65,7 +65,7 @@ err() { echo -e "${RED}[ERR ]${RESET} $*" >&2; }
success() { echo -e "${GREEN}[OK ]${RESET} $*"; }
usage() {
cat << EOF
cat <<EOF
${SCRIPT_NAME} v${VERSION}
Setup or uninstall a self-hosted LibreTranslate instance via Docker.
@ -116,7 +116,7 @@ gen_api_key() {
key=$(head -c 256 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 40 || true)
if [[ -z $key || ${#key} -lt 40 ]]; then
# Fallback using openssl if available
if command -v openssl > /dev/null 2>&1; then
if command -v openssl >/dev/null 2>&1; then
key=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 40 || true)
fi
fi
@ -128,7 +128,7 @@ gen_api_key() {
}
need_cmd() {
command -v "$1" > /dev/null 2>&1 || {
command -v "$1" >/dev/null 2>&1 || {
err "Required command '$1' not found"
return 1
}
@ -250,7 +250,7 @@ ensure_root() {
}
install_docker() {
if command -v docker > /dev/null 2>&1; then
if command -v docker >/dev/null 2>&1; then
log "Docker already installed"
return 0
fi
@ -259,7 +259,7 @@ install_docker() {
exit 1
fi
log "Installing Docker..."
if command -v apt-get > /dev/null 2>&1; then
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
@ -276,7 +276,7 @@ install_docker() {
. /etc/os-release
echo "$VERSION_CODENAME"
) stable" \
> /etc/apt/sources.list.d/docker.list
>/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
@ -284,8 +284,8 @@ install_docker() {
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
(systemctl enable --now docker 2> /dev/null && success "Docker installed and started") || warn "Docker installed; ensure dockerd is running"
if command -v systemctl >/dev/null 2>&1; then
(systemctl enable --now docker 2>/dev/null && success "Docker installed and started") || warn "Docker installed; ensure dockerd is running"
else
warn "Docker installed; please ensure docker daemon is running"
fi
@ -299,12 +299,12 @@ pull_image() {
detect_container_user() {
# Determine uid/gid of configured user inside image so host dirs can be chowned
if ! command -v docker > /dev/null 2>&1; then
if ! command -v docker >/dev/null 2>&1; then
return 0
fi
local uid gid
uid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -u 2> /dev/null || echo "")
gid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -g 2> /dev/null || echo "")
uid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -u 2>/dev/null || echo "")
gid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -g 2>/dev/null || echo "")
if [[ -n $uid && -n $gid ]]; then
CONTAINER_UID=$uid
CONTAINER_GID=$gid
@ -315,12 +315,12 @@ write_env_file() {
mkdir -p "${CONFIG_DIR}" "${DATA_DIR}" "${CACHE_DIR}"
detect_container_user
if [[ -n ${CONTAINER_UID:-} && -n ${CONTAINER_GID:-} ]]; then
if command -v stat > /dev/null 2>&1; then
if command -v stat >/dev/null 2>&1; then
for d in "${DATA_DIR}" "${CACHE_DIR}"; do
if [[ -d $d ]]; then
CUR_UID=$(stat -c %u "$d" 2> /dev/null || echo -1)
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
@ -344,14 +344,14 @@ write_env_file() {
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"
} >"${ENV_FILE}.tmp"
mv "${ENV_FILE}.tmp" "${ENV_FILE}"
chmod 600 "${ENV_FILE}"
success "Environment file written: ${ENV_FILE}"
}
start_container_ephemeral() {
docker rm -f "${SERVICE_NAME}" > /dev/null 2>&1 || true
docker rm -f "${SERVICE_NAME}" >/dev/null 2>&1 || true
docker run -d --name "${SERVICE_NAME}" \
--env-file "${ENV_FILE}" \
-v "${DATA_DIR}:/home/libretranslate/.local/share/argos-translate" \
@ -372,7 +372,7 @@ health_check() {
local attempt=0
while true; do
attempt=$((attempt + 1))
if curl ${DEBUG:+-v} -fsS "$url" > /dev/null 2>&1; then
if curl ${DEBUG:+-v} -fsS "$url" >/dev/null 2>&1; then
success "Service healthy (attempt $attempt)"
return 0
else
@ -405,8 +405,8 @@ sample_request() {
uninstall_all() {
log "Uninstalling LibreTranslate (ephemeral mode)..."
docker rm -f "${SERVICE_NAME}" 2> /dev/null || true
docker rmi "${IMAGE}:${TAG}" 2> /dev/null || true
docker rm -f "${SERVICE_NAME}" 2>/dev/null || true
docker rmi "${IMAGE}:${TAG}" 2>/dev/null || true
if [[ ${KEEP_DATA} -eq 0 ]]; then
rm -rf "${DATA_DIR}" "${CONFIG_DIR}" || true
success "Data directories removed"
@ -448,7 +448,7 @@ main() {
CMD_STATUS=$?
set -e
log "Command exited with status ${CMD_STATUS}; stopping container"
docker stop "${SERVICE_NAME}" > /dev/null 2>&1 || true
docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true
exit ${CMD_STATUS}
fi
@ -457,9 +457,9 @@ main() {
trap 'log "Stopping container"; docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true; exit 0' INT TERM
docker logs -f "${SERVICE_NAME}"
log "Logs ended; stopping container"
docker stop "${SERVICE_NAME}" > /dev/null 2>&1 || true
docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true
else
log "Ephemeral container left running in background (id: $(docker inspect --format '{{.Id}}' ${SERVICE_NAME} 2> /dev/null || echo unknown))"
log "Ephemeral container left running in background (id: $(docker inspect --format '{{.Id}}' ${SERVICE_NAME} 2>/dev/null || echo unknown))"
log "Stop manually with: docker stop ${SERVICE_NAME}"
fi
@ -476,7 +476,9 @@ main() {
else
echo "API key authentication DISABLED (public instance)."
fi
[[ -n ${PRELOAD_LANGS} ]] && echo "Preloaded languages requested: ${PRELOAD_LANGS}" || true
if [[ -n ${PRELOAD_LANGS} ]]; then
echo "Preloaded languages requested: ${PRELOAD_LANGS}"
fi
echo "Environment file: ${ENV_FILE}"
echo "Manage: docker logs -f ${SERVICE_NAME} | docker stop ${SERVICE_NAME}"
echo "Uninstall: sudo ${SCRIPT_NAME} --uninstall"

View File

@ -14,7 +14,7 @@ PY_RUNNER="$TOOLS_DIR/transcribe_fw.py"
VENV_DIR="$PROJECT_DIR/.venv"
usage() {
cat << USAGE
cat <<USAGE
Usage: $(basename "$0") [--online] [--prepare-model NAME --model-dir DIR] [-m model] [-l lang] [-o outdir] [audio_file]
Options:
@ -34,23 +34,23 @@ log() {
}
detect_pkg_mgr() {
if command -v apt-get > /dev/null 2>&1; then
if command -v apt-get >/dev/null 2>&1; then
echo apt
return
fi
if command -v dnf > /dev/null 2>&1; then
if command -v dnf >/dev/null 2>&1; then
echo dnf
return
fi
if command -v yum > /dev/null 2>&1; then
if command -v yum >/dev/null 2>&1; then
echo yum
return
fi
if command -v pacman > /dev/null 2>&1; then
if command -v pacman >/dev/null 2>&1; then
echo pacman
return
fi
if command -v zypper > /dev/null 2>&1; then
if command -v zypper >/dev/null 2>&1; then
echo zypper
return
fi
@ -66,17 +66,21 @@ has_libcublas12() {
/usr/local/cuda-12*/lib64 \
/opt/cuda/lib64 \
/opt/cuda/targets/x86_64-linux/lib; do
[[ -e "$d/libcublas.so.12" ]] && return 0 || true
if [[ -e "$d/libcublas.so.12" ]]; then
return 0
fi
done
# 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)"
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
[[ -e "$d/libcublas.so.12" ]] && return 0 || true
if [[ -e "$d/libcublas.so.12" ]]; then
return 0
fi
done
fi
fi
@ -94,7 +98,7 @@ ensure_cuda_runtime() {
if has_libcublas12; then
return 0
fi
if ! command -v sudo > /dev/null 2>&1; then
if ! command -v sudo >/dev/null 2>&1; then
log "sudo not found; skipping CUDA runtime install attempt."
else
log "CUDA cuBLAS 12 not found; attempting to install CUDA runtime (manager: $mgr)"
@ -125,7 +129,7 @@ ensure_cuda_runtime() {
}
install_system_deps() {
have_cmd() { command -v "$1" > /dev/null 2>&1; }
have_cmd() { command -v "$1" >/dev/null 2>&1; }
local need_ffmpeg=0 need_espeak=0
have_cmd ffmpeg || need_ffmpeg=1
have_cmd espeak-ng || need_espeak=1
@ -153,7 +157,7 @@ install_system_deps() {
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
if ! command -v sudo >/dev/null 2>&1; then
log "sudo not found; skipping system package installation attempt."
return 0
fi
@ -230,13 +234,13 @@ install_python_deps() {
export PIP_DEFAULT_TIMEOUT=${PIP_DEFAULT_TIMEOUT:-20}
if [[ $OFFLINE -eq 1 ]]; then
# Offline: do not install, just verify modules
if ! python -c 'import faster_whisper' > /dev/null 2>&1; then
if ! python -c 'import faster_whisper' >/dev/null 2>&1; then
echo "Python dependency 'faster_whisper' not found in offline mode. Run with --online to install." >&2
exit 7
fi
# If diarization requested offline, check for its deps too (warn-only)
if [[ ${FW_DIARIZE:-} == "1" ]]; then
python - << 'PY' || true
python - <<'PY' || true
try:
import soundfile, speechbrain, torch # noqa: F401
except Exception as e:
@ -247,7 +251,7 @@ PY
fi
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
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 ||
log "Warning: could not reach cu12 wheel index; will proceed with available ctranslate2"
@ -266,7 +270,7 @@ PY
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'
python - <<'PY'
import sys
print(f"[PY] Python {sys.version.split()[0]} dependencies installed.")
PY
@ -282,14 +286,14 @@ ensure_runner() {
generate_test_audio() {
local tmpwav
tmpwav="${PROJECT_DIR}/test_fw.wav"
if command -v espeak-ng > /dev/null 2>&1; then
if command -v espeak-ng >/dev/null 2>&1; then
log "Generating test audio via espeak-ng -> $tmpwav" >&2
espeak-ng -w "$tmpwav" "This is a quick test of faster whisper transcription." > /dev/null 2>&1 || true
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
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
@ -299,7 +303,7 @@ generate_test_audio() {
# Final fallback: tone via ffmpeg
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
ffmpeg -f lavfi -i sine=frequency=1000:duration=3 -ar 16000 -ac 1 -f wav -y "$tmpwav" >/dev/null 2>&1 || true
fi
echo "$tmpwav"
}
@ -311,7 +315,7 @@ prepare_model() {
# shellcheck disable=SC1091
source "$VENV_DIR/bin/activate"
log "Preparing model '$name' into $MODEL_DIR"
python - << PY
python - <<PY
import sys, os
from faster_whisper import WhisperModel
name = os.environ.get('FW_PREPARE_NAME')
@ -402,7 +406,7 @@ main() {
# Detect NVIDIA GPU and enforce CUDA if present
has_nvidia=0
if command -v nvidia-smi > /dev/null 2>&1 && nvidia-smi -L > /dev/null 2>&1; then
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then
has_nvidia=1
fi
install_python_deps "$has_nvidia"
@ -441,7 +445,7 @@ 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)"
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