diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go index 7e14aa16e..7584a7d61 100644 --- a/clientupdate/clientupdate.go +++ b/clientupdate/clientupdate.go @@ -176,10 +176,7 @@ func (up *Updater) getUpdateFunction() (fn updateFunction, canAutoUpdate bool) { case distro.Alpine: return up.updateAlpineLike, true case distro.Unraid: - // Unraid runs from memory, updates must be installed via the Unraid - // plugin manager to be persistent. - // TODO(awly): implement Unraid updates using the 'plugin' CLI. - return nil, false + return up.updateUnraid, true case distro.QNAP: return up.updateQNAP, true } @@ -1148,6 +1145,64 @@ func (up *Updater) updateQNAP() (err error) { return nil } +func (up *Updater) updateUnraid() (err error) { + if up.Version != "" { + return errors.New("installing a specific version on Unraid is not supported") + } + if err := requireRoot(); err != nil { + return err + } + + defer func() { + if err != nil { + err = fmt.Errorf(`%w; you can try updating using "plugin check tailscale.plg && plugin update tailscale.plg"`, err) + } + }() + + // We need to run `plugin check` for the latest tailscale.plg to get + // downloaded. Unfortunately, the output of this command does not contain + // the latest tailscale version available. So we'll parse the downloaded + // tailscale.plg file manually below. + out, err := exec.Command("plugin", "check", "tailscale.plg").CombinedOutput() + if err != nil { + return fmt.Errorf("failed to check if Tailscale plugin is upgradable: %w, output: %q", err, out) + } + + // Note: 'plugin check' downloads plugins to /tmp/plugins. + // The installed .plg files are in /boot/config/plugins/, but the pending + // ones are in /tmp/plugins. We should parse the pending file downloaded by + // 'plugin check'. + latest, err := parseUnraidPluginVersion("/tmp/plugins/tailscale.plg") + if err != nil { + return fmt.Errorf("failed to find latest Tailscale version in /boot/config/plugins/tailscale.plg: %w", err) + } + if !up.confirm(latest) { + return nil + } + + up.Logf("c2n: running 'plugin update tailscale.plg'") + cmd := exec.Command("plugin", "update", "tailscale.plg") + cmd.Stdout = up.Stdout + cmd.Stderr = up.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed tailscale plugin update: %w", err) + } + return nil +} + +func parseUnraidPluginVersion(plgPath string) (string, error) { + plg, err := os.ReadFile(plgPath) + if err != nil { + return "", err + } + re := regexp.MustCompile(``) + match := re.FindStringSubmatch(string(plg)) + if len(match) < 2 { + return "", errors.New("version not found in plg file") + } + return match[1], nil +} + func writeFile(r io.Reader, path string, perm os.FileMode) error { if err := os.Remove(path); err != nil && !os.IsNotExist(err) { return fmt.Errorf("failed to remove existing file at %q: %w", path, err) diff --git a/clientupdate/clientupdate_test.go b/clientupdate/clientupdate_test.go index 36e4e18cc..f0c7ebea0 100644 --- a/clientupdate/clientupdate_test.go +++ b/clientupdate/clientupdate_test.go @@ -795,3 +795,31 @@ func TestCleanupOldDownloads(t *testing.T) { }) } } + +func TestParseUnraidPluginVersion(t *testing.T) { + tests := []struct { + plgPath string + wantVer string + wantErr string + }{ + {plgPath: "testdata/tailscale-1.52.0.plg", wantVer: "1.52.0"}, + {plgPath: "testdata/tailscale-1.54.0.plg", wantVer: "1.54.0"}, + {plgPath: "testdata/tailscale-nover.plg", wantErr: "version not found in plg file"}, + {plgPath: "testdata/tailscale-nover-path-mentioned.plg", wantErr: "version not found in plg file"}, + } + for _, tt := range tests { + t.Run(tt.plgPath, func(t *testing.T) { + got, err := parseUnraidPluginVersion(tt.plgPath) + if got != tt.wantVer { + t.Errorf("got version: %q, want %q", got, tt.wantVer) + } + var gotErr string + if err != nil { + gotErr = err.Error() + } + if gotErr != tt.wantErr { + t.Errorf("got error: %q, want %q", gotErr, tt.wantErr) + } + }) + } +} diff --git a/clientupdate/testdata/tailscale-1.52.0.plg b/clientupdate/testdata/tailscale-1.52.0.plg new file mode 100644 index 000000000..bc6649488 --- /dev/null +++ b/clientupdate/testdata/tailscale-1.52.0.plg @@ -0,0 +1,115 @@ + + + + + + + + + + +https://pkgs.tailscale.com/stable/tailscale_1.52.0_amd64.tgz +b4d15d9908737e08e3f95ed5104603ce + + + +https://github.com/dkaser/unraid-tailscale-utils/releases/download/1.4.1/unraid-tailscale-utils-1.4.1-noarch-1.txz +7095ab4b88b34d8f5da6483865883267 + + + +https://github.com/dkaser/unraid-plugin-diagnostics/releases/download/1.2.2/unraid-plugin-diagnostics-1.2.2-noarch-1.txz +9d358575499305889962d83ebd90c20c + + + + + +> /var/local/emhttp/plugins/tailscale/tailscale.ini + +# start tailscaled +/usr/local/emhttp/plugins/tailscale/restart.sh + +# cleanup old versions +rm -f /boot/config/plugins/tailscale/tailscale-utils-*.txz +rm -f $(ls /boot/config/plugins/tailscale/unraid-tailscale-utils-*.txz 2>/dev/null | grep -v '1.4.1') +rm -f $(ls /boot/config/plugins/tailscale/unraid-plugin-diagnostics-*.txz 2>/dev/null | grep -v '1.2.2') +rm -f $(ls /boot/config/plugins/tailscale/*.tgz 2>/dev/null | grep -v 'tailscale_1.52.0_amd64') + +echo "" +echo "----------------------------------------------------" +echo " tailscale has been installed." +echo " Version: 2023.11.01" +echo "----------------------------------------------------" +echo "" +]]> + + + + + + +/dev/null + +rm /usr/local/sbin/tailscale +rm /usr/local/sbin/tailscaled + +removepkg unraid-tailscale-utils-1.4.1 + +rm -rf /usr/local/emhttp/plugins/tailscale +rm -rf /boot/config/plugins/tailscale +]]> + + + + diff --git a/clientupdate/testdata/tailscale-1.54.0.plg b/clientupdate/testdata/tailscale-1.54.0.plg new file mode 100644 index 000000000..a62bb9d44 --- /dev/null +++ b/clientupdate/testdata/tailscale-1.54.0.plg @@ -0,0 +1,112 @@ + + + + + + + + + + +https://pkgs.tailscale.com/stable/tailscale_1.54.0_amd64.tgz +20187743e0c1c1a0d9fea47a10b6a9ba + + + +https://github.com/dkaser/unraid-tailscale-utils/releases/download/1.4.1/unraid-tailscale-utils-1.4.1-noarch-1.txz +7095ab4b88b34d8f5da6483865883267 + + + +https://github.com/dkaser/unraid-plugin-diagnostics/releases/download/1.2.2/unraid-plugin-diagnostics-1.2.2-noarch-1.txz +9d358575499305889962d83ebd90c20c + + + + + +> /var/local/emhttp/plugins/tailscale/tailscale.ini + +# start tailscaled +/usr/local/emhttp/plugins/tailscale/restart.sh + +# cleanup old versions +rm -f /boot/config/plugins/tailscale/tailscale-utils-*.txz +rm -f $(ls /boot/config/plugins/tailscale/unraid-tailscale-utils-*.txz 2>/dev/null | grep -v '1.4.1') +rm -f $(ls /boot/config/plugins/tailscale/unraid-plugin-diagnostics-*.txz 2>/dev/null | grep -v '1.2.2') +rm -f $(ls /boot/config/plugins/tailscale/*.tgz 2>/dev/null | grep -v 'tailscale_1.54.0_amd64') + +echo "" +echo "----------------------------------------------------" +echo " tailscale has been installed." +echo " Version: 2023.11.18" +echo "----------------------------------------------------" +echo "" +]]> + + + + + + +/dev/null + +rm /usr/local/sbin/tailscale +rm /usr/local/sbin/tailscaled + +removepkg unraid-tailscale-utils-1.4.1 + +rm -rf /usr/local/emhttp/plugins/tailscale +rm -rf /boot/config/plugins/tailscale +]]> + + + + diff --git a/clientupdate/testdata/tailscale-nover-path-mentioned.plg b/clientupdate/testdata/tailscale-nover-path-mentioned.plg new file mode 100644 index 000000000..7813f5686 --- /dev/null +++ b/clientupdate/testdata/tailscale-nover-path-mentioned.plg @@ -0,0 +1,110 @@ + + + + + + + + + + +https://github.com/dkaser/unraid-tailscale-utils/releases/download/1.4.1/unraid-tailscale-utils-1.4.1-noarch-1.txz +7095ab4b88b34d8f5da6483865883267 + + + +https://github.com/dkaser/unraid-plugin-diagnostics/releases/download/1.2.2/unraid-plugin-diagnostics-1.2.2-noarch-1.txz +9d358575499305889962d83ebd90c20c + + + + + +> /var/local/emhttp/plugins/tailscale/tailscale.ini + +# start tailscaled +/usr/local/emhttp/plugins/tailscale/restart.sh + +# cleanup old versions +rm -f /boot/config/plugins/tailscale/tailscale-utils-*.txz +rm -f $(ls /boot/config/plugins/tailscale/unraid-tailscale-utils-*.txz 2>/dev/null | grep -v '1.4.1') +rm -f $(ls /boot/config/plugins/tailscale/unraid-plugin-diagnostics-*.txz 2>/dev/null | grep -v '1.2.2') +rm -f $(ls /boot/config/plugins/tailscale/*.tgz 2>/dev/null | grep -v 'tailscale_1.52.0_amd64') + +echo "" +echo "----------------------------------------------------" +echo " tailscale has been installed." +echo " Version: 2023.11.01" +echo "----------------------------------------------------" +echo "" +]]> + + + + + + +/dev/null + +rm /usr/local/sbin/tailscale +rm /usr/local/sbin/tailscaled + +removepkg unraid-tailscale-utils-1.4.1 + +rm -rf /usr/local/emhttp/plugins/tailscale +rm -rf /boot/config/plugins/tailscale +]]> + + + + diff --git a/clientupdate/testdata/tailscale-nover.plg b/clientupdate/testdata/tailscale-nover.plg new file mode 100644 index 000000000..361ee83a3 --- /dev/null +++ b/clientupdate/testdata/tailscale-nover.plg @@ -0,0 +1,102 @@ + + + + + + + + + + +https://github.com/dkaser/unraid-tailscale-utils/releases/download/1.4.1/unraid-tailscale-utils-1.4.1-noarch-1.txz +7095ab4b88b34d8f5da6483865883267 + + + +https://github.com/dkaser/unraid-plugin-diagnostics/releases/download/1.2.2/unraid-plugin-diagnostics-1.2.2-noarch-1.txz +9d358575499305889962d83ebd90c20c + + + + + +> /var/local/emhttp/plugins/tailscale/tailscale.ini + +# start tailscaled +/usr/local/emhttp/plugins/tailscale/restart.sh + +# cleanup old versions +rm -f /boot/config/plugins/tailscale/tailscale-utils-*.txz +rm -f $(ls /boot/config/plugins/tailscale/unraid-tailscale-utils-*.txz 2>/dev/null | grep -v '1.4.1') +rm -f $(ls /boot/config/plugins/tailscale/unraid-plugin-diagnostics-*.txz 2>/dev/null | grep -v '1.2.2') + +echo "" +echo "----------------------------------------------------" +echo " tailscale has been installed." +echo " Version: 2023.11.01" +echo "----------------------------------------------------" +echo "" +]]> + + + + + + +/dev/null + +rm /usr/local/sbin/tailscale +rm /usr/local/sbin/tailscaled + +removepkg unraid-tailscale-utils-1.4.1 + +rm -rf /usr/local/emhttp/plugins/tailscale +rm -rf /boot/config/plugins/tailscale +]]> + + + + diff --git a/cmd/tailscale/cli/update.go b/cmd/tailscale/cli/update.go index 821ed7373..7206b9a44 100644 --- a/cmd/tailscale/cli/update.go +++ b/cmd/tailscale/cli/update.go @@ -32,7 +32,12 @@ var updateCmd = &ffcli.Command{ // - Arch (and other pacman-based distros) // - Alpine (and other apk-based distros) // - FreeBSD (and other pkg-based distros) - if distro.Get() != distro.Arch && distro.Get() != distro.Alpine && runtime.GOOS != "freebsd" { + // - Unraid/QNAP/Synology + if distro.Get() != distro.Arch && + distro.Get() != distro.Alpine && + distro.Get() != distro.QNAP && + distro.Get() != distro.Synology && + runtime.GOOS != "freebsd" { fs.StringVar(&updateArgs.track, "track", "", `which track to check for updates: "stable" or "unstable" (dev); empty means same as current`) fs.StringVar(&updateArgs.version, "version", "", `explicit version to update/downgrade to`) } diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go index ac64dbe9f..807f1184c 100644 --- a/ipn/ipnlocal/c2n.go +++ b/ipn/ipnlocal/c2n.go @@ -395,13 +395,18 @@ func findCmdTailscale() (string, error) { if self == "/usr/local/sbin/tailscaled" || self == "/usr/local/bin/tailscaled" { ts = "/usr/local/bin/tailscale" } - if distro.Get() == distro.QNAP { + switch distro.Get() { + case distro.QNAP: // The volume under /share/ where qpkg are installed is not // predictable. But the rest of the path is. ok, err := filepath.Match("/share/*/.qpkg/Tailscale/tailscaled", self) if err == nil && ok { ts = filepath.Join(filepath.Dir(self), "tailscale") } + case distro.Unraid: + if self == "/usr/local/emhttp/plugins/tailscale/bin/tailscaled" { + ts = "/usr/local/emhttp/plugins/tailscale/bin/tailscale" + } } case "windows": ts = filepath.Join(filepath.Dir(self), "tailscale.exe")