From c1cd20807294c75aaf2f757c270193d1aa4a5378 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 23 Apr 2026 14:34:57 -0700 Subject: [PATCH 1/2] feat(mount-disks): mount auto-provisioned disks by UUID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Linux device names (/dev/sd*) aren't stable — adding a controller, re-seating a drive, or a kernel upgrade can reorder them on reboot, at which point fstab entries keyed by device path mount the wrong disk (or none). Switch the fstab line to a UUID= entry so the mount follows the filesystem, not the bus position. - Probe the UUID with blkid after mkfs, retrying a few times so udev has a chance to settle. Abort the deploy on failure rather than writing a broken fstab entry. - Script now writes "UUID= ..." and mounts by mount point so /etc/fstab is the single source of truth. - Pre-existing fstab entries are untouched; the change only affects new disks provisioned by --mount-disks. - Also quote the SUDO_PASS interpolation in the script invocation so passwords containing ' are handled correctly. Credit: cherry-picked from the UUID-mount portion of #4. --- pkg/cluster/manager/deploy_volume_server.go | 50 +++++++++++++++++++-- scripts/prepare_disk.sh | 9 ++-- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/pkg/cluster/manager/deploy_volume_server.go b/pkg/cluster/manager/deploy_volume_server.go index d2353c9..eef0e72 100644 --- a/pkg/cluster/manager/deploy_volume_server.go +++ b/pkg/cluster/manager/deploy_volume_server.go @@ -3,11 +3,13 @@ package manager import ( "bytes" "fmt" + "strings" + "time" + "github.com/seaweedfs/seaweed-up/pkg/cluster/spec" "github.com/seaweedfs/seaweed-up/pkg/disks" "github.com/seaweedfs/seaweed-up/pkg/operator" "github.com/seaweedfs/seaweed-up/scripts" - "strings" ) func (m *Manager) DeployVolumeServer(masters []string, volumeServerSpec *spec.VolumeServerSpec, index int) error { @@ -119,7 +121,8 @@ func (m *Manager) prepareUnmountedDisks(op operator.CommandOperator) error { } fmt.Printf("disks2: %+v\n", disks) - // format disk if no fstype + // format disk if no fstype, then resolve the resulting UUID so the fstab + // entry written below can mount by UUID instead of by device path. for _, dev := range disks { if dev.FilesystemType == "" { info("mkfs " + dev.Path) @@ -127,6 +130,13 @@ func (m *Manager) prepareUnmountedDisks(op operator.CommandOperator) error { return fmt.Errorf("create file system on %s: %v", dev.Path, err) } } + if dev.UUID == "" { + uuid, err := m.probeDiskUUID(op, dev.Path) + if err != nil { + return fmt.Errorf("resolve UUID for %s: %v", dev.Path, err) + } + dev.UUID = uuid + } } // mount them @@ -148,6 +158,7 @@ func (m *Manager) prepareUnmountedDisks(op operator.CommandOperator) error { data := map[string]interface{}{ "DevicePath": dev.Path, + "DeviceUUID": dev.UUID, "MountPoint": targetMountPoint, } prepareScript, err := scripts.RenderScript("prepare_disk.sh", data) @@ -160,8 +171,8 @@ func (m *Manager) prepareUnmountedDisks(op operator.CommandOperator) error { return fmt.Errorf("error received during upload mount script: %s", err) } - info("mount " + dev.DeviceName + "...") - err = op.Execute(fmt.Sprintf("cat /tmp/mount_%s.sh | SUDO_PASS=\"%s\" sh -\n", dev.DeviceName, m.sudoPass)) + info(fmt.Sprintf("mount %s (UUID=%s) at %s", dev.DeviceName, dev.UUID, targetMountPoint)) + err = op.Execute(fmt.Sprintf("cat /tmp/mount_%s.sh | SUDO_PASS=%s sh -\n", dev.DeviceName, shellSingleQuote(m.sudoPass))) if err != nil { return fmt.Errorf("error received during mount: %s", err) } @@ -171,3 +182,34 @@ func (m *Manager) prepareUnmountedDisks(op operator.CommandOperator) error { return nil } + +// probeDiskUUID reads the filesystem UUID of path via blkid. After mkfs the +// superblock is written but udev may not yet have re-read it, so we let it +// settle and retry a few times before giving up. Returning an error here +// aborts the deploy rather than writing a broken fstab entry that would leave +// the host unable to boot. +func (m *Manager) probeDiskUUID(op operator.CommandOperator, path string) (string, error) { + // Best-effort settle. Ignore errors — `udevadm` is missing on some + // minimal images and that's OK; the retry loop below will still pick + // up the UUID once it's available. + _ = m.sudo(op, "command -v udevadm >/dev/null 2>&1 && udevadm settle || true") + + const attempts = 5 + var lastErr error + for i := 0; i < attempts; i++ { + out, err := op.Output(fmt.Sprintf("blkid -s UUID -o value %s", shellSingleQuote(path))) + if err == nil { + uuid := strings.TrimSpace(string(out)) + if uuid != "" { + return uuid, nil + } + } else { + lastErr = err + } + time.Sleep(500 * time.Millisecond) + } + if lastErr != nil { + return "", fmt.Errorf("blkid returned no UUID after %d attempts: %v", attempts, lastErr) + } + return "", fmt.Errorf("blkid returned no UUID after %d attempts", attempts) +} diff --git a/scripts/prepare_disk.sh b/scripts/prepare_disk.sh index 25dee89..95d675a 100644 --- a/scripts/prepare_disk.sh +++ b/scripts/prepare_disk.sh @@ -27,16 +27,17 @@ setup_env() { MOUNT_POINT={{.MountPoint}} DEVICE_PATH={{.DevicePath}} + DEVICE_UUID={{.DeviceUUID}} } setup_mount() { info "Setup Mount Point" $SUDO mkdir -p -m 755 ${MOUNT_POINT} - info "add ${DEVICE_PATH} ${MOUNT_POINT} to fstab" - echo "${DEVICE_PATH} ${MOUNT_POINT} ext4 noatime 0 2" | $SUDO tee -a /etc/fstab - info "mount ${DEVICE_PATH} ${MOUNT_POINT}" - $SUDO mount ${DEVICE_PATH} ${MOUNT_POINT} + info "add UUID=${DEVICE_UUID} (${DEVICE_PATH}) ${MOUNT_POINT} to fstab" + echo "UUID=${DEVICE_UUID} ${MOUNT_POINT} ext4 noatime 0 2" | $SUDO tee -a /etc/fstab + info "mount ${DEVICE_PATH} (UUID=${DEVICE_UUID}) at ${MOUNT_POINT}" + $SUDO mount ${MOUNT_POINT} return 0 } From e121f7c19e2c83100dd3c1341bbd28e1f54c02b2 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 23 Apr 2026 15:01:00 -0700 Subject: [PATCH 2/2] fix(review): sudo blkid; make prepare_disk.sh idempotent - blkid -p probes the superblock directly instead of reading the cache, but it needs root on block devices. Wrap with sudo -S when sudoPass is set, mirroring m.sudo(). - prepare_disk.sh now guards both the fstab append and the mount so a re-run after partial failure (disk formatted, fstab written, but mount errored) doesn't duplicate the fstab line or fail with "already mounted". --- pkg/cluster/manager/deploy_volume_server.go | 17 +++++++++++++---- scripts/prepare_disk.sh | 16 ++++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/pkg/cluster/manager/deploy_volume_server.go b/pkg/cluster/manager/deploy_volume_server.go index eef0e72..2018795 100644 --- a/pkg/cluster/manager/deploy_volume_server.go +++ b/pkg/cluster/manager/deploy_volume_server.go @@ -185,19 +185,28 @@ func (m *Manager) prepareUnmountedDisks(op operator.CommandOperator) error { // probeDiskUUID reads the filesystem UUID of path via blkid. After mkfs the // superblock is written but udev may not yet have re-read it, so we let it -// settle and retry a few times before giving up. Returning an error here -// aborts the deploy rather than writing a broken fstab entry that would leave -// the host unable to boot. +// settle and retry a few times before giving up. blkid needs root to read +// raw block devices on most distros (and -p always needs root), so we wrap +// it the same way m.sudo does. Returning an error here aborts the deploy +// rather than writing a broken fstab entry that would leave the host unable +// to boot. func (m *Manager) probeDiskUUID(op operator.CommandOperator, path string) (string, error) { // Best-effort settle. Ignore errors — `udevadm` is missing on some // minimal images and that's OK; the retry loop below will still pick // up the UUID once it's available. _ = m.sudo(op, "command -v udevadm >/dev/null 2>&1 && udevadm settle || true") + // -p bypasses the blkid cache and re-probes the superblock directly, + // which is what we want right after mkfs. + probeCmd := fmt.Sprintf("blkid -p -s UUID -o value %s", shellSingleQuote(path)) + if m.sudoPass != "" { + probeCmd = fmt.Sprintf("echo %s | sudo -S %s", shellSingleQuote(m.sudoPass), probeCmd) + } + const attempts = 5 var lastErr error for i := 0; i < attempts; i++ { - out, err := op.Output(fmt.Sprintf("blkid -s UUID -o value %s", shellSingleQuote(path))) + out, err := op.Output(probeCmd) if err == nil { uuid := strings.TrimSpace(string(out)) if uuid != "" { diff --git a/scripts/prepare_disk.sh b/scripts/prepare_disk.sh index 95d675a..404e6f0 100644 --- a/scripts/prepare_disk.sh +++ b/scripts/prepare_disk.sh @@ -34,10 +34,18 @@ setup_mount() { info "Setup Mount Point" $SUDO mkdir -p -m 755 ${MOUNT_POINT} - info "add UUID=${DEVICE_UUID} (${DEVICE_PATH}) ${MOUNT_POINT} to fstab" - echo "UUID=${DEVICE_UUID} ${MOUNT_POINT} ext4 noatime 0 2" | $SUDO tee -a /etc/fstab - info "mount ${DEVICE_PATH} (UUID=${DEVICE_UUID}) at ${MOUNT_POINT}" - $SUDO mount ${MOUNT_POINT} + if grep -qE "^UUID=${DEVICE_UUID}[[:space:]]" /etc/fstab; then + info "UUID=${DEVICE_UUID} already in fstab; skipping append" + else + info "add UUID=${DEVICE_UUID} (${DEVICE_PATH}) ${MOUNT_POINT} to fstab" + echo "UUID=${DEVICE_UUID} ${MOUNT_POINT} ext4 noatime 0 2" | $SUDO tee -a /etc/fstab + fi + if mountpoint -q "${MOUNT_POINT}"; then + info "${MOUNT_POINT} already mounted; skipping mount" + else + info "mount ${DEVICE_PATH} (UUID=${DEVICE_UUID}) at ${MOUNT_POINT}" + $SUDO mount ${MOUNT_POINT} + fi return 0 }