Files
tailscale/release/dist/unixpkgs/pkgs.go
T
Brad Fitzpatrick b0b1f0f566 go.mod: bump all direct deps to latest
This is the output of the new misc/bumpdeps tool (#21325) run with
--exclude-newer-than-days=7, which asks proxy.golang.org for the newest
version of every direct dependency, ignoring releases younger than a
week in favor of the newest older one, and runs a single go get.
gvisor tracks its "go" branch, wireguard-go its "tailscale" branch,
and golang-x-crypto its "main" branch (the proxy's @latest for it is
a stray v0.91.0 tag from 2024 that predates our acme fork changes).
Indirect deps only moved as far as MVS pulled them.

The week-long cooldown held back gvisor, the gokrazy modules,
chromedp/cdproto, and hashicorp/raft-boltdb/v2, whose only newer
versions are days old; they'll come along next time.

Several upstream changes needed small fixes: nfpm's PrepareForPackager
takes a modification time now (a zero time keeps the old behavior of
using the source file's mtime), esbuild's ServeOptions.Port became an
int while ServeResult.Host became a Hosts slice, client-go's
EventRecorder.Eventf is now recognized by vet as a printf wrapper (so
the k8s-operator calls that passed a preformatted message switch to
Event), google/nftables v0.3.0 reads back the kernel's
NF_NAT_RANGE_PROTO_SPECIFIED flag into a new expr.NAT.Specified field
(so the port map DNAT rule now sets it too or findRule never matches
the rule it just added), and staticcheck v0.8.1 knows encoding/json/v2's
embed tag option, so the two SA5008 suppressions for it are gone.

Two tests assumed old library behavior. client-go's fake clientset now
replays existing objects when a watch starts, as a real apiserver does,
so the k8s-proxy config test must tolerate the loader ignoring that
no-op event before the real reload arrives. fyne.io/systray moved its
dbusmenu object path and answers the first GetLayout with depth 1, so
the systray test now finds the menu via the item's Menu property and
polls until the submenu entries appear.

Then make tidy, make updatedeps, and make kube-generate-all (the
controller-gen bump to v0.22.0 changes doc strings, stops listing
top-level metadata as required, and crd-ref-docs now marks optional
fields).

Updates #8043

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I3f9a2c6e8b1d4705a9e2c7b8d1f4e6a0c2b5d8e3
2026-09-16 16:01:50 -07:00

500 lines
13 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package unixpkgs contains dist Targets for building unix Tailscale packages.
package unixpkgs
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"tailscale.com/release/dist"
)
type tgzTarget struct {
filenameArch string // arch to use in filename instead of deriving from goEnv["GOARCH"]
goEnv map[string]string
signer dist.Signer
}
func (t *tgzTarget) arch() string {
if t.filenameArch != "" {
return t.filenameArch
}
return t.goEnv["GOARCH"]
}
func (t *tgzTarget) os() string {
return t.goEnv["GOOS"]
}
func (t *tgzTarget) String() string {
return fmt.Sprintf("%s/%s/tgz", t.os(), t.arch())
}
func (t *tgzTarget) Build(b *dist.Build) ([]string, error) {
var filename string
if t.goEnv["GOOS"] == "linux" {
// Linux used to be the only tgz architecture, so we didn't put the OS
// name in the filename.
filename = fmt.Sprintf("tailscale_%s_%s.tgz", b.Version.Short, t.arch())
} else {
filename = fmt.Sprintf("tailscale_%s_%s_%s.tgz", b.Version.Short, t.os(), t.arch())
}
if err := b.BuildWebClientAssets(); err != nil {
return nil, err
}
ts, err := b.BuildGoBinary("tailscale.com/cmd/tailscale", t.goEnv)
if err != nil {
return nil, err
}
tsd, err := b.BuildGoBinary("tailscale.com/cmd/tailscaled", t.goEnv)
if err != nil {
return nil, err
}
log.Printf("Building %s", filename)
out := filepath.Join(b.Out, filename)
f, err := os.Create(out)
if err != nil {
return nil, err
}
defer f.Close()
gw := gzip.NewWriter(f)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
addFile := func(src, dst string, mode int64) error {
f, err := os.Open(src)
if err != nil {
return err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return err
}
hdr := &tar.Header{
Name: dst,
Size: fi.Size(),
Mode: mode,
ModTime: b.Time,
Uid: 0,
Gid: 0,
Uname: "root",
Gname: "root",
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
if _, err = io.Copy(tw, f); err != nil {
return err
}
return nil
}
addDir := func(name string) error {
hdr := &tar.Header{
Name: name + "/",
Mode: 0755,
ModTime: b.Time,
Uid: 0,
Gid: 0,
Uname: "root",
Gname: "root",
}
return tw.WriteHeader(hdr)
}
dir := strings.TrimSuffix(filename, ".tgz")
if err := addDir(dir); err != nil {
return nil, err
}
if err := addFile(tsd, filepath.Join(dir, "tailscaled"), 0755); err != nil {
return nil, err
}
if err := addFile(ts, filepath.Join(dir, "tailscale"), 0755); err != nil {
return nil, err
}
if t.os() == "linux" {
dir = filepath.Join(dir, "systemd")
if err := addDir(dir); err != nil {
return nil, err
}
tailscaledDir, err := b.GoPkg("tailscale.com/cmd/tailscaled")
if err != nil {
return nil, err
}
if err := addFile(filepath.Join(tailscaledDir, "tailscaled.service"), filepath.Join(dir, "tailscaled.service"), 0644); err != nil {
return nil, err
}
if err := addFile(filepath.Join(tailscaledDir, "tailscaled.defaults"), filepath.Join(dir, "tailscaled.defaults"), 0644); err != nil {
return nil, err
}
if err := addFile(filepath.Join(tailscaledDir, "tailscale-online.target"), filepath.Join(dir, "tailscale-online.target"), 0644); err != nil {
return nil, err
}
if err := addFile(filepath.Join(tailscaledDir, "tailscale-wait-online.service"), filepath.Join(dir, "tailscale-wait-online.service"), 0644); err != nil {
return nil, err
}
}
if err := tw.Close(); err != nil {
return nil, err
}
if err := gw.Close(); err != nil {
return nil, err
}
if err := f.Close(); err != nil {
return nil, err
}
files := []string{filename}
if t.signer != nil {
outSig := out + ".sig"
if err := t.signer.SignFile(out, outSig); err != nil {
return nil, err
}
files = append(files, filepath.Base(outSig))
}
return files, nil
}
type debTarget struct {
goEnv map[string]string
}
func (t *debTarget) os() string {
return t.goEnv["GOOS"]
}
func (t *debTarget) arch() string {
return t.goEnv["GOARCH"]
}
func (t *debTarget) String() string {
return fmt.Sprintf("linux/%s/deb", t.goEnv["GOARCH"])
}
func (t *debTarget) Build(b *dist.Build) ([]string, error) {
if t.os() != "linux" {
return nil, errors.New("deb only supported on linux")
}
if err := b.BuildWebClientAssets(); err != nil {
return nil, err
}
ts, err := b.BuildGoBinary("tailscale.com/cmd/tailscale", t.goEnv)
if err != nil {
return nil, err
}
tsd, err := b.BuildGoBinary("tailscale.com/cmd/tailscaled", t.goEnv)
if err != nil {
return nil, err
}
tailscaledDir, err := b.GoPkg("tailscale.com/cmd/tailscaled")
if err != nil {
return nil, err
}
repoDir, err := b.GoPkg("tailscale.com")
if err != nil {
return nil, err
}
arch := debArch(t.arch())
contents, err := files.PrepareForPackager(files.Contents{
&files.Content{
Type: files.TypeFile,
Source: ts,
Destination: "/usr/bin/tailscale",
},
&files.Content{
Type: files.TypeFile,
Source: tsd,
Destination: "/usr/sbin/tailscaled",
},
&files.Content{
Type: files.TypeFile,
Source: filepath.Join(tailscaledDir, "tailscaled.service"),
Destination: "/lib/systemd/system/tailscaled.service",
},
&files.Content{
Type: files.TypeFile,
Source: filepath.Join(tailscaledDir, "tailscale-online.target"),
Destination: "/lib/systemd/system/tailscale-online.target",
},
&files.Content{
Type: files.TypeFile,
Source: filepath.Join(tailscaledDir, "tailscale-wait-online.service"),
Destination: "/lib/systemd/system/tailscale-wait-online.service",
},
&files.Content{
Type: files.TypeConfigNoReplace,
Source: filepath.Join(tailscaledDir, "tailscaled.defaults"),
Destination: "/etc/default/tailscaled",
},
}, 0, "deb", false, time.Time{})
if err != nil {
return nil, err
}
info := nfpm.WithDefaults(&nfpm.Info{
Name: "tailscale",
Arch: arch,
Platform: "linux",
Version: b.Version.Short,
Maintainer: "Tailscale Inc <info@tailscale.com>",
Description: "The easiest, most secure, cross platform way to use WireGuard + oauth2 + 2FA/SSO",
Homepage: "https://www.tailscale.com",
License: "MIT",
Section: "net",
Priority: "extra",
Overridables: nfpm.Overridables{
Contents: contents,
Scripts: nfpm.Scripts{
PostInstall: filepath.Join(repoDir, "release/deb/debian.postinst.sh"),
PreRemove: filepath.Join(repoDir, "release/deb/debian.prerm.sh"),
PostRemove: filepath.Join(repoDir, "release/deb/debian.postrm.sh"),
},
Depends: []string{
// iptables is almost always required but not strictly needed.
// Even if you can technically run Tailscale without it (by
// manually configuring nftables or userspace mode), we still
// mark this as "Depends" because our previous experiment in
// https://github.com/tailscale/tailscale/issues/9236 of making
// it only Recommends caused too many problems. Until our
// nftables table is more mature, we'd rather err on the side of
// wasting a little disk by including iptables for people who
// might not need it rather than handle reports of it being
// missing.
"iptables",
},
Recommends: []string{
"tailscale-archive-keyring (>= 1.35.181)",
// The "ip" command isn't needed since 2021-11-01 in
// 408b0923a61972ed but kept as an option as of
// 2021-11-18 in d24ed3f68e35e802d531371. See
// https://github.com/tailscale/tailscale/issues/391.
// We keep it recommended because it's usually
// installed anyway and it's useful for debugging. But
// we can live without it, so it's not Depends.
"iproute2",
},
Replaces: []string{"tailscale-relay"},
Conflicts: []string{"tailscale-relay"},
},
})
pkg, err := nfpm.Get("deb")
if err != nil {
return nil, err
}
filename := fmt.Sprintf("tailscale_%s_%s.deb", b.Version.Short, arch)
log.Printf("Building %s", filename)
f, err := os.Create(filepath.Join(b.Out, filename))
if err != nil {
return nil, err
}
defer f.Close()
if err := pkg.Package(info, f); err != nil {
return nil, err
}
if err := f.Close(); err != nil {
return nil, err
}
return []string{filename}, nil
}
type rpmTarget struct {
goEnv map[string]string
signer dist.Signer
}
func (t *rpmTarget) os() string {
return t.goEnv["GOOS"]
}
func (t *rpmTarget) arch() string {
return t.goEnv["GOARCH"]
}
func (t *rpmTarget) String() string {
return fmt.Sprintf("linux/%s/rpm", t.arch())
}
func (t *rpmTarget) Build(b *dist.Build) ([]string, error) {
if t.os() != "linux" {
return nil, errors.New("rpm only supported on linux")
}
if err := b.BuildWebClientAssets(); err != nil {
return nil, err
}
ts, err := b.BuildGoBinary("tailscale.com/cmd/tailscale", t.goEnv)
if err != nil {
return nil, err
}
tsd, err := b.BuildGoBinary("tailscale.com/cmd/tailscaled", t.goEnv)
if err != nil {
return nil, err
}
tailscaledDir, err := b.GoPkg("tailscale.com/cmd/tailscaled")
if err != nil {
return nil, err
}
repoDir, err := b.GoPkg("tailscale.com")
if err != nil {
return nil, err
}
arch := rpmArch(t.arch())
contents, err := files.PrepareForPackager(files.Contents{
&files.Content{
Type: files.TypeFile,
Source: ts,
Destination: "/usr/bin/tailscale",
},
&files.Content{
Type: files.TypeFile,
Source: tsd,
Destination: "/usr/sbin/tailscaled",
},
&files.Content{
Type: files.TypeFile,
Source: filepath.Join(tailscaledDir, "tailscaled.service"),
Destination: "/lib/systemd/system/tailscaled.service",
},
&files.Content{
Type: files.TypeFile,
Source: filepath.Join(tailscaledDir, "tailscale-online.target"),
Destination: "/lib/systemd/system/tailscale-online.target",
},
&files.Content{
Type: files.TypeFile,
Source: filepath.Join(tailscaledDir, "tailscale-wait-online.service"),
Destination: "/lib/systemd/system/tailscale-wait-online.service",
},
&files.Content{
Type: files.TypeConfigNoReplace,
Source: filepath.Join(tailscaledDir, "tailscaled.defaults"),
Destination: "/etc/default/tailscaled",
},
// SELinux policy on e.g. CentOS 8 forbids writing to /var/cache.
// Creating an empty directory at install time resolves this issue.
&files.Content{
Type: files.TypeDir,
Destination: "/var/cache/tailscale",
},
}, 0, "rpm", false, time.Time{})
if err != nil {
return nil, err
}
info := nfpm.WithDefaults(&nfpm.Info{
Name: "tailscale",
Arch: arch,
Platform: "linux",
Version: b.Version.Short,
Maintainer: "Tailscale Inc <info@tailscale.com>",
Description: "The easiest, most secure, cross platform way to use WireGuard + oauth2 + 2FA/SSO",
Homepage: "https://www.tailscale.com",
License: "MIT",
Overridables: nfpm.Overridables{
Contents: contents,
Scripts: nfpm.Scripts{
PostInstall: filepath.Join(repoDir, "release/rpm/rpm.postinst.sh"),
PreRemove: filepath.Join(repoDir, "release/rpm/rpm.prerm.sh"),
PostRemove: filepath.Join(repoDir, "release/rpm/rpm.postrm.sh"),
},
Depends: []string{"iptables", "iproute"},
Replaces: []string{"tailscale-relay"},
Conflicts: []string{"tailscale-relay"},
RPM: nfpm.RPM{
Group: "Network",
Signature: nfpm.RPMSignature{
PackageSignature: nfpm.PackageSignature{
SignFn: t.signer,
},
},
},
},
})
pkg, err := nfpm.Get("rpm")
if err != nil {
return nil, err
}
filename := fmt.Sprintf("tailscale_%s_%s.rpm", b.Version.Short, arch)
log.Printf("Building %s", filename)
f, err := os.Create(filepath.Join(b.Out, filename))
if err != nil {
return nil, err
}
defer f.Close()
if err := pkg.Package(info, f); err != nil {
return nil, err
}
if err := f.Close(); err != nil {
return nil, err
}
return []string{filename}, nil
}
// debArch returns the debian arch name for the given Go arch name.
// nfpm also does this translation internally, but we need to do it outside nfpm
// because we also need the filename to be correct.
func debArch(arch string) string {
switch arch {
case "386":
return "i386"
case "arm":
// TODO: this is supposed to be "armel" for GOARM=5, and "armhf" for
// GOARM=6 and 7. But we have some tech debt to pay off here before we
// can ship more than 1 ARM deb, so for now match redo's behavior of
// shipping armv5 binaries in an armv7 trenchcoat.
return "armhf"
case "mipsle":
return "mipsel"
case "mips64le":
return "mips64el"
default:
return arch
}
}
// rpmArch returns the RPM arch name for the given Go arch name.
// nfpm also does this translation internally, but we need to do it outside nfpm
// because we also need the filename to be correct.
func rpmArch(arch string) string {
switch arch {
case "amd64":
return "x86_64"
case "386":
return "i386"
case "arm":
return "armv7hl"
case "arm64":
return "aarch64"
case "mipsle":
return "mipsel"
case "mips64le":
return "mips64el"
default:
return arch
}
}