2
This commit is contained in:
@@ -19,6 +19,8 @@ go build -o samba-configer .
|
||||
The program:
|
||||
|
||||
- Reads the Samba config and parses non-`[global]` sections as shares.
|
||||
- Detects a missing Samba installation when the config file does not exist yet.
|
||||
- Offers to install the Samba server package with a detected package manager such as `apt`, `dnf`, `yum`, `pacman`, `zypper`, or `apk`.
|
||||
- Prompts to add, edit, or delete a share.
|
||||
- Checks `valid users` entries against local accounts.
|
||||
- Offers to create missing local accounts with `useradd -M -s /usr/sbin/nologin <user>`.
|
||||
|
||||
57
app.go
57
app.go
@@ -24,6 +24,8 @@ type CommandRunner interface {
|
||||
Run(name string, args ...string) error
|
||||
}
|
||||
|
||||
type LookPathFunc func(file string) (string, error)
|
||||
|
||||
type RealUserManager struct{}
|
||||
|
||||
func (RealUserManager) UserExists(name string) bool {
|
||||
@@ -49,14 +51,18 @@ func (OSCommandRunner) Run(name string, args ...string) error {
|
||||
type App struct {
|
||||
configPath string
|
||||
users UserManager
|
||||
runner CommandRunner
|
||||
lookPath LookPathFunc
|
||||
reader *bufio.Reader
|
||||
writer *bufio.Writer
|
||||
}
|
||||
|
||||
func NewApp(configPath string, users UserManager) *App {
|
||||
func NewApp(configPath string, users UserManager, runner CommandRunner, lookPath LookPathFunc) *App {
|
||||
return &App{
|
||||
configPath: configPath,
|
||||
users: users,
|
||||
runner: runner,
|
||||
lookPath: lookPath,
|
||||
reader: bufio.NewReader(os.Stdin),
|
||||
writer: bufio.NewWriter(os.Stdout),
|
||||
}
|
||||
@@ -65,9 +71,17 @@ func NewApp(configPath string, users UserManager) *App {
|
||||
func (a *App) Run() error {
|
||||
doc, err := ParseConfigFile(a.configPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
doc, err = a.handleMissingConfig()
|
||||
if err == nil {
|
||||
goto loaded
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("read config: %w", err)
|
||||
}
|
||||
|
||||
loaded:
|
||||
for {
|
||||
shareSections := doc.ShareSections()
|
||||
a.println("")
|
||||
@@ -118,6 +132,47 @@ func (a *App) Run() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleMissingConfig() (*Document, error) {
|
||||
a.println("")
|
||||
a.println("Samba is not set up yet on this computer.")
|
||||
a.printf("I couldn't find the Samba config file at %s.\n", a.configPath)
|
||||
|
||||
plan, ok := DetectSambaInstallPlan(a.lookPath, os.Geteuid() == 0)
|
||||
if !ok {
|
||||
a.println("I also couldn't find a supported package manager automatically.")
|
||||
a.println("Install the Samba server package for your Linux distribution, then run this tool again.")
|
||||
a.flush()
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
a.println("")
|
||||
a.printf("I found %s and can try to install the Samba server for you.\n", plan.ManagerName)
|
||||
a.printf("This would run: %s\n", plan.DisplayCommand())
|
||||
a.println("You may be asked for your administrator password.")
|
||||
a.flush()
|
||||
|
||||
install, err := a.confirm("Install Samba server now", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !install {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
if err := a.runner.Run(plan.Command[0], plan.Command[1:]...); err != nil {
|
||||
return nil, fmt.Errorf("install Samba with %s: %w", plan.ManagerName, err)
|
||||
}
|
||||
|
||||
doc, err := ParseConfigFile(a.configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Samba installation finished, but the config file is still missing at %s: %w", a.configPath, err)
|
||||
}
|
||||
|
||||
a.println("Samba was installed and the config file is now available.")
|
||||
a.flush()
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
func (a *App) addShare(doc *Document) error {
|
||||
cfg, err := a.collectShareConfig(ShareConfig{})
|
||||
if err != nil {
|
||||
|
||||
63
install.go
Normal file
63
install.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type InstallPlan struct {
|
||||
ManagerName string
|
||||
Command []string
|
||||
}
|
||||
|
||||
func (p InstallPlan) DisplayCommand() string {
|
||||
return strings.Join(p.Command, " ")
|
||||
}
|
||||
|
||||
func execLookPath(file string) (string, error) {
|
||||
return exec.LookPath(file)
|
||||
}
|
||||
|
||||
func DetectSambaInstallPlan(lookPath LookPathFunc, isRoot bool) (InstallPlan, bool) {
|
||||
sudoPrefix := []string{}
|
||||
if !isRoot {
|
||||
if _, err := lookPath("sudo"); err == nil {
|
||||
sudoPrefix = []string{"sudo"}
|
||||
}
|
||||
}
|
||||
|
||||
plans := []struct {
|
||||
bin string
|
||||
name string
|
||||
command []string
|
||||
}{
|
||||
{bin: "apt-get", name: "apt", command: []string{"apt-get", "update"}},
|
||||
{bin: "apt-get", name: "apt", command: []string{"apt-get", "install", "-y", "samba"}},
|
||||
{bin: "dnf", name: "dnf", command: []string{"dnf", "install", "-y", "samba"}},
|
||||
{bin: "yum", name: "yum", command: []string{"yum", "install", "-y", "samba"}},
|
||||
{bin: "pacman", name: "pacman", command: []string{"pacman", "-Sy", "--noconfirm", "samba"}},
|
||||
{bin: "zypper", name: "zypper", command: []string{"zypper", "--non-interactive", "install", "samba"}},
|
||||
{bin: "apk", name: "apk", command: []string{"apk", "add", "samba"}},
|
||||
}
|
||||
|
||||
if _, err := lookPath("apt-get"); err == nil {
|
||||
return InstallPlan{
|
||||
ManagerName: "apt",
|
||||
Command: append(
|
||||
append([]string{}, sudoPrefix...),
|
||||
"sh", "-c", "apt-get update && apt-get install -y samba",
|
||||
),
|
||||
}, true
|
||||
}
|
||||
|
||||
for _, plan := range plans[2:] {
|
||||
if _, err := lookPath(plan.bin); err == nil {
|
||||
return InstallPlan{
|
||||
ManagerName: plan.name,
|
||||
Command: append(append([]string{}, sudoPrefix...), plan.command...),
|
||||
}, true
|
||||
}
|
||||
}
|
||||
|
||||
return InstallPlan{}, false
|
||||
}
|
||||
2
main.go
2
main.go
@@ -11,7 +11,7 @@ func main() {
|
||||
configPath := flag.String("config", "/etc/samba/smb.conf", "path to smb.conf")
|
||||
flag.Parse()
|
||||
|
||||
app := NewApp(*configPath, RealUserManager{})
|
||||
app := NewApp(*configPath, RealUserManager{}, OSCommandRunner{}, execLookPath)
|
||||
if err := app.Run(); err != nil {
|
||||
if errors.Is(err, ErrCancelled) {
|
||||
fmt.Fprintln(os.Stderr, "cancelled")
|
||||
|
||||
@@ -94,3 +94,50 @@ func TestSerializeProducesSectionHeaders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectSambaInstallPlanPrefersApt(t *testing.T) {
|
||||
lookPath := func(file string) (string, error) {
|
||||
switch file {
|
||||
case "apt-get", "sudo":
|
||||
return "/usr/bin/" + file, nil
|
||||
default:
|
||||
return "", execErr(file)
|
||||
}
|
||||
}
|
||||
|
||||
plan, ok := DetectSambaInstallPlan(lookPath, false)
|
||||
if !ok {
|
||||
t.Fatalf("expected install plan")
|
||||
}
|
||||
if plan.ManagerName != "apt" {
|
||||
t.Fatalf("expected apt manager, got %q", plan.ManagerName)
|
||||
}
|
||||
if plan.DisplayCommand() != "sudo sh -c apt-get update && apt-get install -y samba" {
|
||||
t.Fatalf("unexpected command: %q", plan.DisplayCommand())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectSambaInstallPlanWithoutSudo(t *testing.T) {
|
||||
lookPath := func(file string) (string, error) {
|
||||
switch file {
|
||||
case "pacman":
|
||||
return "/usr/bin/pacman", nil
|
||||
default:
|
||||
return "", execErr(file)
|
||||
}
|
||||
}
|
||||
|
||||
plan, ok := DetectSambaInstallPlan(lookPath, false)
|
||||
if !ok {
|
||||
t.Fatalf("expected install plan")
|
||||
}
|
||||
if plan.DisplayCommand() != "pacman -Sy --noconfirm samba" {
|
||||
t.Fatalf("unexpected command: %q", plan.DisplayCommand())
|
||||
}
|
||||
}
|
||||
|
||||
type execErr string
|
||||
|
||||
func (e execErr) Error() string {
|
||||
return "not found: " + string(e)
|
||||
}
|
||||
|
||||
BIN
samba-configer
BIN
samba-configer
Binary file not shown.
Reference in New Issue
Block a user