e070a3fb5f
OTA update server designed for high-concurrency, low-bandwidth deployments. GORM-backed PostgreSQL, JWT auth, device management, artefact versioning, deployment rollout with gated rate-limited downloads, and a React admin panel.
185 lines
6.6 KiB
Go
185 lines
6.6 KiB
Go
package model
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Device status constants
|
|
const (
|
|
DeviceStatusOnline = "online"
|
|
DeviceStatusOffline = "offline"
|
|
DeviceStatusUpgrading = "upgrading"
|
|
)
|
|
|
|
// Deployment status constants
|
|
const (
|
|
DeploymentStatusDraft = "draft"
|
|
DeploymentStatusActive = "active"
|
|
DeploymentStatusPaused = "paused"
|
|
DeploymentStatusCompleted = "completed"
|
|
DeploymentStatusCancelled = "cancelled"
|
|
)
|
|
|
|
// Deployment device status constants
|
|
const (
|
|
DeployDeviceStatusPending = "pending"
|
|
DeployDeviceStatusDownloading = "downloading"
|
|
DeployDeviceStatusDownloaded = "downloaded"
|
|
DeployDeviceStatusVerifying = "verifying"
|
|
DeployDeviceStatusInstalling = "installing"
|
|
DeployDeviceStatusCompleted = "completed"
|
|
DeployDeviceStatusFailed = "failed"
|
|
DeployDeviceStatusRolledBack = "rolled_back"
|
|
)
|
|
|
|
// Rollout strategy constants
|
|
const (
|
|
RolloutImmediate = "immediate"
|
|
RolloutStaged = "staged"
|
|
)
|
|
|
|
// Target type constants
|
|
const (
|
|
TargetAll = "all"
|
|
TargetDevice = "device"
|
|
TargetGroup = "group"
|
|
)
|
|
|
|
// Device represents a connected daemon instance.
|
|
type Device struct {
|
|
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
|
DeviceID string `json:"deviceId" gorm:"uniqueIndex;size:256;not null"`
|
|
IPAddress string `json:"ipAddress" gorm:"size:64;default:''"`
|
|
Hostname string `json:"hostname" gorm:"size:256;default:''"`
|
|
OS string `json:"os" gorm:"size:64;default:''"`
|
|
CurrentVersion string `json:"currentVersion" gorm:"size:128;default:'0.0.0'"`
|
|
Status string `json:"status" gorm:"size:32;default:'online';index"`
|
|
GroupName string `json:"groupName" gorm:"size:128;default:'default';index"`
|
|
LastHeartbeat time.Time `json:"lastHeartbeat"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// Artefact represents an uploaded update package (JAR or patch).
|
|
type Artefact struct {
|
|
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
|
Version string `json:"version" gorm:"size:128;not null;index"`
|
|
FileName string `json:"fileName" gorm:"size:512;not null"`
|
|
FilePath string `json:"-" gorm:"size:1024;not null"`
|
|
FileSize int64 `json:"fileSize"`
|
|
SHA256Hash string `json:"sha256Hash" gorm:"size:128"`
|
|
DeltaRef string `json:"deltaRef" gorm:"size:128;default:''"`
|
|
IsDelta bool `json:"isDelta"`
|
|
Metadata string `json:"metadata" gorm:"type:text;default:'{}'"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// Deployment defines an update rollout campaign.
|
|
type Deployment struct {
|
|
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
|
ArtefactID int64 `json:"artefactId" gorm:"not null;index"`
|
|
Artefact *Artefact `json:"artefact,omitempty" gorm:"foreignKey:ArtefactID"`
|
|
Name string `json:"name" gorm:"size:256;not null"`
|
|
Description string `json:"description" gorm:"type:text;default:''"`
|
|
TargetType string `json:"targetType" gorm:"size:32;default:'all'"`
|
|
TargetSpec string `json:"targetSpec" gorm:"type:text;default:'{}'"`
|
|
RolloutStrategy string `json:"rolloutStrategy" gorm:"size:32;default:'immediate'"`
|
|
MaxConcurrent int `json:"maxConcurrent" gorm:"default:50"`
|
|
Status string `json:"status" gorm:"size:32;default:'draft';index"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// DeploymentDevice tracks an individual device's progress through a deployment.
|
|
type DeploymentDevice struct {
|
|
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
|
DeploymentID int64 `json:"deploymentId" gorm:"not null;uniqueIndex:idx_dd_dep_dev;index"`
|
|
DeviceID string `json:"deviceId" gorm:"size:256;not null;uniqueIndex:idx_dd_dep_dev;index"`
|
|
Status string `json:"status" gorm:"size:32;default:'pending';index"`
|
|
DownloadedAt *time.Time `json:"downloadedAt"`
|
|
CompletedAt *time.Time `json:"completedAt"`
|
|
Error string `json:"error" gorm:"type:text;default:''"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// AdminUser represents an operator account.
|
|
type AdminUser struct {
|
|
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
|
Username string `json:"username" gorm:"uniqueIndex;size:128;not null"`
|
|
PasswordHash string `json:"-" gorm:"size:256;not null"`
|
|
Role string `json:"role" gorm:"size:32;default:'admin'"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// -- Request/Response DTOs --
|
|
|
|
type LoginRequest struct {
|
|
Username string `json:"username" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
}
|
|
|
|
type LoginResponse struct {
|
|
Token string `json:"token"`
|
|
ExpiresAt int64 `json:"expiresAt"`
|
|
}
|
|
|
|
type RegisterDeviceRequest struct {
|
|
DeviceID string `json:"deviceId" binding:"required"`
|
|
IPAddress string `json:"ipAddress"`
|
|
Hostname string `json:"hostname"`
|
|
OS string `json:"os"`
|
|
CurrentVersion string `json:"currentVersion"`
|
|
GroupName string `json:"groupName"`
|
|
}
|
|
|
|
type HeartbeatRequest struct {
|
|
DeviceID string `json:"deviceId" binding:"required"`
|
|
CurrentVersion string `json:"currentVersion"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
type CheckUpdateResponse struct {
|
|
HasUpdate bool `json:"hasUpdate"`
|
|
ArtefactID int64 `json:"artefactId,omitempty"`
|
|
Version string `json:"version,omitempty"`
|
|
FileSize int64 `json:"fileSize,omitempty"`
|
|
SHA256Hash string `json:"sha256Hash,omitempty"`
|
|
DownloadURL string `json:"downloadUrl,omitempty"`
|
|
}
|
|
|
|
type CreateDeploymentRequest struct {
|
|
ArtefactID int64 `json:"artefactId" binding:"required"`
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description"`
|
|
TargetType string `json:"targetType" binding:"required"`
|
|
TargetSpec string `json:"targetSpec"`
|
|
RolloutStrategy string `json:"rolloutStrategy"`
|
|
MaxConcurrent int `json:"maxConcurrent"`
|
|
}
|
|
|
|
type PaginatedResponse struct {
|
|
Data interface{} `json:"data"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
PageSize int `json:"pageSize"`
|
|
}
|
|
|
|
// -- Hooks --
|
|
|
|
func (d *Device) BeforeCreate(tx *gorm.DB) error {
|
|
now := time.Now()
|
|
if d.LastHeartbeat.IsZero() {
|
|
d.LastHeartbeat = now
|
|
}
|
|
if d.CreatedAt.IsZero() {
|
|
d.CreatedAt = now
|
|
}
|
|
if d.UpdatedAt.IsZero() {
|
|
d.UpdatedAt = now
|
|
}
|
|
return nil
|
|
}
|