first commit
This commit is contained in:
commit
8f8e7206ed
|
|
@ -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"]
|
||||||
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
@ -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}))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue