mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-12 05:37:32 +00:00
release: open-source release build logic for unix packages
Updates tailscale/corp#9221 Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:

committed by
Dave Anderson

parent
44e027abca
commit
fc4b25d9fd
375
release/dist/unixpkgs/pkgs.go
vendored
Normal file
375
release/dist/unixpkgs/pkgs.go
vendored
Normal file
@@ -0,0 +1,375 @@
|
||||
// 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"
|
||||
"tailscale.com/release/dist"
|
||||
)
|
||||
|
||||
type tgzTarget struct {
|
||||
filenameArch string // arch to use in filename instead of deriving from goenv["GOARCH"]
|
||||
goenv map[string]string
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
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()
|
||||
|
||||
buildTime := time.Now()
|
||||
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: buildTime,
|
||||
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: buildTime,
|
||||
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 := 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
|
||||
}
|
||||
|
||||
return []string{filename}, 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")
|
||||
}
|
||||
|
||||
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())
|
||||
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{
|
||||
Files: map[string]string{
|
||||
ts: "/usr/bin/tailscale",
|
||||
tsd: "/usr/sbin/tailscaled",
|
||||
filepath.Join(tailscaledDir, "tailscaled.service"): "/lib/systemd/system/tailscaled.service",
|
||||
},
|
||||
ConfigFiles: map[string]string{
|
||||
filepath.Join(tailscaledDir, "tailscaled.defaults"): "/etc/default/tailscaled",
|
||||
},
|
||||
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", "iproute2"},
|
||||
Recommends: []string{"tailscale-archive-keyring (>= 1.35.181)"},
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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())
|
||||
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{
|
||||
Files: map[string]string{
|
||||
ts: "/usr/bin/tailscale",
|
||||
tsd: "/usr/sbin/tailscaled",
|
||||
filepath.Join(tailscaledDir, "tailscaled.service"): "/lib/systemd/system/tailscaled.service",
|
||||
},
|
||||
ConfigFiles: map[string]string{
|
||||
filepath.Join(tailscaledDir, "tailscaled.defaults"): "/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.
|
||||
EmptyFolders: []string{"/var/cache/tailscale"},
|
||||
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",
|
||||
},
|
||||
},
|
||||
})
|
||||
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"
|
||||
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"
|
||||
default:
|
||||
return arch
|
||||
}
|
||||
}
|
116
release/dist/unixpkgs/targets.go
vendored
Normal file
116
release/dist/unixpkgs/targets.go
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package unixpkgs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/release/dist"
|
||||
|
||||
_ "github.com/goreleaser/nfpm/deb"
|
||||
_ "github.com/goreleaser/nfpm/rpm"
|
||||
)
|
||||
|
||||
func Targets() []dist.Target {
|
||||
var ret []dist.Target
|
||||
for goosgoarch := range tarballs {
|
||||
goos, goarch := splitGoosGoarch(goosgoarch)
|
||||
ret = append(ret, &tgzTarget{
|
||||
goenv: map[string]string{
|
||||
"GOOS": goos,
|
||||
"GOARCH": goarch,
|
||||
},
|
||||
})
|
||||
}
|
||||
for goosgoarch := range debs {
|
||||
goos, goarch := splitGoosGoarch(goosgoarch)
|
||||
ret = append(ret, &debTarget{
|
||||
goenv: map[string]string{
|
||||
"GOOS": goos,
|
||||
"GOARCH": goarch,
|
||||
},
|
||||
})
|
||||
}
|
||||
for goosgoarch := range rpms {
|
||||
goos, goarch := splitGoosGoarch(goosgoarch)
|
||||
ret = append(ret, &rpmTarget{
|
||||
goenv: map[string]string{
|
||||
"GOOS": goos,
|
||||
"GOARCH": goarch,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Special case: AMD Geode is 386 with softfloat. Tarballs only since it's
|
||||
// an ancient architecture.
|
||||
ret = append(ret, &tgzTarget{
|
||||
filenameArch: "geode",
|
||||
goenv: map[string]string{
|
||||
"GOOS": "linux",
|
||||
"GOARCH": "386",
|
||||
"GO386": "softfloat",
|
||||
},
|
||||
})
|
||||
|
||||
sort.Slice(ret, func(i, j int) bool {
|
||||
return ret[i].String() < ret[j].String()
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
var (
|
||||
tarballs = map[string]bool{
|
||||
"linux/386": true,
|
||||
"linux/amd64": true,
|
||||
"linux/arm": true,
|
||||
"linux/arm64": true,
|
||||
"linux/mips64": true,
|
||||
"linux/mips64le": true,
|
||||
"linux/mips": true,
|
||||
"linux/mipsle": true,
|
||||
"linux/riscv64": true,
|
||||
// TODO: more tarballs we could distribute, but don't currently. Leaving
|
||||
// out for initial parity with redo.
|
||||
// "darwin/amd64": true,
|
||||
// "darwin/arm64": true,
|
||||
// "freebsd/amd64": true,
|
||||
// "openbsd/amd64": true,
|
||||
}
|
||||
|
||||
debs = map[string]bool{
|
||||
"linux/386": true,
|
||||
"linux/amd64": true,
|
||||
"linux/arm": true,
|
||||
"linux/arm64": true,
|
||||
"linux/riscv64": true,
|
||||
// TODO: maybe mipses, we accidentally started building them at some
|
||||
// point even though they probably don't work right.
|
||||
// "linux/mips": true,
|
||||
// "linux/mipsle": true,
|
||||
// "linux/mips64": true,
|
||||
// "linux/mips64le": true,
|
||||
}
|
||||
|
||||
rpms = map[string]bool{
|
||||
"linux/386": true,
|
||||
"linux/amd64": true,
|
||||
"linux/arm": true,
|
||||
"linux/arm64": true,
|
||||
"linux/riscv64": true,
|
||||
// TODO: maybe mipses, we accidentally started building them at some
|
||||
// point even though they probably don't work right.
|
||||
// "linux/mips": true,
|
||||
// "linux/mipsle": true,
|
||||
// "linux/mips64": true,
|
||||
// "linux/mips64le": true,
|
||||
}
|
||||
)
|
||||
|
||||
func splitGoosGoarch(s string) (string, string) {
|
||||
goos, goarch, ok := strings.Cut(s, "/")
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("invalid target %q", s))
|
||||
}
|
||||
return goos, goarch
|
||||
}
|
Reference in New Issue
Block a user