feat: add invalid_users directive for Samba shares

Samba shares now support an 'invalid users' list (deny list), written
as 'invalid users = u1,u2' in smb.conf. The UI shows a ChipPicker
for valid_users and invalid_users, mutually exclusive, sourced from
the system user list.

feat: add ImportSystemUsers for fresh installations

When NASCTL_IMPORT_ON_BOOT=true, nasctl now imports existing system
users from /etc/passwd (UID 1000-60000) and /etc/group (supplemental
groups), and detects which have Samba accounts via 'pdbedit -L'.
Imported users are marked dirty so the admin can review before applying.
New POST /api/import/users endpoint for manual re-import.

This mirrors the existing import-on-boot flow for smb.conf and /etc/exports.
This commit is contained in:
2026-07-06 15:17:32 -04:00
parent 3e290164c5
commit 03e9368a91
18 changed files with 505 additions and 77 deletions
+55 -7
View File
@@ -8,34 +8,42 @@ import (
"github.com/darroyo/nasctl/internal/db"
"github.com/darroyo/nasctl/internal/modules/nfs"
"github.com/darroyo/nasctl/internal/modules/samba"
"github.com/darroyo/nasctl/internal/modules/users"
)
type ImportResult struct {
SambaImported int
NFSImported int
UsersImported int
SambaSkipped bool
NFSSkipped bool
NFSSkipped bool
UsersSkipped bool
SambaError string
NFSError string
UsersError string
}
type ImporterDB interface {
ListSambaShares() ([]db.SambaShare, error)
ListNFSExports() ([]db.NFSExport, error)
ListUsers() ([]db.User, error)
ReplaceSambaShares(shares []db.SambaShare) error
ReplaceNFSExports(exports []db.NFSExport) error
ReplaceUsers(users []db.User) error
GetSetting(key string) (string, bool, error)
SetSetting(key, value string) error
MarkDirty(module string) error
}
func ImportOnBoot(ctx context.Context, database ImporterDB, smbPath, exportsPath string) ImportResult {
func ImportOnBoot(ctx context.Context, database ImporterDB, smbPath, exportsPath, adminUsername string) ImportResult {
result := ImportResult{}
sambaDone, _, _ := database.GetSetting("import.samba.done")
nfsDone, _, _ := database.GetSetting("import.nfs.done")
usersDone, _, _ := database.GetSetting("import.users.done")
if sambaDone == "true" && nfsDone == "true" {
allDone := sambaDone == "true" && nfsDone == "true" && usersDone == "true"
if allDone {
return result
}
@@ -59,8 +67,19 @@ func ImportOnBoot(ctx context.Context, database ImporterDB, smbPath, exportsPath
}
}
if result.SambaImported > 0 || result.NFSImported > 0 {
log.Printf("[importer] imported %d samba shares, %d nfs exports", result.SambaImported, result.NFSImported)
if usersDone != "true" {
ur := importUsers(ctx, database, adminUsername)
result.UsersImported = ur.count
result.UsersSkipped = ur.skipped
result.UsersError = ur.err
if ur.err == "" {
_ = database.SetSetting("import.users.done", "true")
}
}
if result.SambaImported > 0 || result.NFSImported > 0 || result.UsersImported > 0 {
log.Printf("[importer] imported %d samba shares, %d nfs exports, %d users",
result.SambaImported, result.NFSImported, result.UsersImported)
}
return result
@@ -69,13 +88,14 @@ func ImportOnBoot(ctx context.Context, database ImporterDB, smbPath, exportsPath
func ResetImportFlags(ctx context.Context, database ImporterDB) error {
_ = database.SetSetting("import.samba.done", "")
_ = database.SetSetting("import.nfs.done", "")
_ = database.SetSetting("import.users.done", "")
return nil
}
type importStep struct {
count int
count int
skipped bool
err string
err string
}
func importSamba(ctx context.Context, database ImporterDB, path string) importStep {
@@ -133,3 +153,31 @@ func importNFS(ctx context.Context, database ImporterDB, path string) importStep
return importStep{count: len(exports)}
}
func importUsers(ctx context.Context, database ImporterDB, adminUsername string) importStep {
imported, err := ImportSystemUsers(ctx, adminUsername)
if err != nil {
return importStep{err: err.Error()}
}
if imported == nil {
return importStep{skipped: true}
}
existing, err := database.ListUsers()
if err != nil {
return importStep{err: err.Error()}
}
if len(existing) > 0 {
return importStep{skipped: true}
}
if err := database.ReplaceUsers(imported); err != nil {
return importStep{err: err.Error()}
}
if err := database.MarkDirty(users.ModuleName); err != nil {
return importStep{count: len(imported), err: fmt.Sprintf("imported but could not mark dirty: %v", err)}
}
return importStep{count: len(imported)}
}