Update README with current features and screenshots

- Cross-platform support (Windows/Linux) with Windows Updates and Winget
- Added dependency confirmation workflow and refresh token authentication
- New screenshots: History, Live Operations, Windows Agent Details
- Local CLI features with terminal output and cache system
- Updated known limitations - Proxmox integration is broken
- Organized docs to docs/ folder and updated .gitignore
- Probably introduced a dozen bugs with Windows agents - stay tuned
This commit is contained in:
Fimeg
2025-10-17 15:28:22 -04:00
parent 61294ba514
commit 2ade509b63
65 changed files with 7342 additions and 424 deletions

View File

@@ -32,6 +32,16 @@ func NewClient(baseURL, token string) *Client {
}
}
// GetToken returns the current JWT token
func (c *Client) GetToken() string {
return c.token
}
// SetToken updates the JWT token
func (c *Client) SetToken(token string) {
c.token = token
}
// RegisterRequest is the payload for agent registration
type RegisterRequest struct {
Hostname string `json:"hostname"`
@@ -44,9 +54,10 @@ type RegisterRequest struct {
// RegisterResponse is returned after successful registration
type RegisterResponse struct {
AgentID uuid.UUID `json:"agent_id"`
Token string `json:"token"`
Config map[string]interface{} `json:"config"`
AgentID uuid.UUID `json:"agent_id"`
Token string `json:"token"` // Short-lived access token (24h)
RefreshToken string `json:"refresh_token"` // Long-lived refresh token (90d)
Config map[string]interface{} `json:"config"`
}
// Register registers the agent with the server
@@ -86,6 +97,59 @@ func (c *Client) Register(req RegisterRequest) (*RegisterResponse, error) {
return &result, nil
}
// TokenRenewalRequest is the payload for token renewal using refresh token
type TokenRenewalRequest struct {
AgentID uuid.UUID `json:"agent_id"`
RefreshToken string `json:"refresh_token"`
}
// TokenRenewalResponse is returned after successful token renewal
type TokenRenewalResponse struct {
Token string `json:"token"` // New short-lived access token (24h)
}
// RenewToken uses refresh token to get a new access token (proper implementation)
func (c *Client) RenewToken(agentID uuid.UUID, refreshToken string) error {
url := fmt.Sprintf("%s/api/v1/agents/renew", c.baseURL)
renewalReq := TokenRenewalRequest{
AgentID: agentID,
RefreshToken: refreshToken,
}
body, err := json.Marshal(renewalReq)
if err != nil {
return err
}
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return err
}
httpReq.Header.Set("Content-Type", "application/json")
resp, err := c.http.Do(httpReq)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("token renewal failed: %s - %s", resp.Status, string(bodyBytes))
}
var result TokenRenewalResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return err
}
// Update client token
c.token = result.Token
return nil
}
// Command represents a command from the server
type Command struct {
ID string `json:"id"`
@@ -98,14 +162,45 @@ type CommandsResponse struct {
Commands []Command `json:"commands"`
}
// SystemMetrics represents lightweight system metrics sent with check-ins
type SystemMetrics struct {
CPUPercent float64 `json:"cpu_percent,omitempty"`
MemoryPercent float64 `json:"memory_percent,omitempty"`
MemoryUsedGB float64 `json:"memory_used_gb,omitempty"`
MemoryTotalGB float64 `json:"memory_total_gb,omitempty"`
DiskUsedGB float64 `json:"disk_used_gb,omitempty"`
DiskTotalGB float64 `json:"disk_total_gb,omitempty"`
DiskPercent float64 `json:"disk_percent,omitempty"`
Uptime string `json:"uptime,omitempty"`
Version string `json:"version,omitempty"` // Agent version
}
// GetCommands retrieves pending commands from the server
func (c *Client) GetCommands(agentID uuid.UUID) ([]Command, error) {
// Optionally sends lightweight system metrics in the request
func (c *Client) GetCommands(agentID uuid.UUID, metrics *SystemMetrics) ([]Command, error) {
url := fmt.Sprintf("%s/api/v1/agents/%s/commands", c.baseURL, agentID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
var req *http.Request
var err error
// If metrics provided, send them in request body
if metrics != nil {
body, err := json.Marshal(metrics)
if err != nil {
return nil, err
}
req, err = http.NewRequest("GET", url, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
} else {
req, err = http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
}
req.Header.Set("Authorization", "Bearer "+c.token)
resp, err := c.http.Do(req)
@@ -220,6 +315,60 @@ func (c *Client) ReportLog(agentID uuid.UUID, report LogReport) error {
return nil
}
// DependencyReport represents a dependency report after dry run
type DependencyReport struct {
PackageName string `json:"package_name"`
PackageType string `json:"package_type"`
Dependencies []string `json:"dependencies"`
UpdateID string `json:"update_id"`
DryRunResult *InstallResult `json:"dry_run_result,omitempty"`
}
// InstallResult represents the result of a package installation attempt
type InstallResult struct {
Success bool `json:"success"`
ErrorMessage string `json:"error_message,omitempty"`
Stdout string `json:"stdout,omitempty"`
Stderr string `json:"stderr,omitempty"`
ExitCode int `json:"exit_code"`
DurationSeconds int `json:"duration_seconds"`
Action string `json:"action,omitempty"`
PackagesInstalled []string `json:"packages_installed,omitempty"`
ContainersUpdated []string `json:"containers_updated,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
IsDryRun bool `json:"is_dry_run"`
}
// ReportDependencies sends dependency report to the server
func (c *Client) ReportDependencies(agentID uuid.UUID, report DependencyReport) error {
url := fmt.Sprintf("%s/api/v1/agents/%s/dependencies", c.baseURL, agentID)
body, err := json.Marshal(report)
if err != nil {
return err
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.token)
resp, err := c.http.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("failed to report dependencies: %s - %s", resp.Status, string(bodyBytes))
}
return nil
}
// DetectSystem returns basic system information (deprecated, use system.GetSystemInfo instead)
func DetectSystem() (osType, osVersion, osArch string) {
osType = runtime.GOOS