Initial commit

This commit is contained in:
2025-04-25 03:07:02 +03:00
commit 88a1f0f50f
16 changed files with 11945 additions and 0 deletions

View 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
View 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
}

View 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
View 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()
}

View 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()
}

View 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")
}