package shares import ( "fmt" "strings" ) // Header marks generated files so install scripts and humans can tell them apart. const Header = "# Managed by nasmon — edits are overwritten. Change shares at http://:8090/shares" func yesno(b bool) string { if b { return "yes" } return "no" } func anyGuest(c Config) bool { for _, s := range c.SMB.Shares { if s.Guest { return true } } return false } // RenderSMB produces the complete smb.conf. owner is the Unix account every // SMB session acts as (force user), so files end up with the same owner the // containers use. includes are drop-in files of other services appended as // "include =" lines. func RenderSMB(c Config, owner string, includes ...string) string { var b strings.Builder fmt.Fprintf(&b, "%s\n\n[global]\n", Header) // Bad User turns unknown accounts (e.g. the Windows login) into guests. // Without guest shares that only yields a guest session Windows refuses // with a misleading "network path not found" instead of a password prompt. mapGuest := "Never" if anyGuest(c) { mapGuest = "Bad User" } minProto := "server min protocol = SMB2" if c.SMB.Legacy { // old embedded clients (e.g. Supermicro BMC virtual media) only offer // SMB1 "NT LM 0.12" and authenticate with NTLMv1 minProto = "server min protocol = NT1\n ntlm auth = ntlmv1-permitted" } g := []string{ "workgroup = " + c.SMB.Workgroup, "server string = " + c.SMB.ServerString, "server role = standalone server", "security = user", "map to guest = " + mapGuest, "guest account = nobody", minProto, "unix charset = UTF-8", "logging = file", "log file = /var/log/samba/log.%m", "max log size = 1000", "log level = 1", "load printers = no", "printing = bsd", "printcap name = /dev/null", "disable spoolss = yes", "ea support = yes", "vfs objects = fruit streams_xattr", // macOS clients: resource forks / Finder metadata as xattrs "fruit:metadata = stream", "fruit:model = MacSamba", "dos filemode = yes", } for _, l := range g { fmt.Fprintf(&b, " %s\n", l) } for _, s := range c.SMB.Shares { fmt.Fprintf(&b, "\n[%s]\n", s.Name) lines := []string{ "path = " + s.Path, } if s.Comment != "" { lines = append(lines, "comment = "+s.Comment) } lines = append(lines, "browseable = "+yesno(s.Browseable), "read only = "+yesno(s.ReadOnly), "guest ok = "+yesno(s.Guest), ) if !s.Guest { lines = append(lines, "valid users = "+strings.Join(s.Users, " ")) } lines = append(lines, "force user = "+owner, "force group = "+owner, "create mask = 0664", "directory mask = 0775", ) if s.HideSysFiles { lines = append(lines, "veto files = /._*/.DS_Store/Thumbs.db/desktop.ini/", "delete veto files = yes") } for _, l := range lines { fmt.Fprintf(&b, " %s\n", l) } } if len(includes) > 0 { b.WriteString("\n# Shares of other services (read-only in nasmon)\n") for _, f := range includes { fmt.Fprintf(&b, "include = %s\n", f) } } return b.String() } // RenderExports produces the exports(5) file. uid/gid are the ids squashed // clients are mapped to. func RenderExports(c Config, uid, gid string) string { var b strings.Builder fmt.Fprintf(&b, "%s\n", Header) for _, e := range c.NFS.Exports { opts := []string{"rw"} if e.ReadOnly { opts = []string{"ro"} } if e.Async { opts = append(opts, "async") } else { opts = append(opts, "sync") } switch e.Squash { case "all": opts = append(opts, "all_squash", "anonuid="+uid, "anongid="+gid) case "none": opts = append(opts, "no_root_squash") default: opts = append(opts, "root_squash", "anonuid="+uid, "anongid="+gid) } // crossmnt: child mounts (ZFS datasets or other filesystems) must be reachable opts = append(opts, "crossmnt", "no_subtree_check") var parts []string for _, cl := range e.Clients { parts = append(parts, cl+"("+strings.Join(opts, ",")+")") } fmt.Fprintf(&b, "%q %s\n", e.Path, strings.Join(parts, " ")) } return b.String() }