Breaking changes for clean alpha releases: - JWT authentication with user-provided secrets (no more development defaults) - Registration token system for secure agent enrollment - Rate limiting with user-adjustable settings - Enhanced agent configuration with proxy support - Interactive server setup wizard (--setup flag) - Heartbeat architecture separation for better UX - Package status synchronization fixes - Accurate timestamp tracking for RMM features Setup process for new installations: 1. docker-compose up -d postgres 2. ./redflag-server --setup 3. ./redflag-server --migrate 4. ./redflag-server 5. Generate tokens via admin UI 6. Deploy agents with registration tokens
196 lines
5.9 KiB
Go
196 lines
5.9 KiB
Go
package installer
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// DockerInstaller handles Docker image updates
|
|
type DockerInstaller struct{}
|
|
|
|
// NewDockerInstaller creates a new Docker installer
|
|
func NewDockerInstaller() (*DockerInstaller, error) {
|
|
// Check if docker is available first
|
|
if _, err := exec.LookPath("docker"); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &DockerInstaller{}, nil
|
|
}
|
|
|
|
// IsAvailable checks if Docker is available on this system
|
|
func (i *DockerInstaller) IsAvailable() bool {
|
|
_, err := exec.LookPath("docker")
|
|
return err == nil
|
|
}
|
|
|
|
// Update pulls a new image using docker CLI
|
|
func (i *DockerInstaller) Update(imageName, targetVersion string) (*InstallResult, error) {
|
|
startTime := time.Now()
|
|
|
|
// Pull the new image
|
|
fmt.Printf("Pulling Docker image: %s...\n", imageName)
|
|
pullCmd := exec.Command("sudo", "docker", "pull", imageName)
|
|
output, err := pullCmd.CombinedOutput()
|
|
if err != nil {
|
|
return &InstallResult{
|
|
Success: false,
|
|
ErrorMessage: fmt.Sprintf("Failed to pull Docker image: %v\nStdout: %s", err, string(output)),
|
|
Stdout: string(output),
|
|
Stderr: "",
|
|
ExitCode: getExitCode(err),
|
|
DurationSeconds: int(time.Since(startTime).Seconds()),
|
|
Action: "pull",
|
|
}, fmt.Errorf("docker pull failed: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Successfully pulled image: %s\n", string(output))
|
|
|
|
duration := int(time.Since(startTime).Seconds())
|
|
return &InstallResult{
|
|
Success: true,
|
|
Stdout: string(output),
|
|
Stderr: "",
|
|
ExitCode: 0,
|
|
DurationSeconds: duration,
|
|
Action: "pull",
|
|
ContainersUpdated: []string{}, // Would find and recreate containers in a real implementation
|
|
}, nil
|
|
}
|
|
|
|
// UpdatePackage updates a specific Docker image (alias for Update method)
|
|
func (i *DockerInstaller) UpdatePackage(imageName string) (*InstallResult, error) {
|
|
// Docker uses same logic for updating as installing
|
|
return i.Update(imageName, "")
|
|
}
|
|
|
|
// Install installs a Docker image (alias for Update)
|
|
func (i *DockerInstaller) Install(imageName string) (*InstallResult, error) {
|
|
return i.Update(imageName, "")
|
|
}
|
|
|
|
// InstallMultiple installs multiple Docker images
|
|
func (i *DockerInstaller) InstallMultiple(imageNames []string) (*InstallResult, error) {
|
|
if len(imageNames) == 0 {
|
|
return &InstallResult{
|
|
Success: false,
|
|
ErrorMessage: "No images specified for installation",
|
|
}, fmt.Errorf("no images specified")
|
|
}
|
|
|
|
startTime := time.Now()
|
|
var allOutput strings.Builder
|
|
var errors []string
|
|
|
|
for _, imageName := range imageNames {
|
|
fmt.Printf("Pulling Docker image: %s...\n", imageName)
|
|
pullCmd := exec.Command("sudo", "docker", "pull", imageName)
|
|
output, err := pullCmd.CombinedOutput()
|
|
allOutput.WriteString(string(output))
|
|
|
|
if err != nil {
|
|
errors = append(errors, fmt.Sprintf("Failed to pull %s: %v", imageName, err))
|
|
} else {
|
|
fmt.Printf("Successfully pulled image: %s\n", imageName)
|
|
}
|
|
}
|
|
|
|
duration := int(time.Since(startTime).Seconds())
|
|
|
|
if len(errors) > 0 {
|
|
return &InstallResult{
|
|
Success: false,
|
|
ErrorMessage: fmt.Sprintf("Docker pull errors: %v", strings.Join(errors, "; ")),
|
|
Stdout: allOutput.String(),
|
|
Stderr: "",
|
|
ExitCode: 1,
|
|
DurationSeconds: duration,
|
|
Action: "pull_multiple",
|
|
}, fmt.Errorf("docker pull failed for some images")
|
|
}
|
|
|
|
return &InstallResult{
|
|
Success: true,
|
|
Stdout: allOutput.String(),
|
|
Stderr: "",
|
|
ExitCode: 0,
|
|
DurationSeconds: duration,
|
|
Action: "pull_multiple",
|
|
ContainersUpdated: imageNames,
|
|
}, nil
|
|
}
|
|
|
|
// Upgrade is not applicable for Docker in the same way
|
|
func (i *DockerInstaller) Upgrade() (*InstallResult, error) {
|
|
return &InstallResult{
|
|
Success: false,
|
|
ErrorMessage: "Docker upgrade not implemented - use specific image updates",
|
|
ExitCode: 1,
|
|
DurationSeconds: 0,
|
|
Action: "upgrade",
|
|
}, fmt.Errorf("docker upgrade not implemented")
|
|
}
|
|
|
|
// DryRun for Docker images checks if the image can be pulled without actually pulling it
|
|
func (i *DockerInstaller) DryRun(imageName string) (*InstallResult, error) {
|
|
startTime := time.Now()
|
|
|
|
// Check if image exists locally
|
|
inspectCmd := exec.Command("sudo", "docker", "image", "inspect", imageName)
|
|
output, err := inspectCmd.CombinedOutput()
|
|
|
|
if err == nil {
|
|
// Image exists locally
|
|
duration := int(time.Since(startTime).Seconds())
|
|
return &InstallResult{
|
|
Success: true,
|
|
Stdout: fmt.Sprintf("Docker image %s is already available locally", imageName),
|
|
Stderr: string(output),
|
|
ExitCode: 0,
|
|
DurationSeconds: duration,
|
|
Dependencies: []string{}, // Docker doesn't have traditional dependencies
|
|
IsDryRun: true,
|
|
Action: "dry_run",
|
|
}, nil
|
|
}
|
|
|
|
// Image doesn't exist locally, check if it exists in registry
|
|
// Use docker manifest command to check remote availability
|
|
manifestCmd := exec.Command("sudo", "docker", "manifest", "inspect", imageName)
|
|
manifestOutput, manifestErr := manifestCmd.CombinedOutput()
|
|
duration := int(time.Since(startTime).Seconds())
|
|
|
|
if manifestErr != nil {
|
|
return &InstallResult{
|
|
Success: false,
|
|
ErrorMessage: fmt.Sprintf("Docker image %s not found locally or in remote registry", imageName),
|
|
Stdout: string(output),
|
|
Stderr: string(manifestOutput),
|
|
ExitCode: getExitCode(manifestErr),
|
|
DurationSeconds: duration,
|
|
Dependencies: []string{},
|
|
IsDryRun: true,
|
|
Action: "dry_run",
|
|
}, fmt.Errorf("docker image not found")
|
|
}
|
|
|
|
return &InstallResult{
|
|
Success: true,
|
|
Stdout: fmt.Sprintf("Docker image %s is available for download", imageName),
|
|
Stderr: string(manifestOutput),
|
|
ExitCode: 0,
|
|
DurationSeconds: duration,
|
|
Dependencies: []string{}, // Docker doesn't have traditional dependencies
|
|
IsDryRun: true,
|
|
Action: "dry_run",
|
|
}, nil
|
|
}
|
|
|
|
// GetPackageType returns type of packages this installer handles
|
|
func (i *DockerInstaller) GetPackageType() string {
|
|
return "docker_image"
|
|
}
|
|
|