mirror of https://github.com/tailscale/tailscale/
release: open-source release build logic for unix packages
Updates tailscale/corp#9221 Signed-off-by: David Anderson <danderson@tailscale.com>pull/7369/head
parent
44e027abca
commit
fc4b25d9fd
@ -0,0 +1,134 @@
|
|||||||
|
// The dist command builds Tailscale release packages for distribution.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
|
"tailscale.com/release/dist"
|
||||||
|
"tailscale.com/release/dist/unixpkgs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var targets []dist.Target
|
||||||
|
targets = append(targets, unixpkgs.Targets()...)
|
||||||
|
sort.Slice(targets, func(i, j int) bool {
|
||||||
|
return targets[i].String() < targets[j].String()
|
||||||
|
})
|
||||||
|
|
||||||
|
rootCmd := &ffcli.Command{
|
||||||
|
Name: "dist",
|
||||||
|
ShortUsage: "dist [flags] <command> [command flags]",
|
||||||
|
ShortHelp: "Build tailscale release packages for distribution",
|
||||||
|
LongHelp: `For help on subcommands, add --help after: "dist list --help".`,
|
||||||
|
Subcommands: []*ffcli.Command{
|
||||||
|
{
|
||||||
|
Name: "list",
|
||||||
|
Exec: func(ctx context.Context, args []string) error {
|
||||||
|
return runList(ctx, args, targets)
|
||||||
|
},
|
||||||
|
ShortUsage: "dist list [target filters]",
|
||||||
|
ShortHelp: "List all available release targets.",
|
||||||
|
LongHelp: strings.TrimSpace(`
|
||||||
|
If filters are provided, only targets matching at least one filter are listed.
|
||||||
|
Filters can use glob patterns (* and ?).
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "build",
|
||||||
|
Exec: func(ctx context.Context, args []string) error {
|
||||||
|
return runBuild(ctx, args, targets)
|
||||||
|
},
|
||||||
|
ShortUsage: "dist build [target filters]",
|
||||||
|
ShortHelp: "Build release files",
|
||||||
|
FlagSet: (func() *flag.FlagSet {
|
||||||
|
fs := flag.NewFlagSet("build", flag.ExitOnError)
|
||||||
|
fs.StringVar(&buildArgs.manifest, "manifest", "", "manifest file to write")
|
||||||
|
return fs
|
||||||
|
})(),
|
||||||
|
LongHelp: strings.TrimSpace(`
|
||||||
|
If filters are provided, only targets matching at least one filter are built.
|
||||||
|
Filters can use glob patterns (* and ?).
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Exec: func(context.Context, []string) error { return flag.ErrHelp },
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rootCmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil && !errors.Is(err, flag.ErrHelp) {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runList(ctx context.Context, filters []string, targets []dist.Target) error {
|
||||||
|
tgts, err := dist.FilterTargets(targets, filters)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, tgt := range tgts {
|
||||||
|
fmt.Println(tgt)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildArgs struct {
|
||||||
|
manifest string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runBuild(ctx context.Context, filters []string, targets []dist.Target) error {
|
||||||
|
tgts, err := dist.FilterTargets(targets, filters)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(tgts) == 0 {
|
||||||
|
return errors.New("no targets matched (did you mean 'dist build all'?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
st := time.Now()
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting working directory: %w", err)
|
||||||
|
}
|
||||||
|
b, err := dist.NewBuild(wd, filepath.Join(wd, "dist"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating build context: %w", err)
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
out, err := b.Build(tgts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("building targets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if buildArgs.manifest != "" {
|
||||||
|
// Make the built paths relative to the manifest file.
|
||||||
|
manifest, err := filepath.Abs(buildArgs.manifest)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting absolute path of manifest: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Println(manifest)
|
||||||
|
fmt.Println(filepath.Join(b.Out, out[0]))
|
||||||
|
for i := range out {
|
||||||
|
rel, err := filepath.Rel(filepath.Dir(manifest), filepath.Join(b.Out, out[i]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("making path relative: %w", err)
|
||||||
|
}
|
||||||
|
out[i] = rel
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(manifest, []byte(strings.Join(out, "\n")), 0644); err != nil {
|
||||||
|
return fmt.Errorf("writing manifest: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Done! Took", time.Since(st))
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
|
||||||
|
deb-systemd-helper unmask 'tailscaled.service' >/dev/null || true
|
||||||
|
if deb-systemd-helper --quiet was-enabled 'tailscaled.service'; then
|
||||||
|
deb-systemd-helper enable 'tailscaled.service' >/dev/null || true
|
||||||
|
else
|
||||||
|
deb-systemd-helper update-state 'tailscaled.service' >/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d /run/systemd/system ]; then
|
||||||
|
systemctl --system daemon-reload >/dev/null || true
|
||||||
|
deb-systemd-invoke restart 'tailscaled.service' >/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
if [ -d /run/systemd/system ] ; then
|
||||||
|
systemctl --system daemon-reload >/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -x "/usr/bin/deb-systemd-helper" ]; then
|
||||||
|
if [ "$1" = "remove" ]; then
|
||||||
|
deb-systemd-helper mask 'tailscaled.service' >/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" = "purge" ]; then
|
||||||
|
deb-systemd-helper purge 'tailscaled.service' >/dev/null || true
|
||||||
|
deb-systemd-helper unmask 'tailscaled.service' >/dev/null || true
|
||||||
|
rm -rf /var/lib/tailscale
|
||||||
|
fi
|
||||||
|
fi
|
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
if [ "$1" = "remove" ]; then
|
||||||
|
if [ -d /run/systemd/system ]; then
|
||||||
|
deb-systemd-invoke stop 'tailscaled.service' >/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
@ -0,0 +1,268 @@
|
|||||||
|
// Package dist is a release artifact builder library.
|
||||||
|
package dist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"tailscale.com/util/multierr"
|
||||||
|
"tailscale.com/version/mkversion"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Target is something that can be build in a Build.
|
||||||
|
type Target interface {
|
||||||
|
String() string
|
||||||
|
Build(build *Build) ([]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Build is a build context for Targets.
|
||||||
|
type Build struct {
|
||||||
|
// Repo is a path to the root Go module for the build.
|
||||||
|
Repo string
|
||||||
|
// Tmp is a temporary directory that gets deleted when the Builder is closed.
|
||||||
|
Tmp string
|
||||||
|
// Out is where build artifacts are written.
|
||||||
|
Out string
|
||||||
|
// Go is the path to the Go binary to use for building.
|
||||||
|
Go string
|
||||||
|
// Version is the version info of the build.
|
||||||
|
Version mkversion.VersionInfo
|
||||||
|
|
||||||
|
// once is a cache of function invocations that should run once per process
|
||||||
|
// (for example building a helper docker container)
|
||||||
|
once once
|
||||||
|
|
||||||
|
extraMu sync.Mutex
|
||||||
|
extra map[any]any
|
||||||
|
|
||||||
|
goBuilds Memoize[string]
|
||||||
|
// When running `dist build all` on a cold Go build cache, the fanout of
|
||||||
|
// gooses and goarches results in a very large number of compile processes,
|
||||||
|
// which bogs down the build machine.
|
||||||
|
//
|
||||||
|
// This throttles the number of concurrent `go build` invocations to the
|
||||||
|
// number of CPU cores, which empirically keeps the builder responsive
|
||||||
|
// without impacting overall build time.
|
||||||
|
goBuildLimit chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuild creates a new Build rooted at repo, and writing artifacts to out.
|
||||||
|
func NewBuild(repo, out string) (*Build, error) {
|
||||||
|
if err := os.MkdirAll(out, 0750); err != nil {
|
||||||
|
return nil, fmt.Errorf("creating out dir: %w", err)
|
||||||
|
}
|
||||||
|
tmp, err := os.MkdirTemp("", "dist-*")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating tempdir: %w", err)
|
||||||
|
}
|
||||||
|
repo, err = findModRoot(repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("finding module root: %w", err)
|
||||||
|
}
|
||||||
|
goTool, err := findGo(repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("finding go binary: %w", err)
|
||||||
|
}
|
||||||
|
b := &Build{
|
||||||
|
Repo: repo,
|
||||||
|
Tmp: tmp,
|
||||||
|
Out: out,
|
||||||
|
Go: goTool,
|
||||||
|
Version: mkversion.Info(),
|
||||||
|
extra: map[any]any{},
|
||||||
|
goBuildLimit: make(chan struct{}, runtime.NumCPU()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close ends the build and cleans up temporary files.
|
||||||
|
func (b *Build) Close() error {
|
||||||
|
return os.RemoveAll(b.Tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build builds all targets concurrently.
|
||||||
|
func (b *Build) Build(targets []Target) (files []string, err error) {
|
||||||
|
if len(targets) == 0 {
|
||||||
|
return nil, errors.New("no targets specified")
|
||||||
|
}
|
||||||
|
log.Printf("Building %d targets: %v", len(targets), targets)
|
||||||
|
var (
|
||||||
|
wg sync.WaitGroup
|
||||||
|
errs = make([]error, len(targets))
|
||||||
|
buildFiles = make([][]string, len(targets))
|
||||||
|
)
|
||||||
|
for i, t := range targets {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int, t Target) {
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
errs[i] = err
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
fs, err := t.Build(b)
|
||||||
|
buildFiles[i] = fs
|
||||||
|
}(i, t)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for _, fs := range buildFiles {
|
||||||
|
files = append(files, fs...)
|
||||||
|
}
|
||||||
|
sort.Strings(files)
|
||||||
|
|
||||||
|
return files, multierr.New(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once runs fn if Once hasn't been called with name before.
|
||||||
|
func (b *Build) Once(name string, fn func() error) error {
|
||||||
|
return b.once.Do(name, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra returns a value from the build's extra state, creating it if necessary.
|
||||||
|
func (b *Build) Extra(key any, constructor func() any) any {
|
||||||
|
b.extraMu.Lock()
|
||||||
|
defer b.extraMu.Unlock()
|
||||||
|
ret, ok := b.extra[key]
|
||||||
|
if !ok {
|
||||||
|
ret = constructor()
|
||||||
|
b.extra[key] = ret
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoPkg returns the path on disk of pkg.
|
||||||
|
// The module of pkg must be imported in b.Repo's go.mod.
|
||||||
|
func (b *Build) GoPkg(pkg string) (string, error) {
|
||||||
|
bs, err := exec.Command(b.Go, "list", "-f", "{{.Dir}}", pkg).Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("finding package %q: %w", pkg, err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(bs)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TmpDir creates and returns a new empty temporary directory.
|
||||||
|
// The caller does not need to clean up the directory after use, it will get
|
||||||
|
// deleted by b.Close().
|
||||||
|
func (b *Build) TmpDir() string {
|
||||||
|
// Because we're creating all temp dirs in our parent temp dir, the only
|
||||||
|
// failures that can happen at this point are sequence breaks (e.g. if b.Tmp
|
||||||
|
// is deleted while stuff is still running). So, panic on error to slightly
|
||||||
|
// simplify callsites.
|
||||||
|
ret, err := os.MkdirTemp(b.Tmp, "")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("creating temp dir: %v", err))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildGoBinary builds the Go binary at path and returns the path to the
|
||||||
|
// binary. Builds are cached by path and env, so each build only happens once
|
||||||
|
// per process execution.
|
||||||
|
func (b *Build) BuildGoBinary(path string, env map[string]string) (string, error) {
|
||||||
|
err := b.Once("init-go", func() error {
|
||||||
|
log.Printf("Initializing Go toolchain")
|
||||||
|
// If the build is using a tool/go, it may need to download a toolchain
|
||||||
|
// and do other initialization. Running `go version` once takes care of
|
||||||
|
// all of that and avoids that initialization happening concurrently
|
||||||
|
// later on in builds.
|
||||||
|
_, err := exec.Command(b.Go, "version").Output()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buildKey := []any{"go-build", path, env}
|
||||||
|
return b.goBuilds.Do(buildKey, func() (string, error) {
|
||||||
|
b.goBuildLimit <- struct{}{}
|
||||||
|
defer func() { <-b.goBuildLimit }()
|
||||||
|
|
||||||
|
var envStrs []string
|
||||||
|
for k, v := range env {
|
||||||
|
envStrs = append(envStrs, k+"="+v)
|
||||||
|
}
|
||||||
|
sort.Strings(envStrs)
|
||||||
|
log.Printf("Building %s (with env %s)", path, strings.Join(envStrs, " "))
|
||||||
|
buildDir := b.TmpDir()
|
||||||
|
cmd := exec.Command(b.Go, "build", "-o", buildDir, path)
|
||||||
|
cmd.Dir = b.Repo
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
for k, v := range env {
|
||||||
|
cmd.Env = append(cmd.Env, k+"="+v)
|
||||||
|
}
|
||||||
|
cmd.Env = append(cmd.Env, "TS_USE_GOCROSS=1")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
out := filepath.Join(buildDir, filepath.Base(path))
|
||||||
|
if env["GOOS"] == "windows" || env["GOOS"] == "windowsgui" {
|
||||||
|
out += ".exe"
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func findModRoot(path string) (string, error) {
|
||||||
|
for {
|
||||||
|
modpath := filepath.Join(path, "go.mod")
|
||||||
|
if _, err := os.Stat(modpath); err == nil {
|
||||||
|
return path, nil
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
path = filepath.Dir(path)
|
||||||
|
if path == "/" {
|
||||||
|
return "", fmt.Errorf("no go.mod found in %q or any parent directory", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findGo(path string) (string, error) {
|
||||||
|
toolGo := filepath.Join(path, "tool/go")
|
||||||
|
if _, err := os.Stat(toolGo); err == nil {
|
||||||
|
return toolGo, nil
|
||||||
|
}
|
||||||
|
toolGo, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return toolGo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterTargets returns the subset of targets that match any of the filters.
|
||||||
|
// If filters is empty, returns all targets.
|
||||||
|
func FilterTargets(targets []Target, filters []string) ([]Target, error) {
|
||||||
|
var filts []*regexp.Regexp
|
||||||
|
for _, f := range filters {
|
||||||
|
if f == "all" {
|
||||||
|
return targets, nil
|
||||||
|
}
|
||||||
|
filt, err := regexp.Compile(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid filter %q: %w", f, err)
|
||||||
|
}
|
||||||
|
filts = append(filts, filt)
|
||||||
|
}
|
||||||
|
var ret []Target
|
||||||
|
for _, t := range targets {
|
||||||
|
for _, filt := range filts {
|
||||||
|
if filt.MatchString(t.String()) {
|
||||||
|
ret = append(ret, t)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package dist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"tailscale.com/util/deephash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MemoizedFn is a function that memoize.Do can call.
|
||||||
|
type MemoizedFn[T any] func() (T, error)
|
||||||
|
|
||||||
|
// Memoize runs MemoizedFns and remembers their results.
|
||||||
|
type Memoize[O any] struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
cond *sync.Cond
|
||||||
|
outs map[deephash.Sum]O
|
||||||
|
errs map[deephash.Sum]error
|
||||||
|
inflight map[deephash.Sum]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do runs fn and returns its result.
|
||||||
|
// fn is only run once per unique key. Subsequent Do calls with the same key
|
||||||
|
// return the memoized result of the first call, even if fn is a different
|
||||||
|
// function.
|
||||||
|
func (m *Memoize[O]) Do(key any, fn MemoizedFn[O]) (ret O, err error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if m.cond == nil {
|
||||||
|
m.cond = sync.NewCond(&m.mu)
|
||||||
|
m.outs = map[deephash.Sum]O{}
|
||||||
|
m.errs = map[deephash.Sum]error{}
|
||||||
|
m.inflight = map[deephash.Sum]bool{}
|
||||||
|
}
|
||||||
|
|
||||||
|
k := deephash.Hash(&key)
|
||||||
|
|
||||||
|
for m.inflight[k] {
|
||||||
|
m.cond.Wait()
|
||||||
|
}
|
||||||
|
if err := m.errs[k]; err != nil {
|
||||||
|
var ret O
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
if ret, ok := m.outs[k]; ok {
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.inflight[k] = true
|
||||||
|
m.mu.Unlock()
|
||||||
|
defer func() {
|
||||||
|
m.mu.Lock()
|
||||||
|
delete(m.inflight, k)
|
||||||
|
if err != nil {
|
||||||
|
m.errs[k] = err
|
||||||
|
} else {
|
||||||
|
m.outs[k] = ret
|
||||||
|
}
|
||||||
|
m.cond.Broadcast()
|
||||||
|
}()
|
||||||
|
|
||||||
|
ret, err = fn()
|
||||||
|
if err != nil {
|
||||||
|
var ret O
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// once is like memoize, but for functions that don't return non-error values.
|
||||||
|
type once struct {
|
||||||
|
m Memoize[any]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do runs fn.
|
||||||
|
// fn is only run once per unique key. Subsequent Do calls with the same key
|
||||||
|
// return the memoized result of the first call, even if fn is a different
|
||||||
|
// function.
|
||||||
|
func (o *once) Do(key any, fn func() error) error {
|
||||||
|
_, err := o.m.Do(key, func() (any, error) {
|
||||||
|
return nil, fn()
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
# $1 == 1 for initial installation.
|
||||||
|
# $1 == 2 for upgrades.
|
||||||
|
|
||||||
|
if [ $1 -eq 1 ] ; then
|
||||||
|
# Normally, the tailscale-relay package would request shutdown of
|
||||||
|
# its service before uninstallation. Unfortunately, the
|
||||||
|
# tailscale-relay package we distributed doesn't have those
|
||||||
|
# scriptlets. We definitely want relaynode to be stopped when
|
||||||
|
# installing tailscaled though, so we blindly try to turn off
|
||||||
|
# relaynode here.
|
||||||
|
#
|
||||||
|
# However, we also want this package installation to look like an
|
||||||
|
# upgrade from relaynode! Therefore, if relaynode is currently
|
||||||
|
# enabled, we want to also enable tailscaled. If relaynode is
|
||||||
|
# currently running, we also want to start tailscaled.
|
||||||
|
#
|
||||||
|
# If there doesn't seem to be an active or enabled relaynode on
|
||||||
|
# the system, we follow the RPM convention for package installs,
|
||||||
|
# which is to not enable or start the service.
|
||||||
|
relaynode_enabled=0
|
||||||
|
relaynode_running=0
|
||||||
|
if systemctl is-enabled tailscale-relay.service >/dev/null 2>&1; then
|
||||||
|
relaynode_enabled=1
|
||||||
|
fi
|
||||||
|
if systemctl is-active tailscale-relay.service >/dev/null 2>&1; then
|
||||||
|
relaynode_running=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl --no-reload disable tailscale-relay.service >/dev/null 2>&1 || :
|
||||||
|
systemctl stop tailscale-relay.service >/dev/null 2>&1 || :
|
||||||
|
|
||||||
|
if [ $relaynode_enabled -eq 1 ]; then
|
||||||
|
systemctl enable tailscaled.service >/dev/null 2>&1 || :
|
||||||
|
else
|
||||||
|
systemctl preset tailscaled.service >/dev/null 2>&1 || :
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $relaynode_running -eq 1 ]; then
|
||||||
|
systemctl start tailscaled.service >/dev/null 2>&1 || :
|
||||||
|
fi
|
||||||
|
fi
|
@ -0,0 +1,8 @@
|
|||||||
|
# $1 == 0 for uninstallation.
|
||||||
|
# $1 == 1 for removing old package during upgrade.
|
||||||
|
|
||||||
|
systemctl daemon-reload >/dev/null 2>&1 || :
|
||||||
|
if [ $1 -ge 1 ] ; then
|
||||||
|
# Package upgrade, not uninstall
|
||||||
|
systemctl try-restart tailscaled.service >/dev/null 2>&1 || :
|
||||||
|
fi
|
@ -0,0 +1,8 @@
|
|||||||
|
# $1 == 0 for uninstallation.
|
||||||
|
# $1 == 1 for removing old package during upgrade.
|
||||||
|
|
||||||
|
if [ $1 -eq 0 ] ; then
|
||||||
|
# Package removal, not upgrade
|
||||||
|
systemctl --no-reload disable tailscaled.service > /dev/null 2>&1 || :
|
||||||
|
systemctl stop tailscaled.service > /dev/null 2>&1 || :
|
||||||
|
fi
|
Loading…
Reference in New Issue