Files
2026-07-20 04:51:20 +00:00

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)
}