mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-27 10:47:35 +00:00
cmd/tailscale/cli: make 'tailscale update' support Debian/Ubuntu apt
Updates #6995 Change-Id: I3355435db583755e0fc73d76347f6423b8939dfb Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
4471e403aa
commit
5ca22a0068
@ -5,6 +5,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
@ -20,6 +21,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -222,13 +224,105 @@ func (up *updater) updateDebLike() error {
|
|||||||
if up.currentOrDryRun(ver) {
|
if up.currentOrDryRun(ver) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://pkgs.tailscale.com/%s/debian/pool/tailscale_%s_%s.deb", up.track, ver, runtime.GOARCH)
|
|
||||||
// TODO(bradfitz): require root/sudo
|
|
||||||
// TODO(bradfitz): check https://pkgs.tailscale.com/stable/debian/dists/sid/InRelease, check gpg, get sha256
|
|
||||||
// And https://pkgs.tailscale.com/stable/debian/dists/sid/main/binary-amd64/Packages.gz and sha256 of it
|
|
||||||
//
|
|
||||||
|
|
||||||
return errors.New("TODO: Debian/Ubuntu deb download of " + url)
|
track := "unstable"
|
||||||
|
if stable, ok := versionIsStable(ver); !ok {
|
||||||
|
return fmt.Errorf("malformed version %q", ver)
|
||||||
|
} else if stable {
|
||||||
|
track = "stable"
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Geteuid() != 0 {
|
||||||
|
return errors.New("must be root; use sudo")
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated, err := updateDebianAptSourcesList(track); err != nil {
|
||||||
|
return err
|
||||||
|
} else if updated {
|
||||||
|
fmt.Printf("Updated %s to use the %s track\n", aptSourcesFile, track)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("apt-get", "update",
|
||||||
|
// Only update the tailscale repo, not the other ones, treating
|
||||||
|
// the tailscale.list file as the main "sources.list" file.
|
||||||
|
"-o", "Dir::Etc::SourceList=sources.list.d/tailscale.list",
|
||||||
|
// Disable the "sources.list.d" directory:
|
||||||
|
"-o", "Dir::Etc::SourceParts=-",
|
||||||
|
// Don't forget about packages in the other repos just because
|
||||||
|
// we're not updating them:
|
||||||
|
"-o", "APT::Get::List-Cleanup=0",
|
||||||
|
)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.Command("apt-get", "install", "--yes", "--allow-downgrades", "tailscale="+ver)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const aptSourcesFile = "/etc/apt/sources.list.d/tailscale.list"
|
||||||
|
|
||||||
|
// updateDebianAptSourcesList updates the /etc/apt/sources.list.d/tailscale.list
|
||||||
|
// file to make sure it has the provided track (stable or unstable) in it.
|
||||||
|
//
|
||||||
|
// If it already has the right track (including containing both stable and
|
||||||
|
// unstable), it does nothing.
|
||||||
|
func updateDebianAptSourcesList(dstTrack string) (rewrote bool, err error) {
|
||||||
|
was, err := os.ReadFile(aptSourcesFile)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
newContent, err := updateDebianAptSourcesListBytes(was, dstTrack)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if bytes.Equal(was, newContent) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, os.WriteFile(aptSourcesFile, newContent, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateDebianAptSourcesListBytes(was []byte, dstTrack string) (newContent []byte, err error) {
|
||||||
|
trackURLPrefix := []byte("https://pkgs.tailscale.com/" + dstTrack + "/")
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var changes int
|
||||||
|
bs := bufio.NewScanner(bytes.NewReader(was))
|
||||||
|
hadCorrect := false
|
||||||
|
commentLine := regexp.MustCompile(`^\s*\#`)
|
||||||
|
pkgsURL := regexp.MustCompile(`\bhttps://pkgs\.tailscale\.com/((un)?stable)/`)
|
||||||
|
for bs.Scan() {
|
||||||
|
line := bs.Bytes()
|
||||||
|
if !commentLine.Match(line) {
|
||||||
|
line = pkgsURL.ReplaceAllFunc(line, func(m []byte) []byte {
|
||||||
|
if bytes.Equal(m, trackURLPrefix) {
|
||||||
|
hadCorrect = true
|
||||||
|
} else {
|
||||||
|
changes++
|
||||||
|
}
|
||||||
|
return trackURLPrefix
|
||||||
|
})
|
||||||
|
}
|
||||||
|
buf.Write(line)
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
}
|
||||||
|
if hadCorrect || (changes == 1 && bytes.Equal(bytes.TrimSpace(was), bytes.TrimSpace(buf.Bytes()))) {
|
||||||
|
// Unchanged or close enough.
|
||||||
|
return was, nil
|
||||||
|
}
|
||||||
|
if changes != 1 {
|
||||||
|
// No changes, or an unexpected number of changes (what?). Bail.
|
||||||
|
// They probably editted it by hand and we don't know what to do.
|
||||||
|
return nil, fmt.Errorf("unexpected/unsupported %s contents", aptSourcesFile)
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (up *updater) updateMacSys() error {
|
func (up *updater) updateMacSys() error {
|
||||||
|
76
cmd/tailscale/cli/update_test.go
Normal file
76
cmd/tailscale/cli/update_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) 2023 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestUpdateDebianAptSourcesListBytes(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
toTrack string
|
||||||
|
in string
|
||||||
|
want string // empty means want no change
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "stable-to-unstable",
|
||||||
|
toTrack: "unstable",
|
||||||
|
in: "# Tailscale packages for debian buster\ndeb https://pkgs.tailscale.com/stable/debian bullseye main\n",
|
||||||
|
want: "# Tailscale packages for debian buster\ndeb https://pkgs.tailscale.com/unstable/debian bullseye main\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stable-unchanged",
|
||||||
|
toTrack: "stable",
|
||||||
|
in: "# Tailscale packages for debian buster\ndeb https://pkgs.tailscale.com/stable/debian bullseye main\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "if-both-stable-and-unstable-dont-change",
|
||||||
|
toTrack: "stable",
|
||||||
|
in: "# Tailscale packages for debian buster\n" +
|
||||||
|
"deb https://pkgs.tailscale.com/stable/debian bullseye main\n" +
|
||||||
|
"deb https://pkgs.tailscale.com/unstable/debian bullseye main\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "if-both-stable-and-unstable-dont-change-unstable",
|
||||||
|
toTrack: "unstable",
|
||||||
|
in: "# Tailscale packages for debian buster\n" +
|
||||||
|
"deb https://pkgs.tailscale.com/stable/debian bullseye main\n" +
|
||||||
|
"deb https://pkgs.tailscale.com/unstable/debian bullseye main\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "signed-by-form",
|
||||||
|
toTrack: "unstable",
|
||||||
|
in: "# Tailscale packages for ubuntu jammy\ndeb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/ubuntu jammy main\n",
|
||||||
|
want: "# Tailscale packages for ubuntu jammy\ndeb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/unstable/ubuntu jammy main\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported-lines",
|
||||||
|
toTrack: "unstable",
|
||||||
|
in: "# Tailscale packages for ubuntu jammy\ndeb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/foobar/ubuntu jammy main\n",
|
||||||
|
wantErr: "unexpected/unsupported /etc/apt/sources.list.d/tailscale.list contents",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
newContent, err := updateDebianAptSourcesListBytes([]byte(tt.in), tt.toTrack)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() != tt.wantErr {
|
||||||
|
t.Fatalf("error = %v; want %q", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
t.Fatalf("got no error; want %q", tt.wantErr)
|
||||||
|
}
|
||||||
|
var gotChange string
|
||||||
|
if string(newContent) != tt.in {
|
||||||
|
gotChange = string(newContent)
|
||||||
|
}
|
||||||
|
if gotChange != tt.want {
|
||||||
|
t.Errorf("wrong result\n got: %q\nwant: %q", gotChange, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user