Добавить установщик, проверку версий и инструкцию деплоя на сервер.

Интерактивная настройка домена и БД, эндпоинты /health и /version,
скрипты install/check для Linux и Windows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
shop
2026-05-16 17:17:19 +03:00
parent 448cf2a465
commit a3d3721724
17 changed files with 784 additions and 23 deletions
+176
View File
@@ -0,0 +1,176 @@
package check
import (
"context"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"shop/internal/version"
)
type Status string
const (
StatusOK Status = "ok"
StatusWarn Status = "warn"
StatusError Status = "error"
)
type Item struct {
Name string `json:"name"`
Status Status `json:"status"`
Detail string `json:"detail"`
Expected string `json:"expected,omitempty"`
}
type Report struct {
AppVersion string `json:"app_version"`
GoVersion string `json:"go_version"`
Items []Item `json:"checks"`
}
func (r Report) Healthy() bool {
for _, it := range r.Items {
if it.Status == StatusError {
return false
}
}
return true
}
func AppInfo() Report {
return Report{
AppVersion: version.AppVersion,
GoVersion: version.GoRuntime(),
Items: []Item{
{
Name: "go_runtime",
Status: goRuntimeStatus(),
Detail: version.GoRuntime(),
Expected: ">=" + version.MinGoVersion,
},
},
}
}
func goRuntimeStatus() Status {
v := strings.TrimPrefix(version.GoRuntime(), "go")
major, minor, ok := parseGoVersion(v)
if !ok {
return StatusWarn
}
expMajor, expMinor, _ := parseGoVersion(version.MinGoVersion)
if major > expMajor || (major == expMajor && minor >= expMinor) {
return StatusOK
}
return StatusWarn
}
func parseGoVersion(v string) (major, minor int, ok bool) {
parts := strings.Split(v, ".")
if len(parts) < 2 {
return 0, 0, false
}
major, err1 := strconv.Atoi(parts[0])
minor, err2 := strconv.Atoi(parts[1])
return major, minor, err1 == nil && err2 == nil
}
func WithDatabase(ctx context.Context, pool *pgxpool.Pool) (Report, error) {
r := AppInfo()
dbItems, err := Database(ctx, pool)
if err != nil {
return r, err
}
r.Items = append(r.Items, dbItems...)
return r, nil
}
func Database(ctx context.Context, pool *pgxpool.Pool) ([]Item, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
var pgVersion string
if err := pool.QueryRow(ctx, `SHOW server_version`).Scan(&pgVersion); err != nil {
return []Item{{
Name: "database",
Status: StatusError,
Detail: err.Error(),
}}, nil
}
major, ok := postgresMajor(pgVersion)
pgStatus := StatusOK
detail := pgVersion
expected := fmt.Sprintf("%d.x", version.ExpectedPostgresMajor)
if !ok {
pgStatus = StatusWarn
detail = pgVersion + " (не удалось определить major)"
} else if major != version.ExpectedPostgresMajor {
pgStatus = StatusWarn
detail = fmt.Sprintf("%s (ожидается PostgreSQL %d)", pgVersion, version.ExpectedPostgresMajor)
}
return []Item{
{Name: "database", Status: StatusOK, Detail: "подключено"},
{Name: "postgresql", Status: pgStatus, Detail: detail, Expected: expected},
}, nil
}
var pgMajorRe = regexp.MustCompile(`^(\d+)`)
func postgresMajor(v string) (int, bool) {
m := pgMajorRe.FindStringSubmatch(strings.TrimSpace(v))
if len(m) < 2 {
return 0, false
}
n, err := strconv.Atoi(m[1])
return n, err == nil
}
func ToolVersions(ctx context.Context) []Item {
var items []Item
if out, err := run(ctx, "docker", "version", "--format", "{{.Server.Version}}"); err == nil {
items = append(items, Item{Name: "docker", Status: StatusOK, Detail: strings.TrimSpace(out)})
} else {
items = append(items, Item{Name: "docker", Status: StatusWarn, Detail: "не найден"})
}
if out, err := run(ctx, "docker", "compose", "version", "--short"); err == nil {
items = append(items, Item{Name: "docker_compose", Status: StatusOK, Detail: strings.TrimSpace(out)})
} else {
items = append(items, Item{Name: "docker_compose", Status: StatusWarn, Detail: "не найден"})
}
return items
}
func run(ctx context.Context, name string, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 8*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
out, err := cmd.Output()
return string(out), err
}
func Merge(reports ...Report) Report {
if len(reports) == 0 {
return AppInfo()
}
out := reports[0]
for _, r := range reports[1:] {
if out.AppVersion == "" {
out.AppVersion = r.AppVersion
}
if out.GoVersion == "" {
out.GoVersion = r.GoVersion
}
out.Items = append(out.Items, r.Items...)
}
return out
}