package main import ( "flag" "log" "net/http" "os" "path/filepath" "strings" "github.com/darroyo/nasctl/internal/db" "github.com/darroyo/nasctl/internal/engine" "github.com/darroyo/nasctl/internal/importer" "github.com/darroyo/nasctl/internal/modules/nfs" "github.com/darroyo/nasctl/internal/modules/samba" "github.com/darroyo/nasctl/internal/modules/users" "github.com/darroyo/nasctl/internal/web" ) func main() { addr := flag.String("addr", envOrDefault("NASCTL_ADDR", ":8080"), "HTTP listen address") dbPath := flag.String("db", envOrDefault("NASCTL_DB", "/var/lib/nasctl/nasctl.db"), "SQLite database path") smbConfPath := flag.String("smb-conf", envOrDefault("NASCTL_SMB_CONF", "/etc/samba/smb.conf"), "Samba config output path") exportsPath := flag.String("exports", envOrDefault("NASCTL_EXPORTS", "/etc/exports"), "NFS exports output path") execSystem := flag.Bool("exec-system", envOrDefault("NASCTL_EXEC_SYSTEM", "true") == "true", "Execute real system commands (reload services, useradd, etc.)") allowedRoots := flag.String("allowed-roots", envOrDefault("NASCTL_ALLOWED_ROOTS", ""), "Comma-separated allowed root dirs for shares/exports (empty = any absolute path)") adminUser := flag.String("admin-user", envOrDefault("NASCTL_ADMIN_USER", "admin"), "Initial admin username (only used if no admin exists)") adminPass := flag.String("admin-pass", envOrDefault("NASCTL_ADMIN_PASSWORD", "admin"), "Initial admin password (only used if no admin exists)") importOnBoot := flag.Bool("import-on-boot", envOrDefault("NASCTL_IMPORT_ON_BOOT", "false") == "true", "Import existing smb.conf and /etc/exports on first boot") flag.Parse() if err := os.MkdirAll(filepath.Dir(*dbPath), 0o755); err != nil { log.Fatalf("create db dir: %v", err) } database, err := db.Open(*dbPath) if err != nil { log.Fatalf("open db: %v", err) } defer database.Close() if err := database.Migrate(); err != nil { log.Fatalf("migrate db: %v", err) } auth, err := web.NewAuthService(database) if err != nil { log.Fatalf("init auth: %v", err) } created, err := auth.EnsureAdmin(*adminUser, *adminPass) if err != nil { log.Fatalf("ensure admin: %v", err) } if created { log.Printf("created initial admin user %q — change the password after first login", *adminUser) } if *importOnBoot { result := importer.ImportOnBoot(nil, database, *smbConfPath, *exportsPath) if result.SambaImported > 0 { log.Printf("[importer] imported %d existing samba shares — review and apply", result.SambaImported) } if result.NFSImported > 0 { log.Printf("[importer] imported %d existing nfs exports — review and apply", result.NFSImported) } } sambaModule := samba.New(samba.Config{ SMBConfPath: *smbConfPath, Reload: *execSystem, }) nfsModule := nfs.New(nfs.Config{ ExportsPath: *exportsPath, Reload: *execSystem, }) usersModule := users.New(users.Config{ Execute: *execSystem, CreateHome: true, }) eng := engine.New(sambaModule, nfsModule, usersModule) srv := web.NewServer(database, eng, web.Options{ AllowedRoots: parseRoots(*allowedRoots), Auth: auth, SMBConfPath: *smbConfPath, ExportsPath: *exportsPath, }) log.Printf("nasctl %s (commit %s) listening on %s (db=%s exec-system=%v)", web.Version, web.Commit, *addr, *dbPath, *execSystem) if err := http.ListenAndServe(*addr, srv.Handler()); err != nil { log.Fatalf("server error: %v", err) } } func parseRoots(raw string) []string { if strings.TrimSpace(raw) == "" { return nil } parts := strings.Split(raw, ",") roots := make([]string, 0, len(parts)) for _, p := range parts { p = strings.TrimSpace(p) if p != "" { roots = append(roots, p) } } return roots } func envOrDefault(key, fallback string) string { if value := os.Getenv(key); value != "" { return value } return fallback }