mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-06 16:53:24 +08:00
feat: 数据上报
This commit is contained in:
15
.github/workflows/backend-ci-cd.yml
vendored
15
.github/workflows/backend-ci-cd.yml
vendored
@@ -87,6 +87,18 @@ jobs:
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get build time
|
||||
id: get_build_time
|
||||
run: |
|
||||
BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
echo "BUILD_TIME=${BUILD_TIME}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get git commit
|
||||
id: get_git_commit
|
||||
run: |
|
||||
GIT_COMMIT=$(git rev-parse HEAD)
|
||||
echo "GIT_COMMIT=${GIT_COMMIT}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
@@ -114,5 +126,8 @@ jobs:
|
||||
GOCACHE=/tmp/go-build
|
||||
GOMODCACHE=/tmp/go-mod
|
||||
REPO_COMMIT=${{ github.sha }}
|
||||
VERSION=${{ steps.get_version.outputs.VERSION }}
|
||||
BUILD_TIME=${{ steps.get_build_time.outputs.BUILD_TIME }}
|
||||
GIT_COMMIT=${{ steps.get_git_commit.outputs.GIT_COMMIT }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -4,6 +4,9 @@ OUTPUT=type=docker,dest=${HOME}/tmp/monkeycode_server.tar
|
||||
GOCACHE=${HOME}/.cache/go-build
|
||||
GOMODCACHE=${HOME}/go/pkg/mod
|
||||
REGISTRY=monkeycode.chaitin.cn/monkeycode
|
||||
VERSION=dev-${shell git rev-parse HEAD}
|
||||
BUILD_TIME=${shell date -u +"%Y-%m-%dT%H:%M:%SZ"}
|
||||
GIT_COMMIT=${shell git rev-parse HEAD}
|
||||
|
||||
# make build PLATFORM= TAG= OUTPUT= GOCACHE=
|
||||
image:
|
||||
@@ -12,6 +15,9 @@ image:
|
||||
--build-arg GOCACHE=${GOCACHE} \
|
||||
--build-arg GOMODCACHE=${GOMODCACHE} \
|
||||
--build-arg REPO_COMMIT=$(shell git rev-parse HEAD) \
|
||||
--build-arg VERSION=${VERSION} \
|
||||
--build-arg BUILD_TIME=${BUILD_TIME} \
|
||||
--build-arg GIT_COMMIT=${GIT_COMMIT} \
|
||||
--platform ${PLATFORM} \
|
||||
--tag ${REGISTRY}/backend:${TAG} \
|
||||
--output ${OUTPUT} \
|
||||
|
||||
@@ -9,10 +9,17 @@ RUN --mount=type=cache,target=${GOMODCACHE} \
|
||||
go mod download
|
||||
|
||||
ARG TARGETOS TARGETARCH GOCACHE
|
||||
ARG VERSION
|
||||
ARG BUILD_TIME
|
||||
ARG GIT_COMMIT
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=${GOMODCACHE} \
|
||||
--mount=type=cache,target=${GOCACHE} \
|
||||
GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/main cmd/server/main.go cmd/server/wire_gen.go
|
||||
GOOS=$TARGETOS GOARCH=$TARGETARCH \
|
||||
go build \
|
||||
-ldflags "-w -s -X 'github.com/chaitin/MonkeyCode/backend/pkg/version.Version=${VERSION}' -X 'github.com/chaitin/MonkeyCode/backend/pkg/version.BuildTime=${BUILD_TIME}' -X 'github.com/chaitin/MonkeyCode/backend/pkg/version.GitCommit=${GIT_COMMIT}'" \
|
||||
-o /out/main \
|
||||
cmd/server/main.go cmd/server/wire_gen.go
|
||||
|
||||
FROM alpine:3.21 as binary
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s.version.Print()
|
||||
s.logger.With("config", s.config).Debug("config")
|
||||
|
||||
if s.config.Debug {
|
||||
@@ -40,6 +41,10 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := s.report.ReportInstallation(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
svc := service.NewService(service.WithPprof())
|
||||
svc.Add(s)
|
||||
if err := svc.Run(); err != nil {
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
v1 "github.com/chaitin/MonkeyCode/backend/internal/model/handler/http/v1"
|
||||
openaiV1 "github.com/chaitin/MonkeyCode/backend/internal/openai/handler/v1"
|
||||
userV1 "github.com/chaitin/MonkeyCode/backend/internal/user/handler/v1"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/report"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/version"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
@@ -29,6 +31,8 @@ type Server struct {
|
||||
userV1 *userV1.UserHandler
|
||||
dashboardV1 *dashv1.DashboardHandler
|
||||
billingV1 *billingv1.BillingHandler
|
||||
version *version.VersionInfo
|
||||
report *report.Reporter
|
||||
}
|
||||
|
||||
func newServer() (*Server, error) {
|
||||
|
||||
@@ -34,8 +34,10 @@ import (
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/ipdb"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/logger"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/report"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/session"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/store"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/version"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
@@ -82,6 +84,8 @@ func newServer() (*Server, error) {
|
||||
billingRepo := repo7.NewBillingRepo(client)
|
||||
billingUsecase := usecase6.NewBillingUsecase(billingRepo)
|
||||
billingHandler := v1_5.NewBillingHandler(web, billingUsecase, authMiddleware, activeMiddleware)
|
||||
versionInfo := version.NewVersionInfo()
|
||||
reporter := report.NewReport(slogLogger, configConfig, versionInfo)
|
||||
server := &Server{
|
||||
config: configConfig,
|
||||
web: web,
|
||||
@@ -92,6 +96,8 @@ func newServer() (*Server, error) {
|
||||
userV1: userHandler,
|
||||
dashboardV1: dashboardHandler,
|
||||
billingV1: billingHandler,
|
||||
version: versionInfo,
|
||||
report: reporter,
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
@@ -108,4 +114,6 @@ type Server struct {
|
||||
userV1 *v1_3.UserHandler
|
||||
dashboardV1 *v1_4.DashboardHandler
|
||||
billingV1 *v1_5.BillingHandler
|
||||
version *version.VersionInfo
|
||||
report *report.Reporter
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@ type Config struct {
|
||||
LimitSecond int `mapstructure:"limit_second"`
|
||||
Limit int `mapstructure:"limit"`
|
||||
} `mapstructure:"extension"`
|
||||
|
||||
DataReport struct {
|
||||
Key string `mapstructure:"key"`
|
||||
} `mapstructure:"data_report"`
|
||||
}
|
||||
|
||||
func Init() (*Config, error) {
|
||||
@@ -103,6 +107,7 @@ func Init() (*Config, error) {
|
||||
v.SetDefault("extension.baseurl", "https://release.baizhi.cloud")
|
||||
v.SetDefault("extension.limit", 1)
|
||||
v.SetDefault("extension.limit_second", 10)
|
||||
v.SetDefault("data_report.key", "")
|
||||
|
||||
c := Config{}
|
||||
if err := v.Unmarshal(&c); err != nil {
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
userV1 "github.com/chaitin/MonkeyCode/backend/internal/user/handler/v1"
|
||||
userrepo "github.com/chaitin/MonkeyCode/backend/internal/user/repo"
|
||||
userusecase "github.com/chaitin/MonkeyCode/backend/internal/user/usecase"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/version"
|
||||
)
|
||||
|
||||
var Provider = wire.NewSet(
|
||||
@@ -50,4 +51,5 @@ var Provider = wire.NewSet(
|
||||
billingusecase.NewBillingUsecase,
|
||||
erepo.NewExtensionRepo,
|
||||
eusecase.NewExtensionUsecase,
|
||||
version.NewVersionInfo,
|
||||
)
|
||||
|
||||
60
backend/pkg/aes/aes.go
Normal file
60
backend/pkg/aes/aes.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package aes
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func Encrypt(key []byte, plaintext string) (string, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
func Decrypt(key []byte, ciphertext string) (string, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(data) < nonceSize {
|
||||
return "", errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, text := data[:nonceSize], data[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, text, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(plaintext), nil
|
||||
}
|
||||
102
backend/pkg/machine/machine.go
Normal file
102
backend/pkg/machine/machine.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MachineInfo struct {
|
||||
Hostname string `json:"hostname"`
|
||||
MACAddresses []string `json:"mac_addresses"`
|
||||
OSType string `json:"os_type"`
|
||||
OSRelease string `json:"os_release"`
|
||||
CPUInfo string `json:"cpu_info"`
|
||||
}
|
||||
|
||||
func GetMachineInfo() (*MachineInfo, error) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get hostname: %w", err)
|
||||
}
|
||||
|
||||
macAddresses, err := getMACAddresses()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get MAC addresses: %w", err)
|
||||
}
|
||||
|
||||
return &MachineInfo{
|
||||
Hostname: hostname,
|
||||
MACAddresses: macAddresses,
|
||||
OSType: runtime.GOOS,
|
||||
OSRelease: getOSRelease(),
|
||||
CPUInfo: getCPUInfo(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GenerateMachineID() (string, error) {
|
||||
machineInfo, err := GetMachineInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var parts []string
|
||||
parts = append(parts, machineInfo.Hostname)
|
||||
parts = append(parts, strings.Join(machineInfo.MACAddresses, ","))
|
||||
parts = append(parts, machineInfo.OSType)
|
||||
parts = append(parts, machineInfo.OSRelease)
|
||||
parts = append(parts, machineInfo.CPUInfo)
|
||||
|
||||
combined := strings.Join(parts, "|")
|
||||
hash := sha256.Sum256([]byte(combined))
|
||||
return fmt.Sprintf("%x", hash), nil
|
||||
}
|
||||
|
||||
func GenerateShortMachineID() (string, error) {
|
||||
machineInfo, err := GetMachineInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var parts []string
|
||||
parts = append(parts, machineInfo.Hostname)
|
||||
if len(machineInfo.MACAddresses) > 0 {
|
||||
parts = append(parts, machineInfo.MACAddresses[0])
|
||||
}
|
||||
parts = append(parts, machineInfo.OSType)
|
||||
|
||||
combined := strings.Join(parts, "|")
|
||||
hash := md5.Sum([]byte(combined))
|
||||
return fmt.Sprintf("%x", hash), nil
|
||||
}
|
||||
|
||||
func getMACAddresses() ([]string, error) {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var macAddresses []string
|
||||
for _, iface := range interfaces {
|
||||
if iface.Flags&net.FlagLoopback != 0 || len(iface.HardwareAddr) == 0 {
|
||||
continue
|
||||
}
|
||||
macAddresses = append(macAddresses, iface.HardwareAddr.String())
|
||||
}
|
||||
|
||||
sort.Strings(macAddresses)
|
||||
return macAddresses, nil
|
||||
}
|
||||
|
||||
func getOSRelease() string {
|
||||
return runtime.GOARCH
|
||||
}
|
||||
|
||||
func getCPUInfo() string {
|
||||
return fmt.Sprintf("%s_%d", runtime.GOARCH, runtime.NumCPU())
|
||||
}
|
||||
13
backend/pkg/machine/machine_test.go
Normal file
13
backend/pkg/machine/machine_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateShortMachineID(t *testing.T) {
|
||||
machineID, err := GenerateMachineID()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(machineID)
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
mid "github.com/chaitin/MonkeyCode/backend/internal/middleware"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/ipdb"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/logger"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/report"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/session"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/store"
|
||||
)
|
||||
@@ -24,6 +25,7 @@ var Provider = wire.NewSet(
|
||||
store.NewRedisCli,
|
||||
session.NewSession,
|
||||
ipdb.NewIPDB,
|
||||
report.NewReport,
|
||||
)
|
||||
|
||||
func NewWeb(cfg *config.Config) *web.Web {
|
||||
|
||||
111
backend/pkg/report/report.go
Normal file
111
backend/pkg/report/report.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/chaitin/MonkeyCode/backend/config"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/aes"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/machine"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/request"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/version"
|
||||
)
|
||||
|
||||
type Reporter struct {
|
||||
client *request.Client
|
||||
version *version.VersionInfo
|
||||
logger *slog.Logger
|
||||
IDFile string
|
||||
machineID string
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewReport(logger *slog.Logger, cfg *config.Config, version *version.VersionInfo) *Reporter {
|
||||
raw := "https://baizhi.cloud"
|
||||
u, _ := url.Parse(raw)
|
||||
client := request.NewClient(u.Scheme, u.Host, 30*time.Second)
|
||||
|
||||
r := &Reporter{
|
||||
client: client,
|
||||
logger: logger.With("module", "reporter"),
|
||||
IDFile: "/app/static/.machine_id",
|
||||
cfg: cfg,
|
||||
version: version,
|
||||
}
|
||||
if _, err := r.readMachineID(); err != nil {
|
||||
r.logger.With("error", err).Warn("read machine id file failed")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Reporter) readMachineID() (string, error) {
|
||||
data, err := os.ReadFile(r.IDFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
r.machineID = string(data)
|
||||
return r.machineID, nil
|
||||
}
|
||||
|
||||
func (r *Reporter) report(data any) error {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encrypt, err := aes.Encrypt([]byte(r.cfg.DataReport.Key), string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := map[string]any{
|
||||
"index": "monkeycode-installation",
|
||||
"id": uuid.NewString(),
|
||||
"data": encrypt,
|
||||
}
|
||||
|
||||
if _, err := request.Post[map[string]any](r.client, "/api/public/data/report", req); err != nil {
|
||||
r.logger.With("error", err).Warn("report installation failed")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reporter) ReportInstallation() error {
|
||||
if r.machineID != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
id, err := machine.GenerateMachineID()
|
||||
if err != nil {
|
||||
r.logger.With("error", err).Warn("generate machine id failed")
|
||||
return err
|
||||
}
|
||||
r.machineID = id
|
||||
|
||||
f, err := os.Create(r.IDFile)
|
||||
if err != nil {
|
||||
r.logger.With("error", err).Warn("create machine id file failed")
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(id)
|
||||
if err != nil {
|
||||
r.logger.With("error", err).Warn("write machine id file failed")
|
||||
return err
|
||||
}
|
||||
|
||||
return r.report(InstallData{
|
||||
MachineID: id,
|
||||
Version: r.version.Version(),
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
Type: "installation",
|
||||
})
|
||||
}
|
||||
8
backend/pkg/report/types.go
Normal file
8
backend/pkg/report/types.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package report
|
||||
|
||||
type InstallData struct {
|
||||
MachineID string `json:"machine_id"`
|
||||
Version string `json:"version"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
34
backend/pkg/version/version.go
Normal file
34
backend/pkg/version/version.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package version
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
Version = "v0.0.0"
|
||||
BuildTime = ""
|
||||
GitCommit = ""
|
||||
)
|
||||
|
||||
type VersionInfo struct{}
|
||||
|
||||
func NewVersionInfo() *VersionInfo {
|
||||
return &VersionInfo{}
|
||||
}
|
||||
|
||||
func (v *VersionInfo) Print() {
|
||||
fmt.Printf("🚀 Starting MonkeyCode Server\n")
|
||||
fmt.Printf("📦 Version: %s\n", Version)
|
||||
fmt.Printf("⏰ BuildTime: %s\n", BuildTime)
|
||||
fmt.Printf("📝 GitCommit: %s\n", GitCommit)
|
||||
}
|
||||
|
||||
func (v *VersionInfo) Version() string {
|
||||
return Version
|
||||
}
|
||||
|
||||
func (v *VersionInfo) BuildTime() string {
|
||||
return BuildTime
|
||||
}
|
||||
|
||||
func (v *VersionInfo) GitCommit() string {
|
||||
return GitCommit
|
||||
}
|
||||
Reference in New Issue
Block a user