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

Интерактивная настройка домена и БД, эндпоинты /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
+148
View File
@@ -0,0 +1,148 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/jackc/pgx/v5/pgxpool"
"shop/internal/check"
)
func main() {
loadDotEnv()
ctx := context.Background()
report := check.AppInfo()
report.Items = append(report.Items, check.ToolVersions(ctx)...)
dbURL := os.Getenv("DATABASE_URL")
if dbURL == "" {
report.Items = append(report.Items, check.Item{
Name: "database",
Status: check.StatusWarn,
Detail: "DATABASE_URL не задан — запустите: go run ./cmd/install",
})
} else {
pool, err := pgxpool.New(ctx, dbURL)
if err != nil {
report.Items = append(report.Items, check.Item{
Name: "database",
Status: check.StatusError,
Detail: err.Error(),
})
} else {
defer pool.Close()
dbItems, err := check.Database(ctx, pool)
if err != nil {
report.Items = append(report.Items, check.Item{
Name: "database",
Status: check.StatusError,
Detail: err.Error(),
})
} else {
report.Items = append(report.Items, dbItems...)
}
}
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
_ = enc.Encode(report)
fmt.Println()
printSummary(report)
if !report.Healthy() {
os.Exit(1)
}
}
func printSummary(r check.Report) {
fmt.Printf("ShopNova %s | %s\n\n", r.AppVersion, r.GoVersion)
for _, it := range r.Items {
mark := "✓"
switch it.Status {
case check.StatusWarn:
mark = "!"
case check.StatusError:
mark = "✗"
}
line := fmt.Sprintf(" %s %-18s %s", mark, it.Name+":", it.Detail)
if it.Expected != "" {
line += " (ожидается " + it.Expected + ")"
}
fmt.Println(line)
}
}
func loadDotEnv() {
root, _ := os.Getwd()
path := filepath.Join(root, ".env")
data, err := os.ReadFile(path)
if err != nil {
return
}
for _, line := range splitLines(string(data)) {
line = trimComment(line)
if line == "" {
continue
}
k, v, ok := splitKV(line)
if !ok {
continue
}
if os.Getenv(k) == "" {
_ = os.Setenv(k, v)
}
}
}
func splitLines(s string) []string {
var lines []string
start := 0
for i := 0; i < len(s); i++ {
if s[i] == '\n' {
lines = append(lines, s[start:i])
start = i + 1
}
}
if start < len(s) {
lines = append(lines, s[start:])
}
return lines
}
func trimComment(s string) string {
if i := indexByte(s, '#'); i >= 0 {
s = s[:i]
}
for len(s) > 0 && (s[len(s)-1] == ' ' || s[len(s)-1] == '\r') {
s = s[:len(s)-1]
}
for len(s) > 0 && s[0] == ' ' {
s = s[1:]
}
return s
}
func splitKV(s string) (string, string, bool) {
for i := 0; i < len(s); i++ {
if s[i] == '=' {
return s[:i], s[i+1:], true
}
}
return "", "", false
}
func indexByte(s string, c byte) int {
for i := 0; i < len(s); i++ {
if s[i] == c {
return i
}
}
return -1
}