WIP: rebase fork onto upstream/main (v1.103.0) #15

Closed
codinget wants to merge 670 commits from webnet into save/webnet-2026-07-29
7 changed files with 171 additions and 33 deletions
Showing only changes of commit fad8b9b8a9 - Show all commits
+69 -2
View File
@@ -45,7 +45,8 @@ type GokrazyUpdateArgs struct {
URL string
// AllowUnsigned permits installing a GAF without signature verification.
// This is intended for tests until signed GAF verification is implemented.
// It is intended for tests that serve a GAF from a fileserver that does
// not publish distsign.pub.
AllowUnsigned bool
// Logf is optional; nil discards log messages.
@@ -216,6 +217,17 @@ func (up *Updater) getUpdateFunction() (fn updateFunction, canAutoUpdate bool) {
// release cadence with Synology Package Center and use their
// auto-update mechanism.
return up.updateSynology, false
case distro.Gokrazy:
// Only the official Tailscale appliance image (built with the
// ts_appliance build tag, which causes hostinfo to report
// Package="tsapp") is auto-updatable. A user running a custom
// Gokrazy build that happens to include tailscaled must not be
// updated with our stock GAFs. TS_FORCE_ALLOW_TSAPP_UPDATE is an
// escape hatch for callers who know what they're doing.
if hi.Package != "tsapp" && !envknob.Bool("TS_FORCE_ALLOW_TSAPP_UPDATE") {
return nil, false
}
return up.updateGokrazy, true
case distro.Debian: // includes Ubuntu
return up.updateDebLike, true
case distro.Arch:
@@ -883,6 +895,56 @@ func (up *Updater) updateFreeBSD() (err error) {
return nil
}
// updateGokrazy fetches the latest signed GAF for this gokrazy device variant
// (vm-amd64, vm-arm64, or pi-arm64) from up.PkgsAddr and applies it via the
// local gokrazy init update API.
func (up *Updater) updateGokrazy() error {
if !GokrazyUpdateFromURL.IsSet() {
return errors.New("gokrazy update support is not linked into this binary")
}
variant, err := gokrazyDeviceVariant()
if err != nil {
return err
}
latest, err := latestPackages(up.Track)
if err != nil {
return err
}
gafName, ok := latest.GAFs[variant]
if !ok {
return fmt.Errorf("no GAF for device %q on %q track", variant, up.Track)
}
if latest.GAFsVersion == "" {
return fmt.Errorf("no GAF version on %q track", up.Track)
}
if !up.confirm(latest.GAFsVersion) {
return nil
}
gafURL := fmt.Sprintf("%s/%s/%s", strings.TrimRight(up.PkgsAddr, "/"), up.Track, gafName)
up.Logf("Updating to %s (%s)", latest.GAFsVersion, gafURL)
return GokrazyUpdateFromURL.Get()(context.Background(), GokrazyUpdateArgs{
URL: gafURL,
Logf: up.Logf,
})
}
// gokrazyDeviceVariant returns the GAFs JSON key for the current gokrazy
// device, e.g. "vm-amd64", "vm-arm64", or "pi-arm64". On arm64, it reads the
// device-tree model to tell a Raspberry Pi apart from a VM.
func gokrazyDeviceVariant() (string, error) {
switch runtime.GOARCH {
case "amd64":
return "vm-amd64", nil
case "arm64":
b, _ := os.ReadFile("/sys/firmware/devicetree/base/model")
if strings.HasPrefix(strings.Trim(string(b), "\x00\r\n\t "), "Raspberry Pi") {
return "pi-arm64", nil
}
return "vm-arm64", nil
}
return "", fmt.Errorf("unsupported gokrazy GOARCH %q", runtime.GOARCH)
}
func (up *Updater) updateLinuxBinary() error {
// Root is needed to overwrite binaries and restart systemd unit.
if err := requireRoot(); err != nil {
@@ -1255,8 +1317,11 @@ func LatestTailscaleVersion(track string) (string, error) {
ver = latest.MacZipsVersion
case "linux":
ver = latest.TarballsVersion
if distro.Get() == distro.Synology {
switch distro.Get() {
case distro.Synology:
ver = latest.SPKsVersion
case distro.Gokrazy:
ver = latest.GAFsVersion
}
}
@@ -1274,6 +1339,8 @@ type trackPackages struct {
ExesVersion string
MSIs map[string]string
MSIsVersion string
GAFs map[string]string
GAFsVersion string
MacZips map[string]string
MacZipsVersion string
SPKs map[string]map[string]string
+64 -24
View File
@@ -13,9 +13,11 @@ import (
"io"
"net"
"net/http"
"net/url"
"os"
"strings"
"tailscale.com/clientupdate/distsign"
"tailscale.com/types/logger"
)
@@ -40,38 +42,23 @@ func gokrazyUpdateFromURL(ctx context.Context, args GokrazyUpdateArgs) error {
if logf == nil {
logf = logger.Discard
}
if !args.AllowUnsigned {
return fmt.Errorf("signed GAF verification is not implemented yet; see https://github.com/tailscale/tailscale/issues/20002")
}
tmp, err := os.CreateTemp("", "tailscale-gokrazy-*.gaf")
if err != nil {
return err
}
tmpName := tmp.Name()
tmp.Close()
defer os.Remove(tmpName)
req, err := http.NewRequestWithContext(ctx, "GET", args.URL, nil)
if err != nil {
tmp.Close()
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
tmp.Close()
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
tmp.Close()
return fmt.Errorf("download GAF: %s", res.Status)
}
if _, err := io.Copy(tmp, res.Body); err != nil {
tmp.Close()
return err
}
if err := tmp.Close(); err != nil {
return err
if args.AllowUnsigned {
if err := downloadGAFUnverified(ctx, args.URL, tmpName); err != nil {
return err
}
} else {
if err := downloadGAFVerified(ctx, logf, args.URL, tmpName); err != nil {
return err
}
}
zr, err := zip.OpenReader(tmpName)
@@ -105,6 +92,59 @@ func gokrazyUpdateFromURL(ctx context.Context, args GokrazyUpdateArgs) error {
return nil
}
// downloadGAFUnverified saves the GAF at srcURL to dstPath without verifying a
// signature. It is used only when args.AllowUnsigned is set, for tests that
// serve the GAF from a fileserver that does not publish distsign.pub.
func downloadGAFUnverified(ctx context.Context, srcURL, dstPath string) error {
req, err := http.NewRequestWithContext(ctx, "GET", srcURL, nil)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf("download GAF: %s", res.Status)
}
f, err := os.Create(dstPath)
if err != nil {
return err
}
if _, err := io.Copy(f, res.Body); err != nil {
f.Close()
return err
}
return f.Close()
}
// downloadGAFVerified saves the GAF at srcURL to dstPath, verifying the
// detached ed25519 signature at "<srcURL>.sig" against the root signing keys
// embedded in this binary via the distsign package.
//
// The signing-key bundle distsign.pub and its signature distsign.pub.sig are
// fetched from the root of the server hosting srcURL.
func downloadGAFVerified(ctx context.Context, logf logger.Logf, srcURL, dstPath string) error {
u, err := url.Parse(srcURL)
if err != nil {
return fmt.Errorf("parsing GAF URL %q: %w", srcURL, err)
}
if u.Scheme == "" || u.Host == "" {
return fmt.Errorf("GAF URL %q is missing scheme or host", srcURL)
}
base := &url.URL{Scheme: u.Scheme, User: u.User, Host: u.Host}
path := strings.TrimPrefix(u.Path, "/")
if path == "" {
return fmt.Errorf("GAF URL %q has no path component", srcURL)
}
c, err := distsign.NewClient(logf, base.String())
if err != nil {
return err
}
return c.Download(ctx, path, dstPath)
}
func gokrazyHTTPClient() *http.Client {
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
+1 -4
View File
@@ -126,7 +126,7 @@ func gokrazyUpdateArgsFromMagicArg(args []string) (*clientupdate.GokrazyUpdateAr
fs.SetOutput(io.Discard)
// This flag path is exercised end-to-end by TestGokrazyUpdatesItselfToSameImage.
fs.StringVar(&updateURL, gokrazyUpdateFromURLMagicArg[2:], "", "URL of the Gokrazy archive format file to install")
fs.BoolVar(&unsigned, "unsigned", false, "allow an unsigned GAF; for tests only")
fs.BoolVar(&unsigned, "unsigned", false, "skip GAF signature verification; for tests only")
if err := fs.Parse(args); err != nil {
return nil, err
}
@@ -136,9 +136,6 @@ func gokrazyUpdateArgsFromMagicArg(args []string) (*clientupdate.GokrazyUpdateAr
if updateURL == "" {
return nil, nil
}
if !unsigned {
return nil, errors.New("signed GAF verification is not implemented yet; see https://github.com/tailscale/tailscale/issues/20002; pass --unsigned for test updates")
}
if !clientupdate.GokrazyUpdateFromURL.IsSet() {
return nil, errors.New("gokrazy update support is not linked into this binary")
}
+9 -1
View File
@@ -24,7 +24,15 @@
"tailscale.com/cmd/tailscale": {
"ExtraFilePaths": {
"/usr": "usr-dir"
}
},
"GoBuildTags": [
"ts_appliance"
]
},
"tailscale.com/cmd/tailscaled": {
"GoBuildTags": [
"ts_appliance"
]
}
},
"Environment": [
+9 -1
View File
@@ -24,7 +24,15 @@
"tailscale.com/cmd/tailscale": {
"ExtraFilePaths": {
"/usr": "usr-dir"
}
},
"GoBuildTags": [
"ts_appliance"
]
},
"tailscale.com/cmd/tailscaled": {
"GoBuildTags": [
"ts_appliance"
]
}
},
"Environment": [
+9 -1
View File
@@ -24,7 +24,15 @@
"tailscale.com/cmd/tailscale": {
"ExtraFilePaths": {
"/usr": "usr-dir"
}
},
"GoBuildTags": [
"ts_appliance"
]
},
"tailscale.com/cmd/tailscaled": {
"GoBuildTags": [
"ts_appliance"
]
}
},
"Environment": [
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux && ts_appliance
package hostinfo
func init() {
linuxBuildTagPackageType = "tsapp"
}