clientupdate: best-effort restart of tailscaled on init.d systems (#18568)
Not all Linux distros use systemd yet, for example GL.iNet KVM devices use busybox's init, which is similar to SysV init. This is a best-effort restart attempt after the update, it probably won't cover 100% of init.d setups out there. Fixes #18567 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
@@ -11,11 +11,11 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"maps"
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/feature"
|
"tailscale.com/feature"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/types/lazy"
|
"tailscale.com/types/lazy"
|
||||||
@@ -288,6 +289,10 @@ func Update(args Arguments) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (up *Updater) confirm(ver string) bool {
|
func (up *Updater) confirm(ver string) bool {
|
||||||
|
if envknob.Bool("TS_UPDATE_SKIP_VERSION_CHECK") {
|
||||||
|
up.Logf("current version: %v, latest version %v; forcing an update due to TS_UPDATE_SKIP_VERSION_CHECK", up.currentVersion, ver)
|
||||||
|
return true
|
||||||
|
}
|
||||||
// Only check version when we're not switching tracks.
|
// Only check version when we're not switching tracks.
|
||||||
if up.Track == "" || up.Track == CurrentTrack {
|
if up.Track == "" || up.Track == CurrentTrack {
|
||||||
switch c := cmpver.Compare(up.currentVersion, ver); {
|
switch c := cmpver.Compare(up.currentVersion, ver); {
|
||||||
@@ -865,12 +870,17 @@ func (up *Updater) updateLinuxBinary() error {
|
|||||||
if err := os.Remove(dlPath); err != nil {
|
if err := os.Remove(dlPath); err != nil {
|
||||||
up.Logf("failed to clean up %q: %v", dlPath, err)
|
up.Logf("failed to clean up %q: %v", dlPath, err)
|
||||||
}
|
}
|
||||||
if err := restartSystemdUnit(context.Background()); err != nil {
|
|
||||||
|
err = restartSystemdUnit(up.Logf)
|
||||||
|
if errors.Is(err, errors.ErrUnsupported) {
|
||||||
|
err = restartInitD()
|
||||||
if errors.Is(err, errors.ErrUnsupported) {
|
if errors.Is(err, errors.ErrUnsupported) {
|
||||||
up.Logf("Tailscale binaries updated successfully.\nPlease restart tailscaled to finish the update.")
|
err = errors.New("tailscaled is not running under systemd or init.d")
|
||||||
} else {
|
|
||||||
up.Logf("Tailscale binaries updated successfully, but failed to restart tailscaled: %s.\nPlease restart tailscaled to finish the update.", err)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
up.Logf("Tailscale binaries updated successfully, but failed to restart tailscaled: %s.\nPlease restart tailscaled to finish the update.", err)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
up.Logf("Success")
|
up.Logf("Success")
|
||||||
}
|
}
|
||||||
@@ -878,13 +888,13 @@ func (up *Updater) updateLinuxBinary() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func restartSystemdUnit(ctx context.Context) error {
|
func restartSystemdUnit(logf logger.Logf) error {
|
||||||
if _, err := exec.LookPath("systemctl"); err != nil {
|
if _, err := exec.LookPath("systemctl"); err != nil {
|
||||||
// Likely not a systemd-managed distro.
|
// Likely not a systemd-managed distro.
|
||||||
return errors.ErrUnsupported
|
return errors.ErrUnsupported
|
||||||
}
|
}
|
||||||
if out, err := exec.Command("systemctl", "daemon-reload").CombinedOutput(); err != nil {
|
if out, err := exec.Command("systemctl", "daemon-reload").CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("systemctl daemon-reload failed: %w\noutput: %s", err, out)
|
logf("systemctl daemon-reload failed: %w\noutput: %s", err, out)
|
||||||
}
|
}
|
||||||
if out, err := exec.Command("systemctl", "restart", "tailscaled.service").CombinedOutput(); err != nil {
|
if out, err := exec.Command("systemctl", "restart", "tailscaled.service").CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("systemctl restart failed: %w\noutput: %s", err, out)
|
return fmt.Errorf("systemctl restart failed: %w\noutput: %s", err, out)
|
||||||
@@ -892,6 +902,40 @@ func restartSystemdUnit(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restartInitD attempts best-effort restart of tailscale on init.d systems
|
||||||
|
// (for example, GL.iNet KVM devices running busybox). It returns
|
||||||
|
// errors.ErrUnsupported if the expected service script is not found.
|
||||||
|
//
|
||||||
|
// There's probably a million variations of init.d configs out there, and this
|
||||||
|
// function does not intend to support all of them.
|
||||||
|
func restartInitD() error {
|
||||||
|
const initDir = "/etc/init.d/"
|
||||||
|
files, err := os.ReadDir(initDir)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return errors.ErrUnsupported
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
// Skip anything other than regular files.
|
||||||
|
if !f.Type().IsRegular() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The script will be called something like /etc/init.d/tailscale or
|
||||||
|
// /etc/init.d/S99tailscale.
|
||||||
|
if n := f.Name(); strings.HasSuffix(n, "tailscale") {
|
||||||
|
path := filepath.Join(initDir, n)
|
||||||
|
if out, err := exec.Command(path, "restart").CombinedOutput(); err != nil {
|
||||||
|
return fmt.Errorf("%q failed: %w\noutput: %s", path+" restart", err, out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Init script for tailscale not found.
|
||||||
|
return errors.ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
func (up *Updater) downloadLinuxTarball(ver string) (string, error) {
|
func (up *Updater) downloadLinuxTarball(ver string) (string, error) {
|
||||||
dlDir, err := os.UserCacheDir()
|
dlDir, err := os.UserCacheDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user