diff --git a/internal/db/db.go b/internal/db/db.go index 4962e3d..cb8ce09 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -84,8 +84,12 @@ func migrate(ctx context.Context, database *sql.DB) error { ssh_password TEXT NOT NULL DEFAULT '', package_manager TEXT NOT NULL DEFAULT '', architecture TEXT NOT NULL DEFAULT '', + host_model TEXT NOT NULL DEFAULT '', kernel_version TEXT NOT NULL DEFAULT '', cpu_model TEXT NOT NULL DEFAULT '', + gpu_model TEXT NOT NULL DEFAULT '', + default_shell TEXT NOT NULL DEFAULT '', + package_count INTEGER NOT NULL DEFAULT 0, memory_total_mb INTEGER NOT NULL DEFAULT 0, disk_total_gb INTEGER NOT NULL DEFAULT 0, cpu_usage REAL NOT NULL DEFAULT 0, @@ -145,8 +149,12 @@ func migrate(ctx context.Context, database *sql.DB) error { `ALTER TABLE nodes ADD COLUMN tag TEXT NOT NULL DEFAULT '';`, `ALTER TABLE nodes ADD COLUMN package_manager TEXT NOT NULL DEFAULT '';`, `ALTER TABLE nodes ADD COLUMN architecture TEXT NOT NULL DEFAULT '';`, + `ALTER TABLE nodes ADD COLUMN host_model TEXT NOT NULL DEFAULT '';`, `ALTER TABLE nodes ADD COLUMN kernel_version TEXT NOT NULL DEFAULT '';`, `ALTER TABLE nodes ADD COLUMN cpu_model TEXT NOT NULL DEFAULT '';`, + `ALTER TABLE nodes ADD COLUMN gpu_model TEXT NOT NULL DEFAULT '';`, + `ALTER TABLE nodes ADD COLUMN default_shell TEXT NOT NULL DEFAULT '';`, + `ALTER TABLE nodes ADD COLUMN package_count INTEGER NOT NULL DEFAULT 0;`, `ALTER TABLE nodes ADD COLUMN memory_total_mb INTEGER NOT NULL DEFAULT 0;`, `ALTER TABLE nodes ADD COLUMN disk_total_gb INTEGER NOT NULL DEFAULT 0;`, `ALTER TABLE automation_jobs ADD COLUMN tag TEXT NOT NULL DEFAULT '';`, diff --git a/internal/models/models.go b/internal/models/models.go index 437d607..31036dd 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -55,8 +55,12 @@ type Node struct { SSHPassword string PackageManager string Architecture string + HostModel string KernelVersion string CPUModel string + GPUModel string + DefaultShell string + PackageCount int64 MemoryTotalMB int64 DiskTotalGB int64 CPUUsage float64 diff --git a/internal/services/node.go b/internal/services/node.go index 84ab2a6..bf587e5 100644 --- a/internal/services/node.go +++ b/internal/services/node.go @@ -25,7 +25,7 @@ func NewNodeService(database *sql.DB, crypto *CryptoService) *NodeService { func (s *NodeService) ListNodes(ctx context.Context, orgID int64) ([]models.Node, error) { rows, err := s.db.QueryContext(ctx, ` SELECT n.id, n.organization_id, n.group_id, COALESCE(g.name, ''), n.tag, n.name, n.distro, n.hostname, n.ip_address, n.mac_address, - n.ssh_port, n.ssh_username, n.ssh_password, n.package_manager, n.architecture, n.kernel_version, n.cpu_model, n.memory_total_mb, n.disk_total_gb, + n.ssh_port, n.ssh_username, n.ssh_password, n.package_manager, n.architecture, n.host_model, n.kernel_version, n.cpu_model, n.gpu_model, n.default_shell, n.package_count, n.memory_total_mb, n.disk_total_gb, n.cpu_usage, n.ram_usage, n.disk_usage, n.uptime_seconds, n.last_seen_at, n.auto_updates_enabled, n.notes, n.created_at, n.updated_at FROM nodes n @@ -44,7 +44,7 @@ func (s *NodeService) ListNodes(ctx context.Context, orgID int64) ([]models.Node func (s *NodeService) ListNodesByGroup(ctx context.Context, orgID, groupID int64) ([]models.Node, error) { rows, err := s.db.QueryContext(ctx, ` SELECT n.id, n.organization_id, n.group_id, COALESCE(g.name, ''), n.tag, n.name, n.distro, n.hostname, n.ip_address, n.mac_address, - n.ssh_port, n.ssh_username, n.ssh_password, n.package_manager, n.architecture, n.kernel_version, n.cpu_model, n.memory_total_mb, n.disk_total_gb, + n.ssh_port, n.ssh_username, n.ssh_password, n.package_manager, n.architecture, n.host_model, n.kernel_version, n.cpu_model, n.gpu_model, n.default_shell, n.package_count, n.memory_total_mb, n.disk_total_gb, n.cpu_usage, n.ram_usage, n.disk_usage, n.uptime_seconds, n.last_seen_at, n.auto_updates_enabled, n.notes, n.created_at, n.updated_at FROM nodes n @@ -63,7 +63,7 @@ func (s *NodeService) ListNodesByGroup(ctx context.Context, orgID, groupID int64 func (s *NodeService) ListNodesByTag(ctx context.Context, orgID int64, tag string) ([]models.Node, error) { rows, err := s.db.QueryContext(ctx, ` SELECT n.id, n.organization_id, n.group_id, COALESCE(g.name, ''), n.tag, n.name, n.distro, n.hostname, n.ip_address, n.mac_address, - n.ssh_port, n.ssh_username, n.ssh_password, n.package_manager, n.architecture, n.kernel_version, n.cpu_model, n.memory_total_mb, n.disk_total_gb, + n.ssh_port, n.ssh_username, n.ssh_password, n.package_manager, n.architecture, n.host_model, n.kernel_version, n.cpu_model, n.gpu_model, n.default_shell, n.package_count, n.memory_total_mb, n.disk_total_gb, n.cpu_usage, n.ram_usage, n.disk_usage, n.uptime_seconds, n.last_seen_at, n.auto_updates_enabled, n.notes, n.created_at, n.updated_at FROM nodes n @@ -86,7 +86,7 @@ func (s *NodeService) scanNodes(rows *sql.Rows) ([]models.Node, error) { if err := rows.Scan( &node.ID, &node.OrganizationID, &node.GroupID, &node.GroupName, &node.Tag, &node.Name, &node.Distro, &node.Hostname, &node.IPAddress, &node.MACAddress, &node.SSHPort, &node.SSHUsername, &node.SSHPassword, - &node.PackageManager, &node.Architecture, &node.KernelVersion, &node.CPUModel, &node.MemoryTotalMB, &node.DiskTotalGB, + &node.PackageManager, &node.Architecture, &node.HostModel, &node.KernelVersion, &node.CPUModel, &node.GPUModel, &node.DefaultShell, &node.PackageCount, &node.MemoryTotalMB, &node.DiskTotalGB, &node.CPUUsage, &node.RAMUsage, &node.DiskUsage, &node.UptimeSeconds, &node.LastSeenAt, &node.AutoUpdatesEnabled, &node.Notes, &node.CreatedAt, &node.UpdatedAt, ); err != nil { @@ -107,7 +107,7 @@ func (s *NodeService) GetNode(ctx context.Context, orgID, nodeID int64) (*models node := &models.Node{} err := s.db.QueryRowContext(ctx, ` SELECT n.id, n.organization_id, n.group_id, COALESCE(g.name, ''), n.tag, n.name, n.distro, n.hostname, n.ip_address, n.mac_address, - n.ssh_port, n.ssh_username, n.ssh_password, n.package_manager, n.architecture, n.kernel_version, n.cpu_model, n.memory_total_mb, n.disk_total_gb, + n.ssh_port, n.ssh_username, n.ssh_password, n.package_manager, n.architecture, n.host_model, n.kernel_version, n.cpu_model, n.gpu_model, n.default_shell, n.package_count, n.memory_total_mb, n.disk_total_gb, n.cpu_usage, n.ram_usage, n.disk_usage, n.uptime_seconds, n.last_seen_at, n.auto_updates_enabled, n.notes, n.created_at, n.updated_at FROM nodes n @@ -116,7 +116,7 @@ func (s *NodeService) GetNode(ctx context.Context, orgID, nodeID int64) (*models `, orgID, nodeID).Scan( &node.ID, &node.OrganizationID, &node.GroupID, &node.GroupName, &node.Tag, &node.Name, &node.Distro, &node.Hostname, &node.IPAddress, &node.MACAddress, &node.SSHPort, &node.SSHUsername, &node.SSHPassword, - &node.PackageManager, &node.Architecture, &node.KernelVersion, &node.CPUModel, &node.MemoryTotalMB, &node.DiskTotalGB, + &node.PackageManager, &node.Architecture, &node.HostModel, &node.KernelVersion, &node.CPUModel, &node.GPUModel, &node.DefaultShell, &node.PackageCount, &node.MemoryTotalMB, &node.DiskTotalGB, &node.CPUUsage, &node.RAMUsage, &node.DiskUsage, &node.UptimeSeconds, &node.LastSeenAt, &node.AutoUpdatesEnabled, &node.Notes, &node.CreatedAt, &node.UpdatedAt, ) @@ -147,11 +147,11 @@ func (s *NodeService) SaveNode(ctx context.Context, node *models.Node) error { result, err := s.db.ExecContext(ctx, ` INSERT INTO nodes ( organization_id, group_id, tag, name, distro, hostname, ip_address, mac_address, - ssh_port, ssh_username, ssh_password, package_manager, architecture, kernel_version, cpu_model, memory_total_mb, disk_total_gb, + ssh_port, ssh_username, ssh_password, package_manager, architecture, host_model, kernel_version, cpu_model, gpu_model, default_shell, package_count, memory_total_mb, disk_total_gb, auto_updates_enabled, notes - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, node.OrganizationID, node.GroupID, node.Tag, node.Name, node.Distro, node.Hostname, node.IPAddress, - node.MACAddress, node.SSHPort, node.SSHUsername, encryptedPassword, node.PackageManager, node.Architecture, node.KernelVersion, node.CPUModel, node.MemoryTotalMB, node.DiskTotalGB, node.AutoUpdatesEnabled, node.Notes) + node.MACAddress, node.SSHPort, node.SSHUsername, encryptedPassword, node.PackageManager, node.Architecture, node.HostModel, node.KernelVersion, node.CPUModel, node.GPUModel, node.DefaultShell, node.PackageCount, node.MemoryTotalMB, node.DiskTotalGB, node.AutoUpdatesEnabled, node.Notes) if err != nil { return err } @@ -162,12 +162,12 @@ func (s *NodeService) SaveNode(ctx context.Context, node *models.Node) error { _, err := s.db.ExecContext(ctx, ` UPDATE nodes SET group_id = ?, tag = ?, name = ?, distro = ?, hostname = ?, ip_address = ?, mac_address = ?, - ssh_port = ?, ssh_username = ?, ssh_password = ?, package_manager = ?, architecture = ?, kernel_version = ?, cpu_model = ?, memory_total_mb = ?, disk_total_gb = ?, + ssh_port = ?, ssh_username = ?, ssh_password = ?, package_manager = ?, architecture = ?, host_model = ?, kernel_version = ?, cpu_model = ?, gpu_model = ?, default_shell = ?, package_count = ?, memory_total_mb = ?, disk_total_gb = ?, auto_updates_enabled = ?, notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND organization_id = ? `, node.GroupID, node.Tag, node.Name, node.Distro, node.Hostname, node.IPAddress, node.MACAddress, - node.SSHPort, node.SSHUsername, encryptedPassword, node.PackageManager, node.Architecture, node.KernelVersion, node.CPUModel, node.MemoryTotalMB, node.DiskTotalGB, node.AutoUpdatesEnabled, node.Notes, + node.SSHPort, node.SSHUsername, encryptedPassword, node.PackageManager, node.Architecture, node.HostModel, node.KernelVersion, node.CPUModel, node.GPUModel, node.DefaultShell, node.PackageCount, node.MemoryTotalMB, node.DiskTotalGB, node.AutoUpdatesEnabled, node.Notes, node.ID, node.OrganizationID) return err } @@ -286,9 +286,20 @@ func (s *NodeService) RefreshNodeInventory(ctx context.Context, node *models.Nod `if [ -r /etc/os-release ]; then . /etc/os-release; echo DISTRO="${PRETTY_NAME:-$ID}"; else echo DISTRO="$(uname -s)"; fi`, `echo HOSTNAME="$(hostname 2>/dev/null || uname -n)"`, `echo ARCH="$(uname -m 2>/dev/null)"`, + `HOST_MODEL="$(cat /sys/devices/virtual/dmi/id/product_name 2>/dev/null || hostnamectl 2>/dev/null | awk -F: '/Chassis|Hardware Model/ {gsub(/^[ \t]+/, "", $2); print $2; exit}')"; echo HOST_MODEL="${HOST_MODEL}"`, `echo KERNEL="$(uname -r 2>/dev/null)"`, `if command -v apt >/dev/null 2>&1; then echo PKG_MGR=apt; elif command -v dnf >/dev/null 2>&1; then echo PKG_MGR=dnf; elif command -v yum >/dev/null 2>&1; then echo PKG_MGR=yum; elif command -v pacman >/dev/null 2>&1; then echo PKG_MGR=pacman; elif command -v zypper >/dev/null 2>&1; then echo PKG_MGR=zypper; elif command -v apk >/dev/null 2>&1; then echo PKG_MGR=apk; elif command -v nix-env >/dev/null 2>&1; then echo PKG_MGR=nix; elif command -v emerge >/dev/null 2>&1; then echo PKG_MGR=emerge; else echo PKG_MGR=unknown; fi`, `CPU_MODEL="$( (command -v lscpu >/dev/null 2>&1 && lscpu | awk -F: '/Model name/ {gsub(/^[ \t]+/, "", $2); print $2; exit}') || awk -F: '/model name/ {gsub(/^[ \t]+/, "", $2); print $2; exit}' /proc/cpuinfo )"; echo CPU_MODEL="${CPU_MODEL}"`, + `GPU_MODEL="$( (command -v lspci >/dev/null 2>&1 && lspci | awk -F': ' '/VGA compatible controller|3D controller|Display controller/ {print $2; exit}') || true )"; echo GPU_MODEL="${GPU_MODEL}"`, + `DEFAULT_SHELL="${SHELL:-$(getent passwd "$(id -un 2>/dev/null)" 2>/dev/null | cut -d: -f7)}"; echo DEFAULT_SHELL="${DEFAULT_SHELL}"`, + `PACKAGE_COUNT="$( + if command -v dpkg-query >/dev/null 2>&1; then dpkg-query -f '.' -W 2>/dev/null | wc -c; + elif command -v rpm >/dev/null 2>&1; then rpm -qa 2>/dev/null | wc -l; + elif command -v pacman >/dev/null 2>&1; then pacman -Qq 2>/dev/null | wc -l; + elif command -v apk >/dev/null 2>&1; then apk info 2>/dev/null | wc -l; + elif command -v nix-store >/dev/null 2>&1; then nix-store --gc --print-live 2>/dev/null | wc -l; + else printf 0; fi + )"; echo PACKAGE_COUNT="${PACKAGE_COUNT:-0}"`, `MEMORY_MB="$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo 2>/dev/null)"; echo MEMORY_MB="${MEMORY_MB:-0}"`, `DISK_GB="$(df -BG / 2>/dev/null | awk 'NR==2 {gsub(/G/, "", $2); print $2}')"; echo DISK_GB="${DISK_GB:-0}"`, }, " ; ")) @@ -308,15 +319,23 @@ func (s *NodeService) RefreshNodeInventory(ctx context.Context, node *models.Nod var memoryMB int64 var diskGB int64 + var packageCount int64 fmt.Sscanf(values["MEMORY_MB"], "%d", &memoryMB) fmt.Sscanf(values["DISK_GB"], "%d", &diskGB) + fmt.Sscanf(values["PACKAGE_COUNT"], "%d", &packageCount) node.Distro = fallbackInventoryValue(values["DISTRO"], node.Distro, "Linux") node.Hostname = fallbackInventoryValue(values["HOSTNAME"], node.Hostname, node.IPAddress) node.PackageManager = fallbackInventoryValue(values["PKG_MGR"], node.PackageManager, "") node.Architecture = fallbackInventoryValue(values["ARCH"], node.Architecture, "") + node.HostModel = fallbackInventoryValue(values["HOST_MODEL"], node.HostModel, "") node.KernelVersion = fallbackInventoryValue(values["KERNEL"], node.KernelVersion, "") node.CPUModel = fallbackInventoryValue(values["CPU_MODEL"], node.CPUModel, "") + node.GPUModel = fallbackInventoryValue(values["GPU_MODEL"], node.GPUModel, "") + node.DefaultShell = fallbackInventoryValue(values["DEFAULT_SHELL"], node.DefaultShell, "") + if packageCount > 0 { + node.PackageCount = packageCount + } if memoryMB > 0 { node.MemoryTotalMB = memoryMB } @@ -326,10 +345,10 @@ func (s *NodeService) RefreshNodeInventory(ctx context.Context, node *models.Nod _, err = s.db.ExecContext(ctx, ` UPDATE nodes - SET distro = ?, hostname = ?, package_manager = ?, architecture = ?, kernel_version = ?, cpu_model = ?, memory_total_mb = ?, disk_total_gb = ?, + SET distro = ?, hostname = ?, package_manager = ?, architecture = ?, host_model = ?, kernel_version = ?, cpu_model = ?, gpu_model = ?, default_shell = ?, package_count = ?, memory_total_mb = ?, disk_total_gb = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND organization_id = ? - `, node.Distro, node.Hostname, node.PackageManager, node.Architecture, node.KernelVersion, node.CPUModel, node.MemoryTotalMB, node.DiskTotalGB, node.ID, node.OrganizationID) + `, node.Distro, node.Hostname, node.PackageManager, node.Architecture, node.HostModel, node.KernelVersion, node.CPUModel, node.GPUModel, node.DefaultShell, node.PackageCount, node.MemoryTotalMB, node.DiskTotalGB, node.ID, node.OrganizationID) if err != nil { return output, err }