feat: initial open-source release of Pipely
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.
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user