cmd/tailscale,ipn:add support for automounting TailFS shares on MacOS
This adds two flags to the "tailscale set" command. --automount-enabled enables automatically mounting TailFS shares --automount-path optionally specifies the path at which to automount If --automount-path is not set, TailFS will be mounted at /Volumes/tailscale. The mount is owned by whatever user invoked "tailscale set" and has mode 0700 set (read,write,execute only by owning user). By default, automounting is not enabled. Updates tailscale/corp#16827 Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:
parent
15b2c674bf
commit
3985947437
|
@ -114,7 +114,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||
💣 tailscale.com/safesocket from tailscale.com/client/tailscale
|
||||
tailscale.com/syncs from tailscale.com/cmd/derper+
|
||||
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
||||
tailscale.com/tailfs from tailscale.com/client/tailscale
|
||||
tailscale.com/tailfs from tailscale.com/client/tailscale+
|
||||
tailscale.com/tka from tailscale.com/client/tailscale+
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
tailscale.com/tstime from tailscale.com/derp+
|
||||
|
@ -264,7 +264,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||
os from crypto/rand+
|
||||
os/exec from github.com/coreos/go-iptables/iptables+
|
||||
os/signal from tailscale.com/cmd/derper
|
||||
W os/user from tailscale.com/util/winutil
|
||||
DW os/user from tailscale.com/util/winutil+
|
||||
path from github.com/prometheus/client_golang/prometheus/internal+
|
||||
path/filepath from crypto/x509+
|
||||
reflect from crypto/x509+
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/tailfs"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/version"
|
||||
|
@ -56,6 +57,8 @@ type setArgsT struct {
|
|||
updateCheck bool
|
||||
updateApply bool
|
||||
postureChecking bool
|
||||
automountEnabled bool
|
||||
automountPath string
|
||||
}
|
||||
|
||||
func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
|
||||
|
@ -76,6 +79,12 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
|
|||
setf.BoolVar(&setArgs.updateApply, "auto-update", false, "automatically update to the latest available version")
|
||||
setf.BoolVar(&setArgs.postureChecking, "posture-checking", false, "HIDDEN: allow management plane to gather device posture information")
|
||||
setf.BoolVar(&setArgs.runWebClient, "webclient", false, "run a web interface for managing this node, served over Tailscale at port 5252")
|
||||
automountDisclaimer := ""
|
||||
if !tailfs.AutomountSupported() {
|
||||
automountDisclaimer = "(NOT AVAILABLE ON THIS SYSTEM) "
|
||||
}
|
||||
setf.BoolVar(&setArgs.automountEnabled, "automount-enabled", false, fmt.Sprintf("%sautomatically mount Tailscale shares", automountDisclaimer))
|
||||
setf.StringVar(&setArgs.automountPath, "automount-path", "", fmt.Sprintf(`%spath at which to automount shares, leave blank to default to %q`, automountDisclaimer, tailfs.DefaultAutomountPath()))
|
||||
|
||||
if safesocket.GOOSUsesPeerCreds(goos) {
|
||||
setf.StringVar(&setArgs.opUser, "operator", "", "Unix username to allow to operate on tailscaled without sudo")
|
||||
|
@ -124,6 +133,10 @@ func runSet(ctx context.Context, args []string) (retErr error) {
|
|||
Advertise: setArgs.advertiseConnector,
|
||||
},
|
||||
PostureChecking: setArgs.postureChecking,
|
||||
AutomountShares: ipn.AutomountPrefs{
|
||||
Enabled: setArgs.automountEnabled,
|
||||
Path: setArgs.automountPath,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -151,6 +164,9 @@ func runSet(ctx context.Context, args []string) (retErr error) {
|
|||
if maskedPrefs.IsEmpty() {
|
||||
return flag.ErrHelp
|
||||
}
|
||||
if maskedPrefs.AutomountSharesSet && !tailfs.AutomountSupported() {
|
||||
return errors.New("flag automount-enabled is not supported on this system")
|
||||
}
|
||||
|
||||
curPrefs, err := localClient.GetPrefs(ctx)
|
||||
if err != nil {
|
||||
|
|
|
@ -722,6 +722,8 @@ func init() {
|
|||
addPrefFlagMapping("auto-update", "AutoUpdate.Apply")
|
||||
addPrefFlagMapping("advertise-connector", "AppConnector")
|
||||
addPrefFlagMapping("posture-checking", "PostureChecking")
|
||||
addPrefFlagMapping("automount-enabled", "AutomountShares")
|
||||
addPrefFlagMapping("automount-path", "AutomountShares")
|
||||
}
|
||||
|
||||
func addPrefFlagMapping(flagName string, prefNames ...string) {
|
||||
|
|
|
@ -56,6 +56,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
|
|||
AppConnector AppConnectorPrefs
|
||||
PostureChecking bool
|
||||
NetfilterKind string
|
||||
AutomountShares AutomountPrefs
|
||||
Persist *persist.Persist
|
||||
}{})
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ func (v PrefsView) AutoUpdate() AutoUpdatePrefs { return v.ж.AutoUpda
|
|||
func (v PrefsView) AppConnector() AppConnectorPrefs { return v.ж.AppConnector }
|
||||
func (v PrefsView) PostureChecking() bool { return v.ж.PostureChecking }
|
||||
func (v PrefsView) NetfilterKind() string { return v.ж.NetfilterKind }
|
||||
func (v PrefsView) AutomountShares() AutomountPrefs { return v.ж.AutomountShares }
|
||||
func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.View() }
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
|
@ -121,6 +122,7 @@ var _PrefsViewNeedsRegeneration = Prefs(struct {
|
|||
AppConnector AppConnectorPrefs
|
||||
PostureChecking bool
|
||||
NetfilterKind string
|
||||
AutomountShares AutomountPrefs
|
||||
Persist *persist.Persist
|
||||
}{})
|
||||
|
||||
|
|
|
@ -1847,6 +1847,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
|||
// use logout instead.
|
||||
cc.Login(nil, controlclient.LoginDefault)
|
||||
}
|
||||
|
||||
b.tailFSConfigureAutomount(ipn.AutomountPrefs{}, prefs.AutomountShares())
|
||||
b.stateMachine()
|
||||
return nil
|
||||
}
|
||||
|
@ -3261,6 +3263,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn
|
|||
b.authReconfig()
|
||||
}
|
||||
|
||||
b.tailFSConfigureAutomount(oldp.AutomountShares(), prefs.AutomountShares())
|
||||
b.send(ipn.Notify{Prefs: &prefs})
|
||||
return prefs
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
|
@ -278,3 +279,24 @@ func (b *LocalBackend) updateTailFSPeersLocked(nm *netmap.NetworkMap) {
|
|||
}
|
||||
fs.SetRemotes(b.netMap.Domain, tailfsRemotes, &tailFSTransport{b: b})
|
||||
}
|
||||
|
||||
func (b *LocalBackend) tailFSConfigureAutomount(old, new ipn.AutomountPrefs) {
|
||||
oldPath := filepath.Clean(old.PathOrDefault())
|
||||
settingsChanged := !new.Equals(old)
|
||||
|
||||
if old.Enabled && pathExists(oldPath) && settingsChanged {
|
||||
b.logf("Unmounting shares from %q", oldPath)
|
||||
tailfs.UnmountShares(oldPath)
|
||||
}
|
||||
|
||||
newPath := filepath.Clean(new.PathOrDefault())
|
||||
if new.Enabled && !pathExists(newPath) {
|
||||
b.logf("Mounting shares at %q as %q", newPath, new.AsUser)
|
||||
tailfs.MountShares(newPath, new.AsUser)
|
||||
}
|
||||
}
|
||||
|
||||
func pathExists(p string) bool {
|
||||
_, err := os.Stat(p)
|
||||
return err == nil
|
||||
}
|
||||
|
|
|
@ -1374,6 +1374,15 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if mp.AutomountSharesSet {
|
||||
// Set AutomountShares user to the connecting username.
|
||||
var err error
|
||||
mp.AutomountShares.AsUser, err = h.getUsername()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
var err error
|
||||
prefs, err = h.b.EditPrefs(mp)
|
||||
if err != nil {
|
||||
|
|
48
ipn/prefs.go
48
ipn/prefs.go
|
@ -21,6 +21,7 @@ import (
|
|||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailfs"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/persist"
|
||||
"tailscale.com/types/preftype"
|
||||
|
@ -222,6 +223,10 @@ type Prefs struct {
|
|||
// Linux-only.
|
||||
NetfilterKind string
|
||||
|
||||
// AutomountShares configures automatic mounting of the TailFS file system
|
||||
// at a local path.
|
||||
AutomountShares AutomountPrefs
|
||||
|
||||
// The Persist field is named 'Config' in the file for backward
|
||||
// compatibility with earlier versions.
|
||||
// TODO(apenwarr): We should move this out of here, it's not a pref.
|
||||
|
@ -259,6 +264,34 @@ type AppConnectorPrefs struct {
|
|||
Advertise bool
|
||||
}
|
||||
|
||||
// AutomountPrefs are the settings for automounting TailFS shares.
|
||||
type AutomountPrefs struct {
|
||||
// Enabled specifies whether or not automounting is enabled.
|
||||
Enabled bool
|
||||
|
||||
// The path at which we mount. If blank, we default to an os-specific
|
||||
// location like /Volumes/tailscale.
|
||||
Path string
|
||||
|
||||
// AsUser specifies the user who will own the mounted folder.
|
||||
AsUser string
|
||||
}
|
||||
|
||||
// PathOrDefault returns the configured Path or the os-specific
|
||||
// [tailfs.DefaultAutomountPath] if no Path was specified.
|
||||
func (am AutomountPrefs) PathOrDefault() string {
|
||||
if am.Path != "" {
|
||||
return am.Path
|
||||
}
|
||||
return tailfs.DefaultAutomountPath()
|
||||
}
|
||||
|
||||
func (am1 AutomountPrefs) Equals(am2 AutomountPrefs) bool {
|
||||
return am1.Enabled == am2.Enabled &&
|
||||
am1.Path == am2.Path &&
|
||||
am1.AsUser == am2.AsUser
|
||||
}
|
||||
|
||||
// MaskedPrefs is a Prefs with an associated bitmask of which fields are set.
|
||||
//
|
||||
// Each FooSet field maps to a corresponding Foo field in Prefs. FooSet can be
|
||||
|
@ -293,6 +326,7 @@ type MaskedPrefs struct {
|
|||
AppConnectorSet bool `json:",omitempty"`
|
||||
PostureCheckingSet bool `json:",omitempty"`
|
||||
NetfilterKindSet bool `json:",omitempty"`
|
||||
AutomountSharesSet bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
type AutoUpdatePrefsMask struct {
|
||||
|
@ -498,6 +532,7 @@ func (p *Prefs) pretty(goos string) string {
|
|||
}
|
||||
sb.WriteString(p.AutoUpdate.Pretty())
|
||||
sb.WriteString(p.AppConnector.Pretty())
|
||||
sb.WriteString(p.AutomountShares.Pretty())
|
||||
if p.Persist != nil {
|
||||
sb.WriteString(p.Persist.Pretty())
|
||||
} else {
|
||||
|
@ -556,7 +591,8 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
|
|||
p.AutoUpdate.Equals(p2.AutoUpdate) &&
|
||||
p.AppConnector == p2.AppConnector &&
|
||||
p.PostureChecking == p2.PostureChecking &&
|
||||
p.NetfilterKind == p2.NetfilterKind
|
||||
p.NetfilterKind == p2.NetfilterKind &&
|
||||
p.AutomountShares.Equals(p2.AutomountShares)
|
||||
}
|
||||
|
||||
func (au AutoUpdatePrefs) Pretty() string {
|
||||
|
@ -576,6 +612,16 @@ func (ap AppConnectorPrefs) Pretty() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (am AutomountPrefs) Pretty() string {
|
||||
if !am.Enabled {
|
||||
return "automount=off "
|
||||
}
|
||||
if am.Path != "" {
|
||||
return fmt.Sprintf("automount=%s ", am.Path)
|
||||
}
|
||||
return "automount=on "
|
||||
}
|
||||
|
||||
func compareIPNets(a, b []netip.Prefix) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
|
|
|
@ -62,6 +62,7 @@ func TestPrefsEqual(t *testing.T) {
|
|||
"AppConnector",
|
||||
"PostureChecking",
|
||||
"NetfilterKind",
|
||||
"AutomountShares",
|
||||
"Persist",
|
||||
}
|
||||
if have := fieldsOf(reflect.TypeFor[Prefs]()); !reflect.DeepEqual(have, prefsHandles) {
|
||||
|
@ -339,6 +340,26 @@ func TestPrefsEqual(t *testing.T) {
|
|||
&Prefs{NetfilterKind: ""},
|
||||
false,
|
||||
},
|
||||
{
|
||||
&Prefs{AutomountShares: AutomountPrefs{Enabled: true, Path: "path", AsUser: "username"}},
|
||||
&Prefs{AutomountShares: AutomountPrefs{Enabled: true, Path: "path", AsUser: "username"}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
&Prefs{AutomountShares: AutomountPrefs{Enabled: true, Path: "path", AsUser: "username"}},
|
||||
&Prefs{AutomountShares: AutomountPrefs{Enabled: false, Path: "path", AsUser: "username"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
&Prefs{AutomountShares: AutomountPrefs{Enabled: true, Path: "path", AsUser: "username"}},
|
||||
&Prefs{AutomountShares: AutomountPrefs{Enabled: true, Path: "path2", AsUser: "username"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
&Prefs{AutomountShares: AutomountPrefs{Enabled: true, Path: "path", AsUser: "username"}},
|
||||
&Prefs{AutomountShares: AutomountPrefs{Enabled: true, Path: "path", AsUser: "username2"}},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := tt.a.Equals(tt.b)
|
||||
|
@ -423,22 +444,22 @@ func TestPrefsPretty(t *testing.T) {
|
|||
{
|
||||
Prefs{},
|
||||
"linux",
|
||||
"Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist=nil}",
|
||||
"Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off automount=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{},
|
||||
"windows",
|
||||
"Prefs{ra=false mesh=false dns=false want=false update=off Persist=nil}",
|
||||
"Prefs{ra=false mesh=false dns=false want=false update=off automount=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{ShieldsUp: true},
|
||||
"windows",
|
||||
"Prefs{ra=false mesh=false dns=false want=false shields=true update=off Persist=nil}",
|
||||
"Prefs{ra=false mesh=false dns=false want=false shields=true update=off automount=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{AllowSingleHosts: true},
|
||||
"windows",
|
||||
"Prefs{ra=false dns=false want=false update=off Persist=nil}",
|
||||
"Prefs{ra=false dns=false want=false update=off automount=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -446,7 +467,7 @@ func TestPrefsPretty(t *testing.T) {
|
|||
AllowSingleHosts: true,
|
||||
},
|
||||
"windows",
|
||||
"Prefs{ra=false dns=false want=false notepad=true update=off Persist=nil}",
|
||||
"Prefs{ra=false dns=false want=false notepad=true update=off automount=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -455,7 +476,7 @@ func TestPrefsPretty(t *testing.T) {
|
|||
ForceDaemon: true, // server mode
|
||||
},
|
||||
"windows",
|
||||
"Prefs{ra=false dns=false want=true server=true update=off Persist=nil}",
|
||||
"Prefs{ra=false dns=false want=true server=true update=off automount=off Persist=nil}",
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -465,14 +486,14 @@ func TestPrefsPretty(t *testing.T) {
|
|||
AdvertiseTags: []string{"tag:foo", "tag:bar"},
|
||||
},
|
||||
"darwin",
|
||||
`Prefs{ra=false dns=false want=true tags=tag:foo,tag:bar url="http://localhost:1234" update=off Persist=nil}`,
|
||||
`Prefs{ra=false dns=false want=true tags=tag:foo,tag:bar url="http://localhost:1234" update=off automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
Persist: &persist.Persist{},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist{lm=, o=, n= u=""}}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off automount=off Persist{lm=, o=, n= u=""}}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -481,21 +502,21 @@ func TestPrefsPretty(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist{lm=, o=, n=[B1VKl] u=""}}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off automount=off Persist{lm=, o=, n=[B1VKl] u=""}}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
ExitNodeIP: netip.MustParseAddr("1.2.3.4"),
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=1.2.3.4 lan=false routes=[] nf=off update=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=1.2.3.4 lan=false routes=[] nf=off update=off automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
ExitNodeID: tailcfg.StableNodeID("myNodeABC"),
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=false routes=[] nf=off update=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=false routes=[] nf=off update=off automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -503,21 +524,21 @@ func TestPrefsPretty(t *testing.T) {
|
|||
ExitNodeAllowLANAccess: true,
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=true routes=[] nf=off update=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=true routes=[] nf=off update=off automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
ExitNodeAllowLANAccess: true,
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
Hostname: "foo",
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off host="foo" update=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off host="foo" update=off automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -527,7 +548,7 @@ func TestPrefsPretty(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=check Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=check automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -537,7 +558,7 @@ func TestPrefsPretty(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=on Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=on automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -546,7 +567,7 @@ func TestPrefsPretty(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off appconnector=advertise Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off appconnector=advertise automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
|
@ -555,21 +576,35 @@ func TestPrefsPretty(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
NetfilterKind: "iptables",
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off netfilterKind=iptables update=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off netfilterKind=iptables update=off automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
NetfilterKind: "",
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off automount=off Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
AutomountShares: AutomountPrefs{Enabled: true},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off automount=on Persist=nil}`,
|
||||
},
|
||||
{
|
||||
Prefs{
|
||||
AutomountShares: AutomountPrefs{Enabled: true, Path: "/some/path"},
|
||||
},
|
||||
"linux",
|
||||
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off update=off automount=/some/path Persist=nil}`,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package tailfs
|
||||
|
||||
import "tailscale.com/version"
|
||||
|
||||
// AutomountSupported reports whether TailFS automounting is supported on this
|
||||
// system.
|
||||
func AutomountSupported() bool {
|
||||
return DefaultAutomountPath() != "" && !version.IsSandboxedMacOS()
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build darwin
|
||||
|
||||
package tailfs
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// DefaultAutomountPath returns the default automount path. If blank, that
|
||||
// means TailFS is disabled on this platform.
|
||||
func DefaultAutomountPath() string {
|
||||
return "/Volumes/tailscale"
|
||||
}
|
||||
|
||||
func MountShares(location string, username string) {
|
||||
u, err := user.Lookup(username)
|
||||
if err != nil {
|
||||
log.Printf("warning: error looking up user %q, won't automount shares: %s", username, err)
|
||||
return
|
||||
}
|
||||
uid, err := strconv.Atoi(u.Uid)
|
||||
if err != nil {
|
||||
log.Printf("warning: failed to parse uid %q, won't automount shares: %s", u.Uid, err)
|
||||
}
|
||||
gid, err := strconv.Atoi(u.Gid)
|
||||
if err != nil {
|
||||
log.Printf("warning: failed to parse gid %q, won't automount shares: %s", u.Gid, err)
|
||||
}
|
||||
|
||||
location = filepath.Clean(location)
|
||||
err = os.MkdirAll(location, 0700)
|
||||
if err != nil {
|
||||
log.Printf("warning: can't make automount location %q: %s", location, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Chown(location, uid, gid)
|
||||
if err != nil {
|
||||
log.Printf("warning: failed to chown automount location, won't automount shares: %s", err)
|
||||
}
|
||||
|
||||
out, err := exec.Command("sudo", "-u", username, "mount", "-t", "webdav", "http://100.100.100.100:8080", location).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("warning: can't automount shares at %q: %s", location, out)
|
||||
}
|
||||
}
|
||||
|
||||
func UnmountShares(location string) {
|
||||
location = filepath.Clean(location)
|
||||
out, err := exec.Command("diskutil", "umount", location).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("warning: can't unmount shares from %q: %s", location, out)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !darwin
|
||||
|
||||
package tailfs
|
||||
|
||||
// DefaultAutomountPath returns the default automount path. If blank, that
|
||||
// means TailFS is disabled on this platform.
|
||||
func DefaultAutomountPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func MountShares(location string, username string) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
func UnmountShares(location string) {
|
||||
// Do nothing.
|
||||
}
|
Loading…
Reference in New Issue