2
This commit is contained in:
@@ -19,6 +19,8 @@ go build -o samba-configer .
|
|||||||
The program:
|
The program:
|
||||||
|
|
||||||
- Reads the Samba config and parses non-`[global]` sections as shares.
|
- 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.
|
- Prompts to add, edit, or delete a share.
|
||||||
- Checks `valid users` entries against local accounts.
|
- Checks `valid users` entries against local accounts.
|
||||||
- Offers to create missing local accounts with `useradd -M -s /usr/sbin/nologin <user>`.
|
- 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
|
Run(name string, args ...string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LookPathFunc func(file string) (string, error)
|
||||||
|
|
||||||
type RealUserManager struct{}
|
type RealUserManager struct{}
|
||||||
|
|
||||||
func (RealUserManager) UserExists(name string) bool {
|
func (RealUserManager) UserExists(name string) bool {
|
||||||
@@ -49,14 +51,18 @@ func (OSCommandRunner) Run(name string, args ...string) error {
|
|||||||
type App struct {
|
type App struct {
|
||||||
configPath string
|
configPath string
|
||||||
users UserManager
|
users UserManager
|
||||||
|
runner CommandRunner
|
||||||
|
lookPath LookPathFunc
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
writer *bufio.Writer
|
writer *bufio.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(configPath string, users UserManager) *App {
|
func NewApp(configPath string, users UserManager, runner CommandRunner, lookPath LookPathFunc) *App {
|
||||||
return &App{
|
return &App{
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
users: users,
|
users: users,
|
||||||
|
runner: runner,
|
||||||
|
lookPath: lookPath,
|
||||||
reader: bufio.NewReader(os.Stdin),
|
reader: bufio.NewReader(os.Stdin),
|
||||||
writer: bufio.NewWriter(os.Stdout),
|
writer: bufio.NewWriter(os.Stdout),
|
||||||
}
|
}
|
||||||
@@ -65,9 +71,17 @@ func NewApp(configPath string, users UserManager) *App {
|
|||||||
func (a *App) Run() error {
|
func (a *App) Run() error {
|
||||||
doc, err := ParseConfigFile(a.configPath)
|
doc, err := ParseConfigFile(a.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
doc, err = a.handleMissingConfig()
|
||||||
|
if err == nil {
|
||||||
|
goto loaded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Errorf("read config: %w", err)
|
return fmt.Errorf("read config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loaded:
|
||||||
for {
|
for {
|
||||||
shareSections := doc.ShareSections()
|
shareSections := doc.ShareSections()
|
||||||
a.println("")
|
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 {
|
func (a *App) addShare(doc *Document) error {
|
||||||
cfg, err := a.collectShareConfig(ShareConfig{})
|
cfg, err := a.collectShareConfig(ShareConfig{})
|
||||||
if err != nil {
|
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")
|
configPath := flag.String("config", "/etc/samba/smb.conf", "path to smb.conf")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
app := NewApp(*configPath, RealUserManager{})
|
app := NewApp(*configPath, RealUserManager{}, OSCommandRunner{}, execLookPath)
|
||||||
if err := app.Run(); err != nil {
|
if err := app.Run(); err != nil {
|
||||||
if errors.Is(err, ErrCancelled) {
|
if errors.Is(err, ErrCancelled) {
|
||||||
fmt.Fprintln(os.Stderr, "cancelled")
|
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