mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
feat: add auto-update support for unified agent
Implement self-update capability for the unified pulse-agent binary: - Add internal/agentupdate package with cross-platform update logic - Hourly version checks against /api/agent/version endpoint - SHA256 checksum verification for downloaded binaries - Atomic binary replacement with backup/rollback on failure - Support for Linux, macOS, and Windows (10 platform/arch combinations) Build and release changes: - Dockerfile builds unified agent for all platforms - build-release.sh includes unified agent in release artifacts - validate-release.sh validates unified agent binaries - Install scripts (install.sh, install.ps1) use correct URL format Related to #727, #737
This commit is contained in:
parent
5e3f1db5b3
commit
0436101ee5
12 changed files with 860 additions and 91 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -7,6 +7,8 @@
|
|||
/pulse-test
|
||||
/pulse-host-agent
|
||||
/pulse-host-agent-*
|
||||
/pulse-agent
|
||||
/pulse-agent-*
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
|
|
|||
65
Dockerfile
65
Dockerfile
|
|
@ -136,6 +136,51 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
|
|||
-trimpath \
|
||||
-o pulse-host-agent-windows-386.exe ./cmd/pulse-host-agent
|
||||
|
||||
# Build unified agent binaries for all platforms (for download endpoint)
|
||||
RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
|
||||
--mount=type=cache,id=pulse-go-build,target=/root/.cache/go-build \
|
||||
VERSION="v$(cat VERSION | tr -d '\n')" && \
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-linux-amd64 ./cmd/pulse-agent && \
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-linux-arm64 ./cmd/pulse-agent && \
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-linux-armv7 ./cmd/pulse-agent && \
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-linux-armv6 ./cmd/pulse-agent && \
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-linux-386 ./cmd/pulse-agent && \
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-darwin-amd64 ./cmd/pulse-agent && \
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-darwin-arm64 ./cmd/pulse-agent && \
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-windows-amd64.exe ./cmd/pulse-agent && \
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-windows-arm64.exe ./cmd/pulse-agent && \
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-trimpath \
|
||||
-o pulse-agent-windows-386.exe ./cmd/pulse-agent
|
||||
|
||||
# Build pulse-sensor-proxy for all Linux architectures (for download endpoint)
|
||||
RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
|
||||
--mount=type=cache,id=pulse-go-build,target=/root/.cache/go-build \
|
||||
|
|
@ -224,7 +269,9 @@ COPY scripts/uninstall-host-agent.sh /opt/pulse/scripts/uninstall-host-agent.sh
|
|||
COPY scripts/uninstall-host-agent.ps1 /opt/pulse/scripts/uninstall-host-agent.ps1
|
||||
COPY scripts/install-sensor-proxy.sh /opt/pulse/scripts/install-sensor-proxy.sh
|
||||
COPY scripts/install-docker.sh /opt/pulse/scripts/install-docker.sh
|
||||
RUN chmod 755 /opt/pulse/scripts/install-docker-agent.sh /opt/pulse/scripts/install-container-agent.sh /opt/pulse/scripts/install-host-agent.sh /opt/pulse/scripts/install-host-agent.ps1 /opt/pulse/scripts/uninstall-host-agent.sh /opt/pulse/scripts/uninstall-host-agent.ps1 /opt/pulse/scripts/install-sensor-proxy.sh /opt/pulse/scripts/install-docker.sh
|
||||
COPY scripts/install.sh /opt/pulse/scripts/install.sh
|
||||
COPY scripts/install.ps1 /opt/pulse/scripts/install.ps1
|
||||
RUN chmod 755 /opt/pulse/scripts/*.sh /opt/pulse/scripts/*.ps1
|
||||
|
||||
# Copy all binaries for download endpoint
|
||||
RUN mkdir -p /opt/pulse/bin
|
||||
|
|
@ -256,6 +303,22 @@ RUN ln -s pulse-host-agent-windows-amd64.exe /opt/pulse/bin/pulse-host-agent-win
|
|||
ln -s pulse-host-agent-windows-arm64.exe /opt/pulse/bin/pulse-host-agent-windows-arm64 && \
|
||||
ln -s pulse-host-agent-windows-386.exe /opt/pulse/bin/pulse-host-agent-windows-386
|
||||
|
||||
# Unified agent binaries (all platforms and architectures)
|
||||
COPY --from=backend-builder /app/pulse-agent-linux-amd64 /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-agent-linux-arm64 /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-agent-linux-armv7 /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-agent-linux-armv6 /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-agent-linux-386 /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-agent-darwin-amd64 /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-agent-darwin-arm64 /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-agent-windows-amd64.exe /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-agent-windows-arm64.exe /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-agent-windows-386.exe /opt/pulse/bin/
|
||||
# Create symlinks for Windows without .exe extension
|
||||
RUN ln -s pulse-agent-windows-amd64.exe /opt/pulse/bin/pulse-agent-windows-amd64 && \
|
||||
ln -s pulse-agent-windows-arm64.exe /opt/pulse/bin/pulse-agent-windows-arm64 && \
|
||||
ln -s pulse-agent-windows-386.exe /opt/pulse/bin/pulse-agent-windows-386
|
||||
|
||||
# Sensor proxy binaries (all Linux architectures)
|
||||
COPY --from=backend-builder /app/pulse-sensor-proxy-linux-amd64 /opt/pulse/bin/
|
||||
COPY --from=backend-builder /app/pulse-sensor-proxy-linux-arm64 /opt/pulse/bin/
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/agentupdate"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/dockeragent"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/hostagent"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/utils"
|
||||
|
|
@ -52,9 +53,27 @@ func main() {
|
|||
Str("pulse_url", cfg.PulseURL).
|
||||
Bool("host_agent", cfg.EnableHost).
|
||||
Bool("docker_agent", cfg.EnableDocker).
|
||||
Bool("auto_update", !cfg.DisableAutoUpdate).
|
||||
Msg("Starting Pulse Unified Agent")
|
||||
|
||||
// 4. Start Host Agent (if enabled)
|
||||
// 4. Start Auto-Updater
|
||||
updater := agentupdate.New(agentupdate.Config{
|
||||
PulseURL: cfg.PulseURL,
|
||||
APIToken: cfg.APIToken,
|
||||
AgentName: "pulse-agent",
|
||||
CurrentVersion: Version,
|
||||
CheckInterval: 1 * time.Hour,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
Logger: &logger,
|
||||
Disabled: cfg.DisableAutoUpdate,
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
updater.RunLoop(ctx)
|
||||
return nil
|
||||
})
|
||||
|
||||
// 5. Start Host Agent (if enabled)
|
||||
if cfg.EnableHost {
|
||||
hostCfg := hostagent.Config{
|
||||
PulseURL: cfg.PulseURL,
|
||||
|
|
@ -138,6 +157,9 @@ type Config struct {
|
|||
// Module flags
|
||||
EnableHost bool
|
||||
EnableDocker bool
|
||||
|
||||
// Auto-update
|
||||
DisableAutoUpdate bool
|
||||
}
|
||||
|
||||
func loadConfig() Config {
|
||||
|
|
@ -152,6 +174,7 @@ func loadConfig() Config {
|
|||
envLogLevel := utils.GetenvTrim("LOG_LEVEL")
|
||||
envEnableHost := utils.GetenvTrim("PULSE_ENABLE_HOST")
|
||||
envEnableDocker := utils.GetenvTrim("PULSE_ENABLE_DOCKER")
|
||||
envDisableAutoUpdate := utils.GetenvTrim("PULSE_DISABLE_AUTO_UPDATE")
|
||||
|
||||
// Defaults
|
||||
defaultInterval := 30 * time.Second
|
||||
|
|
@ -182,12 +205,19 @@ func loadConfig() Config {
|
|||
|
||||
enableHostFlag := flag.Bool("enable-host", defaultEnableHost, "Enable Host Agent module")
|
||||
enableDockerFlag := flag.Bool("enable-docker", defaultEnableDocker, "Enable Docker Agent module")
|
||||
disableAutoUpdateFlag := flag.Bool("disable-auto-update", utils.ParseBool(envDisableAutoUpdate), "Disable automatic updates")
|
||||
showVersion := flag.Bool("version", false, "Print the agent version and exit")
|
||||
|
||||
var tagFlags multiValue
|
||||
flag.Var(&tagFlags, "tag", "Tag to apply (repeatable)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *showVersion {
|
||||
fmt.Println(Version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Validation
|
||||
pulseURL := strings.TrimSpace(*urlFlag)
|
||||
if pulseURL == "" {
|
||||
|
|
@ -218,6 +248,7 @@ func loadConfig() Config {
|
|||
LogLevel: logLevel,
|
||||
EnableHost: *enableHostFlag,
|
||||
EnableDocker: *enableDockerFlag,
|
||||
DisableAutoUpdate: *disableAutoUpdateFlag,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
139
docs/UNIFIED_AGENT.md
Normal file
139
docs/UNIFIED_AGENT.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# Pulse Unified Agent
|
||||
|
||||
The unified agent (`pulse-agent`) combines host and Docker monitoring into a single binary. It replaces the separate `pulse-host-agent` and `pulse-docker-agent` for simpler deployment and management.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Generate an installation command in the UI:
|
||||
**Settings > Agents > "Install New Agent"**
|
||||
|
||||
### Linux (systemd)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
```
|
||||
|
||||
### macOS
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
```
|
||||
|
||||
### Synology NAS
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Host Metrics**: CPU, memory, disk, network I/O, temperatures
|
||||
- **Docker Monitoring**: Container metrics, health checks, Swarm support (when enabled)
|
||||
- **Auto-Update**: Automatically updates when a new version is released
|
||||
- **Multi-Platform**: Linux, macOS, Windows support
|
||||
|
||||
## Configuration
|
||||
|
||||
| Flag | Env Var | Description | Default |
|
||||
|------|---------|-------------|---------|
|
||||
| `--url` | `PULSE_URL` | Pulse server URL | `http://localhost:7655` |
|
||||
| `--token` | `PULSE_TOKEN` | API token | *(required)* |
|
||||
| `--interval` | `PULSE_INTERVAL` | Reporting interval | `30s` |
|
||||
| `--enable-host` | `PULSE_ENABLE_HOST` | Enable host metrics | `true` |
|
||||
| `--enable-docker` | `PULSE_ENABLE_DOCKER` | Enable Docker metrics | `false` |
|
||||
| `--disable-auto-update` | `PULSE_DISABLE_AUTO_UPDATE` | Disable auto-updates | `false` |
|
||||
| `--insecure` | `PULSE_INSECURE_SKIP_VERIFY` | Skip TLS verification | `false` |
|
||||
| `--hostname` | `PULSE_HOSTNAME` | Override hostname | *(OS hostname)* |
|
||||
| `--agent-id` | `PULSE_AGENT_ID` | Unique agent identifier | *(machine-id)* |
|
||||
|
||||
## Installation Options
|
||||
|
||||
### Host Monitoring Only (default)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token>
|
||||
```
|
||||
|
||||
### Host + Docker Monitoring
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-docker
|
||||
```
|
||||
|
||||
### Docker Monitoring Only
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --disable-host --enable-docker
|
||||
```
|
||||
|
||||
## Auto-Update
|
||||
|
||||
The unified agent automatically checks for updates every hour. When a new version is available:
|
||||
|
||||
1. Agent downloads the new binary from the Pulse server
|
||||
2. Verifies the checksum
|
||||
3. Replaces itself atomically (with backup)
|
||||
4. Restarts with the same configuration
|
||||
|
||||
To disable auto-updates:
|
||||
```bash
|
||||
# During installation
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --disable-auto-update
|
||||
|
||||
# Or set environment variable
|
||||
PULSE_DISABLE_AUTO_UPDATE=true
|
||||
```
|
||||
|
||||
## Uninstall
|
||||
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | bash -s -- --uninstall
|
||||
```
|
||||
|
||||
This removes:
|
||||
- The agent binary
|
||||
- The systemd/launchd service
|
||||
- Any legacy agents (pulse-host-agent, pulse-docker-agent)
|
||||
|
||||
## Migration from Legacy Agents
|
||||
|
||||
The install script automatically removes legacy agents when installing the unified agent:
|
||||
- `pulse-host-agent` service is stopped and removed
|
||||
- `pulse-docker-agent` service is stopped and removed
|
||||
- Binaries are deleted from `/usr/local/bin/`
|
||||
|
||||
No manual cleanup is required.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Agent Not Updating
|
||||
- Check logs: `journalctl -u pulse-agent -f`
|
||||
- Verify network connectivity to Pulse server
|
||||
- Ensure auto-update is not disabled
|
||||
|
||||
### Duplicate Hosts
|
||||
If cloned VMs appear as the same host:
|
||||
```bash
|
||||
sudo rm /etc/machine-id && sudo systemd-machine-id-setup
|
||||
```
|
||||
|
||||
Or set a unique agent ID:
|
||||
```bash
|
||||
--agent-id my-unique-host-id
|
||||
```
|
||||
|
||||
### Permission Denied (Docker)
|
||||
Ensure the agent can access the Docker socket:
|
||||
```bash
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
### Check Status
|
||||
```bash
|
||||
# Linux
|
||||
systemctl status pulse-agent
|
||||
|
||||
# macOS
|
||||
launchctl list | grep pulse
|
||||
```
|
||||
361
internal/agentupdate/update.go
Normal file
361
internal/agentupdate/update.go
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
// Package agentupdate provides self-update functionality for Pulse agents.
|
||||
// It handles checking for new versions, downloading binaries, and performing
|
||||
// atomic binary replacement with rollback support.
|
||||
package agentupdate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// Config holds the configuration for the updater.
|
||||
type Config struct {
|
||||
// PulseURL is the base URL of the Pulse server (e.g., "https://pulse.example.com:7655")
|
||||
PulseURL string
|
||||
|
||||
// APIToken is the authentication token for the Pulse server
|
||||
APIToken string
|
||||
|
||||
// AgentName is the name of the agent binary to download (e.g., "pulse-agent", "pulse-docker-agent")
|
||||
AgentName string
|
||||
|
||||
// CurrentVersion is the version currently running
|
||||
CurrentVersion string
|
||||
|
||||
// CheckInterval is how often to check for updates (default: 1 hour)
|
||||
CheckInterval time.Duration
|
||||
|
||||
// InsecureSkipVerify skips TLS certificate verification
|
||||
InsecureSkipVerify bool
|
||||
|
||||
// Logger is the zerolog logger instance
|
||||
Logger *zerolog.Logger
|
||||
|
||||
// Disabled skips all update checks when true
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
// Updater handles automatic updates for Pulse agents.
|
||||
type Updater struct {
|
||||
cfg Config
|
||||
client *http.Client
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
// New creates a new Updater with the given configuration.
|
||||
func New(cfg Config) *Updater {
|
||||
if cfg.CheckInterval == 0 {
|
||||
cfg.CheckInterval = 1 * time.Hour
|
||||
}
|
||||
|
||||
logger := zerolog.Nop()
|
||||
if cfg.Logger != nil {
|
||||
logger = *cfg.Logger
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
},
|
||||
}
|
||||
|
||||
return &Updater{
|
||||
cfg: cfg,
|
||||
client: &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// RunLoop starts the update check loop. It blocks until the context is cancelled.
|
||||
func (u *Updater) RunLoop(ctx context.Context) {
|
||||
if u.cfg.Disabled {
|
||||
u.logger.Info().Msg("Auto-update disabled")
|
||||
return
|
||||
}
|
||||
|
||||
if u.cfg.CurrentVersion == "dev" {
|
||||
u.logger.Debug().Msg("Auto-update disabled in development mode")
|
||||
return
|
||||
}
|
||||
|
||||
// Initial check after a short delay
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(30 * time.Second):
|
||||
u.CheckAndUpdate(ctx)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(u.cfg.CheckInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
u.CheckAndUpdate(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckAndUpdate checks for a new version and performs the update if available.
|
||||
func (u *Updater) CheckAndUpdate(ctx context.Context) {
|
||||
if u.cfg.Disabled {
|
||||
return
|
||||
}
|
||||
|
||||
if u.cfg.CurrentVersion == "dev" {
|
||||
u.logger.Debug().Msg("Skipping update check - running in development mode")
|
||||
return
|
||||
}
|
||||
|
||||
if u.cfg.PulseURL == "" {
|
||||
u.logger.Debug().Msg("Skipping update check - no Pulse URL configured")
|
||||
return
|
||||
}
|
||||
|
||||
u.logger.Debug().Msg("Checking for agent updates")
|
||||
|
||||
serverVersion, err := u.getServerVersion(ctx)
|
||||
if err != nil {
|
||||
u.logger.Warn().Err(err).Msg("Failed to check for updates")
|
||||
return
|
||||
}
|
||||
|
||||
if serverVersion == "dev" {
|
||||
u.logger.Debug().Msg("Skipping update - server is in development mode")
|
||||
return
|
||||
}
|
||||
|
||||
if serverVersion == u.cfg.CurrentVersion {
|
||||
u.logger.Debug().Str("version", u.cfg.CurrentVersion).Msg("Agent is up to date")
|
||||
return
|
||||
}
|
||||
|
||||
u.logger.Info().
|
||||
Str("currentVersion", u.cfg.CurrentVersion).
|
||||
Str("availableVersion", serverVersion).
|
||||
Msg("New agent version available, performing self-update")
|
||||
|
||||
if err := u.performUpdate(ctx); err != nil {
|
||||
u.logger.Error().Err(err).Msg("Failed to self-update agent")
|
||||
return
|
||||
}
|
||||
|
||||
u.logger.Info().Msg("Agent updated successfully, restarting...")
|
||||
}
|
||||
|
||||
// getServerVersion fetches the current version from the Pulse server.
|
||||
func (u *Updater) getServerVersion(ctx context.Context) (string, error) {
|
||||
url := fmt.Sprintf("%s/api/agent/version", strings.TrimRight(u.cfg.PulseURL, "/"))
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
if u.cfg.APIToken != "" {
|
||||
req.Header.Set("X-API-Token", u.cfg.APIToken)
|
||||
req.Header.Set("Authorization", "Bearer "+u.cfg.APIToken)
|
||||
}
|
||||
|
||||
resp, err := u.client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("server returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var versionResp struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&versionResp); err != nil {
|
||||
return "", fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return versionResp.Version, nil
|
||||
}
|
||||
|
||||
// performUpdate downloads and installs the new agent binary.
|
||||
func (u *Updater) performUpdate(ctx context.Context) error {
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
// Build download URL
|
||||
downloadBase := fmt.Sprintf("%s/download/%s", strings.TrimRight(u.cfg.PulseURL, "/"), u.cfg.AgentName)
|
||||
archParam := determineArch()
|
||||
|
||||
// Try architecture-specific binary first, then fall back to default
|
||||
candidates := []string{}
|
||||
if archParam != "" {
|
||||
candidates = append(candidates, fmt.Sprintf("%s?arch=%s", downloadBase, archParam))
|
||||
}
|
||||
candidates = append(candidates, downloadBase)
|
||||
|
||||
var resp *http.Response
|
||||
var lastErr error
|
||||
|
||||
for _, url := range candidates {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("failed to create download request: %w", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if u.cfg.APIToken != "" {
|
||||
req.Header.Set("X-API-Token", u.cfg.APIToken)
|
||||
req.Header.Set("Authorization", "Bearer "+u.cfg.APIToken)
|
||||
}
|
||||
|
||||
response, err := u.client.Do(req)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("failed to download binary: %w", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
lastErr = fmt.Errorf("download failed with status: %s", response.Status)
|
||||
response.Body.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
resp = response
|
||||
u.logger.Debug().Str("url", url).Msg("Downloaded agent binary")
|
||||
break
|
||||
}
|
||||
|
||||
if resp == nil {
|
||||
if lastErr == nil {
|
||||
lastErr = errors.New("failed to download binary")
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Verify checksum if provided
|
||||
checksumHeader := strings.TrimSpace(resp.Header.Get("X-Checksum-Sha256"))
|
||||
|
||||
// Create temporary file
|
||||
tmpFile, err := os.CreateTemp("", u.cfg.AgentName+"-*.tmp")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temp file: %w", err)
|
||||
}
|
||||
tmpPath := tmpFile.Name()
|
||||
defer os.Remove(tmpPath) // Clean up on failure
|
||||
|
||||
// Write downloaded binary with checksum calculation
|
||||
hasher := sha256.New()
|
||||
if _, err := io.Copy(tmpFile, io.TeeReader(resp.Body, hasher)); err != nil {
|
||||
tmpFile.Close()
|
||||
return fmt.Errorf("failed to write binary: %w", err)
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close temp file: %w", err)
|
||||
}
|
||||
|
||||
// Verify checksum
|
||||
downloadChecksum := hex.EncodeToString(hasher.Sum(nil))
|
||||
if checksumHeader != "" {
|
||||
expected := strings.ToLower(strings.TrimSpace(checksumHeader))
|
||||
actual := strings.ToLower(downloadChecksum)
|
||||
if expected != actual {
|
||||
return fmt.Errorf("checksum mismatch: expected %s, got %s", expected, actual)
|
||||
}
|
||||
u.logger.Debug().Str("checksum", downloadChecksum).Msg("Checksum verified")
|
||||
} else {
|
||||
u.logger.Warn().Msg("No checksum header; skipping verification")
|
||||
}
|
||||
|
||||
// Make executable
|
||||
if err := os.Chmod(tmpPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to chmod: %w", err)
|
||||
}
|
||||
|
||||
// Atomic replacement with backup
|
||||
backupPath := execPath + ".backup"
|
||||
if err := os.Rename(execPath, backupPath); err != nil {
|
||||
return fmt.Errorf("failed to backup current binary: %w", err)
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpPath, execPath); err != nil {
|
||||
// Restore backup on failure
|
||||
os.Rename(backupPath, execPath)
|
||||
return fmt.Errorf("failed to replace binary: %w", err)
|
||||
}
|
||||
|
||||
// Remove backup on success
|
||||
os.Remove(backupPath)
|
||||
|
||||
// Restart with same arguments
|
||||
args := os.Args
|
||||
env := os.Environ()
|
||||
|
||||
if err := syscall.Exec(execPath, args, env); err != nil {
|
||||
return fmt.Errorf("failed to restart: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// determineArch returns the architecture string for download URLs (e.g., "linux-amd64", "darwin-arm64").
|
||||
func determineArch() string {
|
||||
os := runtime.GOOS
|
||||
arch := runtime.GOARCH
|
||||
|
||||
// Normalize architecture
|
||||
switch arch {
|
||||
case "arm":
|
||||
arch = "armv7"
|
||||
case "386":
|
||||
arch = "386"
|
||||
}
|
||||
|
||||
// For known OS/arch combinations, return directly
|
||||
switch os {
|
||||
case "linux", "darwin", "windows":
|
||||
return fmt.Sprintf("%s-%s", os, arch)
|
||||
}
|
||||
|
||||
// Fall back to uname for edge cases on unknown OS
|
||||
out, err := exec.Command("uname", "-m").Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.TrimSpace(string(out)))
|
||||
switch normalized {
|
||||
case "x86_64", "amd64":
|
||||
return "linux-amd64"
|
||||
case "aarch64", "arm64":
|
||||
return "linux-arm64"
|
||||
case "armv7l", "armhf", "armv7":
|
||||
return "linux-armv7"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ import (
|
|||
"github.com/rcourtman/pulse-go-rewrite/internal/agentbinaries"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/auth"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/dockeragent"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/system"
|
||||
|
|
@ -1374,7 +1373,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
req.URL.Path != "/install-container-agent.sh" &&
|
||||
req.URL.Path != "/install-host-agent.sh" &&
|
||||
req.URL.Path != "/install-host-agent.ps1" &&
|
||||
req.URL.Path != "/install-host-agent.ps1" &&
|
||||
req.URL.Path != "/uninstall-host-agent.sh" &&
|
||||
req.URL.Path != "/uninstall-host-agent.ps1" &&
|
||||
req.URL.Path != "/install.sh" &&
|
||||
req.URL.Path != "/install.ps1"
|
||||
|
|
@ -2475,17 +2474,18 @@ func (r *Router) handleVersion(w http.ResponseWriter, req *http.Request) {
|
|||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// handleAgentVersion returns the current Docker agent version for update checks
|
||||
// handleAgentVersion returns the current server version for agent update checks.
|
||||
// Agents compare this to their own version to determine if an update is available.
|
||||
func (r *Router) handleAgentVersion(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet && req.Method != http.MethodHead {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Current agent version - matches the version baked into the Docker agent binary
|
||||
version := strings.TrimSpace(dockeragent.Version)
|
||||
if version == "" {
|
||||
version = "dev"
|
||||
// Return the server version - all agents should match the server version
|
||||
version := "dev"
|
||||
if versionInfo, err := updates.GetCurrentVersion(); err == nil {
|
||||
version = versionInfo.Version
|
||||
}
|
||||
|
||||
response := AgentVersionResponse{
|
||||
|
|
@ -2503,13 +2503,15 @@ func (r *Router) handleServerInfo(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
versionInfo, err := updates.GetCurrentVersion()
|
||||
isDev := true
|
||||
version := "dev"
|
||||
if err == nil {
|
||||
isDev = versionInfo.IsDevelopment
|
||||
version = versionInfo.Version
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"isDevelopment": isDev,
|
||||
"version": dockeragent.Version,
|
||||
"version": version,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -52,6 +54,35 @@ func (r *Router) handleDownloadUnifiedInstallScriptPS(w http.ResponseWriter, req
|
|||
http.ServeFile(w, req, scriptPath)
|
||||
}
|
||||
|
||||
// normalizeUnifiedAgentArch normalizes architecture strings for the unified agent.
|
||||
func normalizeUnifiedAgentArch(arch string) string {
|
||||
arch = strings.ToLower(strings.TrimSpace(arch))
|
||||
switch arch {
|
||||
case "linux-amd64", "amd64", "x86_64":
|
||||
return "linux-amd64"
|
||||
case "linux-arm64", "arm64", "aarch64":
|
||||
return "linux-arm64"
|
||||
case "linux-armv7", "armv7", "armv7l", "armhf":
|
||||
return "linux-armv7"
|
||||
case "linux-armv6", "armv6":
|
||||
return "linux-armv6"
|
||||
case "linux-386", "386", "i386", "i686":
|
||||
return "linux-386"
|
||||
case "darwin-amd64", "macos-amd64":
|
||||
return "darwin-amd64"
|
||||
case "darwin-arm64", "macos-arm64":
|
||||
return "darwin-arm64"
|
||||
case "windows-amd64":
|
||||
return "windows-amd64"
|
||||
case "windows-arm64":
|
||||
return "windows-arm64"
|
||||
case "windows-386":
|
||||
return "windows-386"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// handleDownloadUnifiedAgent serves the pulse-agent binary
|
||||
func (r *Router) handleDownloadUnifiedAgent(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet && req.Method != http.MethodHead {
|
||||
|
|
@ -59,52 +90,65 @@ func (r *Router) handleDownloadUnifiedAgent(w http.ResponseWriter, req *http.Req
|
|||
return
|
||||
}
|
||||
|
||||
// For now, we only have the locally built binary.
|
||||
// In production, this would look up the correct binary for the requested OS/Arch.
|
||||
// Query params: ?os=linux&arch=amd64
|
||||
// Prevent caching - always serve the latest version
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
|
||||
osName := req.URL.Query().Get("os")
|
||||
arch := req.URL.Query().Get("arch")
|
||||
archParam := strings.TrimSpace(req.URL.Query().Get("arch"))
|
||||
searchPaths := make([]string, 0, 6)
|
||||
|
||||
if osName == "" {
|
||||
osName = "linux" // Default
|
||||
}
|
||||
if arch == "" {
|
||||
arch = "amd64" // Default
|
||||
if normalized := normalizeUnifiedAgentArch(archParam); normalized != "" {
|
||||
searchPaths = append(searchPaths,
|
||||
filepath.Join(pulseBinDir(), "pulse-agent-"+normalized),
|
||||
filepath.Join("/opt/pulse", "pulse-agent-"+normalized),
|
||||
filepath.Join("/app", "pulse-agent-"+normalized),
|
||||
filepath.Join(r.projectRoot, "bin", "pulse-agent-"+normalized),
|
||||
)
|
||||
}
|
||||
|
||||
// Normalize OS
|
||||
osName = strings.ToLower(osName)
|
||||
if osName == "darwin" {
|
||||
osName = "macos"
|
||||
}
|
||||
// Default locations (host architecture)
|
||||
searchPaths = append(searchPaths,
|
||||
filepath.Join(pulseBinDir(), "pulse-agent"),
|
||||
"/opt/pulse/pulse-agent",
|
||||
filepath.Join("/app", "pulse-agent"),
|
||||
filepath.Join(r.projectRoot, "bin", "pulse-agent"),
|
||||
)
|
||||
|
||||
// In dev mode, we just serve the binary we built in the root
|
||||
// In prod, we'd look in a dist folder
|
||||
binaryName := "pulse-agent"
|
||||
if osName == "windows" {
|
||||
binaryName = "pulse-agent.exe"
|
||||
}
|
||||
|
||||
// Try to find the binary
|
||||
// 1. Check root (dev)
|
||||
binaryPath := filepath.Join(r.config.AppRoot, binaryName)
|
||||
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
|
||||
// 2. Check dist folder (prod/build)
|
||||
binaryPath = filepath.Join(r.config.AppRoot, "dist", fmt.Sprintf("%s-%s", osName, arch), binaryName)
|
||||
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
|
||||
// Fallback for dev: just serve the root binary regardless of requested OS/Arch
|
||||
// This allows testing the flow even if cross-compilation hasn't happened
|
||||
binaryPath = filepath.Join(r.config.AppRoot, "pulse-agent")
|
||||
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
|
||||
log.Error().Str("path", binaryPath).Msg("Unified agent binary not found")
|
||||
http.Error(w, "Agent binary not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
for _, candidate := range searchPaths {
|
||||
if candidate == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := os.Stat(candidate)
|
||||
if err != nil || info.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
file, err := os.Open(candidate)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", candidate).Msg("Failed to open unified agent binary for download")
|
||||
continue
|
||||
}
|
||||
|
||||
hasher := sha256.New()
|
||||
if _, err := io.Copy(hasher, file); err != nil {
|
||||
file.Close()
|
||||
log.Error().Err(err).Str("path", candidate).Msg("Failed to hash unified agent binary")
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := file.Seek(0, io.SeekStart); err != nil {
|
||||
file.Close()
|
||||
log.Error().Err(err).Str("path", candidate).Msg("Failed to rewind unified agent binary")
|
||||
continue
|
||||
}
|
||||
|
||||
w.Header().Set("X-Checksum-Sha256", hex.EncodeToString(hasher.Sum(nil)))
|
||||
http.ServeContent(w, req, filepath.Base(candidate), info.ModTime(), file)
|
||||
file.Close()
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", binaryName))
|
||||
http.ServeFile(w, req, binaryPath)
|
||||
http.Error(w, "Agent binary not found", http.StatusNotFound)
|
||||
}
|
||||
|
|
|
|||
BIN
pulse-agent
BIN
pulse-agent
Binary file not shown.
|
|
@ -64,6 +64,22 @@ for target in "${host_agent_order[@]}"; do
|
|||
./cmd/pulse-host-agent
|
||||
done
|
||||
|
||||
# Build unified agents for every supported platform/architecture
|
||||
echo "Building unified agents for all platforms..."
|
||||
for target in "${host_agent_order[@]}"; do
|
||||
build_env="${host_agent_builds[$target]}"
|
||||
output_path="$BUILD_DIR/pulse-agent-$target"
|
||||
if [[ "$target" == windows-* ]]; then
|
||||
output_path="${output_path}.exe"
|
||||
fi
|
||||
|
||||
env $build_env go build \
|
||||
-ldflags="-s -w -X main.Version=v${VERSION}" \
|
||||
-trimpath \
|
||||
-o "$output_path" \
|
||||
./cmd/pulse-agent
|
||||
done
|
||||
|
||||
# Build for different architectures (server + docker agent + sensor proxy)
|
||||
declare -A builds=(
|
||||
["linux-amd64"]="GOOS=linux GOARCH=amd64"
|
||||
|
|
@ -132,6 +148,18 @@ for build_name in "${build_order[@]}"; do
|
|||
done
|
||||
( cd "$staging_dir/bin" && ln -sf pulse-host-agent-windows-amd64.exe pulse-host-agent-windows-amd64 && ln -sf pulse-host-agent-windows-arm64.exe pulse-host-agent-windows-arm64 && ln -sf pulse-host-agent-windows-386.exe pulse-host-agent-windows-386 )
|
||||
|
||||
# Copy unified agent binaries for every supported platform/architecture
|
||||
for target in "${host_agent_order[@]}"; do
|
||||
src="$BUILD_DIR/pulse-agent-$target"
|
||||
dest="$staging_dir/bin/pulse-agent-$target"
|
||||
if [[ "$target" == windows-* ]]; then
|
||||
src="${src}.exe"
|
||||
dest="${dest}.exe"
|
||||
fi
|
||||
cp "$src" "$dest"
|
||||
done
|
||||
( cd "$staging_dir/bin" && ln -sf pulse-agent-windows-amd64.exe pulse-agent-windows-amd64 && ln -sf pulse-agent-windows-arm64.exe pulse-agent-windows-arm64 && ln -sf pulse-agent-windows-386.exe pulse-agent-windows-386 )
|
||||
|
||||
# Copy scripts and VERSION metadata
|
||||
cp "scripts/install-docker-agent.sh" "$staging_dir/scripts/install-docker-agent.sh"
|
||||
cp "scripts/install-container-agent.sh" "$staging_dir/scripts/install-container-agent.sh"
|
||||
|
|
@ -141,7 +169,10 @@ for build_name in "${build_order[@]}"; do
|
|||
cp "scripts/uninstall-host-agent.ps1" "$staging_dir/scripts/uninstall-host-agent.ps1"
|
||||
cp "scripts/install-sensor-proxy.sh" "$staging_dir/scripts/install-sensor-proxy.sh"
|
||||
cp "scripts/install-docker.sh" "$staging_dir/scripts/install-docker.sh"
|
||||
chmod 755 "$staging_dir/scripts/"*.sh "$staging_dir/scripts/"*.ps1
|
||||
cp "scripts/install.sh" "$staging_dir/scripts/install.sh"
|
||||
[ -f "scripts/install.ps1" ] && cp "scripts/install.ps1" "$staging_dir/scripts/install.ps1"
|
||||
chmod 755 "$staging_dir/scripts/"*.sh
|
||||
chmod 755 "$staging_dir/scripts/"*.ps1 2>/dev/null || true
|
||||
echo "$VERSION" > "$staging_dir/VERSION"
|
||||
|
||||
# Create tarball from staging directory
|
||||
|
|
@ -165,6 +196,7 @@ for build_name in "${build_order[@]}"; do
|
|||
cp "$BUILD_DIR/pulse-$build_name" "$universal_dir/bin/pulse-${build_name}"
|
||||
cp "$BUILD_DIR/pulse-docker-agent-$build_name" "$universal_dir/bin/pulse-docker-agent-${build_name}"
|
||||
cp "$BUILD_DIR/pulse-host-agent-$build_name" "$universal_dir/bin/pulse-host-agent-${build_name}"
|
||||
cp "$BUILD_DIR/pulse-agent-$build_name" "$universal_dir/bin/pulse-agent-${build_name}"
|
||||
cp "$BUILD_DIR/pulse-sensor-proxy-$build_name" "$universal_dir/bin/pulse-sensor-proxy-${build_name}"
|
||||
done
|
||||
|
||||
|
|
@ -176,7 +208,10 @@ cp "scripts/uninstall-host-agent.sh" "$universal_dir/scripts/uninstall-host-agen
|
|||
cp "scripts/uninstall-host-agent.ps1" "$universal_dir/scripts/uninstall-host-agent.ps1"
|
||||
cp "scripts/install-sensor-proxy.sh" "$universal_dir/scripts/install-sensor-proxy.sh"
|
||||
cp "scripts/install-docker.sh" "$universal_dir/scripts/install-docker.sh"
|
||||
chmod 755 "$universal_dir/scripts/"*.sh "$universal_dir/scripts/"*.ps1
|
||||
cp "scripts/install.sh" "$universal_dir/scripts/install.sh"
|
||||
[ -f "scripts/install.ps1" ] && cp "scripts/install.ps1" "$universal_dir/scripts/install.ps1"
|
||||
chmod 755 "$universal_dir/scripts/"*.sh
|
||||
chmod 755 "$universal_dir/scripts/"*.ps1 2>/dev/null || true
|
||||
|
||||
# Create a detection script that creates the pulse symlink based on architecture
|
||||
cat > "$universal_dir/bin/pulse" << 'EOF'
|
||||
|
|
@ -271,6 +306,29 @@ esac
|
|||
EOF
|
||||
chmod +x "$universal_dir/bin/pulse-host-agent"
|
||||
|
||||
cat > "$universal_dir/bin/pulse-agent" << 'EOF'
|
||||
#!/bin/sh
|
||||
# Auto-detect architecture and run appropriate pulse-agent binary
|
||||
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
x86_64|amd64)
|
||||
exec "$(dirname "$0")/pulse-agent-linux-amd64" "$@"
|
||||
;;
|
||||
aarch64|arm64)
|
||||
exec "$(dirname "$0")/pulse-agent-linux-arm64" "$@"
|
||||
;;
|
||||
armv7l|armhf)
|
||||
exec "$(dirname "$0")/pulse-agent-linux-armv7" "$@"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $ARCH" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$universal_dir/bin/pulse-agent"
|
||||
|
||||
# Add VERSION file
|
||||
echo "$VERSION" > "$universal_dir/VERSION"
|
||||
|
||||
|
|
@ -281,6 +339,13 @@ zip -j "$RELEASE_DIR/pulse-host-agent-v${VERSION}-windows-amd64.zip" "$BUILD_DIR
|
|||
zip -j "$RELEASE_DIR/pulse-host-agent-v${VERSION}-windows-arm64.zip" "$BUILD_DIR/pulse-host-agent-windows-arm64.exe"
|
||||
zip -j "$RELEASE_DIR/pulse-host-agent-v${VERSION}-windows-386.zip" "$BUILD_DIR/pulse-host-agent-windows-386.exe"
|
||||
|
||||
# Package standalone unified agent binaries
|
||||
tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-darwin-amd64.tar.gz" -C "$BUILD_DIR" pulse-agent-darwin-amd64
|
||||
tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_DIR" pulse-agent-darwin-arm64
|
||||
zip -j "$RELEASE_DIR/pulse-agent-v${VERSION}-windows-amd64.zip" "$BUILD_DIR/pulse-agent-windows-amd64.exe"
|
||||
zip -j "$RELEASE_DIR/pulse-agent-v${VERSION}-windows-arm64.zip" "$BUILD_DIR/pulse-agent-windows-arm64.exe"
|
||||
zip -j "$RELEASE_DIR/pulse-agent-v${VERSION}-windows-386.zip" "$BUILD_DIR/pulse-agent-windows-386.exe"
|
||||
|
||||
# Copy Windows and macOS binaries into universal tarball for /download/ endpoint
|
||||
echo "Adding Windows and macOS binaries to universal tarball..."
|
||||
cp "$BUILD_DIR/pulse-host-agent-darwin-amd64" "$universal_dir/bin/"
|
||||
|
|
@ -289,11 +354,21 @@ cp "$BUILD_DIR/pulse-host-agent-windows-amd64.exe" "$universal_dir/bin/"
|
|||
cp "$BUILD_DIR/pulse-host-agent-windows-arm64.exe" "$universal_dir/bin/"
|
||||
cp "$BUILD_DIR/pulse-host-agent-windows-386.exe" "$universal_dir/bin/"
|
||||
|
||||
cp "$BUILD_DIR/pulse-agent-darwin-amd64" "$universal_dir/bin/"
|
||||
cp "$BUILD_DIR/pulse-agent-darwin-arm64" "$universal_dir/bin/"
|
||||
cp "$BUILD_DIR/pulse-agent-windows-amd64.exe" "$universal_dir/bin/"
|
||||
cp "$BUILD_DIR/pulse-agent-windows-arm64.exe" "$universal_dir/bin/"
|
||||
cp "$BUILD_DIR/pulse-agent-windows-386.exe" "$universal_dir/bin/"
|
||||
|
||||
# Create symlinks for Windows binaries without .exe extension (required for download endpoint)
|
||||
ln -s pulse-host-agent-windows-amd64.exe "$universal_dir/bin/pulse-host-agent-windows-amd64"
|
||||
ln -s pulse-host-agent-windows-arm64.exe "$universal_dir/bin/pulse-host-agent-windows-arm64"
|
||||
ln -s pulse-host-agent-windows-386.exe "$universal_dir/bin/pulse-host-agent-windows-386"
|
||||
|
||||
ln -s pulse-agent-windows-amd64.exe "$universal_dir/bin/pulse-agent-windows-amd64"
|
||||
ln -s pulse-agent-windows-arm64.exe "$universal_dir/bin/pulse-agent-windows-arm64"
|
||||
ln -s pulse-agent-windows-386.exe "$universal_dir/bin/pulse-agent-windows-386"
|
||||
|
||||
# Create universal tarball
|
||||
cd "$universal_dir"
|
||||
tar -czf "../../$RELEASE_DIR/pulse-v${VERSION}.tar.gz" .
|
||||
|
|
|
|||
|
|
@ -40,7 +40,10 @@ if ([string]::IsNullOrWhiteSpace($Url) -or [string]::IsNullOrWhiteSpace($Token))
|
|||
}
|
||||
|
||||
# --- Download ---
|
||||
$DownloadUrl = "$Url/download/pulse-agent?os=windows&arch=amd64"
|
||||
# Determine architecture
|
||||
$Arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" }
|
||||
$ArchParam = "windows-$Arch"
|
||||
$DownloadUrl = "$Url/download/pulse-agent?arch=$ArchParam"
|
||||
Write-Host "Downloading agent from $DownloadUrl..." -ForegroundColor Cyan
|
||||
|
||||
if (-not (Test-Path $InstallDir)) {
|
||||
|
|
@ -76,7 +79,11 @@ if (Get-Service $AgentName -ErrorAction SilentlyContinue) {
|
|||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
$Args = "--url `"$Url`" --token `"$Token`" --interval `"$Interval`" --enable-host=$EnableHost --enable-docker=$EnableDocker --insecure=$Insecure"
|
||||
# Build command line args
|
||||
$Args = "--url `"$Url`" --token `"$Token`" --interval `"$Interval`""
|
||||
if ($EnableHost) { $Args += " --enable-host" }
|
||||
if ($EnableDocker) { $Args += " --enable-docker" }
|
||||
if ($Insecure) { $Args += " --insecure" }
|
||||
$BinPath = "`"$DestPath`" $Args"
|
||||
|
||||
# Create Service
|
||||
|
|
|
|||
|
|
@ -107,10 +107,16 @@ ARCH=$(uname -m)
|
|||
case "$ARCH" in
|
||||
x86_64) ARCH="amd64" ;;
|
||||
aarch64|arm64) ARCH="arm64" ;;
|
||||
armv7l|armhf) ARCH="armv7" ;;
|
||||
armv6l) ARCH="armv6" ;;
|
||||
i386|i686) ARCH="386" ;;
|
||||
*) fail "Unsupported architecture: $ARCH" ;;
|
||||
esac
|
||||
|
||||
DOWNLOAD_URL="${PULSE_URL}/download/${BINARY_NAME}?os=${OS}&arch=${ARCH}"
|
||||
# Construct arch param in format expected by download endpoint (e.g., linux-amd64)
|
||||
ARCH_PARAM="${OS}-${ARCH}"
|
||||
|
||||
DOWNLOAD_URL="${PULSE_URL}/download/${BINARY_NAME}?arch=${ARCH_PARAM}"
|
||||
log_info "Downloading agent from ${DOWNLOAD_URL}..."
|
||||
|
||||
# Create temp file
|
||||
|
|
@ -169,6 +175,28 @@ if [[ "$OS" == "darwin" ]]; then
|
|||
PLIST="/Library/LaunchDaemons/com.pulse.agent.plist"
|
||||
log_info "Configuring Launchd service at $PLIST..."
|
||||
|
||||
# Build program arguments array
|
||||
PLIST_ARGS=" <string>${INSTALL_DIR}/${BINARY_NAME}</string>
|
||||
<string>--url</string>
|
||||
<string>${PULSE_URL}</string>
|
||||
<string>--token</string>
|
||||
<string>${PULSE_TOKEN}</string>
|
||||
<string>--interval</string>
|
||||
<string>${INTERVAL}</string>"
|
||||
|
||||
if [[ "$ENABLE_HOST" == "true" ]]; then
|
||||
PLIST_ARGS="${PLIST_ARGS}
|
||||
<string>--enable-host</string>"
|
||||
fi
|
||||
if [[ "$ENABLE_DOCKER" == "true" ]]; then
|
||||
PLIST_ARGS="${PLIST_ARGS}
|
||||
<string>--enable-docker</string>"
|
||||
fi
|
||||
if [[ "$INSECURE" == "true" ]]; then
|
||||
PLIST_ARGS="${PLIST_ARGS}
|
||||
<string>--insecure</string>"
|
||||
fi
|
||||
|
||||
cat > "$PLIST" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
|
|
@ -178,16 +206,7 @@ if [[ "$OS" == "darwin" ]]; then
|
|||
<string>com.pulse.agent</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>${INSTALL_DIR}/${BINARY_NAME}</string>
|
||||
<string>--url</string>
|
||||
<string>${PULSE_URL}</string>
|
||||
<string>--token</string>
|
||||
<string>${PULSE_TOKEN}</string>
|
||||
<string>--interval</string>
|
||||
<string>${INTERVAL}</string>
|
||||
<string>--enable-host=${ENABLE_HOST}</string>
|
||||
<string>--enable-docker=${ENABLE_DOCKER}</string>
|
||||
<string>--insecure=${INSECURE}</string>
|
||||
${PLIST_ARGS}
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
|
@ -212,6 +231,12 @@ if [[ -d /usr/syno/etc/rc.sysv ]]; then
|
|||
CONF="/etc/init/${AGENT_NAME}.conf"
|
||||
log_info "Configuring Upstart service at $CONF..."
|
||||
|
||||
# Build command line args
|
||||
EXEC_ARGS="--url \"${PULSE_URL}\" --token \"${PULSE_TOKEN}\" --interval \"${INTERVAL}\""
|
||||
[[ "$ENABLE_HOST" == "true" ]] && EXEC_ARGS="$EXEC_ARGS --enable-host"
|
||||
[[ "$ENABLE_DOCKER" == "true" ]] && EXEC_ARGS="$EXEC_ARGS --enable-docker"
|
||||
[[ "$INSECURE" == "true" ]] && EXEC_ARGS="$EXEC_ARGS --insecure"
|
||||
|
||||
cat > "$CONF" <<EOF
|
||||
description "Pulse Unified Agent"
|
||||
author "Pulse"
|
||||
|
|
@ -222,14 +247,7 @@ stop on runlevel [06]
|
|||
respawn
|
||||
respawn limit 5 10
|
||||
|
||||
exec ${INSTALL_DIR}/${BINARY_NAME} \
|
||||
--url "${PULSE_URL}" \
|
||||
--token "${PULSE_TOKEN}" \
|
||||
--interval "${INTERVAL}" \
|
||||
--enable-host=${ENABLE_HOST} \
|
||||
--enable-docker=${ENABLE_DOCKER} \
|
||||
--insecure=${INSECURE} \
|
||||
>> ${LOG_FILE} 2>&1
|
||||
exec ${INSTALL_DIR}/${BINARY_NAME} ${EXEC_ARGS} >> ${LOG_FILE} 2>&1
|
||||
EOF
|
||||
initctl stop "${AGENT_NAME}" 2>/dev/null || true
|
||||
initctl start "${AGENT_NAME}"
|
||||
|
|
@ -242,6 +260,12 @@ if command -v systemctl >/dev/null 2>&1; then
|
|||
UNIT="/etc/systemd/system/${AGENT_NAME}.service"
|
||||
log_info "Configuring Systemd service at $UNIT..."
|
||||
|
||||
# Build command line args
|
||||
EXEC_ARGS="--url ${PULSE_URL} --token ${PULSE_TOKEN} --interval ${INTERVAL}"
|
||||
[[ "$ENABLE_HOST" == "true" ]] && EXEC_ARGS="$EXEC_ARGS --enable-host"
|
||||
[[ "$ENABLE_DOCKER" == "true" ]] && EXEC_ARGS="$EXEC_ARGS --enable-docker"
|
||||
[[ "$INSECURE" == "true" ]] && EXEC_ARGS="$EXEC_ARGS --insecure"
|
||||
|
||||
cat > "$UNIT" <<EOF
|
||||
[Unit]
|
||||
Description=Pulse Unified Agent
|
||||
|
|
@ -250,13 +274,7 @@ Wants=network-online.target
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=${INSTALL_DIR}/${BINARY_NAME} \
|
||||
--url "${PULSE_URL}" \
|
||||
--token "${PULSE_TOKEN}" \
|
||||
--interval "${INTERVAL}" \
|
||||
--enable-host=${ENABLE_HOST} \
|
||||
--enable-docker=${ENABLE_DOCKER} \
|
||||
--insecure=${INSECURE}
|
||||
ExecStart=${INSTALL_DIR}/${BINARY_NAME} ${EXEC_ARGS}
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
User=root
|
||||
|
|
|
|||
|
|
@ -130,13 +130,13 @@ if [ "$SKIP_DOCKER" = false ]; then
|
|||
|
||||
# Validate all required scripts exist and are executable
|
||||
info "Checking installer/uninstaller scripts in /opt/pulse/scripts/..."
|
||||
docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'set -euo pipefail; cd /opt/pulse/scripts; required="install-docker-agent.sh install-container-agent.sh install-host-agent.sh install-host-agent.ps1 uninstall-host-agent.sh uninstall-host-agent.ps1 install-sensor-proxy.sh install-docker.sh"; for f in $required; do [ -f "$f" ] || { echo "missing script $f" >&2; exit 1; }; case "$f" in *.sh|*.ps1) [ -x "$f" ] || { echo "$f not executable" >&2; exit 1; };; esac; done; echo "All scripts present and executable"' || { error "Script validation failed"; exit 1; }
|
||||
docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'set -euo pipefail; cd /opt/pulse/scripts; required="install-docker-agent.sh install-container-agent.sh install-host-agent.sh install-host-agent.ps1 uninstall-host-agent.sh uninstall-host-agent.ps1 install-sensor-proxy.sh install-docker.sh install.sh"; for f in $required; do [ -f "$f" ] || { echo "missing script $f" >&2; exit 1; }; case "$f" in *.sh|*.ps1) [ -x "$f" ] || { echo "$f not executable" >&2; exit 1; };; esac; done; echo "All scripts present and executable"' || { error "Script validation failed"; exit 1; }
|
||||
success "All installer/uninstaller scripts present and executable"
|
||||
|
||||
# Validate all required binaries exist and are non-empty
|
||||
info "Checking downloadable binaries in /opt/pulse/bin/..."
|
||||
docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'set -euo pipefail; cd /opt/pulse/bin; required="pulse pulse-docker-agent pulse-docker-agent-linux-amd64 pulse-docker-agent-linux-arm64 pulse-docker-agent-linux-armv7 pulse-docker-agent-linux-armv6 pulse-docker-agent-linux-386 pulse-host-agent-linux-amd64 pulse-host-agent-linux-arm64 pulse-host-agent-linux-armv7 pulse-host-agent-linux-armv6 pulse-host-agent-linux-386 pulse-host-agent-darwin-amd64 pulse-host-agent-darwin-arm64 pulse-host-agent-windows-amd64.exe pulse-host-agent-windows-amd64 pulse-host-agent-windows-arm64.exe pulse-host-agent-windows-arm64 pulse-host-agent-windows-386.exe pulse-host-agent-windows-386 pulse-sensor-proxy pulse-sensor-proxy-linux-amd64 pulse-sensor-proxy-linux-arm64 pulse-sensor-proxy-linux-armv7 pulse-sensor-proxy-linux-armv6 pulse-sensor-proxy-linux-386"; for f in $required; do [ -e "$f" ] || { echo "missing binary $f" >&2; exit 1; }; [ -s "$f" ] || { echo "empty binary $f" >&2; exit 1; }; done; [ "$(readlink pulse-host-agent-windows-amd64)" = "pulse-host-agent-windows-amd64.exe" ] || { echo "windows amd64 symlink broken" >&2; exit 1; }; [ "$(readlink pulse-host-agent-windows-arm64)" = "pulse-host-agent-windows-arm64.exe" ] || { echo "windows arm64 symlink broken" >&2; exit 1; }; [ "$(readlink pulse-host-agent-windows-386)" = "pulse-host-agent-windows-386.exe" ] || { echo "windows 386 symlink broken" >&2; exit 1; }; echo "All binaries present"' || { error "Binary validation failed"; exit 1; }
|
||||
success "All downloadable binaries present (26 binaries + 3 Windows symlinks)"
|
||||
docker run --rm --entrypoint /bin/sh "$IMAGE" -c 'set -euo pipefail; cd /opt/pulse/bin; required="pulse pulse-docker-agent pulse-docker-agent-linux-amd64 pulse-docker-agent-linux-arm64 pulse-docker-agent-linux-armv7 pulse-docker-agent-linux-armv6 pulse-docker-agent-linux-386 pulse-host-agent-linux-amd64 pulse-host-agent-linux-arm64 pulse-host-agent-linux-armv7 pulse-host-agent-linux-armv6 pulse-host-agent-linux-386 pulse-host-agent-darwin-amd64 pulse-host-agent-darwin-arm64 pulse-host-agent-windows-amd64.exe pulse-host-agent-windows-amd64 pulse-host-agent-windows-arm64.exe pulse-host-agent-windows-arm64 pulse-host-agent-windows-386.exe pulse-host-agent-windows-386 pulse-agent-linux-amd64 pulse-agent-linux-arm64 pulse-agent-linux-armv7 pulse-agent-linux-armv6 pulse-agent-linux-386 pulse-agent-darwin-amd64 pulse-agent-darwin-arm64 pulse-agent-windows-amd64.exe pulse-agent-windows-amd64 pulse-agent-windows-arm64.exe pulse-agent-windows-arm64 pulse-agent-windows-386.exe pulse-agent-windows-386 pulse-sensor-proxy pulse-sensor-proxy-linux-amd64 pulse-sensor-proxy-linux-arm64 pulse-sensor-proxy-linux-armv7 pulse-sensor-proxy-linux-armv6 pulse-sensor-proxy-linux-386"; for f in $required; do [ -e "$f" ] || { echo "missing binary $f" >&2; exit 1; }; [ -s "$f" ] || { echo "empty binary $f" >&2; exit 1; }; done; [ "$(readlink pulse-host-agent-windows-amd64)" = "pulse-host-agent-windows-amd64.exe" ] || { echo "windows amd64 symlink broken" >&2; exit 1; }; [ "$(readlink pulse-host-agent-windows-arm64)" = "pulse-host-agent-windows-arm64.exe" ] || { echo "windows arm64 symlink broken" >&2; exit 1; }; [ "$(readlink pulse-host-agent-windows-386)" = "pulse-host-agent-windows-386.exe" ] || { echo "windows 386 symlink broken" >&2; exit 1; }; [ "$(readlink pulse-agent-windows-amd64)" = "pulse-agent-windows-amd64.exe" ] || { echo "unified agent windows amd64 symlink broken" >&2; exit 1; }; [ "$(readlink pulse-agent-windows-arm64)" = "pulse-agent-windows-arm64.exe" ] || { echo "unified agent windows arm64 symlink broken" >&2; exit 1; }; [ "$(readlink pulse-agent-windows-386)" = "pulse-agent-windows-386.exe" ] || { echo "unified agent windows 386 symlink broken" >&2; exit 1; }; echo "All binaries present"' || { error "Binary validation failed"; exit 1; }
|
||||
success "All downloadable binaries present (39 binaries + 6 Windows symlinks)"
|
||||
|
||||
# Validate version embedding in Docker image binaries
|
||||
info "Validating version embedding in Docker image binaries..."
|
||||
|
|
@ -157,6 +157,10 @@ if [ "$SKIP_DOCKER" = false ]; then
|
|||
docker run --rm --entrypoint /bin/sh -e EXPECTED_TAG="$PULSE_TAG" "$IMAGE" -c 'set -euo pipefail; grep -aF "$EXPECTED_TAG" /opt/pulse/bin/pulse-docker-agent-linux-amd64 >/dev/null' || { error "Docker agent version string not found"; exit 1; }
|
||||
success "Docker agent version embedded: $PULSE_TAG"
|
||||
|
||||
# Unified agent binary
|
||||
docker run --rm --entrypoint /opt/pulse/bin/pulse-agent-linux-amd64 "$IMAGE" --version 2>/dev/null | grep -Fx "$PULSE_TAG" >/dev/null || { error "Unified agent version mismatch"; exit 1; }
|
||||
success "Unified agent version: $PULSE_TAG"
|
||||
|
||||
# Smoke test download endpoints from a running container
|
||||
info "Running download endpoint smoke tests..."
|
||||
HOST_PORT=8765
|
||||
|
|
@ -308,6 +312,11 @@ required_assets=(
|
|||
"pulse-host-agent-v${PULSE_VERSION}-windows-amd64.zip"
|
||||
"pulse-host-agent-v${PULSE_VERSION}-windows-arm64.zip"
|
||||
"pulse-host-agent-v${PULSE_VERSION}-windows-386.zip"
|
||||
"pulse-agent-v${PULSE_VERSION}-darwin-amd64.tar.gz"
|
||||
"pulse-agent-v${PULSE_VERSION}-darwin-arm64.tar.gz"
|
||||
"pulse-agent-v${PULSE_VERSION}-windows-amd64.zip"
|
||||
"pulse-agent-v${PULSE_VERSION}-windows-arm64.zip"
|
||||
"pulse-agent-v${PULSE_VERSION}-windows-386.zip"
|
||||
)
|
||||
|
||||
missing_count=0
|
||||
|
|
@ -409,6 +418,21 @@ host_agent_entries=(
|
|||
./bin/pulse-host-agent-windows-arm64
|
||||
./bin/pulse-host-agent-windows-386
|
||||
)
|
||||
unified_agent_entries=(
|
||||
./bin/pulse-agent-linux-amd64
|
||||
./bin/pulse-agent-linux-arm64
|
||||
./bin/pulse-agent-linux-armv7
|
||||
./bin/pulse-agent-linux-armv6
|
||||
./bin/pulse-agent-linux-386
|
||||
./bin/pulse-agent-darwin-amd64
|
||||
./bin/pulse-agent-darwin-arm64
|
||||
./bin/pulse-agent-windows-amd64.exe
|
||||
./bin/pulse-agent-windows-arm64.exe
|
||||
./bin/pulse-agent-windows-386.exe
|
||||
./bin/pulse-agent-windows-amd64
|
||||
./bin/pulse-agent-windows-arm64
|
||||
./bin/pulse-agent-windows-386
|
||||
)
|
||||
for arch in "${tar_arches[@]}"; do
|
||||
tarball="pulse-v${PULSE_VERSION}-${arch}.tar.gz"
|
||||
|
||||
|
|
@ -419,9 +443,10 @@ for arch in "${tar_arches[@]}"; do
|
|||
fi
|
||||
|
||||
check_tar_entries_nonempty "$tarball" "${host_agent_entries[@]}"
|
||||
check_tar_entries_nonempty "$tarball" "${unified_agent_entries[@]}"
|
||||
|
||||
# Check scripts
|
||||
tar -tzf "$tarball" ./scripts/install-docker-agent.sh ./scripts/install-container-agent.sh ./scripts/install-host-agent.sh ./scripts/install-host-agent.ps1 ./scripts/uninstall-host-agent.sh ./scripts/uninstall-host-agent.ps1 ./scripts/install-sensor-proxy.sh ./scripts/install-docker.sh >/dev/null 2>&1 || { error "$(basename $tarball) missing scripts"; exit 1; }
|
||||
tar -tzf "$tarball" ./scripts/install-docker-agent.sh ./scripts/install-container-agent.sh ./scripts/install-host-agent.sh ./scripts/install-host-agent.ps1 ./scripts/uninstall-host-agent.sh ./scripts/uninstall-host-agent.ps1 ./scripts/install-sensor-proxy.sh ./scripts/install-docker.sh ./scripts/install.sh >/dev/null 2>&1 || { error "$(basename $tarball) missing scripts"; exit 1; }
|
||||
|
||||
# Check VERSION file
|
||||
tar -tzf "$tarball" ./VERSION >/dev/null 2>&1 || { error "$(basename $tarball) missing VERSION file"; exit 1; }
|
||||
|
|
@ -432,14 +457,16 @@ success "Platform-specific tarballs contain all required files (including cross-
|
|||
section "Validating universal tarball"
|
||||
tar -tzf "pulse-v${PULSE_VERSION}.tar.gz" ./VERSION >/dev/null 2>&1 || { error "Universal tarball missing VERSION file"; exit 1; }
|
||||
|
||||
# Validate universal tarball contains all host agent binaries for download endpoint
|
||||
info "Validating universal tarball contains all host agent binaries..."
|
||||
# Validate universal tarball contains all agent binaries for download endpoint
|
||||
info "Validating universal tarball contains all agent binaries..."
|
||||
check_tar_entries_nonempty "pulse-v${PULSE_VERSION}.tar.gz" "${host_agent_entries[@]}"
|
||||
success "Universal tarball validated (includes cross-platform host agents)"
|
||||
check_tar_entries_nonempty "pulse-v${PULSE_VERSION}.tar.gz" "${unified_agent_entries[@]}"
|
||||
success "Universal tarball validated (includes cross-platform host and unified agents)"
|
||||
|
||||
# Validate macOS tarball
|
||||
tar -tzf "pulse-host-agent-v${PULSE_VERSION}-darwin-arm64.tar.gz" pulse-host-agent-darwin-arm64 >/dev/null 2>&1 || { error "macOS tarball validation failed"; exit 1; }
|
||||
success "macOS host-agent tarball validated"
|
||||
# Validate macOS tarballs
|
||||
tar -tzf "pulse-host-agent-v${PULSE_VERSION}-darwin-arm64.tar.gz" pulse-host-agent-darwin-arm64 >/dev/null 2>&1 || { error "macOS host-agent tarball validation failed"; exit 1; }
|
||||
tar -tzf "pulse-agent-v${PULSE_VERSION}-darwin-arm64.tar.gz" pulse-agent-darwin-arm64 >/dev/null 2>&1 || { error "macOS unified-agent tarball validation failed"; exit 1; }
|
||||
success "macOS agent tarballs validated"
|
||||
|
||||
# Validate checksums.txt
|
||||
info "Validating checksums..."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue