gitea-backup-pull: skip unmount (no error) if another process is busy on WSD2L840

rc.unassigned umount always does a lazy unmount and reports success
unconditionally, even with an open cwd on the mountpoint - it never
actually refuses on its own. Check with fuser -m first instead, and
just leave the disk mounted if anything is using it.
This commit is contained in:
2026-07-26 17:56:27 +02:00
parent 5b286644db
commit 495613018c
3 changed files with 37 additions and 8 deletions
+17 -3
View File
@@ -490,9 +490,23 @@ The SSH keypair needed no special handling: Unraid already auto-mirrors
testing this: the script itself `cd`s into the mounted directory, so testing this: the script itself `cd`s into the mounted directory, so
without explicitly `cd`ing back out first, the unmount would fail with without explicitly `cd`ing back out first, the unmount would fail with
"target is busy" (the shell's own working directory still being on the "target is busy" (the shell's own working directory still being on the
device it's trying to unmount). Verified both directions: already device it's trying to unmount).
mounted → stays mounted after; not mounted → gets mounted, used, and - **Also checks for any *other* process still using the disk before
unmounted again, with no "busy" error. unmounting, and gives up quietly (no error) if it finds one.** This
turned out to be necessary rather than just nice-to-have: the
Unassigned Devices plugin's own `rc.unassigned umount` always does a
*lazy* (`-l`) unmount and reports "success" unconditionally — verified
live that it does **not** refuse, error, or even notice when another
process still has the mountpoint open (a single-device call is never
the "force" path that would otherwise kill blocking processes first).
So the script runs its own `fuser -m` check first and simply skips
calling `rc.unassigned umount` at all if anything turns up, logging a
plain informational line rather than an error and leaving the disk
mounted for next time. Verified all three states end-to-end: already
mounted before the run → untouched either way; not mounted, nothing
else using it → mounted, used, and cleanly unmounted again; not
mounted, but something else `cd`s into it mid-run → mounted, used, and
correctly left mounted with no error.
**Restore path, if ever needed**: manual and deliberate — stop the target **Restore path, if ever needed**: manual and deliberate — stop the target
Gitea container, unpack the chosen dump's DB/repos/etc. into `/data` per Gitea container, unpack the chosen dump's DB/repos/etc. into `/data` per
+1 -1
View File
@@ -39,7 +39,7 @@ of every script here, so it's worth stating plainly before anything else:
| File here | Deployed path | Purpose | | File here | Deployed path | Purpose |
|---|---|---| |---|---|---|
| `user-scripts/gitea-backup-pull/script` | `/boot/config/plugins/user.scripts/scripts/gitea-backup-pull/script` | Pulls new `gitea-dump` archives from `alpha` via the restricted `giteabackup` SFTP account, verifies each one's sha256 checksum, deletes both files on `alpha` only after a confirmed match, prunes its own local retention (30). Resolves and mounts the `WSD2L840` Unassigned Device itself first, and unmounts it again afterward — but only if this run is the one that mounted it, never disturbing a mount it found already in place. See the script's own header comment for the full reasoning. | | `user-scripts/gitea-backup-pull/script` | `/boot/config/plugins/user.scripts/scripts/gitea-backup-pull/script` | Pulls new `gitea-dump` archives from `alpha` via the restricted `giteabackup` SFTP account, verifies each one's sha256 checksum, deletes both files on `alpha` only after a confirmed match, prunes its own local retention (30). Resolves and mounts the `WSD2L840` Unassigned Device itself first, and unmounts it again afterward — but only if this run is the one that mounted it, never disturbing a mount it found already in place, and only if nothing else is using the disk at that moment (checked via `fuser -m`; `rc.unassigned umount`'s own lazy unmount never refuses on its own, so this script checks first and just leaves it mounted, no error, if something else is still on it). See the script's own header comment for the full reasoning. |
| `user-scripts/gitea-backup-pull/description` | `.../gitea-backup-pull/description` | Shown in the User Scripts UI. | | `user-scripts/gitea-backup-pull/description` | `.../gitea-backup-pull/description` | Shown in the User Scripts UI. |
| `user-scripts/gitea-backup-pull-onboot/script` | `.../gitea-backup-pull-onboot/script` | One-line wrapper: `bash`-invokes the script above. Exists only because `schedule.json` supports one schedule per script *path* — this is how the "also run once at every restart" requirement was added alongside the existing daily `03:30` schedule without disturbing it. | | `user-scripts/gitea-backup-pull-onboot/script` | `.../gitea-backup-pull-onboot/script` | One-line wrapper: `bash`-invokes the script above. Exists only because `schedule.json` supports one schedule per script *path* — this is how the "also run once at every restart" requirement was added alongside the existing daily `03:30` schedule without disturbing it. |
| `user-scripts/gitea-backup-pull-onboot/description` | `.../gitea-backup-pull-onboot/description` | Shown in the User Scripts UI. | | `user-scripts/gitea-backup-pull-onboot/description` | `.../gitea-backup-pull-onboot/description` | Shown in the User Scripts UI. |
@@ -56,11 +56,26 @@ DEL_BATCH=$(mktemp)
cleanup() { cleanup() {
rm -f "$GET_BATCH" "$DEL_BATCH" rm -f "$GET_BATCH" "$DEL_BATCH"
if [ "$UD_ALREADY_MOUNTED" = false ]; then if [ "$UD_ALREADY_MOUNTED" = false ]; then
# cd out first - if the shell's own cwd is still on the device # cd out first - our own cwd on the device would otherwise
# (it is, after the "cd $LOCAL_DIR" below), umount fails with # make the fuser check below always find "something" using it
# "target is busy"
cd / || true cd / || true
/usr/local/sbin/rc.unassigned umount "$UD_DEV" >/dev/null 2>&1
# rc.unassigned's umount always does a lazy (-l) unmount and
# reports "success" no matter what - it does NOT refuse or
# error out just because another process still has the
# mountpoint open (verified live: a process with its cwd on
# the device does not stop it, and does not get killed
# either, unless this were a forced "stop array"-style
# unmount, which a single-device call never is). So the only
# way to actually detect and respect another user of the
# disk is to check first, ourselves, and simply not attempt
# the unmount at all if anything is found - no ERROR, just
# leave it mounted for next time.
if fuser -m "$UD_MOUNTPOINT" >/dev/null 2>&1; then
echo "$(date -Is) $UD_MOUNTPOINT still in use by another process - leaving it mounted"
else
/usr/local/sbin/rc.unassigned umount "$UD_DEV" >/dev/null 2>&1
fi
fi fi
} }
trap cleanup EXIT trap cleanup EXIT