diff --git a/cmd/tailscale/cli/diag.go b/cmd/tailscale/cli/diag.go index 5ed6ba92b..ebf26985f 100644 --- a/cmd/tailscale/cli/diag.go +++ b/cmd/tailscale/cli/diag.go @@ -66,7 +66,7 @@ func isSystemdSystem() bool { return false } switch distro.Get() { - case distro.QNAP, distro.Gokrazy, distro.Synology: + case distro.QNAP, distro.Gokrazy, distro.Synology, distro.Unraid: return false } _, err := exec.LookPath("systemctl") diff --git a/cmd/tailscaled/taildrop.go b/cmd/tailscaled/taildrop.go index 71be089db..11f026e27 100644 --- a/cmd/tailscaled/taildrop.go +++ b/cmd/tailscaled/taildrop.go @@ -18,7 +18,7 @@ import ( func configureTaildrop(logf logger.Logf, lb *ipnlocal.LocalBackend) { dg := distro.Get() switch dg { - case distro.Synology, distro.TrueNAS, distro.QNAP: + case distro.Synology, distro.TrueNAS, distro.QNAP, distro.Unraid: // See if they have a "Taildrop" share. // See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319 path, err := findTaildropDir(dg) @@ -42,6 +42,8 @@ func findTaildropDir(dg distro.Distro) (string, error) { return findTrueNASTaildropDir(name) case distro.QNAP: return findQnapTaildropDir(name) + case distro.Unraid: + return findUnraidTaildropDir(name) } return "", fmt.Errorf("%s is an unsupported distro for Taildrop dir", dg) } @@ -103,3 +105,25 @@ func findQnapTaildropDir(name string) (string, error) { } return "", fmt.Errorf("shared folder %q not found", name) } + +// findUnraidTaildropDir looks for a directory linked at +// /var/lib/tailscale/Taildrop. This is a symlink to the +// path specified by the user in the Unraid Web UI +func findUnraidTaildropDir(name string) (string, error) { + dir := fmt.Sprintf("/var/lib/tailscale/%s", name) + _, err := os.Stat(dir) + if err != nil { + return "", fmt.Errorf("symlink %q not found", name) + } + + fullpath, err := filepath.EvalSymlinks(dir) + if err != nil { + return "", fmt.Errorf("symlink %q to shared folder not valid", name) + } + + fi, err := os.Stat(fullpath) + if err == nil && fi.IsDir() { + return dir, nil // return the symlink + } + return "", fmt.Errorf("shared folder %q not found", name) +} diff --git a/hostinfo/hostinfo_linux.go b/hostinfo/hostinfo_linux.go index 6c2ab8ff8..d52c084b8 100644 --- a/hostinfo/hostinfo_linux.go +++ b/hostinfo/hostinfo_linux.go @@ -95,6 +95,8 @@ func linuxVersionMeta() (meta versionMeta) { propFile = "/etc.defaults/VERSION" case distro.OpenWrt: propFile = "/etc/openwrt_release" + case distro.Unraid: + propFile = "/etc/unraid-version" case distro.WDMyCloud: slurp, _ := os.ReadFile("/etc/version") meta.DistroVersion = string(bytes.TrimSpace(slurp)) @@ -153,6 +155,8 @@ func linuxVersionMeta() (meta versionMeta) { meta.DistroVersion = m["productversion"] case distro.OpenWrt: meta.DistroVersion = m["DISTRIB_RELEASE"] + case distro.Unraid: + meta.DistroVersion = m["version"] } return } diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index fa749d995..e8e06b6ee 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -49,6 +49,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/util/clientmetric" "tailscale.com/util/multierr" + "tailscale.com/version/distro" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" ) @@ -1090,6 +1091,10 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) { http.Error(w, errNoTaildrop.Error(), http.StatusInternalServerError) return } + if distro.Get() == distro.Unraid && !h.ps.directFileMode { + http.Error(w, "Taildrop folder not configured or accessible", http.StatusInternalServerError) + return + } rawPath := r.URL.EscapedPath() suffix, ok := strings.CutPrefix(rawPath, "/v0/put/") if !ok { diff --git a/version/distro/distro.go b/version/distro/distro.go index 0724265a4..e319d1ba7 100644 --- a/version/distro/distro.go +++ b/version/distro/distro.go @@ -29,6 +29,7 @@ const ( TrueNAS = Distro("truenas") Gokrazy = Distro("gokrazy") WDMyCloud = Distro("wdmycloud") + Unraid = Distro("unraid") ) var distro lazy.SyncValue[Distro] @@ -90,6 +91,8 @@ func linuxDistro() Distro { return WDMyCloud case have("/usr/sbin/wd_crontab.sh"): // Western Digital MyCloud OS5 return WDMyCloud + case have("/etc/unraid-version"): + return Unraid } return "" }