mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
78 lines
3.5 KiB
Go
78 lines
3.5 KiB
Go
//go:build linux
|
|
|
|
package scenario
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/client"
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/dashboard"
|
|
)
|
|
|
|
func terminalServerID(ctx context.Context, mcpClient *client.Client, uuid string) (uint64, error) {
|
|
servers, err := client.CallTool[serverListArguments, serverListResult](ctx, mcpClient, client.ToolCall[serverListArguments]{Name: "server.list", Arguments: serverListArguments{OnlineOnly: true}})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
for _, server := range servers.StructuredContent.Servers {
|
|
if server.UUID == uuid && server.Online {
|
|
return server.ID, nil
|
|
}
|
|
}
|
|
return 0, errors.New("terminal agent server ID not found")
|
|
}
|
|
|
|
func createTerminalPATClient(ctx context.Context, dashboardInstance *dashboard.Dashboard, name string, scopes []string, serverIDs []uint64) (*client.Client, error) {
|
|
pat, err := client.DoREST[terminalPATRequest, terminalPATResponse](ctx, dashboardInstance.Clients().REST, client.RESTRequest[terminalPATRequest]{Method: http.MethodPost, Path: "/api/v1/api-tokens", Body: &terminalPATRequest{Name: name, Scopes: scopes, ServerIDs: serverIDs}})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return dashboardInstance.AuthenticatedClient(pat.Token)
|
|
}
|
|
|
|
func createForeignTerminalPATClient(ctx context.Context, dashboardInstance *dashboard.Dashboard) (*client.Client, func() error, error) {
|
|
const username = "terminal-member"
|
|
const password = "terminal-member-password"
|
|
admin := dashboardInstance.Clients().REST
|
|
userID, err := client.DoREST[terminalUserRequest, uint64](ctx, admin, client.RESTRequest[terminalUserRequest]{Method: http.MethodPost, Path: "/api/v1/user", Body: &terminalUserRequest{Role: 1, Username: username, Password: password}})
|
|
if err != nil {
|
|
return nil, func() error { return nil }, err
|
|
}
|
|
cleanup := func() error {
|
|
cleanupContext, cancel := context.WithTimeout(context.WithoutCancel(ctx), 10*time.Second)
|
|
defer cancel()
|
|
_, cleanupErr := client.DoREST[[]uint64, struct{}](cleanupContext, admin, client.RESTRequest[[]uint64]{Method: http.MethodPost, Path: "/api/v1/batch-delete/user", Body: &[]uint64{userID}})
|
|
return cleanupErr
|
|
}
|
|
member, err := client.New(client.Config{BaseURL: dashboardInstance.URL()})
|
|
if err != nil {
|
|
return nil, func() error { return nil }, errors.Join(err, cleanup())
|
|
}
|
|
if _, err := member.Login(ctx, client.LoginRequest{Username: username, Password: password}); err != nil {
|
|
return nil, func() error { return nil }, errors.Join(err, cleanup())
|
|
}
|
|
pat, err := client.DoREST[terminalPATRequest, terminalPATResponse](ctx, member, client.RESTRequest[terminalPATRequest]{Method: http.MethodPost, Path: "/api/v1/api-tokens", Body: &terminalPATRequest{Name: "terminal-foreign", Scopes: []string{terminalAttachPATScope}}})
|
|
if err != nil {
|
|
return nil, func() error { return nil }, errors.Join(err, cleanup())
|
|
}
|
|
foreign, err := dashboardInstance.AuthenticatedClient(pat.Token)
|
|
if err != nil {
|
|
return nil, func() error { return nil }, errors.Join(err, cleanup())
|
|
}
|
|
return foreign, cleanup, nil
|
|
}
|
|
|
|
func isWebSocketDenied(err error) bool {
|
|
var handshakeError *client.WebSocketHandshakeError
|
|
return errors.As(err, &handshakeError) && (handshakeError.StatusCode == http.StatusForbidden || webSocketFailureContains(err, "permission denied") || webSocketFailureContains(err, "ApiErrorUnauthorized"))
|
|
}
|
|
|
|
func webSocketFailureContains(err error, text string) bool {
|
|
var handshakeError *client.WebSocketHandshakeError
|
|
return errors.As(err, &handshakeError) && strings.Contains(handshakeError.Message, text)
|
|
}
|