mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-18 20:51:45 +00:00
cmd/containerboot: add EXPERIMENTAL_TS_CONFIGFILE_PATH env var to allow passing tailscaled config in a file (#10759)
* cmd/containerboot: optionally configure tailscaled with a configfile. If EXPERIMENTAL_TS_CONFIGFILE_PATH env var is set, only run tailscaled with the provided config file. Do not run 'tailscale up' or 'tailscale set'. * cmd/containerboot: store containerboot accept_dns val in bool pointer So that we can distinguish between the value being set to false explicitly bs being unset. Signed-off-by: Irbe Krumina <irbe@tailscale.com>
This commit is contained in:
parent
c05c4bdce4
commit
133699284e
@ -48,6 +48,13 @@
|
|||||||
// ${TS_CERT_DOMAIN}, it will be replaced with the value of the available FQDN.
|
// ${TS_CERT_DOMAIN}, it will be replaced with the value of the available FQDN.
|
||||||
// It cannot be used in conjunction with TS_DEST_IP. The file is watched for changes,
|
// It cannot be used in conjunction with TS_DEST_IP. The file is watched for changes,
|
||||||
// and will be re-applied when it changes.
|
// and will be re-applied when it changes.
|
||||||
|
// - EXPERIMENTAL_TS_CONFIGFILE_PATH: if specified, a path to tailscaled
|
||||||
|
// config. If this is set, TS_HOSTNAME, TS_EXTRA_ARGS, TS_AUTHKEY,
|
||||||
|
// TS_ROUTES, TS_ACCEPT_DNS env vars must not be set. If this is set,
|
||||||
|
// containerboot only runs `tailscaled --config <path-to-this-configfile>`
|
||||||
|
// and not `tailscale up` or `tailscale set`.
|
||||||
|
// The config file contents are currently read once on container start.
|
||||||
|
// NB: This env var is currently experimental and the logic will likely change!
|
||||||
//
|
//
|
||||||
// When running on Kubernetes, containerboot defaults to storing state in the
|
// When running on Kubernetes, containerboot defaults to storing state in the
|
||||||
// "tailscale" kube secret. To store state on local disk instead, set
|
// "tailscale" kube secret. To store state on local disk instead, set
|
||||||
@ -83,6 +90,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/client/tailscale"
|
"tailscale.com/client/tailscale"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
|
"tailscale.com/ipn/conffile"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/types/ptr"
|
"tailscale.com/types/ptr"
|
||||||
@ -102,39 +110,29 @@ func main() {
|
|||||||
tailscale.I_Acknowledge_This_API_Is_Unstable = true
|
tailscale.I_Acknowledge_This_API_Is_Unstable = true
|
||||||
|
|
||||||
cfg := &settings{
|
cfg := &settings{
|
||||||
AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
|
AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
|
||||||
Hostname: defaultEnv("TS_HOSTNAME", ""),
|
Hostname: defaultEnv("TS_HOSTNAME", ""),
|
||||||
Routes: defaultEnvPointer("TS_ROUTES"),
|
Routes: defaultEnvStringPointer("TS_ROUTES"),
|
||||||
ServeConfigPath: defaultEnv("TS_SERVE_CONFIG", ""),
|
ServeConfigPath: defaultEnv("TS_SERVE_CONFIG", ""),
|
||||||
ProxyTo: defaultEnv("TS_DEST_IP", ""),
|
ProxyTo: defaultEnv("TS_DEST_IP", ""),
|
||||||
TailnetTargetIP: defaultEnv("TS_TAILNET_TARGET_IP", ""),
|
TailnetTargetIP: defaultEnv("TS_TAILNET_TARGET_IP", ""),
|
||||||
TailnetTargetFQDN: defaultEnv("TS_TAILNET_TARGET_FQDN", ""),
|
TailnetTargetFQDN: defaultEnv("TS_TAILNET_TARGET_FQDN", ""),
|
||||||
DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
|
DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
|
||||||
ExtraArgs: defaultEnv("TS_EXTRA_ARGS", ""),
|
ExtraArgs: defaultEnv("TS_EXTRA_ARGS", ""),
|
||||||
InKubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "",
|
InKubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "",
|
||||||
UserspaceMode: defaultBool("TS_USERSPACE", true),
|
UserspaceMode: defaultBool("TS_USERSPACE", true),
|
||||||
StateDir: defaultEnv("TS_STATE_DIR", ""),
|
StateDir: defaultEnv("TS_STATE_DIR", ""),
|
||||||
AcceptDNS: defaultBool("TS_ACCEPT_DNS", false),
|
AcceptDNS: defaultEnvBoolPointer("TS_ACCEPT_DNS"),
|
||||||
KubeSecret: defaultEnv("TS_KUBE_SECRET", "tailscale"),
|
KubeSecret: defaultEnv("TS_KUBE_SECRET", "tailscale"),
|
||||||
SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""),
|
SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""),
|
||||||
HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""),
|
HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""),
|
||||||
Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"),
|
Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"),
|
||||||
AuthOnce: defaultBool("TS_AUTH_ONCE", false),
|
AuthOnce: defaultBool("TS_AUTH_ONCE", false),
|
||||||
Root: defaultEnv("TS_TEST_ONLY_ROOT", "/"),
|
Root: defaultEnv("TS_TEST_ONLY_ROOT", "/"),
|
||||||
|
TailscaledConfigFilePath: defaultEnv("EXPERIMENTAL_TS_CONFIGFILE_PATH", ""),
|
||||||
}
|
}
|
||||||
|
if err := cfg.validate(); err != nil {
|
||||||
if cfg.ProxyTo != "" && cfg.UserspaceMode {
|
log.Fatalf("invalid configuration: %v", err)
|
||||||
log.Fatal("TS_DEST_IP is not supported with TS_USERSPACE")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.TailnetTargetIP != "" && cfg.UserspaceMode {
|
|
||||||
log.Fatal("TS_TAILNET_TARGET_IP is not supported with TS_USERSPACE")
|
|
||||||
}
|
|
||||||
if cfg.TailnetTargetFQDN != "" && cfg.UserspaceMode {
|
|
||||||
log.Fatal("TS_TAILNET_TARGET_FQDN is not supported with TS_USERSPACE")
|
|
||||||
}
|
|
||||||
if cfg.TailnetTargetFQDN != "" && cfg.TailnetTargetIP != "" {
|
|
||||||
log.Fatal("Both TS_TAILNET_TARGET_IP and TS_TAILNET_FQDN cannot be set")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.UserspaceMode {
|
if !cfg.UserspaceMode {
|
||||||
@ -171,7 +169,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
cfg.KubernetesCanPatch = canPatch
|
cfg.KubernetesCanPatch = canPatch
|
||||||
|
|
||||||
if cfg.AuthKey == "" {
|
if cfg.AuthKey == "" && !isOneStepConfig(cfg) {
|
||||||
key, err := findKeyInKubeSecret(bootCtx, cfg.KubeSecret)
|
key, err := findKeyInKubeSecret(bootCtx, cfg.KubeSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Getting authkey from kube secret: %v", err)
|
log.Fatalf("Getting authkey from kube secret: %v", err)
|
||||||
@ -253,7 +251,7 @@ func main() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.AuthOnce {
|
if isTwoStepConfigAlwaysAuth(cfg) {
|
||||||
if err := authTailscale(); err != nil {
|
if err := authTailscale(); err != nil {
|
||||||
log.Fatalf("failed to auth tailscale: %v", err)
|
log.Fatalf("failed to auth tailscale: %v", err)
|
||||||
}
|
}
|
||||||
@ -269,6 +267,13 @@ authLoop:
|
|||||||
if n.State != nil {
|
if n.State != nil {
|
||||||
switch *n.State {
|
switch *n.State {
|
||||||
case ipn.NeedsLogin:
|
case ipn.NeedsLogin:
|
||||||
|
if isOneStepConfig(cfg) {
|
||||||
|
// This could happen if this is the
|
||||||
|
// first time tailscaled was run for
|
||||||
|
// this device and the auth key was not
|
||||||
|
// passed via the configfile.
|
||||||
|
log.Fatalf("invalid state: tailscaled daemon started with a config file, but tailscale is not logged in: ensure you pass a valid auth key in the config file.")
|
||||||
|
}
|
||||||
if err := authTailscale(); err != nil {
|
if err := authTailscale(); err != nil {
|
||||||
log.Fatalf("failed to auth tailscale: %v", err)
|
log.Fatalf("failed to auth tailscale: %v", err)
|
||||||
}
|
}
|
||||||
@ -293,7 +298,7 @@ authLoop:
|
|||||||
ctx, cancel := contextWithExitSignalWatch()
|
ctx, cancel := contextWithExitSignalWatch()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if cfg.AuthOnce {
|
if isTwoStepConfigAuthOnce(cfg) {
|
||||||
// Now that we are authenticated, we can set/reset any of the
|
// Now that we are authenticated, we can set/reset any of the
|
||||||
// settings that we need to.
|
// settings that we need to.
|
||||||
if err := tailscaleSet(ctx, cfg); err != nil {
|
if err := tailscaleSet(ctx, cfg); err != nil {
|
||||||
@ -309,7 +314,7 @@ authLoop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.InKubernetes && cfg.KubeSecret != "" && cfg.KubernetesCanPatch && cfg.AuthOnce {
|
if cfg.InKubernetes && cfg.KubeSecret != "" && cfg.KubernetesCanPatch && isTwoStepConfigAuthOnce(cfg) {
|
||||||
// We were told to only auth once, so any secret-bound
|
// We were told to only auth once, so any secret-bound
|
||||||
// authkey is no longer needed. We don't strictly need to
|
// authkey is no longer needed. We don't strictly need to
|
||||||
// wipe it, but it's good hygiene.
|
// wipe it, but it's good hygiene.
|
||||||
@ -634,6 +639,9 @@ func tailscaledArgs(cfg *settings) []string {
|
|||||||
if cfg.HTTPProxyAddr != "" {
|
if cfg.HTTPProxyAddr != "" {
|
||||||
args = append(args, "--outbound-http-proxy-listen="+cfg.HTTPProxyAddr)
|
args = append(args, "--outbound-http-proxy-listen="+cfg.HTTPProxyAddr)
|
||||||
}
|
}
|
||||||
|
if cfg.TailscaledConfigFilePath != "" {
|
||||||
|
args = append(args, "--config="+cfg.TailscaledConfigFilePath)
|
||||||
|
}
|
||||||
if cfg.DaemonExtraArgs != "" {
|
if cfg.DaemonExtraArgs != "" {
|
||||||
args = append(args, strings.Fields(cfg.DaemonExtraArgs)...)
|
args = append(args, strings.Fields(cfg.DaemonExtraArgs)...)
|
||||||
}
|
}
|
||||||
@ -644,7 +652,7 @@ func tailscaledArgs(cfg *settings) []string {
|
|||||||
// if TS_AUTH_ONCE is set, only the first time containerboot starts.
|
// if TS_AUTH_ONCE is set, only the first time containerboot starts.
|
||||||
func tailscaleUp(ctx context.Context, cfg *settings) error {
|
func tailscaleUp(ctx context.Context, cfg *settings) error {
|
||||||
args := []string{"--socket=" + cfg.Socket, "up"}
|
args := []string{"--socket=" + cfg.Socket, "up"}
|
||||||
if cfg.AcceptDNS {
|
if cfg.AcceptDNS != nil && *cfg.AcceptDNS {
|
||||||
args = append(args, "--accept-dns=true")
|
args = append(args, "--accept-dns=true")
|
||||||
} else {
|
} else {
|
||||||
args = append(args, "--accept-dns=false")
|
args = append(args, "--accept-dns=false")
|
||||||
@ -680,7 +688,7 @@ func tailscaleUp(ctx context.Context, cfg *settings) error {
|
|||||||
// node is in Running state and only if TS_AUTH_ONCE is set.
|
// node is in Running state and only if TS_AUTH_ONCE is set.
|
||||||
func tailscaleSet(ctx context.Context, cfg *settings) error {
|
func tailscaleSet(ctx context.Context, cfg *settings) error {
|
||||||
args := []string{"--socket=" + cfg.Socket, "set"}
|
args := []string{"--socket=" + cfg.Socket, "set"}
|
||||||
if cfg.AcceptDNS {
|
if cfg.AcceptDNS != nil && *cfg.AcceptDNS {
|
||||||
args = append(args, "--accept-dns=true")
|
args = append(args, "--accept-dns=true")
|
||||||
} else {
|
} else {
|
||||||
args = append(args, "--accept-dns=false")
|
args = append(args, "--accept-dns=false")
|
||||||
@ -873,21 +881,46 @@ type settings struct {
|
|||||||
// TailnetTargetFQDN is an MagicDNS name to which all incoming
|
// TailnetTargetFQDN is an MagicDNS name to which all incoming
|
||||||
// non-Tailscale traffic should be proxied. This must be a full Tailnet
|
// non-Tailscale traffic should be proxied. This must be a full Tailnet
|
||||||
// node FQDN.
|
// node FQDN.
|
||||||
TailnetTargetFQDN string
|
TailnetTargetFQDN string
|
||||||
ServeConfigPath string
|
ServeConfigPath string
|
||||||
DaemonExtraArgs string
|
DaemonExtraArgs string
|
||||||
ExtraArgs string
|
ExtraArgs string
|
||||||
InKubernetes bool
|
InKubernetes bool
|
||||||
UserspaceMode bool
|
UserspaceMode bool
|
||||||
StateDir string
|
StateDir string
|
||||||
AcceptDNS bool
|
AcceptDNS *bool
|
||||||
KubeSecret string
|
KubeSecret string
|
||||||
SOCKSProxyAddr string
|
SOCKSProxyAddr string
|
||||||
HTTPProxyAddr string
|
HTTPProxyAddr string
|
||||||
Socket string
|
Socket string
|
||||||
AuthOnce bool
|
AuthOnce bool
|
||||||
Root string
|
Root string
|
||||||
KubernetesCanPatch bool
|
KubernetesCanPatch bool
|
||||||
|
TailscaledConfigFilePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settings) validate() error {
|
||||||
|
if s.TailscaledConfigFilePath != "" {
|
||||||
|
if _, err := conffile.Load(s.TailscaledConfigFilePath); err != nil {
|
||||||
|
return fmt.Errorf("error validating tailscaled configfile contents: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.ProxyTo != "" && s.UserspaceMode {
|
||||||
|
return errors.New("TS_DEST_IP is not supported with TS_USERSPACE")
|
||||||
|
}
|
||||||
|
if s.TailnetTargetIP != "" && s.UserspaceMode {
|
||||||
|
return errors.New("TS_TAILNET_TARGET_IP is not supported with TS_USERSPACE")
|
||||||
|
}
|
||||||
|
if s.TailnetTargetFQDN != "" && s.UserspaceMode {
|
||||||
|
return errors.New("TS_TAILNET_TARGET_FQDN is not supported with TS_USERSPACE")
|
||||||
|
}
|
||||||
|
if s.TailnetTargetFQDN != "" && s.TailnetTargetIP != "" {
|
||||||
|
return errors.New("Both TS_TAILNET_TARGET_IP and TS_TAILNET_FQDN cannot be set")
|
||||||
|
}
|
||||||
|
if s.TailscaledConfigFilePath != "" && (s.AcceptDNS != nil || s.AuthKey != "" || s.Routes != nil || s.ExtraArgs != "" || s.Hostname != "") {
|
||||||
|
return errors.New("EXPERIMENTAL_TS_CONFIGFILE_PATH cannot be set in combination with TS_HOSTNAME, TS_EXTRA_ARGS, TS_AUTHKEY, TS_ROUTES, TS_ACCEPT_DNS.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultEnv returns the value of the given envvar name, or defVal if
|
// defaultEnv returns the value of the given envvar name, or defVal if
|
||||||
@ -899,16 +932,28 @@ func defaultEnv(name, defVal string) string {
|
|||||||
return defVal
|
return defVal
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultEnvPointer returns a pointer to the given envvar value if set, else
|
// defaultEnvStringPointer returns a pointer to the given envvar value if set, else
|
||||||
// returns nil. This is useful in cases where we need to distinguish between a
|
// returns nil. This is useful in cases where we need to distinguish between a
|
||||||
// variable being set to empty string vs unset.
|
// variable being set to empty string vs unset.
|
||||||
func defaultEnvPointer(name string) *string {
|
func defaultEnvStringPointer(name string) *string {
|
||||||
if v, ok := os.LookupEnv(name); ok {
|
if v, ok := os.LookupEnv(name); ok {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultEnvBoolPointer returns a pointer to the given envvar value if set, else
|
||||||
|
// returns nil. This is useful in cases where we need to distinguish between a
|
||||||
|
// variable being explicitly set to false vs unset.
|
||||||
|
func defaultEnvBoolPointer(name string) *bool {
|
||||||
|
v := os.Getenv(name)
|
||||||
|
ret, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &ret
|
||||||
|
}
|
||||||
|
|
||||||
func defaultEnvs(names []string, defVal string) string {
|
func defaultEnvs(names []string, defVal string) string {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if v, ok := os.LookupEnv(name); ok {
|
if v, ok := os.LookupEnv(name); ok {
|
||||||
@ -950,3 +995,27 @@ func contextWithExitSignalWatch() (context.Context, func()) {
|
|||||||
}
|
}
|
||||||
return ctx, f
|
return ctx, f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isTwoStepConfigAuthOnce returns true if the Tailscale node should be configured
|
||||||
|
// in two steps and login should only happen once.
|
||||||
|
// Step 1: run 'tailscaled'
|
||||||
|
// Step 2):
|
||||||
|
// A) if this is the first time starting this node run 'tailscale up --authkey <authkey> <config opts>'
|
||||||
|
// B) if this is not the first time starting this node run 'tailscale set <config opts>'.
|
||||||
|
func isTwoStepConfigAuthOnce(cfg *settings) bool {
|
||||||
|
return cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTwoStepConfigAlwaysAuth returns true if the Tailscale node should be configured
|
||||||
|
// in two steps and we should log in every time it starts.
|
||||||
|
// Step 1: run 'tailscaled'
|
||||||
|
// Step 2): run 'tailscale up --authkey <authkey> <config opts>'
|
||||||
|
func isTwoStepConfigAlwaysAuth(cfg *settings) bool {
|
||||||
|
return !cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// isOneStepConfig returns true if the Tailscale node should always be ran and
|
||||||
|
// configured in a single step by running 'tailscaled <config opts>'
|
||||||
|
func isOneStepConfig(cfg *settings) bool {
|
||||||
|
return cfg.TailscaledConfigFilePath != ""
|
||||||
|
}
|
||||||
|
@ -52,6 +52,12 @@ func TestContainerBoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer kube.Close()
|
defer kube.Close()
|
||||||
|
|
||||||
|
tailscaledConf := &ipn.ConfigVAlpha{AuthKey: func(s string) *string { return &s }("foo"), Version: "alpha0"}
|
||||||
|
tailscaledConfBytes, err := json.Marshal(tailscaledConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error unmarshaling tailscaled config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
dirs := []string{
|
dirs := []string{
|
||||||
"var/lib",
|
"var/lib",
|
||||||
"usr/bin",
|
"usr/bin",
|
||||||
@ -59,6 +65,7 @@ func TestContainerBoot(t *testing.T) {
|
|||||||
"dev/net",
|
"dev/net",
|
||||||
"proc/sys/net/ipv4",
|
"proc/sys/net/ipv4",
|
||||||
"proc/sys/net/ipv6/conf/all",
|
"proc/sys/net/ipv6/conf/all",
|
||||||
|
"etc",
|
||||||
}
|
}
|
||||||
for _, path := range dirs {
|
for _, path := range dirs {
|
||||||
if err := os.MkdirAll(filepath.Join(d, path), 0700); err != nil {
|
if err := os.MkdirAll(filepath.Join(d, path), 0700); err != nil {
|
||||||
@ -73,6 +80,7 @@ func TestContainerBoot(t *testing.T) {
|
|||||||
"dev/net/tun": []byte(""),
|
"dev/net/tun": []byte(""),
|
||||||
"proc/sys/net/ipv4/ip_forward": []byte("0"),
|
"proc/sys/net/ipv4/ip_forward": []byte("0"),
|
||||||
"proc/sys/net/ipv6/conf/all/forwarding": []byte("0"),
|
"proc/sys/net/ipv6/conf/all/forwarding": []byte("0"),
|
||||||
|
"etc/tailscaled": tailscaledConfBytes,
|
||||||
}
|
}
|
||||||
resetFiles := func() {
|
resetFiles := func() {
|
||||||
for path, content := range files {
|
for path, content := range files {
|
||||||
@ -310,7 +318,7 @@ func TestContainerBoot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "ingres proxy",
|
Name: "ingress proxy",
|
||||||
Env: map[string]string{
|
Env: map[string]string{
|
||||||
"TS_AUTHKEY": "tskey-key",
|
"TS_AUTHKEY": "tskey-key",
|
||||||
"TS_DEST_IP": "1.2.3.4",
|
"TS_DEST_IP": "1.2.3.4",
|
||||||
@ -629,6 +637,21 @@ func TestContainerBoot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "experimental tailscaled configfile",
|
||||||
|
Env: map[string]string{
|
||||||
|
"EXPERIMENTAL_TS_CONFIGFILE_PATH": filepath.Join(d, "etc/tailscaled"),
|
||||||
|
},
|
||||||
|
Phases: []phase{
|
||||||
|
{
|
||||||
|
WantCmds: []string{
|
||||||
|
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking --config=/etc/tailscaled",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Notify: runningNotify,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user