From 3786d6bae14f24fc5e0a17f3c4e609a7885342e0 Mon Sep 17 00:00:00 2001 From: 28allday Date: Wed, 3 Jun 2026 22:05:37 +0100 Subject: [PATCH] Fix media drive not mounting after reboot (stale fstab entries) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every fstab writer deduped on the exact line, so a stale entry for the same mount point (e.g. the UUID of a pool that was since recreated) survived forever. systemd-fstab-generator only creates one mount unit per mount point and the FIRST line wins — if that line is stale, the pool never mounts at boot, shows up as detached, and the user has to re-import it every reboot (manual `mount -a` walks fstab sequentially, so the import itself appeared to work). All four writers (add-drive, create-pool, import-pool, jellyfin bind mounts) now remove any existing entry for the target mount point before appending the fresh line — idempotent and self-healing. Also add a --version flag, stamped at build time via -ldflags "-X main.version=...". Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/tuistream/main.go | 11 +++++++++++ internal/drives/mount.go | 8 +++++++- internal/drives/pool.go | 8 +++++++- internal/drives/pool_import.go | 8 +++++++- internal/jellyfin/storage.go | 9 +++++++-- 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/cmd/tuistream/main.go b/cmd/tuistream/main.go index bb1fc7f..abe9f11 100644 --- a/cmd/tuistream/main.go +++ b/cmd/tuistream/main.go @@ -23,11 +23,22 @@ import ( "tuistream/internal/tui" ) +// version is stamped at build time via: +// +// -ldflags "-X main.version=v0.1.1" +var version = "dev" + func main() { readOnly := flag.Bool("read-only", false, "open the TUI without checking for root; only the inventory views work") + showVersion := flag.Bool("version", false, "print the version and exit") flag.Parse() + if *showVersion { + fmt.Println("tuistream", version) + return + } + if !*readOnly && os.Geteuid() != 0 { fmt.Fprintln(os.Stderr, "tuistream needs administrator rights to install Jellyfin, edit /etc/fstab, etc.") diff --git a/internal/drives/mount.go b/internal/drives/mount.go index 6cbd6c7..024311d 100644 --- a/internal/drives/mount.go +++ b/internal/drives/mount.go @@ -199,7 +199,13 @@ case "$FS" in esac LINE="UUID=${UUID} ${MP} ${FS} ${OPTS} 0 2" -grep -qF -- "$LINE" /etc/fstab || printf '%%s\n' "$LINE" >> /etc/fstab +# Drop any stale entries for this mount point first (e.g. the UUID of a drive +# that has since been reformatted). Duplicate mount points make +# systemd-fstab-generator keep only the FIRST line — if that one is stale the +# drive never mounts at boot. +awk -v mp="$MP" '$0 ~ /^[[:space:]]*#/ || $2 != mp' /etc/fstab > /etc/fstab.tuistream \ + && cat /etc/fstab.tuistream > /etc/fstab && rm -f /etc/fstab.tuistream +printf '%%s\n' "$LINE" >> /etc/fstab echo "fstab entry: $LINE" `, shellQuote(target), diff --git a/internal/drives/pool.go b/internal/drives/pool.go index 2fa975d..ecb0cd2 100644 --- a/internal/drives/pool.go +++ b/internal/drives/pool.go @@ -253,7 +253,13 @@ fi OPTS="defaults,nofail,x-gvfs-show,x-gvfs-name=$LABEL" LINE="UUID=${UUID} ${MP} btrfs ${OPTS} 0 0" -grep -qF -- "$LINE" /etc/fstab || printf '%%s\n' "$LINE" >> /etc/fstab +# Drop any stale entries for this mount point first (e.g. the UUID of a pool +# that has since been recreated). Duplicate mount points make +# systemd-fstab-generator keep only the FIRST line — if that one is stale the +# pool never mounts at boot and shows up as detached again every reboot. +awk -v mp="$MP" '$0 ~ /^[[:space:]]*#/ || $2 != mp' /etc/fstab > /etc/fstab.tuistream \ + && cat /etc/fstab.tuistream > /etc/fstab && rm -f /etc/fstab.tuistream +printf '%%s\n' "$LINE" >> /etc/fstab echo "fstab: $LINE" `, shellQuote(devs[0]), diff --git a/internal/drives/pool_import.go b/internal/drives/pool_import.go index d12b203..e57139d 100644 --- a/internal/drives/pool_import.go +++ b/internal/drives/pool_import.go @@ -169,7 +169,13 @@ fi OPTS="defaults,nofail,x-gvfs-show,x-gvfs-name=$LABEL" LINE="UUID=${UUID} ${MP} btrfs ${OPTS} 0 0" -grep -qF -- "$LINE" /etc/fstab || printf '%%s\n' "$LINE" >> /etc/fstab +# Drop any stale entries for this mount point first (e.g. the UUID of a pool +# that has since been recreated). Duplicate mount points make +# systemd-fstab-generator keep only the FIRST line — if that one is stale the +# pool never mounts at boot and shows up as detached again every reboot. +awk -v mp="$MP" '$0 ~ /^[[:space:]]*#/ || $2 != mp' /etc/fstab > /etc/fstab.tuistream \ + && cat /etc/fstab.tuistream > /etc/fstab && rm -f /etc/fstab.tuistream +printf '%%s\n' "$LINE" >> /etc/fstab echo "fstab: $LINE" `, shellQuote(dev), diff --git a/internal/jellyfin/storage.go b/internal/jellyfin/storage.go index 55c9b23..a147df6 100644 --- a/internal/jellyfin/storage.go +++ b/internal/jellyfin/storage.go @@ -131,8 +131,13 @@ install -d %[2]s %[3]s`, { Title: "Add bind mounts to /etc/fstab", Cmd: bashAsRoot(fmt.Sprintf(` -grep -qsF ' %[2]s ' /etc/fstab || printf '%%s %%s none bind,x-systemd.requires-mounts-for=%[5]s 0 0\n' %[1]s %[2]s >> /etc/fstab -grep -qsF ' %[4]s ' /etc/fstab || printf '%%s %%s none bind,x-systemd.requires-mounts-for=%[5]s 0 0\n' %[3]s %[4]s >> /etc/fstab`, +# Drop any stale bind entries for these targets first (e.g. pointing at a +# previous media drive) — duplicate mount points make systemd-fstab-generator +# keep only the FIRST line, so a stale one would win at boot. +awk -v a=%[2]s -v b=%[4]s '$0 ~ /^[[:space:]]*#/ || ($2 != a && $2 != b)' /etc/fstab > /etc/fstab.tuistream \ + && cat /etc/fstab.tuistream > /etc/fstab && rm -f /etc/fstab.tuistream +printf '%%s %%s none bind,x-systemd.requires-mounts-for=%[5]s 0 0\n' %[1]s %[2]s >> /etc/fstab +printf '%%s %%s none bind,x-systemd.requires-mounts-for=%[5]s 0 0\n' %[3]s %[4]s >> /etc/fstab`, shellQuote(data), defaultDataDir, shellQuote(cache), defaultCacheDir, mp)),