Add API to register server (#472)

This commit is contained in:
Tao Chen 2024-11-06 23:38:15 +08:00 committed by GitHub
parent 3d6edd602c
commit f4b7483807
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 190 additions and 0 deletions

8
.dockerignore Normal file
View File

@ -0,0 +1,8 @@
.git
.gitignore
docker-compose.yml
Dockerfile
Dockerfile.dev
data/*

63
Dockerfile.dev Normal file
View File

@ -0,0 +1,63 @@
# Use build arguments for Go version and architecture
ARG GO_VERSION=1.22
ARG BUILDARCH=amd64
# Stage 1: Builder Stage
# FROM golang:${GO_VERSION}-alpine AS builder
FROM crazymax/xgo:${GO_VERSION} AS builder
# Set up working directory
WORKDIR /app
# Step 1: Copy the source code
COPY . .
# use --mount=type=cache,target=/go/pkg/mod to cache the go mod
# Step 2: Download dependencies
RUN --mount=type=cache,target=/go/pkg/mod \
go mod tidy && go mod download
# Step 3: Build the Go application with CGO enabled and specified ldflags
RUN --mount=type=cache,target=/go/pkg/mod \
CGO_ENABLED=1 GOOS=linux go build -a \
-ldflags "-s -w --extldflags '-static -fpic'" \
-installsuffix cgo -o dashboard cmd/dashboard/main.go
# Stage 2: Create the final image
FROM alpine:latest
ARG COUNTRY
# Install required tools without caching index to minimize image size
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating the repositories"; \
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories; \
fi && \
apk update && apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'Asia/Shanghai' >/etc/timezone && \
rm -rf /var/cache/apk/* && \
mkdir -p /dashboard/data
# Copy the entrypoint script and ensure it is executable
COPY ./script/entrypoint.sh /entrypoint.sh
# Set up the entrypoint script
RUN chmod +x /entrypoint.sh
WORKDIR /dashboard
# Copy the statically linked binary from the builder stage
COPY --from=builder /app/dashboard ./app
# Copy the configuration file and the resource directory
COPY ./script/config.yaml ./data/config.yaml
COPY ./resource ./resource
# Set up volume and expose ports
VOLUME ["/dashboard/data"]
EXPOSE 80 5555 443
# Define the entrypoint
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -28,6 +28,7 @@ func (v *apiV1) serve() {
}))
r.GET("/server/list", v.serverList)
r.GET("/server/details", v.serverDetails)
r.POST("/server/register", v.RegisterServer)
// 不强制认证的 API
mr := v.r.Group("monitor")
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
@ -83,6 +84,45 @@ func (v *apiV1) serverDetails(c *gin.Context) {
c.JSON(200, singleton.ServerAPI.GetAllStatus())
}
// RegisterServer adds a server and responds with the full ServerRegisterResponse
// header: Authorization: Token
// body: RegisterServer
// response: ServerRegisterResponse or Secret string
func (v *apiV1) RegisterServer(c *gin.Context) {
var rs singleton.RegisterServer
// Attempt to bind JSON to RegisterServer struct
if err := c.ShouldBindJSON(&rs); err != nil {
c.JSON(400, singleton.ServerRegisterResponse{
CommonResponse: singleton.CommonResponse{
Code: 400,
Message: "Parse JSON failed",
},
})
return
}
// Check if simple mode is requested
simple := c.Query("simple") == "true" || c.Query("simple") == "1"
// Set defaults if fields are empty
if rs.Name == "" {
rs.Name = c.ClientIP()
}
if rs.Tag == "" {
rs.Tag = "AutoRegister"
}
if rs.HideForGuest == "" {
rs.HideForGuest = "on"
}
// Call the Register function and get the response
response := singleton.ServerAPI.Register(&rs)
// Respond with Secret only if in simple mode, otherwise full response
if simple {
c.JSON(response.Code, response.Secret)
} else {
c.JSON(response.Code, response)
}
}
func (v *apiV1) monitorHistoriesById(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)

17
docker-compose.yml Normal file
View File

@ -0,0 +1,17 @@
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
args:
COUNTRY: CN
image: nezha:dev
container_name: nezha-dev
ports:
- ${NEZHA_PORT:-80}:18080
- 5555:5555
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- ./data:/dashboard/data
# - ./resource:/dashboard/resource

View File

@ -25,6 +25,19 @@ type CommonResponse struct {
Message string `json:"message"`
}
type RegisterServer struct {
Name string
Tag string
Note string
HideForGuest string
}
type ServerRegisterResponse struct {
CommonResponse
Secret string `json:"secret"`
}
type CommonServerInfo struct {
ID uint64 `json:"id"`
Name string `json:"name"`
@ -227,6 +240,55 @@ func (s *ServerAPIService) GetAllList() *ServerInfoResponse {
}
return res
}
func (s *ServerAPIService) Register(rs *RegisterServer) *ServerRegisterResponse {
var serverInfo model.Server
var err error
// Populate serverInfo fields
serverInfo.Name = rs.Name
serverInfo.Tag = rs.Tag
serverInfo.Note = rs.Note
serverInfo.HideForGuest = rs.HideForGuest == "on"
// Generate a random secret
serverInfo.Secret, err = utils.GenerateRandomString(18)
if err != nil {
return &ServerRegisterResponse{
CommonResponse: CommonResponse{
Code: 500,
Message: "Generate secret failed: " + err.Error(),
},
Secret: "",
}
}
// Attempt to save serverInfo in the database
err = DB.Create(&serverInfo).Error
if err != nil {
return &ServerRegisterResponse{
CommonResponse: CommonResponse{
Code: 500,
Message: "Database error: " + err.Error(),
},
Secret: "",
}
}
serverInfo.Host = &model.Host{}
serverInfo.State = &model.HostState{}
serverInfo.TaskCloseLock = new(sync.Mutex)
ServerLock.Lock()
SecretToID[serverInfo.Secret] = serverInfo.ID
ServerList[serverInfo.ID] = &serverInfo
ServerTagToIDList[serverInfo.Tag] = append(ServerTagToIDList[serverInfo.Tag], serverInfo.ID)
ServerLock.Unlock()
ReSortServer()
// Successful response
return &ServerRegisterResponse{
CommonResponse: CommonResponse{
Code: 200,
Message: "Server created successfully",
},
Secret: serverInfo.Secret,
}
}
func (m *MonitorAPIService) GetMonitorHistories(query map[string]any) *MonitorInfoResponse {
var (