From d62c8cef34c1c38e198532c805b49b9fe3b1996f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Jurmanovi=C4=87?= Date: Sun, 22 Feb 2026 17:49:44 +0100 Subject: [PATCH] add build scripts and resolve migration --- local/migrations/004_fix_ddl_comments.go | 106 +++++++++++++++++++++++ local/utl/db/db.go | 6 ++ scripts/build.ps1 | 57 ++++++++++++ scripts/build.sh | 79 +++++++++++++++++ scripts/deploy.ps1 | 42 ++------- 5 files changed, 257 insertions(+), 33 deletions(-) create mode 100644 local/migrations/004_fix_ddl_comments.go create mode 100644 scripts/build.ps1 create mode 100755 scripts/build.sh diff --git a/local/migrations/004_fix_ddl_comments.go b/local/migrations/004_fix_ddl_comments.go new file mode 100644 index 0000000..f3ee372 --- /dev/null +++ b/local/migrations/004_fix_ddl_comments.go @@ -0,0 +1,106 @@ +package migrations + +import ( + "acc-server-manager/local/utl/logging" + "fmt" + "regexp" + "strings" + + "gorm.io/gorm" +) + +var sqlLineCommentRegexp = regexp.MustCompile(`--[^\n]*`) + +type Migration004FixDDLComments struct { + DB *gorm.DB +} + +func NewMigration004FixDDLComments(db *gorm.DB) *Migration004FixDDLComments { + return &Migration004FixDDLComments{DB: db} +} + +func (m *Migration004FixDDLComments) Up() error { + if err := m.DB.AutoMigrate(&MigrationRecord{}); err != nil { + return fmt.Errorf("failed to create migration tracking table: %v", err) + } + + var record MigrationRecord + if err := m.DB.Where("migration_name = ?", "004_fix_ddl_comments").First(&record).Error; err == nil { + logging.Info("DDL comment fix migration already applied, skipping") + return nil + } + + var tables []string + if err := m.DB.Raw( + "SELECT name FROM sqlite_master WHERE type = 'table' AND sql LIKE '%--%' ORDER BY name", + ).Scan(&tables).Error; err != nil { + return fmt.Errorf("failed to query tables with DDL comments: %v", err) + } + + if len(tables) == 0 { + logging.Info("No tables with DDL comments found, skipping") + } + + for _, table := range tables { + if err := m.rebuildTable(table); err != nil { + return fmt.Errorf("failed to rebuild table %q: %v", table, err) + } + } + + record = MigrationRecord{ + MigrationName: "004_fix_ddl_comments", + AppliedAt: "datetime('now')", + Success: true, + Notes: fmt.Sprintf("Rebuilt %d table(s): %s", len(tables), strings.Join(tables, ", ")), + } + if err := m.DB.Create(&record).Error; err != nil { + return fmt.Errorf("failed to record migration: %v", err) + } + + return nil +} + +func (m *Migration004FixDDLComments) rebuildTable(table string) error { + var originalDDL string + if err := m.DB.Raw( + "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?", table, + ).Row().Scan(&originalDDL); err != nil { + return err + } + + cleanDDL := sqlLineCommentRegexp.ReplaceAllString(originalDDL, "") + + tmpTable := table + "__fixed" + createTmp := strings.Replace(cleanDDL, `"`+table+`"`, "`"+tmpTable+"`", 1) + if createTmp == cleanDDL { + createTmp = strings.Replace(cleanDDL, table, "`"+tmpTable+"`", 1) + } + + logging.Info("Rebuilding table %q to remove DDL comments...", table) + + return m.DB.Transaction(func(tx *gorm.DB) error { + for _, sql := range []string{ + "PRAGMA foreign_keys = OFF", + fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tmpTable), + createTmp, + fmt.Sprintf("INSERT INTO `%s` SELECT * FROM `%s`", tmpTable, table), + fmt.Sprintf("DROP TABLE `%s`", table), + fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tmpTable, table), + "PRAGMA foreign_keys = ON", + } { + if err := tx.Exec(sql).Error; err != nil { + return fmt.Errorf("exec %q: %w", sql, err) + } + } + logging.Info("Table %q rebuilt successfully", table) + return nil + }) +} + +func (m *Migration004FixDDLComments) Down() error { + return fmt.Errorf("DDL comment fix migration rollback is not supported") +} + +func RunFixDDLCommentsMigration(db *gorm.DB) error { + return NewMigration004FixDDLComments(db).Up() +} diff --git a/local/utl/db/db.go b/local/utl/db/db.go index 85c80e3..55ca071 100644 --- a/local/utl/db/db.go +++ b/local/utl/db/db.go @@ -33,6 +33,8 @@ func Start(di *dig.Container) { func Migrate(db *gorm.DB) { logging.Info("Migrating database") + runMigrations(db) + err := db.AutoMigrate( &model.ServiceControlModel{}, &model.Config{}, @@ -66,6 +68,10 @@ func Migrate(db *gorm.DB) { func runMigrations(db *gorm.DB) { logging.Info("Running custom database migrations...") + if err := migrations.RunFixDDLCommentsMigration(db); err != nil { + logging.Error("Failed to run DDL comment fix migration: %v", err) + } + if err := migrations.RunPasswordSecurityMigration(db); err != nil { logging.Error("Failed to run password security migration: %v", err) } diff --git a/scripts/build.ps1 b/scripts/build.ps1 new file mode 100644 index 0000000..a83ad5f --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,57 @@ +param( + [string]$BinaryName = "acc-server-manager", + [string]$MigrateBinaryName = "acc-migrate", + [string]$GOOS = "windows", + [string]$GOARCH = "amd64", + [string]$OutputPath = ".\build", + [switch]$SkipTests +) + +$ErrorActionPreference = "Stop" + +function Write-Status { + param([string]$Message, [string]$Color = "Green") + Write-Host "[$(Get-Date -Format 'HH:mm:ss')] $Message" -ForegroundColor $Color +} + +# Check if Go is installed +if (-not (Get-Command go -ErrorAction SilentlyContinue)) { + throw "Go is not installed or not in PATH" +} + +# Clean output directory +if (Test-Path $OutputPath) { + Remove-Item -Path $OutputPath -Recurse -Force +} +New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null + +# Run tests +if (-not $SkipTests) { + Write-Status "Running tests..." + & go test ./... + if ($LASTEXITCODE -ne 0) { + throw "Tests failed" + } +} + +# Set build environment +$env:GOOS = $GOOS +$env:GOARCH = $GOARCH + +$ext = if ($GOOS -eq "windows") { ".exe" } else { "" } + +# Build API binary +Write-Status "Building API binary ($GOOS/$GOARCH)..." +& go build -o "$OutputPath\$BinaryName$ext" .\cmd\api +if ($LASTEXITCODE -ne 0) { throw "Failed to build API binary" } + +# Build migration binary +Write-Status "Building migration binary ($GOOS/$GOARCH)..." +& go build -o "$OutputPath\$MigrateBinaryName$ext" .\cmd\migrate +if ($LASTEXITCODE -ne 0) { throw "Failed to build migration binary" } + +# Reset environment +Remove-Item Env:\GOOS -ErrorAction SilentlyContinue +Remove-Item Env:\GOARCH -ErrorAction SilentlyContinue + +Write-Status "Build completed -> $OutputPath" diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..f5306e2 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -euo pipefail + +BINARY_NAME="acc-server-manager" +MIGRATE_BINARY_NAME="acc-migrate" +GOOS="windows" +GOARCH="amd64" +OUTPUT_PATH="./build" +SKIP_TESTS=false + +usage() { + cat < Main binary name (default: acc-server-manager) + --migrate-binary-name Migration binary name (default: acc-migrate) + --goos Target OS (default: windows) + --goarch Target arch (default: amd64) + --output-path Output directory (default: ./build) + --skip-tests Skip running tests + -h, --help Show this help +EOF +} + +log() { echo "[$(date '+%H:%M:%S')] $*"; } +die() { echo "[$(date '+%H:%M:%S')] ERROR: $*" >&2; exit 1; } + +while [[ $# -gt 0 ]]; do + case $1 in + --binary-name) BINARY_NAME="$2"; shift 2 ;; + --migrate-binary-name) MIGRATE_BINARY_NAME="$2"; shift 2 ;; + --goos) GOOS="$2"; shift 2 ;; + --goarch) GOARCH="$2"; shift 2 ;; + --output-path) OUTPUT_PATH="$2"; shift 2 ;; + --skip-tests) SKIP_TESTS=true; shift ;; + -h|--help) usage; exit 0 ;; + *) die "Unknown option: $1" ;; + esac +done + +command -v go >/dev/null 2>&1 || die "Go is not installed or not in PATH" + +# Clean output directory +rm -rf "$OUTPUT_PATH" +mkdir -p "$OUTPUT_PATH" + +# Run tests on host before setting cross-compilation environment +if [[ "$SKIP_TESTS" == false ]]; then + log "Running tests..." + go test ./... +fi + +# CGo cross-compilation setup (set after tests so they run natively) +HOST_OS="$(go env GOHOSTOS)" +if [[ "$GOOS" == "windows" && "$HOST_OS" != "windows" ]]; then + command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1 \ + || die "mingw-w64 not found. Install it with: sudo apt install gcc-mingw-w64-x86-64" + export CC=x86_64-w64-mingw32-gcc + export CGO_ENABLED=1 + log "Cross-compiling for Windows (CC=$CC)" +fi + +EXT="" +[[ "$GOOS" == "windows" ]] && EXT=".exe" + +export GOOS GOARCH + +# Build API binary +log "Building API binary ($GOOS/$GOARCH)..." +go build -o "$OUTPUT_PATH/$BINARY_NAME$EXT" ./cmd/api + +# Build migration binary +log "Building migration binary ($GOOS/$GOARCH)..." +go build -o "$OUTPUT_PATH/$MIGRATE_BINARY_NAME$EXT" ./cmd/migrate + +unset GOOS GOARCH CC CGO_ENABLED + +log "Build completed -> $OUTPUT_PATH" diff --git a/scripts/deploy.ps1 b/scripts/deploy.ps1 index ccc0652..bab30f7 100644 --- a/scripts/deploy.ps1 +++ b/scripts/deploy.ps1 @@ -61,42 +61,18 @@ function Build-Application { Write-Status "Building application..." - # Clean build directory - if (Test-Path $LocalBuildPath) { - Remove-Item -Path $LocalBuildPath -Recurse -Force - } - New-Item -ItemType Directory -Path $LocalBuildPath -Force | Out-Null + $buildScript = Join-Path $PSScriptRoot "build.ps1" + $buildArgs = @( + "-BinaryName", $BinaryName, + "-MigrateBinaryName", $MigrateBinaryName, + "-OutputPath", $LocalBuildPath + ) + if ($SkipTests) { $buildArgs += "-SkipTests" } - # Run tests - if (-not $SkipTests) { - Write-Status "Running tests..." - & go test -v ./... - if ($LASTEXITCODE -ne 0) { - throw "Tests failed" - } - } - - # Build API for Windows - Write-Status "Building API binary..." - $env:GOOS = "windows" - $env:GOARCH = "amd64" - & go build -o "$LocalBuildPath\$BinaryName.exe" .\cmd\api + & $buildScript @buildArgs if ($LASTEXITCODE -ne 0) { - throw "Failed to build API binary" + throw "Build failed" } - - # Build migration tool for Windows - Write-Status "Building migration binary..." - & go build -o "$LocalBuildPath\$MigrateBinaryName.exe" .\cmd\migrate - if ($LASTEXITCODE -ne 0) { - throw "Failed to build migration binary" - } - - # Reset environment variables - Remove-Item Env:\GOOS -ErrorAction SilentlyContinue - Remove-Item Env:\GOARCH -ErrorAction SilentlyContinue - - Write-Status "Build completed successfully" } function Copy-FilesToServer {