Updater update
This commit is contained in:
@ -4,34 +4,68 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/internal/ssh"
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/config"
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/fileutils"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// ProgressInfo contains information about the update progress
|
||||
type ProgressInfo struct {
|
||||
Stage string
|
||||
Percentage float64
|
||||
EstimatedTimeRemaining time.Duration
|
||||
}
|
||||
|
||||
// ProgressCallback is a function that reports update progress
|
||||
type ProgressCallback func(progress ProgressInfo)
|
||||
|
||||
// UpdateMethod defines the method used for updating
|
||||
type UpdateMethod int
|
||||
|
||||
const (
|
||||
// UpdateMethodEmbedded uses the binary embedded in the program
|
||||
UpdateMethodEmbedded UpdateMethod = iota
|
||||
// UpdateMethodDirectDownload uses wget to download directly on the device
|
||||
UpdateMethodDirectDownload
|
||||
)
|
||||
|
||||
// Updater handles the software update process
|
||||
type Updater struct {
|
||||
Config *config.UpdaterConfig
|
||||
BinaryData []byte
|
||||
Config *config.UpdaterConfig
|
||||
BinaryData []byte
|
||||
UpdateMethod UpdateMethod
|
||||
}
|
||||
|
||||
// NewUpdater creates a new updater with the provided configuration and binary data
|
||||
func NewUpdater(config *config.UpdaterConfig, binaryData []byte) *Updater {
|
||||
return &Updater{
|
||||
Config: config,
|
||||
BinaryData: binaryData,
|
||||
Config: config,
|
||||
BinaryData: binaryData,
|
||||
UpdateMethod: UpdateMethodEmbedded, // Default to embedded method
|
||||
}
|
||||
}
|
||||
|
||||
// SetUpdateMethod sets the update method to use
|
||||
func (u *Updater) SetUpdateMethod(method UpdateMethod) {
|
||||
u.UpdateMethod = method
|
||||
}
|
||||
|
||||
// Update performs the software update process
|
||||
func (u *Updater) Update() error {
|
||||
// Save binary to temporary file
|
||||
tempFile := "update_binary"
|
||||
if err := os.WriteFile(tempFile, u.BinaryData, 0644); err != nil {
|
||||
return fmt.Errorf("не удалось сохранить временный файл: %w", err)
|
||||
func (u *Updater) Update(progressCallback ProgressCallback) error {
|
||||
// If no callback is provided, use a no-op callback
|
||||
if progressCallback == nil {
|
||||
progressCallback = func(progress ProgressInfo) {}
|
||||
}
|
||||
defer os.Remove(tempFile) // Clean up temporary file
|
||||
|
||||
// Report initial progress
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Подготовка",
|
||||
Percentage: 0,
|
||||
EstimatedTimeRemaining: 0,
|
||||
})
|
||||
|
||||
// Create SSH client configuration
|
||||
clientConfig := ssh.NewClientConfig(
|
||||
@ -42,12 +76,44 @@ func (u *Updater) Update() error {
|
||||
)
|
||||
|
||||
// Connect to SSH server
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Подключение к серверу",
|
||||
Percentage: 10,
|
||||
EstimatedTimeRemaining: 0,
|
||||
})
|
||||
|
||||
sshClient, err := ssh.CreateSSHClient(clientConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка подключения SSH: %w", err)
|
||||
}
|
||||
defer sshClient.Close()
|
||||
|
||||
// Choose update method
|
||||
switch u.UpdateMethod {
|
||||
case UpdateMethodEmbedded:
|
||||
return u.updateWithEmbeddedBinary(sshClient, clientConfig, progressCallback)
|
||||
case UpdateMethodDirectDownload:
|
||||
return u.updateWithDirectDownload(sshClient, progressCallback)
|
||||
default:
|
||||
return fmt.Errorf("неизвестный метод обновления")
|
||||
}
|
||||
}
|
||||
|
||||
// updateWithEmbeddedBinary updates using the binary embedded in the program
|
||||
func (u *Updater) updateWithEmbeddedBinary(sshClient *gossh.Client, clientConfig *ssh.ClientConfig, progressCallback ProgressCallback) error {
|
||||
// Save binary to temporary file
|
||||
tempFile := "update_binary"
|
||||
if err := os.WriteFile(tempFile, u.BinaryData, 0644); err != nil {
|
||||
return fmt.Errorf("не удалось сохранить временный файл: %w", err)
|
||||
}
|
||||
defer os.Remove(tempFile) // Clean up temporary file
|
||||
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Создание SFTP-соединения",
|
||||
Percentage: 20,
|
||||
EstimatedTimeRemaining: 0,
|
||||
})
|
||||
|
||||
// Create SFTP client
|
||||
sftpClient, err := ssh.CreateSFTPClient(clientConfig)
|
||||
if err != nil {
|
||||
@ -55,12 +121,70 @@ func (u *Updater) Update() error {
|
||||
}
|
||||
defer sftpClient.Close()
|
||||
|
||||
// Upload binary
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Загрузка файла",
|
||||
Percentage: 30,
|
||||
EstimatedTimeRemaining: 0,
|
||||
})
|
||||
|
||||
// Upload binary with progress reporting
|
||||
remotePath := "/root/fema/build.new"
|
||||
if err = fileutils.UploadFile(sftpClient, tempFile, remotePath); err != nil {
|
||||
|
||||
// Create a file upload progress callback
|
||||
uploadProgressCallback := func(percentage float64, estimatedTimeRemaining time.Duration) {
|
||||
// Map the upload progress (0-100%) to the overall progress (30-80%)
|
||||
overallPercentage := 30 + (percentage * 0.5)
|
||||
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Загрузка файла",
|
||||
Percentage: overallPercentage,
|
||||
EstimatedTimeRemaining: estimatedTimeRemaining,
|
||||
})
|
||||
}
|
||||
|
||||
if err = fileutils.UploadFileWithProgress(sftpClient, tempFile, remotePath, uploadProgressCallback); err != nil {
|
||||
return fmt.Errorf("ошибка загрузки файла: %w", err)
|
||||
}
|
||||
|
||||
return u.finalizeUpdate(sshClient, progressCallback)
|
||||
}
|
||||
|
||||
// updateWithDirectDownload updates by downloading directly on the device using wget
|
||||
func (u *Updater) updateWithDirectDownload(sshClient *gossh.Client, progressCallback ProgressCallback) error {
|
||||
if u.Config.DownloadURL == "" {
|
||||
return fmt.Errorf("URL загрузки не указан в конфигурации")
|
||||
}
|
||||
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Загрузка файла на устройство",
|
||||
Percentage: 30,
|
||||
EstimatedTimeRemaining: 0,
|
||||
})
|
||||
|
||||
// Download file directly on the device using wget
|
||||
downloadCmd := fmt.Sprintf("wget -O /root/fema/build.new %s", u.Config.DownloadURL)
|
||||
|
||||
if err := ssh.ExecuteCommand(sshClient, downloadCmd); err != nil {
|
||||
return fmt.Errorf("ошибка загрузки файла на устройство: %w", err)
|
||||
}
|
||||
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Загрузка файла на устройство",
|
||||
Percentage: 80,
|
||||
EstimatedTimeRemaining: 0,
|
||||
})
|
||||
|
||||
return u.finalizeUpdate(sshClient, progressCallback)
|
||||
}
|
||||
|
||||
// finalizeUpdate applies the update by moving files and restarting the service
|
||||
func (u *Updater) finalizeUpdate(sshClient *gossh.Client, progressCallback ProgressCallback) error {
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Применение обновления",
|
||||
Percentage: 80,
|
||||
EstimatedTimeRemaining: 0,
|
||||
})
|
||||
|
||||
// Execute update commands
|
||||
commands := []string{
|
||||
"mv -f /root/fema/build.new /root/fema/build",
|
||||
@ -68,12 +192,28 @@ func (u *Updater) Update() error {
|
||||
"systemctl restart fema.service",
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
for i, cmd := range commands {
|
||||
// Calculate progress for each command (80-100%)
|
||||
cmdProgress := 80 + float64(i+1)*20/float64(len(commands))
|
||||
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Применение обновления",
|
||||
Percentage: cmdProgress,
|
||||
EstimatedTimeRemaining: 0,
|
||||
})
|
||||
|
||||
if err := ssh.ExecuteCommand(sshClient, cmd); err != nil {
|
||||
return fmt.Errorf("ошибка выполнения команды '%s': %w", cmd, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Report completion
|
||||
progressCallback(ProgressInfo{
|
||||
Stage: "Завершено",
|
||||
Percentage: 100,
|
||||
EstimatedTimeRemaining: 0,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user