feat: import existing smb.conf and /etc/exports on first boot
Adds auto-detection of pre-existing Samba shares and NFS exports when nasctl is installed on a host that already has these configs. New package internal/importer parses smb.conf (INI-style) and /etc/exports (line-based) and imports them into SQLite. Imported shares/exports are marked dirty so the user must review and apply manually before any file is overwritten. Backup: before the first Apply, each module backs up the original config to <path>.nasctl.bak.<timestamp> (one time only). New CLI flag --import-on-boot / NASCTL_IMPORT_ON_BOOT env var (default false, opt-in). New API endpoints: GET /api/import/status POST /api/import/samba POST /api/import/nfs New DB methods ReplaceSambaShares/ReplaceNFSExports (transactional replace-all), guarded by import.samba.done / import.nfs.done settings flags.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseSambaConf(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantLen int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "basic share",
|
||||
input: `[global]
|
||||
workgroup = WORKGROUP
|
||||
security = user
|
||||
|
||||
[share1]
|
||||
path = /srv/samba/share1
|
||||
comment = Test Share
|
||||
read only = no
|
||||
guest ok = no
|
||||
`,
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "multiple shares with valid users and groups",
|
||||
input: `[global]
|
||||
server string = Test Server
|
||||
|
||||
[public]
|
||||
path = /srv/samba/public
|
||||
comment = Public Files
|
||||
read only = yes
|
||||
guest ok = yes
|
||||
|
||||
[data]
|
||||
path = /srv/samba/data
|
||||
comment = Data Share
|
||||
read only = no
|
||||
writable = yes
|
||||
guest ok = no
|
||||
valid users = alice, bob
|
||||
valid groups = staff
|
||||
`,
|
||||
wantLen: 2,
|
||||
},
|
||||
{
|
||||
name: "ignores global section",
|
||||
input: `[global]
|
||||
workgroup = WORKGROUP
|
||||
security = user
|
||||
server string = test
|
||||
|
||||
[global]
|
||||
printing = cups
|
||||
|
||||
[myshare]
|
||||
path = /srv/samba/myshare
|
||||
`,
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "ignores printers section",
|
||||
input: `[printers]
|
||||
comment = All Printers
|
||||
path = /var/spool/samba
|
||||
printable = yes
|
||||
guest ok = yes
|
||||
|
||||
[global]
|
||||
workgroup = WORKGROUP
|
||||
|
||||
[myshare]
|
||||
path = /srv/samba/myshare
|
||||
`,
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "handles comments with semicolon",
|
||||
input: `; This is a comment
|
||||
[global]
|
||||
workgroup = WORKGROUP
|
||||
|
||||
[myshare]
|
||||
path = /srv/samba/myshare
|
||||
`,
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "handles continuation lines",
|
||||
input: `[myshare]
|
||||
path = /srv/samba/\
|
||||
myshare
|
||||
comment = continued\
|
||||
line
|
||||
`,
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "handles quoted values",
|
||||
input: `[myshare]
|
||||
path = "/srv/samba/myshare"
|
||||
comment = "Test comment"
|
||||
`,
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "empty input",
|
||||
input: "",
|
||||
wantLen: 0,
|
||||
},
|
||||
{
|
||||
name: "skip share without path",
|
||||
input: `[nopath]
|
||||
comment = No path here
|
||||
`,
|
||||
wantLen: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseSambaConf([]byte(tt.input))
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseSambaConf() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if len(got) != tt.wantLen {
|
||||
t.Errorf("parseSambaConf() got %d shares, want %d", len(got), tt.wantLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBool(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"yes", true},
|
||||
{"no", false},
|
||||
{"true", true},
|
||||
{"false", false},
|
||||
{"1", true},
|
||||
{"0", false},
|
||||
{"on", true},
|
||||
{"off", false},
|
||||
{"YES", true},
|
||||
{"NO", false},
|
||||
{"True", true},
|
||||
{"False", false},
|
||||
{"", false},
|
||||
{"maybe", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
if got := parseBool(tt.input); got != tt.expected {
|
||||
t.Errorf("parseBool(%q) = %v, want %v", tt.input, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user