]> ###2026.07.19b - Fixed the Simplified Chinese Settings tile title by merging the plugin-local menu catalog before Unraid builds menu panels. ###2026.07.19a - Moved the Settings tile to Settings > User Programs. - Added the Simplified Chinese tile name USB安全弹出, following the active Unraid language. - Added an English and Simplified Chinese Plugin Manager description with safe-unplug precautions. ###2026.07.18f - Changed the Unraid and Community Applications support link to the USB Guardian forum topic. ###2026.07.18e - Added a confirmed Clear logs action to Settings in English and Simplified Chinese. - Refused log deletion while a safe-eject job, transaction lock, adapter recovery state, or diagnostic writer is active. - Preserved the transaction lock while removing existing flat logs and transaction directories; new events continue logging normally. ###2026.07.18d - Adopted the YYYY.MM.DD-letter release naming scheme. - Published the static UD-row control workflow after successful Unraid 7.3.2 and UD 2025.11.18 user validation. ###0.1.0-beta6 - Fixed static controls being hidden when UD uses a non-umount role for a mounted, running, disabled, or partition-aggregated disk row. - Made the static entry point depend only on a canonical UD disk row and device identifier; all eligibility decisions remain click-time backend checks. ###0.1.0-beta5 - Added a static mounted-device control to every UD disk row; eligibility and safety requests now start only after a click. - Decorated UD 2025.11.18 disk HTML before its atomic tbody replacement so the control renders in the same frame as UD controls. - Removed the safe-eject control from table layout flow and reserved a fixed slot to prevent column movement during redraws. ###0.1.0-beta4 - Fixed the certified Unassigned Devices 2025.11.18 library loader so UD file-scope state remains globally visible. - Added strict support for Unraid 7.3.2 systems with separate /mnt/user0 and /mnt/user shfs processes. - Stopped unchanged device-list polling from replacing safe-eject controls and retained warning controls across transient list failures. - Added an English and Chinese Enable USB Guardian setting, enforced by both list and eject APIs. ###0.1.0-beta3 - Fixed integration with Unraid's global CSRF validation for plugin POST requests. - Added clear refresh guidance when Unraid rejects a stale page request. - Disabled embedded Git VCS metadata so release packages are reproducible across documentation-only commits. ###0.1.0-beta2 - Added Community Applications metadata and single-URL online installation. - Added a SHA-256 verified release-package download to the PLG manifest. ###0.1.0-beta1 - Initial guarded USB safe-eject beta for Unraid 7.2.4 and newer. &packageURL; a808e3eb71b255a4e4f30ef14511f188629efc16628e21bfe303237b7bd4f224 /dev/null || true)" [[ -n "${awk_bin}" && -x "${awk_bin}" && -r /proc/self/mountinfo ]] || return 1 "${awk_bin}" ' { separator = 0 for (i = 7; i <= NF; i++) if ($i == "-") { separator = i; break } if ($5 == "/boot") { count++ fstype = $(separator + 1) source = $(separator + 2) if (separator > 0 && $4 == "/" && $6 ~ /(^|,)rw(,|$)/ && (fstype == "vfat" || fstype == "msdos" || fstype == "fat") && source ~ /^\/dev\//) valid++ } protected = "/boot/config/plugins/usb.guardian" if ($5 != "/boot" && (index(protected "/", $5 "/") == 1 || index($5 "/", protected "/") == 1)) shadow++ } END { exit !(count == 1 && valid == 1 && shadow == 0) } ' /proc/self/mountinfo } if ! boot_flash_is_safe; then echo "USB Guardian install refused: /boot is not a single writable FAT block-device mount" exit 1 fi if [[ ! -f "${PACKAGE}" ]]; then echo "USB Guardian package is missing: ${PACKAGE}" exit 1 fi ACTUAL_SHA256=$(/usr/bin/sha256sum "${PACKAGE}" | /usr/bin/cut -d' ' -f1) if [[ "${ACTUAL_SHA256}" != "${EXPECTED_SHA256}" ]]; then echo "USB Guardian package checksum mismatch" exit 1 fi CONFIG_ROOT="/boot/config/plugins/usb.guardian" CONFIG_FILE="${CONFIG_ROOT}/usb.guardian.cfg" LOG_DIR="${CONFIG_ROOT}/logs" RUN_ROOT="/run/usb-guardian" JOB_DIR="${RUN_ROOT}/jobs" ADAPTER_STATE_DIR="${RUN_ROOT}/ud-adapter" TRANSACTION_DIR="${LOG_DIR}/transactions" LOCK_FILE="${LOG_DIR}/.transaction.lock" OLD_BINARY="/usr/local/emhttp/plugins/usb.guardian/bin/usb-guardian" /usr/bin/install -d -m 0700 "${CONFIG_ROOT}" "${LOG_DIR}" "${RUN_ROOT}" "${JOB_DIR}" "${ADAPTER_STATE_DIR}" # Give an installed worker one bounded chance to resolve a dead transaction before locking the upgrade. if [[ -x "${OLD_BINARY}" ]]; then if [[ ! -x /usr/bin/timeout ]]; then echo "USB Guardian upgrade refused: timeout is unavailable for bounded recovery" exit 1 fi if ! /usr/bin/timeout --signal=TERM --kill-after=5s 70s "${OLD_BINARY}" recover --config "${CONFIG_FILE}" --job-dir "${JOB_DIR}" --log-dir "${LOG_DIR}"; then echo "USB Guardian upgrade refused: the installed worker could not recover interrupted state" exit 1 fi fi FLOCK_BIN=$(command -v flock 2>/dev/null || true) if [[ -z "${FLOCK_BIN}" || ! -x "${FLOCK_BIN}" ]]; then echo "USB Guardian upgrade refused: flock is unavailable" exit 1 fi exec 9>>"${LOCK_FILE}" if ! "${FLOCK_BIN}" -n 9; then echo "USB Guardian upgrade refused: a transaction owns the persistent lock" exit 1 fi # All state checks are repeated under FD 9, which remains locked through upgrade and startup. if /usr/bin/pgrep -f '^/usr/local/emhttp/plugins/usb[.]guardian/bin/usb-guardian eject ' >/dev/null 2>&1; then echo "USB Guardian upgrade refused: a safe-eject worker is running" exit 1 fi shopt -s nullglob active_markers=("${TRANSACTION_DIR}"/*/active.json) adapter_states=("${ADAPTER_STATE_DIR}"/*.json) adapter_artifacts=("${ADAPTER_STATE_DIR}"/*) if (( ${#active_markers[@]} > 0 )); then echo "USB Guardian upgrade refused: an active transaction marker remains" exit 1 fi if (( ${#adapter_states[@]} > 0 )); then echo "USB Guardian upgrade refused: Unassigned Devices rollback state remains" exit 1 fi if (( ${#adapter_artifacts[@]} > 0 )); then echo "USB Guardian upgrade refused: unrecognized adapter recovery artifacts remain" exit 1 fi /sbin/upgradepkg --install-new "${PACKAGE}" /usr/local/emhttp/plugins/usb.guardian/event/started for old_package in "${CONFIG_ROOT}"/usb.guardian-*.txz; do if [[ "${old_package}" != "${PACKAGE}" ]]; then /bin/rm -f -- "${old_package}" fi done # FD 9 is intentionally held until the install script exits. ]]>