feat(update): владелец пакета вшивается в бинарь
All checks were successful
CI / test (push) Successful in 42s
CI / build-and-package (amd64, darwin) (push) Successful in 39s
CI / build-and-package (amd64, linux) (push) Successful in 42s
CI / build-and-package (amd64, windows) (push) Successful in 41s
CI / build-and-package (arm64, darwin) (push) Successful in 48s
CI / build-and-package (arm64, linux) (push) Successful in 47s

- -X main.updateOwner (из vars.TC_GITEA_OWNER / Make UPDATE_OWNER), фоллбэк на update.owner в конфиге.
- update.owner больше не обязателен: гейты в startAutoCheck и handleUpdateCmd требуют только update.base_url.
- Обновлены main, app.New (сигнатура), CI, Makefile, тесты.
This commit is contained in:
Hermes
2026-08-16 12:33:17 +05:00
parent 4de6a4241a
commit 8fcafcdffd
5 changed files with 40 additions and 19 deletions

View File

@@ -58,7 +58,7 @@ jobs:
fi fi
VERSION="commit-$(echo '${{ gitea.sha }}' | cut -c1-7)" VERSION="commit-$(echo '${{ gitea.sha }}' | cut -c1-7)"
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \ GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \
go build -ldflags="-s -w -X main.version=${VERSION} -X main.updateToken=${{ secrets.TC_UPDATE_TOKEN }}" -o "${FILENAME}" ./cmd/ratatoskr/ go build -ldflags="-s -w -X main.version=${VERSION} -X main.updateToken=${{ secrets.TC_UPDATE_TOKEN }} -X main.updateOwner=${{ vars.TC_GITEA_OWNER }}" -o "${FILENAME}" ./cmd/ratatoskr/
echo "FILENAME=${FILENAME}" >> "${GITEA_ENV}" echo "FILENAME=${FILENAME}" >> "${GITEA_ENV}"
echo "VERSION=${VERSION}" >> "${GITEA_ENV}" echo "VERSION=${VERSION}" >> "${GITEA_ENV}"

View File

@@ -6,7 +6,8 @@ GO ?= go
GOPATH := $(shell $(GO) env GOPATH) GOPATH := $(shell $(GO) env GOPATH)
GIT_SHA := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) GIT_SHA := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev)
UPDATE_TOKEN ?= "" UPDATE_TOKEN ?= ""
GOFLAGS ?= -ldflags="-s -w -X main.version=commit-$(GIT_SHA) -X main.updateToken=$(UPDATE_TOKEN)" UPDATE_OWNER ?= ""
GOFLAGS ?= -ldflags="-s -w -X main.version=commit-$(GIT_SHA) -X main.updateToken=$(UPDATE_TOKEN) -X main.updateOwner=$(UPDATE_OWNER)"
all: test build all: test build

View File

@@ -24,6 +24,11 @@ var version = "dev"
// Без ldflags — пустой; тогда используется update.token из конфига (если задан). // Без ldflags — пустой; тогда используется update.token из конфига (если задан).
var updateToken = "" var updateToken = ""
// updateOwner — владелец Gitea-пакета (обычно git-юзер), вшивается при сборке:
// go build -ldflags "-X main.updateOwner=kamelion"
// Без ldflags — пустой; тогда используется update.owner из конфига (если задан).
var updateOwner = ""
func main() { func main() {
cfg := flag.String("config", "", "путь к config.yaml (по умолчанию — CWD/config.yaml)") cfg := flag.String("config", "", "путь к config.yaml (по умолчанию — CWD/config.yaml)")
versionFlag := flag.Bool("version", false, "показать версию и выйти") versionFlag := flag.Bool("version", false, "показать версию и выйти")
@@ -34,7 +39,7 @@ func main() {
return return
} }
a, err := app.New(*cfg, version, updateToken) a, err := app.New(*cfg, version, updateToken, updateOwner)
if err != nil { if err != nil {
log.Fatalf("app init: %v", err) log.Fatalf("app init: %v", err)
} }

View File

@@ -41,7 +41,9 @@ type App struct {
// version — вшитая версия бинаря (ldflag -X main.version). // version — вшитая версия бинаря (ldflag -X main.version).
// updateToken — вшитый токен read:package для авто-обновления // updateToken — вшитый токен read:package для авто-обновления
// (ldflag -X main.updateToken); имеет приоритет над update.token из конфига. // (ldflag -X main.updateToken); имеет приоритет над update.token из конфига.
func New(configPath, version, updateToken string) (*App, error) { // updateOwner — вшитый владелец Gitea-пакета (ldflag -X main.updateOwner);
// имеет приоритет над update.owner из конфига.
func New(configPath, version, updateToken, updateOwner string) (*App, error) {
cfg, err := config.Load(configPath) cfg, err := config.Load(configPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrConfig, err) return nil, fmt.Errorf("%w: %v", ErrConfig, err)
@@ -122,9 +124,14 @@ func New(configPath, version, updateToken string) (*App, error) {
if udToken == "" { if udToken == "" {
udToken = cfg.Update.Token udToken = cfg.Update.Token
} }
// Владелец пакета: вшитый updateOwner приоритетнее update.owner из конфига.
udOwner := updateOwner
if udOwner == "" {
udOwner = cfg.Update.Owner
}
ud := &update.Updater{ ud := &update.Updater{
BaseURL: cfg.Update.BaseURL, BaseURL: cfg.Update.BaseURL,
Owner: cfg.Update.Owner, Owner: udOwner,
Package: cfg.Update.Package, Package: cfg.Update.Package,
Token: udToken, Token: udToken,
CurrentVersion: version, CurrentVersion: version,
@@ -293,8 +300,8 @@ func (a *App) startAutoCheck(ctx context.Context) {
if !a.Config.Update.Enabled { if !a.Config.Update.Enabled {
return return
} }
if a.Updater == nil || a.Updater.BaseURL == "" || a.Updater.Owner == "" { if a.Updater == nil || a.Updater.BaseURL == "" {
log.Print("app: update авто-проверка выключена (не заполнен git.base_url / update.owner)") log.Print("app: update авто-проверка выключена (не заполнен update.base_url)")
return return
} }
interval := a.Config.Update.CheckInterval.Duration() interval := a.Config.Update.CheckInterval.Duration()
@@ -376,8 +383,8 @@ func (a *App) version() string {
// handleUpdateCmd — команда /update: Check → Download → Verify → Swap/Restart. // handleUpdateCmd — команда /update: Check → Download → Verify → Swap/Restart.
// При активной задаче в чате — требует подтверждения, чтобы не прерывать работу. // При активной задаче в чате — требует подтверждения, чтобы не прерывать работу.
func (a *App) handleUpdateCmd(ctx context.Context, uid chat.UserID) { func (a *App) handleUpdateCmd(ctx context.Context, uid chat.UserID) {
if a.Updater == nil || a.Updater.BaseURL == "" || a.Updater.Owner == "" { if a.Updater == nil || a.Updater.BaseURL == "" {
a.send(ctx, uid, "Обновление не настроено (нужен update.base_url и update.owner в config).") a.send(ctx, uid, "Обновление не настроено (нужен update.base_url в config).")
return return
} }
if !a.Config.Update.Enabled { if !a.Config.Update.Enabled {

View File

@@ -20,7 +20,7 @@ func TestNew(t *testing.T) {
t.Setenv("RATATOSKR_DB", dbPath) t.Setenv("RATATOSKR_DB", dbPath)
// Загружаем без config-файла (дефолты + env) // Загружаем без config-файла (дефолты + env)
a, err := New("", "dev", "") a, err := New("", "dev", "", "")
if err != nil { if err != nil {
t.Fatalf("New() err = %v", err) t.Fatalf("New() err = %v", err)
} }
@@ -50,7 +50,7 @@ func TestNew_MissingToken(t *testing.T) {
t.Setenv("TG_CHAT_ID", "12345") t.Setenv("TG_CHAT_ID", "12345")
t.Setenv("RATATOSKR_DB", dbPath) t.Setenv("RATATOSKR_DB", dbPath)
_, err := New("", "dev", "") _, err := New("", "dev", "", "")
if err == nil { if err == nil {
t.Fatal("expected error for missing token") t.Fatal("expected error for missing token")
} }
@@ -65,7 +65,7 @@ func TestNew_BadDB(t *testing.T) {
t.Setenv("TG_CHAT_ID", "12345") t.Setenv("TG_CHAT_ID", "12345")
t.Setenv("RATATOSKR_DB", dbPath) t.Setenv("RATATOSKR_DB", dbPath)
_, err := New("", "dev", "") _, err := New("", "dev", "", "")
if err == nil { if err == nil {
t.Fatal("expected error for invalid db path") t.Fatal("expected error for invalid db path")
} }
@@ -82,7 +82,7 @@ func TestNew_RunCtxCancel(t *testing.T) {
t.Setenv("TG_CHAT_ID", "12345") t.Setenv("TG_CHAT_ID", "12345")
t.Setenv("RATATOSKR_DB", dbPath) t.Setenv("RATATOSKR_DB", dbPath)
a, err := New("", "dev", "") a, err := New("", "dev", "", "")
if err != nil { if err != nil {
t.Fatalf("New() err = %v", err) t.Fatalf("New() err = %v", err)
} }
@@ -109,7 +109,8 @@ func TestMain(t *testing.T) {
} }
// TestNew_UpdateWiring проверяет: 1) update.base_url берётся из update-блока // TestNew_UpdateWiring проверяет: 1) update.base_url берётся из update-блока
// (не git.base_url); 2) приоритет токена: вшитый updateToken > update.token. // (не git.base_url); 2) приоритет токена: вшитый updateToken > update.token;
// 3) приоритет владельца: вшитый updateOwner > update.owner.
func TestNew_UpdateWiring(t *testing.T) { func TestNew_UpdateWiring(t *testing.T) {
tmp := t.TempDir() tmp := t.TempDir()
dbPath := filepath.Join(tmp, "test.db") dbPath := filepath.Join(tmp, "test.db")
@@ -126,7 +127,7 @@ func TestNew_UpdateWiring(t *testing.T) {
" enabled: true", " enabled: true",
" base_url: \"https://hub.example.com\"", " base_url: \"https://hub.example.com\"",
" token: \"cfg-update-token\"", " token: \"cfg-update-token\"",
" owner: \"kamelion\"", " owner: \"cfg-owner\"",
"paths:", "paths:",
" db: \"" + dbPath + "\"", " db: \"" + dbPath + "\"",
"", // пустая строка в конце "", // пустая строка в конце
@@ -136,7 +137,7 @@ func TestNew_UpdateWiring(t *testing.T) {
} }
// 1) base_url из update-блока, а НЕ git // 1) base_url из update-блока, а НЕ git
a, err := New(configPath, "dev", "") a, err := New(configPath, "dev", "", "")
if err != nil { if err != nil {
t.Fatalf("New() err = %v", err) t.Fatalf("New() err = %v", err)
} }
@@ -146,17 +147,24 @@ func TestNew_UpdateWiring(t *testing.T) {
if want := "https://hub.example.com"; a.Updater.BaseURL != want { if want := "https://hub.example.com"; a.Updater.BaseURL != want {
t.Errorf("BaseURL = %q, want %q (должен быть update.base_url, не git.base_url)", a.Updater.BaseURL, want) t.Errorf("BaseURL = %q, want %q (должен быть update.base_url, не git.base_url)", a.Updater.BaseURL, want)
} }
// без вшитого токена берём update.token из конфига // без вшитого токена и владельца берём их из конфига
if a.Updater.Token != "cfg-update-token" { if a.Updater.Token != "cfg-update-token" {
t.Errorf("Token = %q, want cfg-update-token (при пустом вшитом)", a.Updater.Token) t.Errorf("Token = %q, want cfg-update-token (при пустом вшитом)", a.Updater.Token)
} }
if a.Updater.Owner != "cfg-owner" {
t.Errorf("Owner = %q, want cfg-owner (при пустом вшитом)", a.Updater.Owner)
}
// 2) вшитый updateToken перекрывает оба источника // 2) вшитый updateToken перекрывает конфиг
a2, err := New(configPath, "dev", "embedded-update-token") a2, err := New(configPath, "dev", "embedded-update-token", "embedded-owner")
if err != nil { if err != nil {
t.Fatalf("New() err = %v", err) t.Fatalf("New() err = %v", err)
} }
if a2.Updater.Token != "embedded-update-token" { if a2.Updater.Token != "embedded-update-token" {
t.Errorf("Token = %q, want embedded-update-token (вшитый приоритетнее)", a2.Updater.Token) t.Errorf("Token = %q, want embedded-update-token (вшитый приоритетнее)", a2.Updater.Token)
} }
// 3) вшитый updateOwner перекрывает конфиг
if a2.Updater.Owner != "embedded-owner" {
t.Errorf("Owner = %q, want embedded-owner (вшитый приоритетнее)", a2.Updater.Owner)
}
} }