Ctrl K
ComunidadGuías…

simple_nas/app/mount_helper.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/sh
# mount_helper.sh
# Privilegierter Daemon für Mount-Operationen, kommuniziert über FIFO

FIFO="/tmp/mount_cmd"
RESULT="/tmp/mount_result"

# Aufräumen
rm -f "$FIFO" "$RESULT"

# FIFO anlegen
mkfifo "$FIFO" || { echo "FEHLER: mkfifo gescheitert"; exit 1; }

echo "[mount_helper] Bereit auf $FIFO"
echo "[mount_helper] Capabilities: $(cat /proc/self/status 2>/dev/null | grep Cap || echo 'n/a')"

# Wrapper: try util-linux mount first; if fsconfig() is blocked (HA-OS seccomp),
# fall back to busybox mount which uses the old mount(2) syscall.
do_mount() {
    local _OUT _RC
    _OUT=$(mount "$@" 2>&1)
    _RC=$?
    if [ $_RC -ne 0 ] && echo "$_OUT" | grep -q "fsconfig()"; then
        echo "[mount_helper] fsconfig() blocked — retrying with busybox mount"
        _OUT=$(busybox mount "$@" 2>&1)
        _RC=$?
    fi
    echo "$_OUT"
    return $_RC
}

# Detect the classic "no CAP_SYS_ADMIN" symptom. On Home Assistant this means
# Protection Mode is still ENABLED — which makes the Supervisor ignore the
# add-on's full_access / privileged / apparmor:false settings, so mount(2)
# returns EPERM.
is_perm_error() {
    echo "$1" | grep -qiE "permission denied|operation not permitted|are you root"
}

# Mount with a specific FS type. For NTFS, prefer the in-kernel ntfs3 driver
# (Linux 5.15+, no FUSE required) and fall back to ntfs-3g (FUSE) only if the
# kernel driver isn't available. HA-OS add-on containers expose no /dev/fuse,
# so ntfs-3g fails there with "fuse device is missing, try 'modprobe fuse'".
mount_fs() {
    _FS="$1"; shift
    case "$_FS" in
        ntfs|ntfs-3g|ntfs3|fuseblk)
            echo "[mount_helper] NTFS: trying kernel driver ntfs3 first"
            _OUT=$(do_mount -t ntfs3 "$@" 2>&1)
            _RC=$?
            if [ $_RC -ne 0 ]; then
                echo "[mount_helper] ntfs3 failed ($_OUT) — falling back to ntfs-3g (FUSE)"
                _OUT=$(do_mount -t ntfs-3g "$@" 2>&1)
                _RC=$?
            fi
            ;;
        ext2|ext3|ext4)
            # noatime: no metadata write on every read.
            # errors=remount-ro: on a filesystem error go read-only instead
            # of silently writing on (the ext4 default is errors=continue).
            _OUT=$(do_mount -t "$_FS" -o noatime,errors=remount-ro "$@" 2>&1)
            _RC=$?
            ;;
        *)
            _OUT=$(do_mount -t "$_FS" "$@" 2>&1)
            _RC=$?
            ;;
    esac
    echo "$_OUT"
    return $_RC
}

# Keep the FIFO open persistently on fd 3 so there's no moment between
# command-iterations where the FIFO has no reader. Without this, the Python
# side (which opens with O_NONBLOCK) hits ENXIO right after each command.
# We also open fd 4 as a dummy writer so `read` never sees EOF when the
# client side closes — that would otherwise drop us out of the loop.
exec 3<"$FIFO" 4>"$FIFO"

while true; do
    if read -r line <&3; then
        ACTION=$(echo "$line" | cut -d'|' -f1)
        ARG1=$(  echo "$line" | cut -d'|' -f2)
        ARG2=$(  echo "$line" | cut -d'|' -f3)
        ARG3=$(  echo "$line" | cut -d'|' -f4)

        case "$ACTION" in
            MOUNT)
                DEVICE="$ARG1"
                MOUNTPOINT="$ARG2"
                FSTYPE="$ARG3"

                echo "[mount_helper] MOUNT: $DEVICE -> $MOUNTPOINT (fs=$FSTYPE)"
                mkdir -p "$MOUNTPOINT" 2>/dev/null

                # Verify device exists
                if [ ! -b "$DEVICE" ]; then
                    echo "[mount_helper] FEHLER: $DEVICE ist kein Block-Device"
                    printf '1|%s ist kein Block-Device oder nicht gefunden\n' "$DEVICE" > "$RESULT"
                    continue
                fi

                # Try mount
                if [ -n "$FSTYPE" ] && [ "$FSTYPE" != "auto" ]; then
                    echo "[mount_helper] Trying: mount -t $FSTYPE $DEVICE $MOUNTPOINT"
                    OUT=$(mount_fs "$FSTYPE" "$DEVICE" "$MOUNTPOINT" 2>&1)
                    RC=$?
                else
                    # AUTO: blkid/lsblk sometimes return nothing for by-id
                    # symlinks even though mount -t works fine. Resolve the
                    # symlink first, then escalate through several detectors,
                    # and as a last resort try common FS types directly.
                    REAL_DEV=$(readlink -f "$DEVICE" 2>/dev/null)
                    [ -z "$REAL_DEV" ] && REAL_DEV="$DEVICE"
                    echo "[mount_helper] auto: real device = $REAL_DEV"

                    DETECTED=$(blkid -o value -s TYPE "$REAL_DEV" 2>/dev/null | head -1)
                    [ -z "$DETECTED" ] && DETECTED=$(blkid -p -o value -s TYPE "$REAL_DEV" 2>/dev/null | head -1)
                    [ -z "$DETECTED" ] && DETECTED=$(lsblk -no FSTYPE "$REAL_DEV" 2>/dev/null | head -1 | tr -d ' ')
                    # file -s reads the first bytes directly — works when blkid can't
                    if [ -z "$DETECTED" ] && command -v file >/dev/null 2>&1; then
                        FILE_OUT=$(file -sL "$REAL_DEV" 2>/dev/null)
                        case "$FILE_OUT" in
                            *ext4*)  DETECTED=ext4 ;;
                            *ext3*)  DETECTED=ext3 ;;
                            *ext2*)  DETECTED=ext2 ;;
                            *NTFS*)  DETECTED=ntfs ;;
                            *FAT*)   DETECTED=vfat ;;
                            *exFAT*) DETECTED=exfat ;;
                            *BTRFS*|*btrfs*) DETECTED=btrfs ;;
                            *XFS*)   DETECTED=xfs ;;
                        esac
                        [ -n "$DETECTED" ] && echo "[mount_helper] auto: file -s detected '$DETECTED'"
                    fi
                    RC=1
                    if [ -n "$DETECTED" ]; then
                        echo "[mount_helper] auto: detected '$DETECTED'"
                        echo "[mount_helper] Trying: mount -t $DETECTED $DEVICE $MOUNTPOINT"
                        OUT=$(mount_fs "$DETECTED" "$DEVICE" "$MOUNTPOINT" 2>&1)
                        RC=$?
                    fi
                    # Last resort: brute-force the common Linux/Windows types.
                    # mount -t <wrong> on a USB blockdev fails fast and cleanly,
                    # so we can iterate without harm.
                    if [ $RC -ne 0 ]; then
                        if [ -n "$DETECTED" ]; then
                            echo "[mount_helper] auto: '$DETECTED' failed ($OUT) — trying common FS types"
                        else
                            echo "[mount_helper] auto: no FS detected — brute-force common types"
                        fi
                        for TRY_FS in ext4 ext3 ext2 ntfs3 vfat exfat btrfs xfs; do
                            OUT=$(mount_fs "$TRY_FS" "$DEVICE" "$MOUNTPOINT" 2>&1)
                            RC=$?
                            if [ $RC -eq 0 ]; then
                                echo "[mount_helper] auto: brute-force succeeded with -t $TRY_FS"
                                DETECTED="$TRY_FS"
                                break
                            fi
                        done
                    fi
                    # Final fallback: bare mount (relies on kernel auto-probe)
                    if [ $RC -ne 0 ]; then
                        echo "[mount_helper] auto: brute-force failed — last bare mount attempt"
                        OUT=$(do_mount "$DEVICE" "$MOUNTPOINT" 2>&1)
                        RC=$?
                    fi
                fi

                # If failed, log debug info
                if [ $RC -ne 0 ]; then
                    echo "[mount_helper] Mount fehlgeschlagen (rc=$RC): $OUT"
                    echo "[mount_helper] DEBUG: id=$(id)"
                    echo "[mount_helper] DEBUG: ls -la $DEVICE = $(ls -la "$DEVICE" 2>&1)"
                    echo "[mount_helper] DEBUG: blkid $DEVICE = $(blkid "$DEVICE" 2>&1)"
                    echo "[mount_helper] DEBUG: mountinfo (last 5):"
                    tail -5 /proc/self/mountinfo 2>/dev/null
                    # Permission error → almost always Protection Mode still on
                    if is_perm_error "$OUT"; then
                        CAPEFF=$(grep CapEff /proc/self/status 2>/dev/null | awk '{print $2}')
                        echo "[mount_helper] ====================================================="
                        echo "[mount_helper] PERMISSION ERROR — this is NOT a filesystem problem."
                        echo "[mount_helper] CapEff=$CAPEFF (all-zero = NO capabilities granted)"
                        echo "[mount_helper] The add-on has no CAP_SYS_ADMIN, so mount() returns EPERM."
                        echo "[mount_helper] CAUSE: 'Protection mode' is still ENABLED for this add-on."
                        echo "[mount_helper] FIX:  Open the add-on page -> Info tab ->"
                        echo "[mount_helper]       turn OFF the 'Protection mode' toggle -> Restart."
                        echo "[mount_helper] ====================================================="
                        OUT="Permission denied (no CAP_SYS_ADMIN). Disable 'Protection mode' in the add-on Info tab and restart the add-on. (raw: $OUT)"
                    fi
                fi

                printf '%s|%s\n' "$RC" "$OUT" > "$RESULT"
                echo "[mount_helper] MOUNT result: rc=$RC"
                ;;
            UMOUNT)
                echo "[mount_helper] UMOUNT: $ARG1"
                OUT=$(umount "$ARG1" 2>&1)
                RC=$?
                printf '%s|%s\n' "$RC" "$OUT" > "$RESULT"
                echo "[mount_helper] UMOUNT result: rc=$RC"
                ;;
            UMOUNT_LAZY)
                echo "[mount_helper] UMOUNT_LAZY: $ARG1"
                OUT=$(umount -l "$ARG1" 2>&1)
                RC=$?
                printf '%s|%s\n' "$RC" "$OUT" > "$RESULT"
                echo "[mount_helper] UMOUNT_LAZY result: rc=$RC"
                ;;
            FUSER)
                # Show what's holding the mount busy
                OUT=$(fuser -mv "$ARG1" 2>&1; lsof +D "$ARG1" 2>&1 | head -20)
                printf '0|%s\n' "$OUT" > "$RESULT"
                ;;
            MKDIR)
                mkdir -p "$ARG1" 2>/dev/null
                printf '0|\n' > "$RESULT"
                ;;
            PARTLIST)
                # Machine-readable partition layout incl. free-space gaps.
                # ARG1 = /dev/sdX (whole disk)
                DISK="$ARG1"
                OUT=$(parted -m -s "$DISK" unit MiB print free 2>&1)
                RC=$?
                # Newlines break our FIFO line protocol → encode as \\n
                ENC=$(printf '%s' "$OUT" | sed ':a;N;$!ba;s/\n/\\n/g')
                printf '%s|%s\n' "$RC" "$ENC" > "$RESULT"
                echo "[mount_helper] PARTLIST $DISK rc=$RC"
                ;;
            PARTMKLABEL)
                # Initialize new partition table. ARG1 = /dev/sdX, ARG2 = gpt|msdos
                DISK="$ARG1"; LBL="$ARG2"
                echo "[mount_helper] PARTMKLABEL $DISK $LBL"
                OUT=$(parted -s "$DISK" mklabel "$LBL" 2>&1)
                RC=$?
                partprobe "$DISK" 2>/dev/null
                printf '%s|%s\n' "$RC" "$OUT" > "$RESULT"
                ;;
            PARTADD)
                # Create partition. ARG1 = /dev/sdX, ARG2 = START (MiB),
                # ARG3 = END (MiB or "100%")
                DISK="$ARG1"; PSTART="$ARG2"; PEND="$ARG3"
                echo "[mount_helper] PARTADD $DISK ${PSTART}MiB → $PEND"
                OUT=$(parted -s -a optimal "$DISK" mkpart primary "${PSTART}MiB" "$PEND" 2>&1)
                RC=$?
                partprobe "$DISK" 2>/dev/null
                # parted itself sometimes returns 0 even when partprobe needs a moment
                sleep 1
                printf '%s|%s\n' "$RC" "$OUT" > "$RESULT"
                ;;
            PARTRM)
                # Remove partition. ARG1 = /dev/sdX, ARG2 = partition number
                DISK="$ARG1"; PNUM="$ARG2"
                echo "[mount_helper] PARTRM $DISK $PNUM"
                OUT=$(parted -s "$DISK" rm "$PNUM" 2>&1)
                RC=$?
                partprobe "$DISK" 2>/dev/null
                printf '%s|%s\n' "$RC" "$OUT" > "$RESULT"
                ;;
            MKFS)
                # Format partition. ARG1 = /dev/sdXN, ARG2 = fstype, ARG3 = label
                PART="$ARG1"; FS="$ARG2"; LABEL="$ARG3"
                echo "[mount_helper] MKFS $PART -t $FS -L '$LABEL'"
                if [ ! -b "$PART" ]; then
                    printf '1|%s ist kein Block-Device\n' "$PART" > "$RESULT"
                    continue
                fi
                # Refuse if currently mounted
                if grep -q "^$PART " /proc/mounts 2>/dev/null; then
                    printf '1|%s ist eingehängt — bitte zuerst aushängen\n' "$PART" > "$RESULT"
                    continue
                fi
                case "$FS" in
                    ext4)
                        OUT=$(mkfs.ext4 -F -L "$LABEL" "$PART" 2>&1) ;;
                    ext3)
                        OUT=$(mkfs.ext3 -F -L "$LABEL" "$PART" 2>&1) ;;
                    ext2)
                        OUT=$(mkfs.ext2 -F -L "$LABEL" "$PART" 2>&1) ;;
                    exfat)
                        OUT=$(mkfs.exfat -n "$LABEL" "$PART" 2>&1) ;;
                    vfat|fat32)
                        OUT=$(mkfs.vfat -F 32 -n "$LABEL" "$PART" 2>&1) ;;
                    ntfs)
                        OUT=$(mkfs.ntfs -f -L "$LABEL" "$PART" 2>&1) ;;
                    *)
                        OUT="Unsupported filesystem: $FS"
                        RC=1
                        printf '%s|%s\n' "$RC" "$OUT" > "$RESULT"
                        continue
                        ;;
                esac
                RC=$?
                # New filesystem signature isn't always picked up immediately;
                # ask kernel to re-read & udev to refresh symlinks
                partprobe "$(echo "$PART" | sed 's/[0-9]*$//')" 2>/dev/null
                printf '%s|%s\n' "$RC" "$OUT" > "$RESULT"
                echo "[mount_helper] MKFS rc=$RC"
                ;;
            PARTPROBE)
                partprobe "$ARG1" 2>/dev/null
                printf '0|\n' > "$RESULT"
                ;;
            *)
                printf '1|Unbekannter Befehl: %s\n' "$ACTION" > "$RESULT"
                ;;
        esac
    fi
done