Initial commit
This commit is contained in:
96
internal/installer/installer.go
Normal file
96
internal/installer/installer.go
Normal file
@ -0,0 +1,96 @@
|
||||
package installer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/internal/ssh"
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/config"
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/fileutils"
|
||||
)
|
||||
|
||||
// Installer handles the installation process
|
||||
type Installer struct {
|
||||
DefaultSettings string
|
||||
}
|
||||
|
||||
// NewInstaller creates a new installer with the provided default settings
|
||||
func NewInstaller(defaultSettings string) *Installer {
|
||||
return &Installer{
|
||||
DefaultSettings: defaultSettings,
|
||||
}
|
||||
}
|
||||
|
||||
// Install performs the installation process
|
||||
func (i *Installer) Install(sshConfig *config.SSHConfig) error {
|
||||
// Validate serial number
|
||||
if len([]rune(sshConfig.Serial)) != 16 {
|
||||
return fmt.Errorf("serial number must be 16 characters")
|
||||
}
|
||||
|
||||
// Update settings with user configuration
|
||||
updatedSettings, err := config.UpdateSettingsJSON(i.DefaultSettings, sshConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update settings: %w", err)
|
||||
}
|
||||
|
||||
// Save updated settings to file
|
||||
err = config.SaveSettingsJSON(updatedSettings, "settings.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save settings: %w", err)
|
||||
}
|
||||
|
||||
// Create SSH client configuration
|
||||
clientConfig := ssh.NewClientConfig(
|
||||
sshConfig.IP,
|
||||
sshConfig.Port,
|
||||
sshConfig.Login,
|
||||
sshConfig.Password,
|
||||
)
|
||||
|
||||
// Connect to SSH server
|
||||
sshClient, err := ssh.CreateSSHClient(clientConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SSH connection failed: %w", err)
|
||||
}
|
||||
defer sshClient.Close()
|
||||
|
||||
// Create SFTP client
|
||||
sftpClient, err := ssh.CreateSFTPClient(clientConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SFTP connection failed: %w", err)
|
||||
}
|
||||
defer sftpClient.Close()
|
||||
|
||||
// Upload archive
|
||||
remotePath := "/root/dict.tar"
|
||||
if err = fileutils.UploadFile(sftpClient, sshConfig.ArchivePath, remotePath); err != nil {
|
||||
return fmt.Errorf("file upload failed: %w", err)
|
||||
}
|
||||
|
||||
// Upload settings
|
||||
settingsPath := "/root/settings.json"
|
||||
if err = fileutils.UploadFile(sftpClient, "settings.json", settingsPath); err != nil {
|
||||
return fmt.Errorf("settings upload failed: %w", err)
|
||||
}
|
||||
|
||||
// Execute installation commands
|
||||
commands := []string{
|
||||
"tar -xf /root/dict.tar -C /root/",
|
||||
"mkdir -p /root/fema/storage",
|
||||
"mv -f ~/settings.json /root/fema/storage",
|
||||
"chmod +x /root/dict/*",
|
||||
fmt.Sprintf("sudo /root/dict/install.sh -s %s", sshConfig.Serial),
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
if err := ssh.ExecuteCommand(sshClient, cmd); err != nil {
|
||||
return fmt.Errorf("command execution failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up temporary files
|
||||
os.Remove("settings.json")
|
||||
|
||||
return nil
|
||||
}
|
89
internal/ssh/client.go
Normal file
89
internal/ssh/client.go
Normal file
@ -0,0 +1,89 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// ClientConfig holds the configuration for SSH connections
|
||||
type ClientConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
Username string
|
||||
Password string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// NewClientConfig creates a new SSH client configuration
|
||||
func NewClientConfig(host, port, username, password string) *ClientConfig {
|
||||
return &ClientConfig{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSSHClient creates and returns an SSH client using the provided configuration
|
||||
func CreateSSHClient(config *ClientConfig) (*ssh.Client, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("SSH config cannot be nil")
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: config.Username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(config.Password),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
Timeout: config.Timeout,
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", config.Host+":"+config.Port, sshConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to SSH server: %w", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// CreateSFTPClient creates and returns an SFTP client using the provided configuration
|
||||
func CreateSFTPClient(config *ClientConfig) (*sftp.Client, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("SFTP config cannot be nil")
|
||||
}
|
||||
|
||||
// Create SSH client
|
||||
sshClient, err := CreateSSHClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create SFTP client
|
||||
sftpClient, err := sftp.NewClient(sshClient)
|
||||
if err != nil {
|
||||
sshClient.Close() // Close the SSH client in case of failure
|
||||
return nil, fmt.Errorf("failed to create SFTP client: %w", err)
|
||||
}
|
||||
|
||||
return sftpClient, nil
|
||||
}
|
||||
|
||||
// ExecuteCommand executes a command on the remote server
|
||||
func ExecuteCommand(client *ssh.Client, command string) error {
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return fmt.Errorf("session creation failed: %w", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
if err := session.Run(command); err != nil {
|
||||
return fmt.Errorf("command '%s' failed: %w", command, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
49
internal/ui/file_selector.go
Normal file
49
internal/ui/file_selector.go
Normal file
@ -0,0 +1,49 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// CustomFileSelector displays a custom file selection dialog
|
||||
func CustomFileSelector(window fyne.Window, setText func(string)) {
|
||||
// Get list of files in current directory
|
||||
files, _ := filepath.Glob("*")
|
||||
|
||||
// Create list widget with files
|
||||
fileList := widget.NewList(
|
||||
func() int {
|
||||
return len(files)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("File")
|
||||
},
|
||||
func(id widget.ListItemID, obj fyne.CanvasObject) {
|
||||
obj.(*widget.Label).SetText(files[id])
|
||||
},
|
||||
)
|
||||
|
||||
// Set selection handler
|
||||
fileList.OnSelected = func(id widget.ListItemID) {
|
||||
setText(files[id]) // Set the selected path in the text field
|
||||
}
|
||||
|
||||
// Create dialog title
|
||||
title := widget.NewLabel("Files in current directory:")
|
||||
title.Alignment = fyne.TextAlignCenter
|
||||
|
||||
// Create layout with title and file list
|
||||
content := container.NewBorder(
|
||||
title, nil, nil, nil, // Title at the top
|
||||
container.NewMax(fileList), // List takes maximum space
|
||||
)
|
||||
|
||||
// Create and show dialog
|
||||
fileDialog := dialog.NewCustom("Select a file", "Close", content, window)
|
||||
fileDialog.Resize(fyne.NewSize(400, 500)) // Set dialog size
|
||||
fileDialog.Show()
|
||||
}
|
120
internal/ui/main_window.go
Normal file
120
internal/ui/main_window.go
Normal file
@ -0,0 +1,120 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/config"
|
||||
)
|
||||
|
||||
// MainWindow represents the main application window
|
||||
type MainWindow struct {
|
||||
Window fyne.Window
|
||||
IPEntry *widget.Entry
|
||||
PortEntry *widget.Entry
|
||||
LoginEntry *widget.Entry
|
||||
PasswordEntry *widget.Entry
|
||||
SerialEntry *widget.Entry
|
||||
TailNumberEntry *widget.Entry
|
||||
DefaultHostEntry *widget.Entry
|
||||
ArchivePathEntry *widget.Entry
|
||||
StatusLabel *widget.Label
|
||||
InstallButton *widget.Button
|
||||
}
|
||||
|
||||
// NewMainWindow creates a new main window for the application
|
||||
func NewMainWindow(app fyne.App, installHandler func(*config.SSHConfig) error) *MainWindow {
|
||||
window := app.NewWindow("Fema Installer")
|
||||
|
||||
// Create form fields
|
||||
mainWindow := &MainWindow{
|
||||
Window: window,
|
||||
IPEntry: widget.NewEntry(),
|
||||
PortEntry: widget.NewEntry(),
|
||||
LoginEntry: widget.NewEntry(),
|
||||
PasswordEntry: widget.NewPasswordEntry(),
|
||||
SerialEntry: widget.NewEntry(),
|
||||
TailNumberEntry: widget.NewEntry(),
|
||||
DefaultHostEntry: widget.NewEntry(),
|
||||
ArchivePathEntry: widget.NewEntry(),
|
||||
StatusLabel: widget.NewLabel(""),
|
||||
}
|
||||
|
||||
// Disable archive path entry (will be set by file selector)
|
||||
mainWindow.ArchivePathEntry.Disable()
|
||||
|
||||
// Create archive selection button
|
||||
selectArchiveBtn := widget.NewButton("Select Archive", func() {
|
||||
CustomFileSelector(window, func(path string) {
|
||||
mainWindow.ArchivePathEntry.SetText(path)
|
||||
})
|
||||
})
|
||||
|
||||
// Create install button
|
||||
mainWindow.InstallButton = widget.NewButton("Install", func() {
|
||||
config := &config.SSHConfig{
|
||||
IP: mainWindow.IPEntry.Text,
|
||||
Port: mainWindow.PortEntry.Text,
|
||||
Login: mainWindow.LoginEntry.Text,
|
||||
Password: mainWindow.PasswordEntry.Text,
|
||||
Serial: mainWindow.SerialEntry.Text,
|
||||
TailNumber: mainWindow.TailNumberEntry.Text,
|
||||
DefaultHost: mainWindow.DefaultHostEntry.Text,
|
||||
ArchivePath: mainWindow.ArchivePathEntry.Text,
|
||||
}
|
||||
|
||||
mainWindow.StatusLabel.SetText("Starting installation...")
|
||||
go func() {
|
||||
// Validate serial number
|
||||
if len([]rune(config.Serial)) != 16 {
|
||||
mainWindow.StatusLabel.SetText("Serial number must be 16 characters")
|
||||
dialog.ShowInformation("Error", "Serial number must be 16 characters", window)
|
||||
return
|
||||
}
|
||||
|
||||
// Perform installation
|
||||
err := installHandler(config)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, window)
|
||||
mainWindow.StatusLabel.SetText("Installation failed")
|
||||
} else {
|
||||
mainWindow.StatusLabel.SetText("Installation completed successfully!")
|
||||
dialog.ShowInformation("Success", "Installation completed successfully!", window)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
// Create form layout
|
||||
form := container.NewVBox(
|
||||
widget.NewForm(
|
||||
widget.NewFormItem("IP", mainWindow.IPEntry),
|
||||
widget.NewFormItem("Port", mainWindow.PortEntry),
|
||||
widget.NewFormItem("Login", mainWindow.LoginEntry),
|
||||
widget.NewFormItem("Password", mainWindow.PasswordEntry),
|
||||
widget.NewFormItem("Serial Number", mainWindow.SerialEntry),
|
||||
widget.NewFormItem("Tail Number", mainWindow.TailNumberEntry),
|
||||
widget.NewFormItem("Default Server", mainWindow.DefaultHostEntry),
|
||||
widget.NewFormItem("Archive", container.NewHBox(mainWindow.ArchivePathEntry, selectArchiveBtn)),
|
||||
),
|
||||
mainWindow.InstallButton,
|
||||
mainWindow.StatusLabel,
|
||||
)
|
||||
|
||||
// Set window content and size
|
||||
window.SetContent(form)
|
||||
window.Resize(fyne.NewSize(400, 300))
|
||||
|
||||
return mainWindow
|
||||
}
|
||||
|
||||
// Show displays the main window
|
||||
func (w *MainWindow) Show() {
|
||||
w.Window.Show()
|
||||
}
|
||||
|
||||
// ShowAndRun displays the main window and starts the application
|
||||
func (w *MainWindow) ShowAndRun() {
|
||||
w.Window.ShowAndRun()
|
||||
}
|
81
internal/ui/updater_window.go
Normal file
81
internal/ui/updater_window.go
Normal file
@ -0,0 +1,81 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/config"
|
||||
)
|
||||
|
||||
// UpdaterWindow represents the main window for the updater application
|
||||
type UpdaterWindow struct {
|
||||
Window fyne.Window
|
||||
ConfigDisplay *widget.Label
|
||||
UpdateButton *widget.Button
|
||||
StatusLabel *widget.Label
|
||||
}
|
||||
|
||||
// NewUpdaterWindow creates a new window for the updater application
|
||||
func NewUpdaterWindow(app fyne.App, config *config.UpdaterConfig, updateHandler func() error) *UpdaterWindow {
|
||||
window := app.NewWindow("Обновление ПО Фема")
|
||||
|
||||
// Create updater window
|
||||
updaterWindow := &UpdaterWindow{
|
||||
Window: window,
|
||||
ConfigDisplay: widget.NewLabel(config.String()),
|
||||
StatusLabel: widget.NewLabel(""),
|
||||
}
|
||||
|
||||
// Create update button
|
||||
updaterWindow.UpdateButton = widget.NewButton("Обновить ПО", func() {
|
||||
updaterWindow.StatusLabel.SetText("Начало обновления...")
|
||||
updaterWindow.UpdateButton.Disable()
|
||||
|
||||
go func() {
|
||||
err := updateHandler()
|
||||
if err != nil {
|
||||
updaterWindow.StatusLabel.SetText("Ошибка обновления: " + err.Error())
|
||||
} else {
|
||||
updaterWindow.StatusLabel.SetText("Обновление успешно завершено!")
|
||||
}
|
||||
updaterWindow.UpdateButton.Enable()
|
||||
}()
|
||||
})
|
||||
|
||||
// Create title
|
||||
title := widget.NewLabel("Программа обновления ПО Фема")
|
||||
title.Alignment = fyne.TextAlignCenter
|
||||
title.TextStyle = fyne.TextStyle{Bold: true}
|
||||
|
||||
// Create config section title
|
||||
configTitle := widget.NewLabel("Текущая конфигурация:")
|
||||
configTitle.TextStyle = fyne.TextStyle{Bold: true}
|
||||
|
||||
// Create layout
|
||||
content := container.NewVBox(
|
||||
title,
|
||||
widget.NewSeparator(),
|
||||
configTitle,
|
||||
updaterWindow.ConfigDisplay,
|
||||
widget.NewSeparator(),
|
||||
updaterWindow.UpdateButton,
|
||||
updaterWindow.StatusLabel,
|
||||
)
|
||||
|
||||
// Set window content and size
|
||||
window.SetContent(content)
|
||||
window.Resize(fyne.NewSize(400, 300))
|
||||
|
||||
return updaterWindow
|
||||
}
|
||||
|
||||
// Show displays the updater window
|
||||
func (w *UpdaterWindow) Show() {
|
||||
w.Window.Show()
|
||||
}
|
||||
|
||||
// ShowAndRun displays the updater window and starts the application
|
||||
func (w *UpdaterWindow) ShowAndRun() {
|
||||
w.Window.ShowAndRun()
|
||||
}
|
89
internal/updater/updater.go
Normal file
89
internal/updater/updater.go
Normal file
@ -0,0 +1,89 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/internal/ssh"
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/config"
|
||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/fileutils"
|
||||
)
|
||||
|
||||
// Updater handles the software update process
|
||||
type Updater struct {
|
||||
Config *config.UpdaterConfig
|
||||
BinaryData []byte
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
defer os.Remove(tempFile) // Clean up temporary file
|
||||
|
||||
// Create SSH client configuration
|
||||
clientConfig := ssh.NewClientConfig(
|
||||
u.Config.IP,
|
||||
u.Config.Port,
|
||||
u.Config.Login,
|
||||
u.Config.Password,
|
||||
)
|
||||
|
||||
// Connect to SSH server
|
||||
sshClient, err := ssh.CreateSSHClient(clientConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка подключения SSH: %w", err)
|
||||
}
|
||||
defer sshClient.Close()
|
||||
|
||||
// Create SFTP client
|
||||
sftpClient, err := ssh.CreateSFTPClient(clientConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка подключения SFTP: %w", err)
|
||||
}
|
||||
defer sftpClient.Close()
|
||||
|
||||
// Upload binary
|
||||
remotePath := "/root/fema/build.new"
|
||||
if err = fileutils.UploadFile(sftpClient, tempFile, remotePath); err != nil {
|
||||
return fmt.Errorf("ошибка загрузки файла: %w", err)
|
||||
}
|
||||
|
||||
// Execute update commands
|
||||
commands := []string{
|
||||
"mv -f /root/fema/build.new /root/fema/build",
|
||||
"chmod +x /root/fema/build",
|
||||
"systemctl restart fema.service",
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
if err := ssh.ExecuteCommand(sshClient, cmd); err != nil {
|
||||
return fmt.Errorf("ошибка выполнения команды '%s': %w", cmd, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfigFilePath returns the path to the configuration file
|
||||
func GetConfigFilePath() string {
|
||||
// Get executable directory
|
||||
execDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
if err != nil {
|
||||
execDir = "."
|
||||
}
|
||||
|
||||
return filepath.Join(execDir, "updater_config.json")
|
||||
}
|
Reference in New Issue
Block a user