feat: add license

This commit is contained in:
xiaomakuaiz
2025-07-20 21:50:43 +08:00
parent 86360dee7a
commit 12af6e9442
24 changed files with 400 additions and 199 deletions

View File

@@ -4,11 +4,6 @@ on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+*"
pull_request:
branches:
- main
paths:
- 'backend/**'
jobs:
build:
@@ -26,6 +21,8 @@ jobs:
uses: actions/checkout@v4
with:
lfs: true
submodules: true
token: ${{ secrets.PRO_TOKEN }}
- name: Get version
id: get_version
@@ -60,7 +57,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: ./backend
file: ./backend/Dockerfile.${{ matrix.service }}
file: ./backend/Dockerfile.${{ matrix.service }}.pro
push: ${{ startsWith(github.ref, 'refs/tags/') }}
platforms: linux/amd64, linux/arm64
tags: chaitin-registry.cn-hangzhou.cr.aliyuncs.com/chaitin/panda-wiki-${{ matrix.service }}:${{ steps.get_version.outputs.VERSION }}

View File

@@ -40,6 +40,7 @@ jobs:
- name: Check go.mod formatting
working-directory: backend
run: |
rm -rf cmd/api_pro
if ! go mod tidy --diff ; then
echo "::error::go.mod or go.sum is not properly formatted. Please run 'go mod tidy' locally and commit the changes."
exit 1
@@ -47,4 +48,55 @@ jobs:
if ! go mod verify ; then
echo "::error::go.mod or go.sum has unverified dependencies. Please run 'go mod verify' locally and commit the changes."
exit 1
fi
fi
build:
runs-on: ubuntu-latest
strategy:
matrix:
service: [api, consumer]
timeout-minutes: 30
outputs:
version: ${{ steps.get_version.outputs.VERSION }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
lfs: true
- name: Get version
id: get_version
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
if [[ $GITHUB_REF == refs/tags/backend-* ]]; then
echo "VERSION=${GITHUB_REF#refs/tags/backend-}" >> $GITHUB_OUTPUT
else
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi
else
echo "VERSION=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
fi
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: 'arm64,amd64'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build
uses: docker/build-push-action@v5
with:
context: ./backend
file: ./backend/Dockerfile.${{ matrix.service }}
push: false
platforms: linux/amd64, linux/arm64
tags: chaitin-registry.cn-hangzhou.cr.aliyuncs.com/chaitin/panda-wiki-${{ matrix.service }}:${{ steps.get_version.outputs.VERSION }}
build-args: |
VERSION=${{ steps.get_version.outputs.VERSION }}
cache-from: |
type=gha,scope=${{ matrix.service }}
cache-to: |
type=gha,scope=${{ matrix.service }},mode=max

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "backend/pro"]
path = backend/pro
url = git@github.com:chaitin/PandaWikiPro.git

View File

@@ -0,0 +1,32 @@
FROM --platform=$BUILDPLATFORM golang:1.24.3-alpine AS builder
WORKDIR /src
ENV CGO_ENABLED=0
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
ARG TARGETOS TARGETARCH VERSION
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-s -w -extldflags '-static' -X github.com/chaitin/panda-wiki/telemetry.Version=${VERSION}" -o /build/panda-wiki-api pro/cmd/api_pro/main.go pro/cmd/api_pro/wire_gen.go \
&& GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-s -w -extldflags '-static' -X github.com/chaitin/panda-wiki/telemetry.Version=${VERSION}" -o /build/panda-wiki-migrate cmd/migrate/main.go cmd/migrate/wire_gen.go
FROM alpine:3.21 AS api
RUN apk update \
&& apk upgrade \
&& apk add --no-cache ca-certificates tzdata \
&& update-ca-certificates 2>/dev/null || true \
&& rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=builder /build/panda-wiki-api /app/panda-wiki-api
COPY --from=builder /build/panda-wiki-migrate /app/panda-wiki-migrate
COPY --from=builder /src/store/pg/migration /app/migration
CMD ["sh", "-c", "/app/panda-wiki-migrate && /app/panda-wiki-api"]

View File

@@ -0,0 +1,29 @@
FROM --platform=$BUILDPLATFORM golang:1.24.3-alpine AS builder
WORKDIR /src
ENV CGO_ENABLED=0
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
ARG TARGETOS TARGETARCH
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-s -w -extldflags '-static'" -o /build/panda-wiki-consumer cmd/consumer/main.go cmd/consumer/wire_gen.go
FROM alpine:3.21 AS consumer
RUN apk update \
&& apk upgrade \
&& apk add --no-cache ca-certificates tzdata \
&& update-ca-certificates 2>/dev/null || true \
&& rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=builder /build/panda-wiki-consumer /app/panda-wiki-consumer
COPY --from=builder /src/store/pg/migration /app/migration
CMD ["./panda-wiki-consumer"]

View File

@@ -30,3 +30,8 @@ dev:generate
make image PLATFORM=${LOCAL_PLATFORM} DOCKERFILE=Dockerfile.api IMAGE_NAME=chaitin-registry.cn-hangzhou.cr.aliyuncs.com/chaitin/panda-wiki-api:latest OUTPUT=type=docker VERSION=${COMMIT_HASH} \
&& make image PLATFORM=${LOCAL_PLATFORM} DOCKERFILE=Dockerfile.consumer IMAGE_NAME=chaitin-registry.cn-hangzhou.cr.aliyuncs.com/chaitin/panda-wiki-consumer:latest OUTPUT=type=docker VERSION=${COMMIT_HASH} \
&& cd deploy && docker compose up -d
pro:generate
make image PLATFORM=${LOCAL_PLATFORM} DOCKERFILE=Dockerfile.api.pro IMAGE_NAME=chaitin-registry.cn-hangzhou.cr.aliyuncs.com/chaitin/panda-wiki-api:latest OUTPUT=type=docker VERSION=${COMMIT_HASH} \
&& make image PLATFORM=${LOCAL_PLATFORM} DOCKERFILE=Dockerfile.consumer IMAGE_NAME=chaitin-registry.cn-hangzhou.cr.aliyuncs.com/chaitin/panda-wiki-consumer:latest OUTPUT=type=docker VERSION=${COMMIT_HASH} \
&& cd deploy && docker compose up -d

View File

@@ -45,6 +45,11 @@ func createApp() (*App, error) {
if err != nil {
return nil, err
}
userAccessRepository := pg2.NewUserAccessRepository(db, logger)
authMiddleware, err := middleware.NewAuthMiddleware(configConfig, logger, userAccessRepository)
if err != nil {
return nil, err
}
ragService, err := rag.NewRAGService(configConfig, logger)
if err != nil {
return nil, err
@@ -66,17 +71,12 @@ func createApp() (*App, error) {
return nil, err
}
shareAuthMiddleware := middleware.NewShareAuthMiddleware(logger, knowledgeBaseUsecase)
baseHandler := handler.NewBaseHandler(echo, logger, configConfig, shareAuthMiddleware)
baseHandler := handler.NewBaseHandler(echo, logger, configConfig, authMiddleware, shareAuthMiddleware)
userRepository := pg2.NewUserRepository(db, logger)
userUsecase, err := usecase.NewUserUsecase(userRepository, logger, configConfig)
if err != nil {
return nil, err
}
userAccessRepository := pg2.NewUserAccessRepository(db, logger)
authMiddleware, err := middleware.NewAuthMiddleware(configConfig, logger, userAccessRepository)
if err != nil {
return nil, err
}
userHandler := v1.NewUserHandler(echo, baseHandler, logger, userUsecase, authMiddleware, configConfig)
conversationRepository := pg2.NewConversationRepository(db, logger)
modelRepository := pg2.NewModelRepository(db, logger)
@@ -150,7 +150,10 @@ func createApp() (*App, error) {
ShareStatHandler: shareStatHandler,
ShareCommentHandler: shareCommentHandler,
}
client := telemetry.NewClient(logger, knowledgeBaseRepository)
client, err := telemetry.NewClient(logger, knowledgeBaseRepository)
if err != nil {
return nil, err
}
app := &App{
HTTPServer: httpServer,
Handlers: apiHandlers,

View File

@@ -174,7 +174,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/handler_v1.CommentLists"
"$ref": "#/definitions/v1.CommentLists"
}
}
}
@@ -280,7 +280,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/handler_v1.ConversationListItems"
"$ref": "#/definitions/v1.ConversationListItems"
}
}
}
@@ -432,7 +432,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/github_com_chaitin_panda-wiki_domain.PaginatedResult-array_domain_ConversationMessageListItem"
"$ref": "#/definitions/domain.PaginatedResult-array_domain_ConversationMessageListItem"
}
}
}
@@ -2824,7 +2824,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/handler_share.ShareCommentLists"
"$ref": "#/definitions/share.ShareCommentLists"
}
}
}
@@ -4722,6 +4722,20 @@ const docTemplate = `{
}
}
},
"domain.PaginatedResult-array_domain_ConversationMessageListItem": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ConversationMessageListItem"
}
},
"total": {
"type": "integer"
}
}
},
"domain.ParseURLItem": {
"type": "object",
"properties": {
@@ -5279,62 +5293,6 @@ const docTemplate = `{
}
}
},
"github_com_chaitin_panda-wiki_domain.PaginatedResult-array_domain_ConversationMessageListItem": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ConversationMessageListItem"
}
},
"total": {
"type": "integer"
}
}
},
"handler_share.ShareCommentLists": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ShareCommentListItem"
}
},
"total": {
"type": "integer"
}
}
},
"handler_v1.CommentLists": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.CommentListItem"
}
},
"total": {
"type": "integer"
}
}
},
"handler_v1.ConversationListItems": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ConversationListItem"
}
},
"total": {
"type": "integer"
}
}
},
"schema.RoleType": {
"type": "string",
"enum": [
@@ -5349,6 +5307,48 @@ const docTemplate = `{
"System",
"Tool"
]
},
"share.ShareCommentLists": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ShareCommentListItem"
}
},
"total": {
"type": "integer"
}
}
},
"v1.CommentLists": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.CommentListItem"
}
},
"total": {
"type": "integer"
}
}
},
"v1.ConversationListItems": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ConversationListItem"
}
},
"total": {
"type": "integer"
}
}
}
}
}`

View File

@@ -163,7 +163,7 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/handler_v1.CommentLists"
"$ref": "#/definitions/v1.CommentLists"
}
}
}
@@ -269,7 +269,7 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/handler_v1.ConversationListItems"
"$ref": "#/definitions/v1.ConversationListItems"
}
}
}
@@ -421,7 +421,7 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/github_com_chaitin_panda-wiki_domain.PaginatedResult-array_domain_ConversationMessageListItem"
"$ref": "#/definitions/domain.PaginatedResult-array_domain_ConversationMessageListItem"
}
}
}
@@ -2813,7 +2813,7 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/handler_share.ShareCommentLists"
"$ref": "#/definitions/share.ShareCommentLists"
}
}
}
@@ -4711,6 +4711,20 @@
}
}
},
"domain.PaginatedResult-array_domain_ConversationMessageListItem": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ConversationMessageListItem"
}
},
"total": {
"type": "integer"
}
}
},
"domain.ParseURLItem": {
"type": "object",
"properties": {
@@ -5268,62 +5282,6 @@
}
}
},
"github_com_chaitin_panda-wiki_domain.PaginatedResult-array_domain_ConversationMessageListItem": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ConversationMessageListItem"
}
},
"total": {
"type": "integer"
}
}
},
"handler_share.ShareCommentLists": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ShareCommentListItem"
}
},
"total": {
"type": "integer"
}
}
},
"handler_v1.CommentLists": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.CommentListItem"
}
},
"total": {
"type": "integer"
}
}
},
"handler_v1.ConversationListItems": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ConversationListItem"
}
},
"total": {
"type": "integer"
}
}
},
"schema.RoleType": {
"type": "string",
"enum": [
@@ -5338,6 +5296,48 @@
"System",
"Tool"
]
},
"share.ShareCommentLists": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ShareCommentListItem"
}
},
"total": {
"type": "integer"
}
}
},
"v1.CommentLists": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.CommentListItem"
}
},
"total": {
"type": "integer"
}
}
},
"v1.ConversationListItems": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.ConversationListItem"
}
},
"total": {
"type": "integer"
}
}
}
}
}

View File

@@ -1180,6 +1180,15 @@ definitions:
title:
type: string
type: object
domain.PaginatedResult-array_domain_ConversationMessageListItem:
properties:
data:
items:
$ref: '#/definitions/domain.ConversationMessageListItem'
type: array
total:
type: integer
type: object
domain.ParseURLItem:
properties:
desc:
@@ -1547,42 +1556,6 @@ definitions:
title:
type: string
type: object
github_com_chaitin_panda-wiki_domain.PaginatedResult-array_domain_ConversationMessageListItem:
properties:
data:
items:
$ref: '#/definitions/domain.ConversationMessageListItem'
type: array
total:
type: integer
type: object
handler_share.ShareCommentLists:
properties:
data:
items:
$ref: '#/definitions/domain.ShareCommentListItem'
type: array
total:
type: integer
type: object
handler_v1.CommentLists:
properties:
data:
items:
$ref: '#/definitions/domain.CommentListItem'
type: array
total:
type: integer
type: object
handler_v1.ConversationListItems:
properties:
data:
items:
$ref: '#/definitions/domain.ConversationListItem'
type: array
total:
type: integer
type: object
schema.RoleType:
enum:
- assistant
@@ -1595,6 +1568,33 @@ definitions:
- User
- System
- Tool
share.ShareCommentLists:
properties:
data:
items:
$ref: '#/definitions/domain.ShareCommentListItem'
type: array
total:
type: integer
type: object
v1.CommentLists:
properties:
data:
items:
$ref: '#/definitions/domain.CommentListItem'
type: array
total:
type: integer
type: object
v1.ConversationListItems:
properties:
data:
items:
$ref: '#/definitions/domain.ConversationListItem'
type: array
total:
type: integer
type: object
info:
contact: {}
paths:
@@ -1699,7 +1699,7 @@ paths:
- $ref: '#/definitions/domain.Response'
- properties:
data:
$ref: '#/definitions/handler_v1.CommentLists'
$ref: '#/definitions/v1.CommentLists'
type: object
summary: GetCommentList
tags:
@@ -1765,7 +1765,7 @@ paths:
- $ref: '#/definitions/domain.Response'
- properties:
data:
$ref: '#/definitions/handler_v1.ConversationListItems'
$ref: '#/definitions/v1.ConversationListItems'
type: object
summary: get conversation list
tags:
@@ -1857,7 +1857,7 @@ paths:
- $ref: '#/definitions/domain.Response'
- properties:
data:
$ref: '#/definitions/github_com_chaitin_panda-wiki_domain.PaginatedResult-array_domain_ConversationMessageListItem'
$ref: '#/definitions/domain.PaginatedResult-array_domain_ConversationMessageListItem'
type: object
summary: GetMessageFeedBackList
tags:
@@ -3340,7 +3340,7 @@ paths:
- $ref: '#/definitions/domain.Response'
- properties:
data:
$ref: '#/definitions/handler_share.ShareCommentLists'
$ref: '#/definitions/share.ShareCommentLists'
type: object
summary: GetCommentList
tags:

View File

@@ -59,6 +59,7 @@ type CreateKnowledgeBaseReq struct {
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
Hosts []string `json:"hosts"`
MaxKB int `json:"-"`
}
type UpdateKnowledgeBaseReq struct {

View File

@@ -84,6 +84,8 @@ type CreateNodeReq struct {
Emoji string `json:"emoji"`
Visibility *NodeVisibility `json:"visibility"`
MaxNode int `json:"-"`
}
type GetNodeListReq struct {

View File

@@ -54,6 +54,7 @@ require (
google.golang.org/api v0.197.0
google.golang.org/genai v1.13.0
google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
gorm.io/driver/postgres v1.5.11
gorm.io/gorm v1.26.1
)
@@ -179,7 +180,6 @@ require (
golang.org/x/tools v0.33.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

View File

@@ -21,14 +21,16 @@ type BaseHandler struct {
baseLogger *log.Logger
config *config.Config
ShareAuthMiddleware *middleware.ShareAuthMiddleware
V1Auth middleware.AuthMiddleware
}
func NewBaseHandler(echo *echo.Echo, logger *log.Logger, config *config.Config, shareAuthMiddleware *middleware.ShareAuthMiddleware) *BaseHandler {
func NewBaseHandler(echo *echo.Echo, logger *log.Logger, config *config.Config, v1Auth middleware.AuthMiddleware, shareAuthMiddleware *middleware.ShareAuthMiddleware) *BaseHandler {
return &BaseHandler{
Router: echo,
baseLogger: logger.WithModule("http_base_handler"),
config: config,
ShareAuthMiddleware: shareAuthMiddleware,
V1Auth: v1Auth,
}
}

View File

@@ -80,6 +80,12 @@ func (h *KnowledgeBaseHandler) CreateKnowledgeBase(c echo.Context) error {
return h.NewResponseWithError(c, "ports is required", nil)
}
req.MaxKB = 1
maxKB := c.Get("max_kb")
if maxKB != nil {
req.MaxKB = maxKB.(int)
}
did, err := h.usecase.CreateKnowledgeBase(c.Request().Context(), &req)
if err != nil {
if errors.Is(err, domain.ErrPortHostAlreadyExists) {

View File

@@ -69,6 +69,10 @@ func (h *NodeHandler) CreateNode(c echo.Context) error {
if err := c.Validate(req); err != nil {
return h.NewResponseWithError(c, "validate request body failed", err)
}
req.MaxNode = 300
if maxNode := c.Get("max_node"); maxNode != nil {
req.MaxNode = maxNode.(int)
}
id, err := h.usecase.Create(c.Request().Context(), req)
if err != nil {
return h.NewResponseWithError(c, "create node failed", err)

1
backend/pro Submodule

Submodule backend/pro added at 212541073b

5
backend/pro_imports.go Normal file
View File

@@ -0,0 +1,5 @@
package backend
import (
_ "google.golang.org/protobuf/types/known/emptypb"
)

View File

@@ -292,7 +292,7 @@ func (r *KnowledgeBaseRepository) SyncKBAccessSettingsToCaddy(ctx context.Contex
return nil
}
func (r *KnowledgeBaseRepository) CreateKnowledgeBase(ctx context.Context, kb *domain.KnowledgeBase) error {
func (r *KnowledgeBaseRepository) CreateKnowledgeBase(ctx context.Context, maxKB int, kb *domain.KnowledgeBase) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(kb).Error; err != nil {
return err
@@ -304,7 +304,7 @@ func (r *KnowledgeBaseRepository) CreateKnowledgeBase(ctx context.Context, kb *d
Find(&kbs).Error; err != nil {
return err
}
if len(kbs) > 1 {
if len(kbs) > maxKB {
return errors.New("kb is too many")
}

View File

@@ -39,7 +39,7 @@ func (r *NodeRepository) Create(ctx context.Context, req *domain.CreateNodeReq)
Count(&count).Error; err != nil {
return err
}
if count >= 300 {
if count >= int64(req.MaxNode) {
return errors.New("node is too many")
}
var maxPos float64

View File

@@ -0,0 +1,2 @@
-- Downgrade script for creating the 'licenses' table
DROP TABLE licenses;

View File

@@ -0,0 +1,8 @@
-- create table licenses
CREATE TABLE IF NOT EXISTS licenses (
id SERIAL PRIMARY KEY,
"type" text,
code text,
data bytea,
created_at timestamptz NOT NULL DEFAULT NOW()
);

View File

@@ -36,7 +36,7 @@ type Client struct {
}
// NewClient creates a new telemetry client
func NewClient(logger *log.Logger, repo *pg.KnowledgeBaseRepository) *Client {
func NewClient(logger *log.Logger, repo *pg.KnowledgeBaseRepository) (*Client, error) {
baseURL := "https://baizhi.cloud/api/public/data/report"
client := &Client{
baseURL: baseURL,
@@ -50,7 +50,12 @@ func NewClient(logger *log.Logger, repo *pg.KnowledgeBaseRepository) *Client {
}
// get or create machine ID
client.machineID = client.getOrCreateMachineID()
machineID, err := client.getOrCreateMachineID()
if err != nil {
logger.Error("failed to get or create machine ID", log.Error(err))
return nil, fmt.Errorf("failed to get or create machine ID: %w", err)
}
client.machineID = machineID
// report immediately on startup
if err := client.reportInstallation(); err != nil {
@@ -60,34 +65,79 @@ func NewClient(logger *log.Logger, repo *pg.KnowledgeBaseRepository) *Client {
// start periodic report
go client.startPeriodicReport()
return client
return client, nil
}
// getOrCreateMachineID 获取或创建机器ID
func (c *Client) getOrCreateMachineID() string {
// try to read from file
func (c *Client) GetMachineID() string {
return c.machineID
}
func (c *Client) getOrCreateMachineID() (string, error) {
// get machine id from file
if id, err := os.ReadFile(machineIDFile); err == nil {
c.firstReport = false
return strings.TrimSpace(string(id))
return strings.TrimSpace(string(id)), nil
} else if !os.IsNotExist(err) {
return "", fmt.Errorf("failed to read machine ID file: %w", err)
}
// try to get hardware ID
// ensure dir is exists
dir := filepath.Dir(machineIDFile)
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", fmt.Errorf("failed to create machine ID directory: %w", err)
}
// create lock file to prevent concurrent access
lockFile := machineIDFile + ".lock"
lock, err := os.OpenFile(lockFile, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644)
if err != nil {
if os.IsExist(err) {
// if lock file already exists, wait and try again
c.logger.Info("lock file already exists, waiting and trying again")
time.Sleep(100 * time.Millisecond)
return c.getOrCreateMachineID()
}
return "", fmt.Errorf("failed to create lock file: %w", err)
}
defer func() {
if err := lock.Close(); err != nil {
c.logger.Error("failed to close lock file", log.Error(err))
}
if err := os.Remove(lockFile); err != nil {
c.logger.Error("failed to remove lock file", log.Error(err))
}
}()
if id, err := os.ReadFile(machineIDFile); err == nil {
c.firstReport = false
return strings.TrimSpace(string(id)), nil
}
// generate unique ID based on hardware information
id := c.getHardwareID()
if id == "" {
// if get hardware ID failed, generate random ID
// if no hardware information available, use UUID as fallback
id = uuid.New().String()
}
// ensure directory exists
dir := filepath.Dir(machineIDFile)
if err := os.MkdirAll(dir, 0o755); err == nil {
// write to file
if err := os.WriteFile(machineIDFile, []byte(id), 0o644); err != nil {
c.logger.Error("write machine ID to file failed", log.Error(err))
}
// write machine ID to file and ensure data is written to disk
if err := os.WriteFile(machineIDFile, []byte(id), 0o644); err != nil {
return "", fmt.Errorf("failed to write machine ID file: %w", err)
}
return id
// sync file to ensure data is written to disk
if file, err := os.OpenFile(machineIDFile, os.O_RDWR, 0o644); err == nil {
if err := file.Sync(); err != nil {
if err := file.Close(); err != nil {
c.logger.Error("failed to close machine ID file after write", log.Error(err))
}
return "", fmt.Errorf("failed to sync machine ID file: %w", err)
}
if err := file.Close(); err != nil {
c.logger.Error("failed to close machine ID file after sync", log.Error(err))
}
}
return id, nil
}
// getHardwareID generates a unique ID based on hardware information
@@ -97,8 +147,7 @@ func (c *Client) getHardwareID() string {
// get CPU information
if cpuInfo, err := os.ReadFile("/proc/cpuinfo"); err == nil {
// extract CPU model
lines := strings.Split(string(cpuInfo), "\n")
for _, line := range lines {
for line := range strings.SplitSeq(string(cpuInfo), "\n") {
if strings.HasPrefix(line, "model name") {
parts := strings.Split(line, ":")
if len(parts) > 1 {

View File

@@ -58,7 +58,7 @@ func (u *KnowledgeBaseUsecase) CreateKnowledgeBase(ctx context.Context, req *dom
Hosts: req.Hosts,
},
}
if err := u.repo.CreateKnowledgeBase(ctx, kb); err != nil {
if err := u.repo.CreateKnowledgeBase(ctx, req.MaxKB, kb); err != nil {
return "", err
}
return kbID, nil