mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
fix: add cluster endpoint IP override and Windows agent download support
1. Add IPOverride field to ClusterEndpoint struct - Allows users to specify a custom IP that takes precedence over auto-discovered IPs - Fixes #929 and #1066 where Pulse used internal cluster IPs instead of management IPs - Added EffectiveIP() method to cleanly handle the override logic 2. Update connection code to use EffectiveIP() - monitor.go: Use override when building endpoint URLs - temperature_proxy.go: Use override for proxy connections 3. Add bare Windows EXE files to GitHub releases - Fixes #1064 where LXC/barebone installs couldn't download Windows agents - Modified build-release.sh to copy EXEs alongside ZIPs - Added EXEs to checksum generation
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import { createSignal, Show, For, onMount, createMemo, onCleanup, createEffect } from 'solid-js';
|
||||
import { Shield, CheckCircle, XCircle, AlertTriangle, RefreshCw, Filter, Info, Play, X } from 'lucide-solid';
|
||||
import { Shield, CheckCircle, XCircle, RefreshCw, Filter, Info, Play, X } from 'lucide-solid';
|
||||
import { showTooltip, hideTooltip } from '@/components/shared/Tooltip';
|
||||
import Toggle from '@/components/shared/Toggle';
|
||||
import {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
import { createSignal, createMemo } from 'solid-js';
|
||||
import { LicenseAPI, type LicenseStatus } from '@/api/license';
|
||||
import { logger } from '@/utils/logger';
|
||||
|
||||
// Reactive signals for license status
|
||||
const [licenseStatus, setLicenseStatus] = createSignal<LicenseStatus | null>(null);
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
const [loaded, setLoaded] = createSignal(false);
|
||||
|
||||
/**
|
||||
* Load the license status from the server.
|
||||
*/
|
||||
export async function loadLicenseStatus(force = false): Promise<void> {
|
||||
if (loaded() && !force) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const status = await LicenseAPI.getStatus();
|
||||
setLicenseStatus(status);
|
||||
setLoaded(true);
|
||||
logger.debug('[licenseStore] License status loaded', { tier: status.tier, valid: status.valid });
|
||||
} catch (err) {
|
||||
logger.error('[licenseStore] Failed to load license status', err);
|
||||
// Fallback to free tier on error to avoid breaking UI
|
||||
setLicenseStatus({
|
||||
valid: false,
|
||||
tier: 'free',
|
||||
is_lifetime: false,
|
||||
days_remaining: 0,
|
||||
features: [],
|
||||
});
|
||||
setLoaded(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to check if the current license is Pulse Pro or Enterprise.
|
||||
*/
|
||||
export const isPro = createMemo(() => {
|
||||
const current = licenseStatus();
|
||||
return Boolean(current?.valid && current.tier !== 'free');
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to check if the current license is Enterprise.
|
||||
*/
|
||||
export const isEnterprise = createMemo(() => {
|
||||
const current = licenseStatus();
|
||||
return Boolean(current?.valid && (current.tier === 'enterprise' || current.tier === 'msp'));
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if a specific feature is enabled by the current license.
|
||||
*/
|
||||
export function hasFeature(feature: string): boolean {
|
||||
const current = licenseStatus();
|
||||
if (!current?.valid) return false;
|
||||
return current.features.includes(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full license status.
|
||||
*/
|
||||
export { licenseStatus, loading as licenseLoading, loaded as licenseLoaded };
|
||||
@@ -150,7 +150,8 @@ func buildAuthorizedNodeList(instances []config.PVEInstance) []authorizedNode {
|
||||
if instance.ClusterEndpoints != nil {
|
||||
for _, ep := range instance.ClusterEndpoints {
|
||||
name := ep.NodeName
|
||||
ip := ep.IP
|
||||
// Use EffectiveIP() which prefers IPOverride over auto-discovered IP
|
||||
ip := ep.EffectiveIP()
|
||||
if ip == "" {
|
||||
ip = extractHostPart(ep.Host)
|
||||
}
|
||||
|
||||
@@ -458,7 +458,8 @@ type ClusterEndpoint struct {
|
||||
NodeName string // Node name
|
||||
Host string // Full URL (e.g., https://node1.lan:8006)
|
||||
GuestURL string // Optional guest-accessible URL (for navigation)
|
||||
IP string // IP address
|
||||
IP string // IP address (auto-discovered from cluster)
|
||||
IPOverride string // User-specified IP override (takes precedence over IP if set)
|
||||
Fingerprint string // TLS certificate fingerprint (SHA256, auto-captured via TOFU)
|
||||
Online bool // Current online status from Proxmox
|
||||
LastSeen time.Time // Last successful connection
|
||||
@@ -470,6 +471,14 @@ type ClusterEndpoint struct {
|
||||
TemperatureProxyControlToken string // Control-plane token for this specific node
|
||||
}
|
||||
|
||||
// EffectiveIP returns the IP to use for this endpoint, preferring IPOverride if set
|
||||
func (e ClusterEndpoint) EffectiveIP() string {
|
||||
if e.IPOverride != "" {
|
||||
return e.IPOverride
|
||||
}
|
||||
return e.IP
|
||||
}
|
||||
|
||||
// PBSInstance represents a Proxmox Backup Server connection
|
||||
type PBSInstance struct {
|
||||
Name string
|
||||
|
||||
@@ -537,18 +537,21 @@ func clusterEndpointEffectiveURL(endpoint config.ClusterEndpoint, verifySSL bool
|
||||
// bypasses hostname checks), prefer IP to reduce DNS lookups (refs #620).
|
||||
requiresHostnameForTLS := verifySSL && !hasFingerprint
|
||||
|
||||
// Use EffectiveIP() which prefers user-specified IPOverride over auto-discovered IP
|
||||
effectiveIP := endpoint.EffectiveIP()
|
||||
|
||||
if requiresHostnameForTLS {
|
||||
// Prefer hostname for proper TLS certificate validation
|
||||
if endpoint.Host != "" {
|
||||
return ensureClusterEndpointURL(endpoint.Host)
|
||||
}
|
||||
if endpoint.IP != "" {
|
||||
return ensureClusterEndpointURL(endpoint.IP)
|
||||
if effectiveIP != "" {
|
||||
return ensureClusterEndpointURL(effectiveIP)
|
||||
}
|
||||
} else {
|
||||
// Prefer IP address to avoid excessive DNS lookups
|
||||
if endpoint.IP != "" {
|
||||
return ensureClusterEndpointURL(endpoint.IP)
|
||||
if effectiveIP != "" {
|
||||
return ensureClusterEndpointURL(effectiveIP)
|
||||
}
|
||||
if endpoint.Host != "" {
|
||||
return ensureClusterEndpointURL(endpoint.Host)
|
||||
@@ -3879,10 +3882,13 @@ func (m *Monitor) getConfiguredHostIPs() []string {
|
||||
// Add PVE hosts
|
||||
for _, pve := range m.config.PVEInstances {
|
||||
addHost(pve.Host)
|
||||
// Also add cluster endpoints
|
||||
// Also add cluster endpoints (include both auto-discovered IP and override if set)
|
||||
for _, ep := range pve.ClusterEndpoints {
|
||||
addHost(ep.Host)
|
||||
addHost(ep.IP)
|
||||
if ep.IPOverride != "" && ep.IPOverride != ep.IP {
|
||||
addHost(ep.IPOverride)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4265,7 +4271,8 @@ func (m *Monitor) retryFailedConnections(ctx context.Context) {
|
||||
endpointFingerprints := make(map[string]string)
|
||||
|
||||
for _, ep := range pve.ClusterEndpoints {
|
||||
host := ep.IP
|
||||
// Use EffectiveIP() which prefers IPOverride over auto-discovered IP
|
||||
host := ep.EffectiveIP()
|
||||
if host == "" {
|
||||
host = ep.Host
|
||||
}
|
||||
|
||||
@@ -344,11 +344,21 @@ tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_D
|
||||
# FreeBSD
|
||||
tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-freebsd-amd64.tar.gz" -C "$BUILD_DIR" pulse-agent-freebsd-amd64
|
||||
tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-freebsd-arm64.tar.gz" -C "$BUILD_DIR" pulse-agent-freebsd-arm64
|
||||
# Windows
|
||||
# Windows (zip archives with version in filename)
|
||||
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"
|
||||
|
||||
# Also copy bare Windows EXEs for /releases/latest/download/ redirect compatibility
|
||||
# These allow LXC/barebone installs to redirect to GitHub without needing versioned URLs
|
||||
echo "Copying bare Windows EXEs to release directory for redirect compatibility..."
|
||||
cp "$BUILD_DIR/pulse-agent-windows-amd64.exe" "$RELEASE_DIR/"
|
||||
cp "$BUILD_DIR/pulse-agent-windows-arm64.exe" "$RELEASE_DIR/"
|
||||
cp "$BUILD_DIR/pulse-agent-windows-386.exe" "$RELEASE_DIR/"
|
||||
cp "$BUILD_DIR/pulse-host-agent-windows-amd64.exe" "$RELEASE_DIR/"
|
||||
cp "$BUILD_DIR/pulse-host-agent-windows-arm64.exe" "$RELEASE_DIR/"
|
||||
cp "$BUILD_DIR/pulse-host-agent-windows-386.exe" "$RELEASE_DIR/"
|
||||
|
||||
# Copy Windows, macOS, and FreeBSD binaries into universal tarball for /download/ endpoint
|
||||
echo "Adding Windows, macOS, and FreeBSD binaries to universal tarball..."
|
||||
cp "$BUILD_DIR/pulse-host-agent-darwin-amd64" "$universal_dir/bin/"
|
||||
@@ -410,8 +420,8 @@ cp scripts/pulse-auto-update.sh "$RELEASE_DIR/"
|
||||
# Generate checksums (include tarballs, zip files, helm chart, and install.sh)
|
||||
cd "$RELEASE_DIR"
|
||||
shopt -s nullglob extglob
|
||||
# Match all tarballs, zip files, and install scripts
|
||||
checksum_files=( *.tar.gz *.zip install.sh install-sensor-proxy.sh install-docker.sh pulse-auto-update.sh )
|
||||
# Match all tarballs, zip files, exe files, and install scripts
|
||||
checksum_files=( *.tar.gz *.zip *.exe install.sh install-sensor-proxy.sh install-docker.sh pulse-auto-update.sh )
|
||||
if compgen -G "pulse-*.tgz" > /dev/null; then
|
||||
checksum_files+=( pulse-*.tgz )
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user