628 lines
24 KiB
Go
628 lines
24 KiB
Go
package service
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"github.com/google/uuid"
|
||
"github.com/lejianwen/rustdesk-api/v2/global"
|
||
"github.com/lejianwen/rustdesk-api/v2/model"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type ClientBuildService struct {
|
||
}
|
||
|
||
// BuildConfig конфигурация для сборки клиента
|
||
type BuildConfig struct {
|
||
Platform string `json:"platform"`
|
||
Version string `json:"version"`
|
||
AppName string `json:"app_name"`
|
||
FileName string `json:"file_name"`
|
||
ServerIP string `json:"server_ip"`
|
||
ApiServer string `json:"api_server"`
|
||
Key string `json:"key"`
|
||
Custom map[string]string `json:"custom"` // дополнительные настройки
|
||
IconPath string `json:"icon_path,omitempty"`
|
||
LogoPath string `json:"logo_path,omitempty"`
|
||
CustomConfigFile string `json:"custom_config_file,omitempty"` // кастомный конфигурационный файл
|
||
UseCustomConfig bool `json:"use_custom_config"` // использовать кастомный конфиг
|
||
}
|
||
|
||
// CreateBuild создает новую задачу компиляции
|
||
func (cbs *ClientBuildService) CreateBuild(userId uint, config *BuildConfig) (*model.ClientBuild, error) {
|
||
buildUuid := uuid.New().String()
|
||
|
||
// Сохраняем конфигурацию в JSON
|
||
configJson, err := json.Marshal(config)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to marshal config: %v", err)
|
||
}
|
||
|
||
build := &model.ClientBuild{
|
||
UserId: userId,
|
||
BuildUuid: buildUuid,
|
||
Platform: config.Platform,
|
||
Version: config.Version,
|
||
AppName: config.AppName,
|
||
FileName: config.FileName,
|
||
Status: "pending",
|
||
BuildConfig: string(configJson),
|
||
}
|
||
|
||
if err := DB.Create(build).Error; err != nil {
|
||
return nil, fmt.Errorf("failed to create build: %v", err)
|
||
}
|
||
|
||
// Запускаем компиляцию в фоне
|
||
go cbs.buildClient(build)
|
||
|
||
return build, nil
|
||
}
|
||
|
||
// buildClient выполняет компиляцию клиента
|
||
func (cbs *ClientBuildService) buildClient(build *model.ClientBuild) {
|
||
// Обновляем статус на "building"
|
||
DB.Model(build).Update("status", "building")
|
||
DB.Model(build).Update("build_log", "Starting build process...\n")
|
||
|
||
// Парсим конфигурацию
|
||
var config BuildConfig
|
||
if err := json.Unmarshal([]byte(build.BuildConfig), &config); err != nil {
|
||
cbs.updateBuildStatus(build, "failed", fmt.Sprintf("Failed to parse config: %v", err), "")
|
||
return
|
||
}
|
||
|
||
// Создаем директорию для сборки
|
||
buildDir := filepath.Join(global.Config.Gin.ResourcesPath, "builds", build.BuildUuid)
|
||
if err := os.MkdirAll(buildDir, 0755); err != nil {
|
||
cbs.updateBuildStatus(build, "failed", fmt.Sprintf("Failed to create build directory: %v", err), "")
|
||
return
|
||
}
|
||
|
||
// Выполняем компиляцию в зависимости от платформы
|
||
var err error
|
||
var outputPath string
|
||
|
||
switch config.Platform {
|
||
case "windows":
|
||
outputPath, err = cbs.buildWindows(build, config, buildDir)
|
||
case "linux":
|
||
outputPath, err = cbs.buildLinux(build, config, buildDir)
|
||
case "macos", "macos-x86":
|
||
outputPath, err = cbs.buildMacOS(build, config, buildDir)
|
||
case "android":
|
||
outputPath, err = cbs.buildAndroid(build, config, buildDir)
|
||
default:
|
||
err = fmt.Errorf("unsupported platform: %s", config.Platform)
|
||
}
|
||
|
||
if err != nil {
|
||
cbs.updateBuildStatus(build, "failed", err.Error(), "")
|
||
return
|
||
}
|
||
|
||
// Получаем размер файла
|
||
fileInfo, err := os.Stat(outputPath)
|
||
if err != nil {
|
||
cbs.updateBuildStatus(build, "failed", fmt.Sprintf("Failed to get file info: %v", err), "")
|
||
return
|
||
}
|
||
|
||
// Обновляем имя файла, если оно не соответствует расширению
|
||
fileName := build.FileName
|
||
// Получаем расширение из пути
|
||
if len(outputPath) > 4 {
|
||
ext := filepath.Ext(outputPath)
|
||
if ext != "" && !strings.HasSuffix(strings.ToLower(fileName), strings.ToLower(ext)) {
|
||
// Обновляем расширение
|
||
if strings.Contains(fileName, ".") {
|
||
fileName = fileName[:strings.LastIndex(fileName, ".")] + ext
|
||
} else {
|
||
fileName = fileName + ext
|
||
}
|
||
}
|
||
}
|
||
|
||
// Обновляем статус на успех
|
||
DB.Model(build).Updates(map[string]interface{}{
|
||
"status": "success",
|
||
"file_path": outputPath,
|
||
"file_name": fileName,
|
||
"file_size": fileInfo.Size(),
|
||
"build_log": build.BuildLog + "\nBuild completed successfully!",
|
||
})
|
||
}
|
||
|
||
// cloneRustDeskSource клонирует исходный код RustDesk
|
||
func (cbs *ClientBuildService) cloneRustDeskSource(build *model.ClientBuild, version string, sourceDir string) error {
|
||
cbs.appendLog(build, fmt.Sprintf("Cloning RustDesk source code (version: %s)...\n", version))
|
||
|
||
// Проверяем, существует ли уже клонированный репозиторий
|
||
if _, err := os.Stat(filepath.Join(sourceDir, ".git")); err == nil {
|
||
cbs.appendLog(build, "Repository already exists, updating...\n")
|
||
// Обновляем существующий репозиторий
|
||
cmd := exec.Command("git", "fetch", "origin")
|
||
cmd.Dir = sourceDir
|
||
if output, err := cmd.CombinedOutput(); err != nil {
|
||
cbs.appendLog(build, fmt.Sprintf("Warning: failed to fetch updates: %s\n", string(output)))
|
||
}
|
||
|
||
// Обновляем submodules
|
||
cbs.appendLog(build, "Updating submodules...\n")
|
||
cmd = exec.Command("git", "submodule", "update", "--init", "--recursive")
|
||
cmd.Dir = sourceDir
|
||
if output, err := cmd.CombinedOutput(); err != nil {
|
||
cbs.appendLog(build, fmt.Sprintf("Warning: failed to update submodules: %s\n", string(output)))
|
||
} else {
|
||
cbs.appendLog(build, "Submodules updated successfully\n")
|
||
}
|
||
} else {
|
||
// Клонируем репозиторий
|
||
cbs.appendLog(build, "Cloning repository...\n")
|
||
normalizedVersion := strings.TrimPrefix(version, "v")
|
||
|
||
// Клонируем репозиторий (без --depth 1, чтобы получить все файлы и submodules)
|
||
cmd := exec.Command("git", "clone", "--recursive", "https://github.com/rustdesk/rustdesk.git", sourceDir)
|
||
if output, err := cmd.CombinedOutput(); err != nil {
|
||
return fmt.Errorf("failed to clone repository: %v\nOutput: %s", err, string(output))
|
||
}
|
||
cbs.appendLog(build, "Repository cloned successfully\n")
|
||
|
||
// Переключаемся на нужную версию, если указана
|
||
if normalizedVersion != "master" && normalizedVersion != "" {
|
||
cbs.appendLog(build, fmt.Sprintf("Checking out version: %s\n", normalizedVersion))
|
||
cmd := exec.Command("git", "checkout", normalizedVersion)
|
||
cmd.Dir = sourceDir
|
||
if output, err := cmd.CombinedOutput(); err != nil {
|
||
// Пробуем с префиксом v
|
||
cmd = exec.Command("git", "checkout", "v"+normalizedVersion)
|
||
cmd.Dir = sourceDir
|
||
if output2, err2 := cmd.CombinedOutput(); err2 != nil {
|
||
return fmt.Errorf("failed to checkout version %s: %v\nOutput: %s\nTried v%s: %s", normalizedVersion, err, string(output), normalizedVersion, string(output2))
|
||
}
|
||
}
|
||
cbs.appendLog(build, fmt.Sprintf("Checked out version: %s\n", normalizedVersion))
|
||
|
||
// Обновляем submodules для конкретной версии
|
||
cbs.appendLog(build, "Updating submodules...\n")
|
||
cmd = exec.Command("git", "submodule", "update", "--init", "--recursive")
|
||
cmd.Dir = sourceDir
|
||
if output, err := cmd.CombinedOutput(); err != nil {
|
||
return fmt.Errorf("failed to update submodules: %v\nOutput: %s", err, string(output))
|
||
}
|
||
cbs.appendLog(build, "Submodules updated successfully\n")
|
||
} else {
|
||
// Для master обновляем submodules
|
||
cbs.appendLog(build, "Updating submodules...\n")
|
||
cmd := exec.Command("git", "submodule", "update", "--init", "--recursive")
|
||
cmd.Dir = sourceDir
|
||
if output, err := cmd.CombinedOutput(); err != nil {
|
||
return fmt.Errorf("failed to update submodules: %v\nOutput: %s", err, string(output))
|
||
}
|
||
cbs.appendLog(build, "Submodules updated successfully\n")
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// copyFile копирует файл из источника в назначение
|
||
func (cbs *ClientBuildService) copyFile(src, dst string) error {
|
||
sourceFile, err := os.Open(src)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to open source file: %v", err)
|
||
}
|
||
defer sourceFile.Close()
|
||
|
||
destFile, err := os.Create(dst)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to create destination file: %v", err)
|
||
}
|
||
defer destFile.Close()
|
||
|
||
_, err = io.Copy(destFile, sourceFile)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to copy file: %v", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// buildWindows компилирует клиент для Windows
|
||
func (cbs *ClientBuildService) buildWindows(build *model.ClientBuild, config BuildConfig, buildDir string) (string, error) {
|
||
cbs.appendLog(build, "Building Windows client from source...\n")
|
||
|
||
// Проверяем наличие Rust toolchain
|
||
if _, err := exec.LookPath("cargo"); err != nil {
|
||
return "", fmt.Errorf("Rust toolchain not found. Please install Rust: https://rustup.rs/")
|
||
}
|
||
|
||
// Создаем директорию для исходников
|
||
sourceDir := filepath.Join(buildDir, "rustdesk-source")
|
||
if err := os.MkdirAll(sourceDir, 0755); err != nil {
|
||
return "", fmt.Errorf("failed to create source directory: %v", err)
|
||
}
|
||
|
||
// Клонируем исходный код
|
||
if err := cbs.cloneRustDeskSource(build, config.Version, sourceDir); err != nil {
|
||
return "", fmt.Errorf("failed to clone source: %v", err)
|
||
}
|
||
|
||
// Создаем конфигурационный файл custom.txt
|
||
var customConfig string
|
||
if config.UseCustomConfig && config.CustomConfigFile != "" {
|
||
customConfig = config.CustomConfigFile
|
||
cbs.appendLog(build, "Using custom configuration file\n")
|
||
} else {
|
||
customConfig = cbs.generateCustomConfig(config)
|
||
cbs.appendLog(build, "Using auto-generated configuration\n")
|
||
}
|
||
|
||
// Записываем custom.txt в исходники
|
||
customPath := filepath.Join(sourceDir, "custom.txt")
|
||
if err := os.WriteFile(customPath, []byte(customConfig), 0644); err != nil {
|
||
return "", fmt.Errorf("failed to write custom.txt: %v", err)
|
||
}
|
||
|
||
// Компилируем
|
||
cbs.appendLog(build, "Compiling Windows client with cargo...\n")
|
||
cmd := exec.Command("cargo", "build", "--release", "--target", "x86_64-pc-windows-msvc")
|
||
cmd.Dir = sourceDir
|
||
cmd.Env = append(os.Environ(), "RUSTFLAGS=-C target-feature=+crt-static")
|
||
|
||
output, err := cmd.CombinedOutput()
|
||
cbs.appendLog(build, string(output))
|
||
|
||
if err != nil {
|
||
return "", fmt.Errorf("build failed: %v", err)
|
||
}
|
||
|
||
// Копируем скомпилированный файл
|
||
exePath := filepath.Join(sourceDir, "target", "x86_64-pc-windows-msvc", "release", "rustdesk.exe")
|
||
if _, err := os.Stat(exePath); os.IsNotExist(err) {
|
||
return "", fmt.Errorf("compiled file not found: %s", exePath)
|
||
}
|
||
|
||
// Определяем имя выходного файла
|
||
outputFileName := config.FileName
|
||
if !strings.HasSuffix(strings.ToLower(outputFileName), ".exe") {
|
||
outputFileName = outputFileName + ".exe"
|
||
}
|
||
outputPath := filepath.Join(buildDir, outputFileName)
|
||
|
||
if err := cbs.copyFile(exePath, outputPath); err != nil {
|
||
return "", fmt.Errorf("failed to copy compiled file: %v", err)
|
||
}
|
||
|
||
// Копируем custom.txt рядом с исполняемым файлом
|
||
customOutputPath := filepath.Join(buildDir, "custom.txt")
|
||
if err := os.WriteFile(customOutputPath, []byte(customConfig), 0644); err != nil {
|
||
cbs.appendLog(build, fmt.Sprintf("Warning: failed to copy custom.txt: %v\n", err))
|
||
}
|
||
|
||
cbs.appendLog(build, fmt.Sprintf("Windows client compiled successfully: %s\n", outputFileName))
|
||
return outputPath, nil
|
||
}
|
||
|
||
// buildLinux компилирует клиент для Linux
|
||
func (cbs *ClientBuildService) buildLinux(build *model.ClientBuild, config BuildConfig, buildDir string) (string, error) {
|
||
cbs.appendLog(build, "Building Linux client from source...\n")
|
||
|
||
// Проверяем наличие Rust toolchain
|
||
if _, err := exec.LookPath("cargo"); err != nil {
|
||
return "", fmt.Errorf("Rust toolchain not found. Please install Rust: https://rustup.rs/")
|
||
}
|
||
|
||
// Создаем директорию для исходников
|
||
sourceDir := filepath.Join(buildDir, "rustdesk-source")
|
||
if err := os.MkdirAll(sourceDir, 0755); err != nil {
|
||
return "", fmt.Errorf("failed to create source directory: %v", err)
|
||
}
|
||
|
||
// Клонируем исходный код
|
||
if err := cbs.cloneRustDeskSource(build, config.Version, sourceDir); err != nil {
|
||
return "", fmt.Errorf("failed to clone source: %v", err)
|
||
}
|
||
|
||
// Создаем конфигурационный файл custom.txt
|
||
var customConfig string
|
||
if config.UseCustomConfig && config.CustomConfigFile != "" {
|
||
customConfig = config.CustomConfigFile
|
||
cbs.appendLog(build, "Using custom configuration file\n")
|
||
} else {
|
||
customConfig = cbs.generateCustomConfig(config)
|
||
cbs.appendLog(build, "Using auto-generated configuration\n")
|
||
}
|
||
|
||
// Записываем custom.txt в исходники
|
||
customPath := filepath.Join(sourceDir, "custom.txt")
|
||
if err := os.WriteFile(customPath, []byte(customConfig), 0644); err != nil {
|
||
return "", fmt.Errorf("failed to write custom.txt: %v", err)
|
||
}
|
||
|
||
// Компилируем
|
||
cbs.appendLog(build, "Compiling Linux client with cargo...\n")
|
||
cmd := exec.Command("cargo", "build", "--release", "--target", "x86_64-unknown-linux-gnu")
|
||
cmd.Dir = sourceDir
|
||
|
||
output, err := cmd.CombinedOutput()
|
||
cbs.appendLog(build, string(output))
|
||
|
||
if err != nil {
|
||
return "", fmt.Errorf("build failed: %v", err)
|
||
}
|
||
|
||
// Копируем скомпилированный файл
|
||
binPath := filepath.Join(sourceDir, "target", "x86_64-unknown-linux-gnu", "release", "rustdesk")
|
||
if _, err := os.Stat(binPath); os.IsNotExist(err) {
|
||
return "", fmt.Errorf("compiled file not found: %s", binPath)
|
||
}
|
||
|
||
// Определяем имя выходного файла (без расширения для Linux бинарника)
|
||
outputFileName := config.FileName
|
||
if strings.HasSuffix(strings.ToLower(outputFileName), ".deb") || strings.HasSuffix(strings.ToLower(outputFileName), ".rpm") {
|
||
// Если указано расширение пакета, оставляем как есть, но создаем бинарник
|
||
outputFileName = strings.TrimSuffix(outputFileName, filepath.Ext(outputFileName))
|
||
}
|
||
outputPath := filepath.Join(buildDir, outputFileName)
|
||
|
||
if err := cbs.copyFile(binPath, outputPath); err != nil {
|
||
return "", fmt.Errorf("failed to copy compiled file: %v", err)
|
||
}
|
||
|
||
// Делаем файл исполняемым
|
||
if err := os.Chmod(outputPath, 0755); err != nil {
|
||
cbs.appendLog(build, fmt.Sprintf("Warning: failed to set executable permissions: %v\n", err))
|
||
}
|
||
|
||
// Копируем custom.txt рядом с исполняемым файлом
|
||
customOutputPath := filepath.Join(buildDir, "custom.txt")
|
||
if err := os.WriteFile(customOutputPath, []byte(customConfig), 0644); err != nil {
|
||
cbs.appendLog(build, fmt.Sprintf("Warning: failed to copy custom.txt: %v\n", err))
|
||
}
|
||
|
||
cbs.appendLog(build, fmt.Sprintf("Linux client compiled successfully: %s\n", outputFileName))
|
||
return outputPath, nil
|
||
}
|
||
|
||
// buildMacOS компилирует клиент для macOS
|
||
func (cbs *ClientBuildService) buildMacOS(build *model.ClientBuild, config BuildConfig, buildDir string) (string, error) {
|
||
cbs.appendLog(build, "Building macOS client from source...\n")
|
||
|
||
// Проверяем наличие Rust toolchain
|
||
if _, err := exec.LookPath("cargo"); err != nil {
|
||
return "", fmt.Errorf("Rust toolchain not found. Please install Rust: https://rustup.rs/")
|
||
}
|
||
|
||
// Определяем целевую архитектуру
|
||
target := "x86_64-apple-darwin"
|
||
if config.Platform == "macos" {
|
||
// Для Apple Silicon
|
||
target = "aarch64-apple-darwin"
|
||
}
|
||
|
||
// Создаем директорию для исходников
|
||
sourceDir := filepath.Join(buildDir, "rustdesk-source")
|
||
if err := os.MkdirAll(sourceDir, 0755); err != nil {
|
||
return "", fmt.Errorf("failed to create source directory: %v", err)
|
||
}
|
||
|
||
// Клонируем исходный код
|
||
if err := cbs.cloneRustDeskSource(build, config.Version, sourceDir); err != nil {
|
||
return "", fmt.Errorf("failed to clone source: %v", err)
|
||
}
|
||
|
||
// Создаем конфигурационный файл custom.txt
|
||
var customConfig string
|
||
if config.UseCustomConfig && config.CustomConfigFile != "" {
|
||
customConfig = config.CustomConfigFile
|
||
cbs.appendLog(build, "Using custom configuration file\n")
|
||
} else {
|
||
customConfig = cbs.generateCustomConfig(config)
|
||
cbs.appendLog(build, "Using auto-generated configuration\n")
|
||
}
|
||
|
||
// Записываем custom.txt в исходники
|
||
customPath := filepath.Join(sourceDir, "custom.txt")
|
||
if err := os.WriteFile(customPath, []byte(customConfig), 0644); err != nil {
|
||
return "", fmt.Errorf("failed to write custom.txt: %v", err)
|
||
}
|
||
|
||
// Компилируем
|
||
cbs.appendLog(build, fmt.Sprintf("Compiling macOS client with cargo (target: %s)...\n", target))
|
||
cmd := exec.Command("cargo", "build", "--release", "--target", target)
|
||
cmd.Dir = sourceDir
|
||
|
||
output, err := cmd.CombinedOutput()
|
||
cbs.appendLog(build, string(output))
|
||
|
||
if err != nil {
|
||
return "", fmt.Errorf("build failed: %v", err)
|
||
}
|
||
|
||
// Копируем скомпилированный файл
|
||
binPath := filepath.Join(sourceDir, "target", target, "release", "rustdesk")
|
||
if _, err := os.Stat(binPath); os.IsNotExist(err) {
|
||
return "", fmt.Errorf("compiled file not found: %s", binPath)
|
||
}
|
||
|
||
// Определяем имя выходного файла
|
||
outputFileName := config.FileName
|
||
if !strings.HasSuffix(strings.ToLower(outputFileName), ".app") && !strings.HasSuffix(strings.ToLower(outputFileName), ".dmg") {
|
||
outputFileName = outputFileName + ".app"
|
||
}
|
||
outputPath := filepath.Join(buildDir, outputFileName)
|
||
|
||
// Для macOS создаем .app bundle (упрощенная версия)
|
||
// В реальности нужно создать полноценный .app bundle, но для начала просто копируем бинарник
|
||
if err := cbs.copyFile(binPath, outputPath); err != nil {
|
||
return "", fmt.Errorf("failed to copy compiled file: %v", err)
|
||
}
|
||
|
||
// Делаем файл исполняемым
|
||
if err := os.Chmod(outputPath, 0755); err != nil {
|
||
cbs.appendLog(build, fmt.Sprintf("Warning: failed to set executable permissions: %v\n", err))
|
||
}
|
||
|
||
// Копируем custom.txt рядом с исполняемым файлом
|
||
customOutputPath := filepath.Join(buildDir, "custom.txt")
|
||
if err := os.WriteFile(customOutputPath, []byte(customConfig), 0644); err != nil {
|
||
cbs.appendLog(build, fmt.Sprintf("Warning: failed to copy custom.txt: %v\n", err))
|
||
}
|
||
|
||
cbs.appendLog(build, fmt.Sprintf("macOS client compiled successfully: %s\n", outputFileName))
|
||
return outputPath, nil
|
||
}
|
||
|
||
// buildAndroid компилирует клиент для Android
|
||
func (cbs *ClientBuildService) buildAndroid(build *model.ClientBuild, config BuildConfig, buildDir string) (string, error) {
|
||
cbs.appendLog(build, "Building Android client from source...\n")
|
||
|
||
// Проверяем наличие Rust toolchain
|
||
if _, err := exec.LookPath("cargo"); err != nil {
|
||
return "", fmt.Errorf("Rust toolchain not found. Please install Rust: https://rustup.rs/")
|
||
}
|
||
|
||
// Проверяем наличие Android SDK
|
||
if os.Getenv("ANDROID_HOME") == "" && os.Getenv("ANDROID_SDK_ROOT") == "" {
|
||
return "", fmt.Errorf("Android SDK not found. Please set ANDROID_HOME or ANDROID_SDK_ROOT environment variable")
|
||
}
|
||
|
||
cbs.appendLog(build, "Android compilation requires additional setup (NDK, etc.)\n")
|
||
cbs.appendLog(build, "This is a placeholder - full Android build requires Android NDK and Gradle setup\n")
|
||
|
||
// Для Android компиляция более сложная и требует настройки NDK
|
||
// Пока возвращаем ошибку с инструкциями
|
||
return "", fmt.Errorf("Android compilation from source requires Android NDK setup. Please refer to RustDesk documentation for Android build instructions")
|
||
}
|
||
|
||
// generateCustomConfig генерирует конфигурационный файл custom.txt
|
||
func (cbs *ClientBuildService) generateCustomConfig(config BuildConfig) string {
|
||
var lines []string
|
||
|
||
// Базовые настройки сервера
|
||
if config.ServerIP != "" {
|
||
lines = append(lines, fmt.Sprintf("rendezvous-server = %s", config.ServerIP))
|
||
}
|
||
if config.ApiServer != "" {
|
||
lines = append(lines, fmt.Sprintf("api-server = %s", config.ApiServer))
|
||
}
|
||
if config.Key != "" {
|
||
lines = append(lines, fmt.Sprintf("key = %s", config.Key))
|
||
}
|
||
if config.AppName != "" && strings.ToLower(config.AppName) != "rustdesk" {
|
||
lines = append(lines, fmt.Sprintf("app-name = %s", config.AppName))
|
||
}
|
||
|
||
// Добавляем дополнительные настройки из custom
|
||
for key, value := range config.Custom {
|
||
lines = append(lines, fmt.Sprintf("%s = %s", key, value))
|
||
}
|
||
|
||
return strings.Join(lines, "\n")
|
||
}
|
||
|
||
// updateBuildStatus обновляет статус сборки
|
||
func (cbs *ClientBuildService) updateBuildStatus(build *model.ClientBuild, status, errorMsg, log string) {
|
||
updates := map[string]interface{}{
|
||
"status": status,
|
||
}
|
||
if errorMsg != "" {
|
||
updates["error_msg"] = errorMsg
|
||
}
|
||
if log != "" {
|
||
updates["build_log"] = build.BuildLog + "\n" + log
|
||
}
|
||
DB.Model(build).Updates(updates)
|
||
}
|
||
|
||
// appendLog добавляет строку в лог сборки
|
||
func (cbs *ClientBuildService) appendLog(build *model.ClientBuild, logLine string) {
|
||
var currentBuild model.ClientBuild
|
||
DB.First(¤tBuild, build.Id)
|
||
newLog := currentBuild.BuildLog + logLine
|
||
DB.Model(build).Update("build_log", newLog)
|
||
build.BuildLog = newLog
|
||
}
|
||
|
||
// GetByUuid получает задачу компиляции по UUID
|
||
func (cbs *ClientBuildService) GetByUuid(uuid string) *model.ClientBuild {
|
||
build := &model.ClientBuild{}
|
||
DB.Where("build_uuid = ?", uuid).First(build)
|
||
return build
|
||
}
|
||
|
||
// GetByUserId получает список задач компиляции пользователя
|
||
func (cbs *ClientBuildService) GetByUserId(userId uint) []*model.ClientBuild {
|
||
var builds []*model.ClientBuild
|
||
DB.Where("user_id = ?", userId).Order("created_at DESC").Find(&builds)
|
||
return builds
|
||
}
|
||
|
||
// List получает список задач компиляции с пагинацией
|
||
func (cbs *ClientBuildService) List(page, pageSize uint, scopes ...func(*gorm.DB) *gorm.DB) *model.ClientBuildList {
|
||
var builds []*model.ClientBuild
|
||
var total int64
|
||
|
||
query := DB.Model(&model.ClientBuild{})
|
||
for _, scope := range scopes {
|
||
query = scope(query)
|
||
}
|
||
|
||
query.Count(&total)
|
||
query = Paginate(page, pageSize)(query)
|
||
query.Order("created_at DESC").Find(&builds)
|
||
|
||
return &model.ClientBuildList{
|
||
ClientBuilds: builds,
|
||
Pagination: model.Pagination{
|
||
Page: int64(page),
|
||
PageSize: int64(pageSize),
|
||
Total: total,
|
||
},
|
||
}
|
||
}
|
||
|
||
// Delete удаляет задачу компиляции
|
||
func (cbs *ClientBuildService) Delete(id, userId uint) error {
|
||
build := &model.ClientBuild{}
|
||
DB.Where("id = ? AND user_id = ?", id, userId).First(build)
|
||
if build.Id == 0 {
|
||
return fmt.Errorf("build not found")
|
||
}
|
||
|
||
// Удаляем файлы сборки
|
||
if build.FilePath != "" {
|
||
os.Remove(build.FilePath)
|
||
// Удаляем директорию сборки
|
||
buildDir := filepath.Dir(build.FilePath)
|
||
os.RemoveAll(buildDir)
|
||
}
|
||
|
||
return DB.Delete(build).Error
|
||
}
|
||
|
||
// checkBuildTools проверяет наличие инструментов для сборки
|
||
func (cbs *ClientBuildService) checkBuildTools(platform string) error {
|
||
switch platform {
|
||
case "windows", "linux", "macos":
|
||
// Проверяем наличие Rust
|
||
if _, err := exec.LookPath("cargo"); err != nil {
|
||
return fmt.Errorf("Rust toolchain not found. Please install Rust: https://rustup.rs/")
|
||
}
|
||
case "android":
|
||
// Проверяем наличие Android SDK
|
||
if os.Getenv("ANDROID_HOME") == "" {
|
||
return fmt.Errorf("Android SDK not found. Please set ANDROID_HOME environment variable")
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|