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,124 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/darroyo/nasctl/internal/db"
|
||||
"github.com/darroyo/nasctl/internal/importer"
|
||||
"github.com/darroyo/nasctl/internal/modules/nfs"
|
||||
"github.com/darroyo/nasctl/internal/modules/samba"
|
||||
)
|
||||
|
||||
type importStatus struct {
|
||||
Samba moduleImportStatus `json:"samba"`
|
||||
NFS moduleImportStatus `json:"nfs"`
|
||||
}
|
||||
|
||||
type moduleImportStatus struct {
|
||||
Done bool `json:"done"`
|
||||
LastImportAt string `json:"last_import_at,omitempty"`
|
||||
Count int `json:"count,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Server) handleImportStatus(w http.ResponseWriter, r *http.Request) {
|
||||
sambaDone, _, _ := s.DB.GetSetting("import.samba.done")
|
||||
nfsDone, _, _ := s.DB.GetSetting("import.nfs.done")
|
||||
|
||||
shares, _ := s.DB.ListSambaShares()
|
||||
exports, _ := s.DB.ListNFSExports()
|
||||
|
||||
status := importStatus{
|
||||
Samba: moduleImportStatus{
|
||||
Done: sambaDone == "true",
|
||||
Count: len(shares),
|
||||
},
|
||||
NFS: moduleImportStatus{
|
||||
Done: nfsDone == "true",
|
||||
Count: len(exports),
|
||||
},
|
||||
}
|
||||
|
||||
if ts, ok, _ := s.DB.GetSetting("import.samba.at"); ok {
|
||||
status.Samba.LastImportAt = ts
|
||||
}
|
||||
if ts, ok, _ := s.DB.GetSetting("import.nfs.at"); ok {
|
||||
status.NFS.LastImportAt = ts
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, status)
|
||||
}
|
||||
|
||||
func (s *Server) handleImportSamba(w http.ResponseWriter, r *http.Request) {
|
||||
if err := importer.ResetImportFlags(r.Context(), s.DB); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
shares, err := importer.ImportSambaShares(s.SMBConfPath)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(shares) == 0 {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"imported": 0, "message": "no shares found in smb.conf"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.DB.ReplaceSambaShares(shares); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.DB.MarkDirty(samba.ModuleName); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_ = s.DB.SetSetting("import.samba.done", "true")
|
||||
_ = s.DB.SetSetting("import.samba.at", time.Now().Format(time.RFC3339))
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{"imported": len(shares)})
|
||||
}
|
||||
|
||||
func (s *Server) handleImportNFS(w http.ResponseWriter, r *http.Request) {
|
||||
if err := importer.ResetImportFlags(r.Context(), s.DB); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
exports, err := importer.ImportNFSExports(s.ExportsPath)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(exports) == 0 {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"imported": 0, "message": "no exports found in exports file"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.DB.ReplaceNFSExports(exports); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.DB.MarkDirty(nfs.ModuleName); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_ = s.DB.SetSetting("import.nfs.done", "true")
|
||||
_ = s.DB.SetSetting("import.nfs.at", time.Now().Format(time.RFC3339))
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{"imported": len(exports)})
|
||||
}
|
||||
|
||||
type dbExporter interface {
|
||||
ReplaceSambaShares(shares []db.SambaShare) error
|
||||
ReplaceNFSExports(exports []db.NFSExport) error
|
||||
MarkDirty(module string) error
|
||||
}
|
||||
|
||||
var _ dbExporter = (*db.DB)(nil)
|
||||
Reference in New Issue
Block a user