Files
glchat/Makefile
T
grendervill 8814fe7cf4 release: glchat 0.1.1
Версия — в одном месте: файл VERSION в корне, из него её берут Makefile,
deploy/install.sh (IMAGE_TAG) и desktop-обёртка (tauri.conf.json, Cargo.toml).

Публикация обновлений (scripts/desktop-release.sh):
- манифесты прошлых релизов удаляются: оставшийся <старая версия>.json
  перекрывал новый релиз — обёртка спрашивает манифест своей версии и видела
  «обновлений нет»;
- mktemp-каталог раскладки получает 755, каталоги на инстансе
  нормализуются: rsync -a переносил режим 0700 на корень каталога обновлений,
  и приложение не могло читать манифесты (404 при живых файлах);
- --remote работает без TTY и без sudo, если каталог обновлений записываем;
  опции ssh — в GLCHAT_SSH_OPTS;
- предупреждение при публикации версии старше уже опубликованной.

Сервер: HEAD /updates/... (chi не выводит его из GET) — размер и тип артефакта
без скачивания; тест TestUpdatesAnswersHead.

Приёмка на живом канале: 0.1.0 → 0.1.1 на стенде (скачивание, проверка
подписи, установка, перезапуск), /Applications/glchat.app = 0.1.1.
2026-09-26 20:14:25 +03:00

288 lines
13 KiB
Makefile
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
SHELL := /usr/bin/env bash
.DEFAULT_GOAL := help
APP := glchat
MODULE := glchat
# Версия релиза — единственный источник правды: файл VERSION в корне (его же
# читает установщик, а tauri.conf.json/Cargo.toml держат то же значение).
# Без файла (например, в архиве без него) работает прежнее правило по git.
VERSION ?= $(shell cat $(CURDIR)/VERSION 2>/dev/null || git describe --tags --always --dirty 2>/dev/null || echo dev)
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo none)
BUILD_DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
ROOT := $(CURDIR)
CACHE_DIR := $(ROOT)/.cache
BIN_DIR := $(CACHE_DIR)/bin
BUILD_DIR := $(ROOT)/build
export GOCACHE := $(CACHE_DIR)/go-build
export GOMODCACHE:= $(CACHE_DIR)/go-mod
export GOPATH := $(CACHE_DIR)/gopath
export GOTOOLCHAIN := local
export GOLANGCI_LINT_CACHE := $(CACHE_DIR)/golangci-lint
# Сети без доступа к storage.googleapis.com (например, домашний сервер) не могут
# скачать модули через proxy.golang.org; goproxy.io работает как fallback.
export GOPROXY ?= https://proxy.golang.org,direct
export GOSUMDB ?= sum.golang.org
export CGO_ENABLED ?= 1
# sqlite_fts5 включает полнотекстовый поиск в mattn/go-sqlite3 (AGENT.md 7.15):
# без тега поиск по сообщениям не работает, поэтому он обязателен для всех сборок.
GO_TAGS ?= sqlite_fts5
GOFLAGS_LOCAL := -trimpath -tags $(GO_TAGS)
GO_LDFLAGS := -s -w \
-X main.version=$(VERSION) \
-X main.commit=$(COMMIT) \
-X main.buildDate=$(BUILD_DATE)
# Инструмент берётся из .cache/bin; если он уже установлен в системе
# (PATH), повторная установка не нужна — это важно для CI и серверов,
# где сборка инструментов из исходников недоступна.
SYSTEM_TOOLS := golangci-lint gofumpt sqlc goose govulncheck gitleaks
export PATH := $(BIN_DIR):$(PATH)
GO_PACKAGES := ./cmd/... ./internal/...
GO_LINT_TIMEOUT ?= 5m
WEB_DIR := $(ROOT)/web
DOCKER_IMAGE ?= glchat
DOCKER_TAG ?= $(VERSION)
SKIP_TLS ?= 0
.PHONY: help
help: ## Показать список целей
@printf 'glchat — доступные цели:\n\n'
@grep -hE '^[a-zA-Z0-9_.-]+:.*?## ' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-22s\033[0m %s\n", $$1, $$2}'
@printf '\nПрофиль: %s | Кэш: %s\n' "$${PROFILE:-auto}" "$(CACHE_DIR)"
.PHONY: tools
tools: ## Установить dev-инструменты в .cache/bin (уже установленные в системе пропускаются)
@mkdir -p $(CACHE_DIR) $(BIN_DIR)
@for tool in $(SYSTEM_TOOLS); do \
if command -v $$tool >/dev/null 2>&1; then \
printf ' ok %-16s %s\n' "$$tool" "$$(command -v $$tool)"; \
else \
$(MAKE) --no-print-directory $(BIN_DIR)/$$tool || exit 1; \
fi; \
done
$(BIN_DIR)/golangci-lint: | $(CACHE_DIR)
@echo "==> install golangci-lint v2.13.2"
@GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2
$(BIN_DIR)/gofumpt: | $(CACHE_DIR)
@echo "==> install gofumpt"
@GOBIN=$(BIN_DIR) go install mvdan.cc/gofumpt@v0.12.0
$(BIN_DIR)/sqlc: | $(CACHE_DIR)
@echo "==> install sqlc"
@GOBIN=$(BIN_DIR) go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1
$(BIN_DIR)/goose: | $(CACHE_DIR)
@echo "==> install goose"
@GOBIN=$(BIN_DIR) go install github.com/pressly/goose/v3/cmd/goose@v3.28.0
$(BIN_DIR)/govulncheck: | $(CACHE_DIR)
@echo "==> install govulncheck"
@GOBIN=$(BIN_DIR) go install golang.org/x/vuln/cmd/govulncheck@v1.1.4
$(BIN_DIR)/gitleaks: | $(CACHE_DIR)
@echo "==> install gitleaks"
@GOBIN=$(BIN_DIR) go install github.com/zricethezav/gitleaks/v8@v8.30.0
.PHONY: dev
dev: ## Запустить сервер локально (данные в ./build/dev-data)
@mkdir -p $(BUILD_DIR)/dev-data/db
DATA_DIR=$(BUILD_DIR)/dev-data \
LISTEN_ADDR=127.0.0.1:8080 \
DOMAIN=localhost \
LOG_FORMAT=text \
LOG_LEVEL=debug \
go run ./cmd/glchat
.PHONY: build
build: ## Собрать бинарник сервера в ./build/glchat
@mkdir -p $(BUILD_DIR)
go build $(GOFLAGS_LOCAL) -ldflags '$(GO_LDFLAGS)' -o $(BUILD_DIR)/$(APP) ./cmd/glchat
@ls -lh $(BUILD_DIR)/$(APP)
.PHONY: test
test: ## Тесты Go с race-детектором
go test -tags $(GO_TAGS) -race -count=1 $(GO_PACKAGES)
.PHONY: test-cover
test-cover: ## Тесты с отчётом покрытия (./build/coverage.out)
@mkdir -p $(BUILD_DIR)
go test -tags $(GO_TAGS) -race -count=1 -coverprofile=$(BUILD_DIR)/coverage.out $(GO_PACKAGES)
go tool cover -func=$(BUILD_DIR)/coverage.out | tail -1
.PHONY: lint
lint: tools go-lint web-lint ## Полный линт (Go + web)
.PHONY: go-lint
go-lint: tools ## Линт Go: формат, vet, golangci-lint
@echo "==> формат (gofumpt + golangci-lint fmt)"
@out=$$(gofumpt -l ./cmd ./internal ./scripts 2>/dev/null || true); \
if [ -n "$$out" ]; then printf '%s\n' "$$out"; echo "run: make fmt"; exit 1; fi
@out=$$(golangci-lint fmt --diff $(GO_PACKAGES)); \
if [ -n "$$out" ]; then printf '%s\n' "$$out"; echo "run: make fmt"; exit 1; fi
@echo "==> go vet"
@go vet $(GO_PACKAGES)
@echo "==> golangci-lint"
@golangci-lint run --timeout $(GO_LINT_TIMEOUT) $(GO_PACKAGES)
.PHONY: fmt
fmt: tools ## Форматировать Go-код
gofumpt -w ./cmd ./internal
golangci-lint fmt $(GO_PACKAGES)
.PHONY: generate
generate: tools ## Проверить сгенерированные артефакты (OpenAPI, sqlc — по мере появления)
@echo "==> openapi: обслуживается из internal/server/openapi.go (/api/v1/openapi.json)"
@test -s internal/server/openapi.go || { echo "openapi.go отсутствует"; exit 1; }
@if [ -d queries ] && [ -n "$$(ls -A queries 2>/dev/null | grep -v '^.gitkeep$$')" ]; then \
echo "==> sqlc generate"; sqlc generate; \
else echo "==> sqlc: запросов пока нет, пропускаем"; fi
.PHONY: security
security: tools ## AppSec: govulncheck, gitleaks, npm audit, trivy (по возможности)
@mkdir -p $(BUILD_DIR)
@echo "==> govulncheck"
@govulncheck ./cmd/... ./internal/... || { echo "govulncheck нашёл уязвимости"; exit 1; }
@echo "==> gitleaks"
@$(ROOT)/scripts/gitleaks-scan.sh
@echo "==> npm audit (web, только production-зависимости)"
@cd $(WEB_DIR) && npm audit --omit=dev --audit-level=high || { echo "npm audit нашёл уязвимости"; exit 1; }
@echo "==> trivy filesystem scan"
@if command -v trivy >/dev/null 2>&1; then \
trivy fs --quiet --scanners vuln,secret,misconfig --exit-code 1 \
--skip-dirs .cache --skip-dirs .git --skip-dirs web/node_modules .; \
else echo "trivy not installed — skipped"; fi
.PHONY: sbom
sbom: ## SBOM зависимостей в build/sbom.cdx.json (trivy, если установлен)
@mkdir -p $(BUILD_DIR)
@if command -v trivy >/dev/null 2>&1; then \
trivy fs --quiet --format cyclonedx --output $(BUILD_DIR)/sbom.cdx.json \
--skip-dirs .cache --skip-dirs web/node_modules . && \
echo "SBOM: $(BUILD_DIR)/sbom.cdx.json"; \
else echo "trivy не установлен — SBOM не собран (запустите на сервере сборки)"; fi
.PHONY: secrets-scan
secrets-scan: tools ## Только проверка на утечки секретов
@$(ROOT)/scripts/gitleaks-scan.sh
.PHONY: web-install
web-install: ## Установить зависимости web-клиента
cd $(WEB_DIR) && npm ci
.PHONY: web-lint
web-lint: ## Линт и типизация web-клиента
cd $(WEB_DIR) && npm run lint
cd $(WEB_DIR) && npm run check
.PHONY: web-test
web-test: ## Тесты web-клиента (vitest)
cd $(WEB_DIR) && npm test
.PHONY: web-build
web-build: ## Сборка web-клиента в web/dist
cd $(WEB_DIR) && npm run build
# Desktop-обёртка (Фаза 6, docs/client-tauri.md). Кэши инструментов — в .cache,
# адрес инстанса по умолчанию задаёт сама обёртка (https://gl.mhspx.su).
DESKTOP_DIR := $(ROOT)/desktop
DESKTOP_TAURI_DIR := $(DESKTOP_DIR)/src-tauri
DESKTOP_KEY ?= $(BUILD_DIR)/desktop-keys/glchat-updater.key
# Бандлы Tauri: `all` — обычная сборка (значение по умолчанию из tauri.conf.json),
# `app` — только .app (и артефакты обновления) без .dmg: сборка .dmg монтирует
# образ и требует Finder. Tauri CLI не принимает `all` в --bundles: при `all`
# флаг не передаётся вовсе, и список бандлов берётся из bundle.targets.
DESKTOP_BUNDLES ?= all
DESKTOP_BUNDLES_ARG := $(if $(filter-out all,$(DESKTOP_BUNDLES)),--bundles $(DESKTOP_BUNDLES),)
TAURI_CLI ?= npx --yes @tauri-apps/cli@^2
export CARGO_HOME ?= $(CACHE_DIR)/cargo
export NPM_CONFIG_CACHE ?= $(CACHE_DIR)/npm
.PHONY: desktop-check
desktop-check: ## Проверки Rust-обёртки: fmt, clippy, тесты
cd $(DESKTOP_TAURI_DIR) && cargo fmt --check
cd $(DESKTOP_TAURI_DIR) && cargo clippy --release --all-targets
cd $(DESKTOP_TAURI_DIR) && cargo test --release
.PHONY: desktop-build
desktop-build: ## Собрать desktop-приложение (бандлы по умолчанию для текущей ОС)
@if [ -f "$(DESKTOP_KEY)" ]; then \
echo " ключ подписи обновлений: $(DESKTOP_KEY)"; \
cd $(DESKTOP_TAURI_DIR) && \
TAURI_SIGNING_PRIVATE_KEY="$$(cat $(DESKTOP_KEY))" \
TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-glchat-local-dev}" \
$(TAURI_CLI) build $(DESKTOP_BUNDLES_ARG); \
else \
echo " ключа $(DESKTOP_KEY) нет: сборка без артефактов автообновления"; \
cd $(DESKTOP_TAURI_DIR) && \
$(TAURI_CLI) build $(DESKTOP_BUNDLES_ARG) \
--config '{"bundle":{"createUpdaterArtifacts":false}}'; \
fi
.PHONY: desktop-run
desktop-run: ## Запустить собранное приложение (macOS); доп. аргументы — ARGS=…
open -a "$(DESKTOP_TAURI_DIR)/target/release/bundle/macos/glchat.app" $(ARGS)
.PHONY: desktop-build-app
desktop-build-app: ## Собрать .app и артефакты обновления без .dmg (dmg требует Finder/AppleScript)
@$(MAKE) --no-print-directory desktop-build DESKTOP_BUNDLES=app
# Каталог обновлений инстанса и его публичный адрес (docs/client-tauri.md §4).
# Раскладку делает scripts/desktop-release.sh: манифест версии + latest.json,
# артефакт и подпись; приложение отдаёт их по /updates/{target}/{arch}/{version}.
DESKTOP_UPDATES_DIR ?= /opt/glchat/updates
DESKTOP_BASE_URL ?= https://gl.mhspx.su
.PHONY: desktop-release
desktop-release: ## Разложить артефакты обновления в DESKTOP_UPDATES_DIR (нужен sudo)
bash scripts/desktop-release.sh --dir "$(DESKTOP_UPDATES_DIR)" \
--base-url "$(DESKTOP_BASE_URL)" $(ARGS)
.PHONY: desktop-release-upload
desktop-release-upload: ## Загрузить обновления на инстанс: make desktop-release-upload HOST=user@server
bash scripts/desktop-release.sh --remote "$(HOST)" \
--base-url "$(DESKTOP_BASE_URL)" $(ARGS)
.PHONY: docker-build
docker-build: ## Собрать образ приложения
docker build \
--build-arg VERSION=$(VERSION) \
--build-arg COMMIT=$(COMMIT) \
--build-arg BUILD_DATE=$(BUILD_DATE) \
--build-arg GOPROXY=$(GOPROXY) \
--build-arg GOSUMDB=$(GOSUMDB) \
-t $(DOCKER_IMAGE):$(DOCKER_TAG) .
.PHONY: install-dry-run
install-dry-run: ## Проверить установщик без изменений системы
bash deploy/install.sh --dry-run --domain gl.mhspx.su --email admin@gl.mhspx.su --yes
.PHONY: install-test
install-test: ## Прогон install.sh в чистом контейнере Ubuntu 24.04
bash scripts/test-install.sh
.PHONY: profiles-test
profiles-test: ## Проверить таблицу профилей производительности (AGENT.md 10.10)
bash scripts/test-profiles.sh
.PHONY: backup-test
backup-test: ## Проверить бэкап и offsite-выгрузку на синтетическом инстансе
bash scripts/test-backup.sh
.PHONY: profiles-env
profiles-env: ## Сверить .env установленного инстанса с таблицей профилей
sudo bash scripts/test-profiles.sh --env "$${GLCHAT_ETC_DIR:-/opt/glchat}/.env"
.PHONY: ops-test
ops-test: profiles-test backup-test ## Проверки эксплуатационных скриптов
.PHONY: clean
clean: ## Удалить артефакты сборки
rm -rf $(BUILD_DIR)