feat: update agent button on server page
This commit is contained in:
@@ -32,6 +32,9 @@ func RegisterRoutes(r *gin.Engine) {
|
||||
apiGroup.GET("/servers/:id", getServer)
|
||||
apiGroup.DELETE("/servers/:id", deleteServer)
|
||||
apiGroup.POST("/servers/:id/generate-key", generateKey)
|
||||
apiGroup.POST("/servers/:id/update-agent", updateAgent)
|
||||
|
||||
apiGroup.GET("/agent/latest-version", getLatestAgentVersion)
|
||||
|
||||
apiGroup.GET("/keys", listKeys)
|
||||
apiGroup.POST("/keys", createKey)
|
||||
@@ -259,6 +262,34 @@ func revokeAssignment(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"revoked": true})
|
||||
}
|
||||
|
||||
func getLatestAgentVersion(c *gin.Context) {
|
||||
version, err := services.GetLatestAgentVersion()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"version": version})
|
||||
}
|
||||
|
||||
func updateAgent(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
s, err := services.GetServer(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
|
||||
return
|
||||
}
|
||||
|
||||
version, err := services.DispatchUpdateAgent(s.ServerID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusAccepted, gin.H{
|
||||
"message": "update command sent to agent",
|
||||
"version": version,
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateScript(c *gin.Context) {
|
||||
giteaHost := os.Getenv("GITEA_HOST")
|
||||
if giteaHost == "" {
|
||||
|
||||
@@ -26,8 +26,9 @@ type RegisterResponse struct {
|
||||
}
|
||||
|
||||
type SyncRequest struct {
|
||||
ServerId string `json:"server_id"`
|
||||
AgentToken string `json:"agent_token"`
|
||||
ServerId string `json:"server_id"`
|
||||
AgentToken string `json:"agent_token"`
|
||||
AgentVersion string `json:"agent_version,omitempty"`
|
||||
}
|
||||
|
||||
type SyncResponse struct {
|
||||
@@ -52,12 +53,18 @@ type ServerCommand struct {
|
||||
CommandId string `json:"command_id"`
|
||||
GenerateKey *GenerateKeyCmd `json:"generate_key,omitempty"`
|
||||
DeleteKey *DeleteKeyCmd `json:"delete_key,omitempty"`
|
||||
UpdateAgent *UpdateAgentCmd `json:"update_agent,omitempty"`
|
||||
}
|
||||
|
||||
type DeleteKeyCmd struct {
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
type UpdateAgentCmd struct {
|
||||
Version string `json:"version"`
|
||||
GiteaBaseURL string `json:"gitea_base_url"`
|
||||
}
|
||||
|
||||
type GenerateKeyCmd struct {
|
||||
Label string `json:"label"`
|
||||
KeyType string `json:"key_type,omitempty"`
|
||||
|
||||
@@ -36,7 +36,7 @@ func (s *keyManagerServer) SyncKeys(ctx context.Context, req *pb.SyncRequest) (*
|
||||
return nil, status.Errorf(codes.Unauthenticated, "invalid agent token")
|
||||
}
|
||||
|
||||
if err := services.UpdateServerLastSeen(srv.ServerID); err != nil {
|
||||
if err := services.UpdateServerLastSeen(srv.ServerID, req.AgentVersion); err != nil {
|
||||
log.Printf("failed to update last seen for %s: %v", srv.ServerID, err)
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func (s *keyManagerServer) CommandStream(stream pb.KeyManager_CommandStreamServe
|
||||
return status.Errorf(codes.Unauthenticated, "invalid agent token")
|
||||
}
|
||||
|
||||
if err := services.UpdateServerLastSeen(srv.ServerID); err != nil {
|
||||
if err := services.UpdateServerLastSeen(srv.ServerID, ""); err != nil {
|
||||
log.Printf("update last seen %s: %v", srv.ServerID, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,15 @@ import (
|
||||
|
||||
type Server struct {
|
||||
ID bson.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
|
||||
ServerID string `bson:"server_id" json:"server_id"`
|
||||
Hostname string `bson:"hostname" json:"hostname"`
|
||||
IPAddress string `bson:"ip_address" json:"ip_address"`
|
||||
OSInfo string `bson:"os_info" json:"os_info"`
|
||||
PreRegToken string `bson:"pre_reg_token,omitempty" json:"pre_reg_token,omitempty"`
|
||||
PreRegExpires *time.Time `bson:"pre_reg_expires,omitempty" json:"pre_reg_expires,omitempty"`
|
||||
AgentTokenHash string `bson:"agent_token_hash,omitempty" json:"-"`
|
||||
Status string `bson:"status" json:"status"`
|
||||
LastSeen *time.Time `bson:"last_seen,omitempty" json:"last_seen,omitempty"`
|
||||
CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
||||
ServerID string `bson:"server_id" json:"server_id"`
|
||||
Hostname string `bson:"hostname" json:"hostname"`
|
||||
IPAddress string `bson:"ip_address" json:"ip_address"`
|
||||
OSInfo string `bson:"os_info" json:"os_info"`
|
||||
PreRegToken string `bson:"pre_reg_token,omitempty" json:"pre_reg_token,omitempty"`
|
||||
PreRegExpires *time.Time `bson:"pre_reg_expires,omitempty" json:"pre_reg_expires,omitempty"`
|
||||
AgentTokenHash string `bson:"agent_token_hash,omitempty" json:"-"`
|
||||
Status string `bson:"status" json:"status"`
|
||||
AgentVersion string `bson:"agent_version,omitempty" json:"agent_version,omitempty"`
|
||||
LastSeen *time.Time `bson:"last_seen,omitempty" json:"last_seen,omitempty"`
|
||||
CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -67,6 +71,69 @@ type KeyGenParams struct {
|
||||
Comment string
|
||||
}
|
||||
|
||||
// GetLatestAgentVersion queries the Gitea API for the latest agent/v* release tag
|
||||
// and returns just the version number (e.g. "1.2.3").
|
||||
func GetLatestAgentVersion() (string, error) {
|
||||
giteaHost := os.Getenv("GITEA_HOST")
|
||||
if giteaHost == "" {
|
||||
giteaHost = "gitea.example.com"
|
||||
}
|
||||
url := fmt.Sprintf("https://%s/api/v1/repos/mrhid6/keymanager/releases?limit=20", giteaHost)
|
||||
resp, err := http.Get(url) //nolint:gosec
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fetch releases: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("Gitea API returned HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var releases []struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil {
|
||||
return "", fmt.Errorf("decode releases: %w", err)
|
||||
}
|
||||
|
||||
for _, r := range releases {
|
||||
if strings.HasPrefix(r.TagName, "agent/v") {
|
||||
return strings.TrimPrefix(r.TagName, "agent/v"), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no agent release found")
|
||||
}
|
||||
|
||||
// DispatchUpdateAgent sends an update command to the named server's agent.
|
||||
// It fetches the latest version from Gitea and includes the download base URL.
|
||||
func DispatchUpdateAgent(serverID string) (string, error) {
|
||||
if !Dispatcher.IsConnected(serverID) {
|
||||
return "", fmt.Errorf("agent is not connected to the command stream")
|
||||
}
|
||||
|
||||
version, err := GetLatestAgentVersion()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get latest version: %w", err)
|
||||
}
|
||||
|
||||
giteaHost := os.Getenv("GITEA_HOST")
|
||||
if giteaHost == "" {
|
||||
giteaHost = "gitea.example.com"
|
||||
}
|
||||
|
||||
cmdID := uuid.New().String()
|
||||
cmd := &pb.ServerCommand{
|
||||
CommandId: cmdID,
|
||||
UpdateAgent: &pb.UpdateAgentCmd{
|
||||
Version: version,
|
||||
GiteaBaseURL: "https://" + giteaHost,
|
||||
},
|
||||
}
|
||||
if err := Dispatcher.dispatch(serverID, cmd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// DispatchDeleteKey sends a delete-key command to the named server's agent.
|
||||
// It is best-effort: if the agent is offline the local files will remain until next connection.
|
||||
func DispatchDeleteKey(serverID, label string) {
|
||||
|
||||
@@ -134,14 +134,18 @@ func ValidateAgentToken(serverID, agentToken string) (*models.Server, error) {
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func UpdateServerLastSeen(serverID string) error {
|
||||
func UpdateServerLastSeen(serverID, agentVersion string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
now := time.Now()
|
||||
fields := bson.M{"last_seen": now, "status": "active"}
|
||||
if agentVersion != "" {
|
||||
fields["agent_version"] = agentVersion
|
||||
}
|
||||
_, err := db.Col("servers").UpdateOne(ctx,
|
||||
bson.M{"server_id": serverID},
|
||||
bson.M{"$set": bson.M{"last_seen": now, "status": "active"}},
|
||||
bson.M{"$set": fields},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user