controller v0.92.0: NAS network storage Part A2 (registry + UI + per-share health)
Controller-side of NAS network storage, proxying to agent A1 /netstorage/*. Distinct 'network' storage kind (no drive lifecycle); add/list/remove + per-share health UI; unreachable NAS is a recoverable warning, never the drive missing/stop cascade; SMB creds pass through to the agent, never persisted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
This commit is contained in:
@@ -115,9 +115,22 @@ type CrossDriveBackup struct {
|
||||
PreferredTarget string `json:"preferred_target,omitempty"`
|
||||
}
|
||||
|
||||
// Storage path kinds. A DRIVE is a physical disk with the full enroll/eject/decommission/migrate/wipe
|
||||
// lifecycle (agent /disks). A NETWORK share is a NAS (Part A2) proxied to the agent /netstorage — a
|
||||
// DISTINCT class with NO drive lifecycle (no eject/decommission/migrate/wipe/SMART; remove is the only
|
||||
// lifecycle action). An empty Kind means "drive" (back-compat with already-persisted paths).
|
||||
const (
|
||||
StorageKindDrive = "drive"
|
||||
StorageKindNetwork = "network"
|
||||
)
|
||||
|
||||
// NetworkMountRoot is the in-guest path under which the agent propagates NAS shares (mirrors the agent's
|
||||
// /mnt/felhom-drives bind root). A registered network path is NetworkMountRoot + "/" + <share name>.
|
||||
const NetworkMountRoot = "/mnt/felhom-drives"
|
||||
|
||||
// StoragePath represents a registered external storage location.
|
||||
type StoragePath struct {
|
||||
Path string `json:"path"` // e.g., "/mnt/hdd_1"
|
||||
Path string `json:"path"` // e.g., "/mnt/hdd_1" (drive) or "/mnt/felhom-drives/<name>" (network)
|
||||
Label string `json:"label,omitempty"` // e.g., "Külső HDD 1TB"
|
||||
IsDefault bool `json:"is_default,omitempty"` // new apps use this by default
|
||||
Schedulable bool `json:"schedulable"` // whether new apps can be deployed here
|
||||
@@ -128,8 +141,23 @@ type StoragePath struct {
|
||||
Decommissioned bool `json:"decommissioned,omitempty"` // true when drive data migrated to another
|
||||
DecommissionedAt string `json:"decommissioned_at,omitempty"` // RFC3339 timestamp
|
||||
MigratedTo string `json:"migrated_to,omitempty"` // path of target drive
|
||||
|
||||
// Kind discriminates a physical drive ("" / "drive") from a NAS network share ("network"). A network
|
||||
// share is bulk-media only and carries the fields below; the drive lifecycle does NOT apply to it.
|
||||
Kind string `json:"kind,omitempty"`
|
||||
// Network-storage descriptors (Kind=="network" only; mirror the agent A1 add request). NO password
|
||||
// is stored — the SMB credential is passed through to the agent at add-time and never persisted here.
|
||||
Protocol string `json:"protocol,omitempty"` // nfs | smb
|
||||
Server string `json:"server,omitempty"`
|
||||
Export string `json:"export,omitempty"`
|
||||
MappedUID int `json:"mapped_uid,omitempty"`
|
||||
MappedGID int `json:"mapped_gid,omitempty"`
|
||||
}
|
||||
|
||||
// IsNetwork reports whether this is a NAS network-storage path (vs a physical drive). The drive
|
||||
// lifecycle (eject/decommission/migrate/wipe/SMART) must NEVER be applied to a network path.
|
||||
func (p StoragePath) IsNetwork() bool { return p.Kind == StorageKindNetwork }
|
||||
|
||||
// NotificationPrefs holds customer notification preferences.
|
||||
type NotificationPrefs struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
@@ -846,6 +874,20 @@ func (s *Settings) IsStoragePathKnown(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNetworkStoragePath reports whether `path` belongs to a registered NAS network-storage path
|
||||
// (Kind=="network"). The drive lifecycle (eject/decommission/migrate/wipe) must refuse such a path —
|
||||
// a NAS has no device lifecycle. Matches the exact path or a child under it.
|
||||
func (s *Settings) IsNetworkStoragePath(path string) bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
for _, sp := range s.StoragePaths {
|
||||
if path == sp.Path || strings.HasPrefix(path, sp.Path+"/") {
|
||||
return sp.IsNetwork()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsStoragePathSchedulable returns whether a path belongs to a registered,
|
||||
// schedulable (active) storage path. Returns false if the path is unknown,
|
||||
// disconnected, decommissioned, or inactive.
|
||||
|
||||
Reference in New Issue
Block a user