commit 8f8e7206ed88b4842626effc24e87b7a05bd79ca Author: hailin Date: Fri Jun 13 13:19:49 2025 +0000 first commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..edecedd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.22 AS builder + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN go build -o license-server main.go + +FROM alpine:latest + +RUN apk add --no-cache ca-certificates sqlite + +WORKDIR /root/ + +COPY --from=builder /app/license-server . + +EXPOSE 13579 + +CMD ["./license-server"] + diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..25060ef --- /dev/null +++ b/deploy.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# 设置变量 +CONTAINER_NAME="license" +IMAGE_NAME="license-server" +IMAGE_TAG="1.0.0" +GIT_REPO_DIR="/home/ceshi/work/license-server" # 替换成你的代码路径 +PORT=13579 + +FULL_IMAGE_NAME="${IMAGE_NAME}:${IMAGE_TAG}" + +echo "===> Step 1: 停止并删除老的容器..." +docker stop $CONTAINER_NAME 2>/dev/null || true +docker rm $CONTAINER_NAME 2>/dev/null || true + +echo "===> Step 2: 删除旧镜像..." +docker rmi $FULL_IMAGE_NAME 2>/dev/null || true + +echo "===> Step 3: 拉取最新代码..." +cd "$GIT_REPO_DIR" || exit 1 +git pull + +echo "===> Step 4: 构建新镜像 $FULL_IMAGE_NAME..." +docker build -t $FULL_IMAGE_NAME . + +echo "===> Step 5: 启动新容器..." +docker run -d --name $CONTAINER_NAME -p $PORT:$PORT $FULL_IMAGE_NAME + +echo "✅ 部署完成,服务运行在端口 $PORT,镜像版本为 $FULL_IMAGE_NAME" + diff --git a/license/crypto.go b/license/crypto.go new file mode 100644 index 0000000..12ad909 --- /dev/null +++ b/license/crypto.go @@ -0,0 +1,50 @@ +package license + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "math/big" + "time" +) + +var ( + privateKey *ecdsa.PrivateKey +) + +func init() { + privateKey, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +} + +func SignPayload(payload []byte) (string, error) { + hash := sha256.Sum256(payload) + r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash[:]) + if err != nil { + return "", err + } + + sig := append(r.Bytes(), s.Bytes()...) + return base64.StdEncoding.EncodeToString(sig), nil +} + +func VerifySignature(pubKey *ecdsa.PublicKey, payload []byte, signature string) bool { + sigBytes, _ := base64.StdEncoding.DecodeString(signature) + r := big.Int{} + s := big.Int{} + r.SetBytes(sigBytes[:len(sigBytes)/2]) + s.SetBytes(sigBytes[len(sigBytes)/2:]) + + hash := sha256.Sum256(payload) + return ecdsa.Verify(pubKey, hash[:], &r, &s) +} + +func ExportPublicKeyPEM() string { + pubKeyBytes, _ := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + return string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubKeyBytes})) +} + diff --git a/license/model.go b/license/model.go new file mode 100644 index 0000000..133e45f --- /dev/null +++ b/license/model.go @@ -0,0 +1,20 @@ +package license + +type LicenseRequest struct { + MachineID string `json:"machine_id"` + Expiry string `json:"expiry"` // YYYY-MM-DD + Features []string `json:"features"` +} + +type LicenseFile struct { + Payload string `json:"payload"` + Signature string `json:"signature"` +} + +type ActivationRecord struct { + ID int + MachineID string + License string + Activated bool +} + diff --git a/license/service.go b/license/service.go new file mode 100644 index 0000000..a7dfed4 --- /dev/null +++ b/license/service.go @@ -0,0 +1,78 @@ +package license + +import ( + "encoding/base64" + "encoding/json" + "github.com/gofiber/fiber/v2" + "license-server/storage" + "time" +) + +func GenerateLicenseHandler(db storage.Database) fiber.Handler { + return func(c *fiber.Ctx) error { + var req LicenseRequest + if err := c.BodyParser(&req); err != nil { + return fiber.ErrBadRequest + } + + payloadBytes, _ := json.Marshal(req) + payloadB64 := base64.StdEncoding.EncodeToString(payloadBytes) + signature, _ := SignPayload(payloadBytes) + + licenseFile := LicenseFile{ + Payload: payloadB64, + Signature: signature, + } + + return c.JSON(licenseFile) + } +} + +func ActivateLicenseHandler(db storage.Database) fiber.Handler { + return func(c *fiber.Ctx) error { + var lf LicenseFile + if err := c.BodyParser(&lf); err != nil { + return fiber.ErrBadRequest + } + + payloadBytes, _ := base64.StdEncoding.DecodeString(lf.Payload) + var req LicenseRequest + json.Unmarshal(payloadBytes, &req) + + if db.HasActivated(req.MachineID) { + return fiber.NewError(403, "This machine is already activated.") + } + + if !VerifySignature(&privateKey.PublicKey, payloadBytes, lf.Signature) { + return fiber.NewError(401, "Invalid license signature") + } + + db.SaveActivation(req.MachineID, lf) + return c.SendString("License activated successfully.") + } +} + +func ValidateLicenseHandler(db storage.Database) fiber.Handler { + return func(c *fiber.Ctx) error { + var lf LicenseFile + if err := c.BodyParser(&lf); err != nil { + return fiber.ErrBadRequest + } + + payloadBytes, _ := base64.StdEncoding.DecodeString(lf.Payload) + var req LicenseRequest + json.Unmarshal(payloadBytes, &req) + + if !VerifySignature(&privateKey.PublicKey, payloadBytes, lf.Signature) { + return fiber.NewError(401, "Invalid license signature") + } + + expiry, _ := time.Parse("2006-01-02", req.Expiry) + if time.Now().After(expiry) { + return fiber.NewError(403, "License expired") + } + + return c.JSON(fiber.Map{"valid": true, "features": req.Features}) + } +} + diff --git a/main.go b/main.go new file mode 100644 index 0000000..9d822ad --- /dev/null +++ b/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/gofiber/fiber/v2" + "license-server/license" + "license-server/storage" +) + +func main() { + db := storage.InitDB() + app := fiber.New() + + app.Post("/api/license/generate", license.GenerateLicenseHandler(db)) + app.Post("/api/license/activate", license.ActivateLicenseHandler(db)) + app.Post("/api/license/validate", license.ValidateLicenseHandler(db)) + + app.Listen(":13579") +} + diff --git a/storage/db.go b/storage/db.go new file mode 100644 index 0000000..3c33b7a --- /dev/null +++ b/storage/db.go @@ -0,0 +1,35 @@ +package storage + +import ( + "database/sql" + _ "github.com/mattn/go-sqlite3" + "license-server/license" +) + +type Database struct { + db *sql.DB +} + +func InitDB() Database { + db, _ := sql.Open("sqlite3", "./license.db") + db.Exec(`CREATE TABLE IF NOT EXISTS activations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + machine_id TEXT UNIQUE, + license TEXT, + activated INTEGER + )`) + return Database{db} +} + +func (d Database) HasActivated(machineID string) bool { + row := d.db.QueryRow("SELECT activated FROM activations WHERE machine_id = ?", machineID) + var activated int + err := row.Scan(&activated) + return err == nil && activated == 1 +} + +func (d Database) SaveActivation(machineID string, lf license.LicenseFile) { + d.db.Exec("INSERT OR REPLACE INTO activations(machine_id, license, activated) VALUES (?, ?, 1)", + machineID, lf.Payload+"."+lf.Signature) +} +