Updated FirstRunSetup to show generic container commands that work
across different orchestration platforms:
- Use <container-name> placeholder instead of hardcoded "pulse"
- Add kubectl exec example for Kubernetes/Helm deployments
- Clarify "From container host" applies to Docker, Podman, etc.
This ensures the instructions work for Docker Compose, Swarm, Helm,
and any other container orchestrator where the container might have
a different name.
The bootstrap-token CLI command had two bugs:
1. Used PULSE_DATA_PATH instead of PULSE_DATA_DIR (typo)
2. Used /var/lib/pulse as fallback instead of /etc/pulse
This caused the command to look in the wrong location for non-Docker
deployments. Fixed to match config.Load() logic:
- Check PULSE_DATA_DIR env var first
- Fall back to /data for Docker, /etc/pulse otherwise
The first-run setup UI was displaying incorrect bootstrap token paths for
Docker deployments. It showed `/etc/pulse/.bootstrap_token` regardless of
deployment type, but Docker containers use `/data/.bootstrap_token` by
default (via PULSE_DATA_DIR env var).
Changes:
- Extended `/api/security/status` endpoint to include `bootstrapTokenPath`
and `isDocker` fields when a bootstrap token is active
- Updated FirstRunSetup component to fetch and display the correct path
dynamically based on actual deployment configuration
- For Docker deployments, UI now shows both `docker exec` command and
in-container command
- Falls back to showing both standard and Docker paths if API data
unavailable (backward compatibility)
This fix ensures users always see the correct command for their specific
deployment, including custom PULSE_DATA_DIR configurations.
Change sparkline wrapper from inline-block to block w-full to properly
fill flex parent container. Inline-block was preventing the canvas from
calculating the correct width when width={0} (auto-size mode).
Add comprehensive sparkline chart support as an alternative to progress bars
for CPU, Memory, and Disk metrics across all tables.
Features:
- Toggle between bars/trends view modes (persisted to localStorage)
- 30-second sampling with 2-hour retention window using ring buffer
- Canvas-based rendering with shared requestAnimationFrame for efficiency
- Hover tooltips showing exact values and timestamps
- Threshold reference lines (warning/critical) for context
- localStorage persistence survives page refreshes (12-hour max age)
- Dynamic width adaptation to column size
- Namespaced resource IDs prevent collisions
- Lifecycle cleanup prevents memory leaks
Performance optimizations:
- Decoupled sampling from WebSocket handler (6x reduction in recording)
- O(1) ring buffer insertions (no array cloning)
- Batched canvas rendering (single rAF for all sparklines)
- Debounced localStorage writes
- Automatic pruning of removed resources
UI improvements:
- Consistent radio toggle styling matching other filters
- Fixed column widths prevent layout shift during toggle
- Fixed row heights prevent vertical size changes
- Sparklines fill available column width proportionally
Users upgrading from v4.25 (where DISABLE_AUTH actually disabled auth) to
v4.27.1 (where DISABLE_AUTH is ignored but triggers a deprecation warning)
were stuck in a catch-22:
- They had no credentials (old version had auth disabled)
- DISABLE_AUTH detection incorrectly required authentication
- Setup wizard returned 401, preventing first credential creation
- Could not complete setup to create credentials and remove flag
Root cause: When DISABLE_AUTH was detected, the code set forceRequested=true
which triggered the authentication requirement even when authConfigured=false.
Fix: Only require authentication when credentials actually exist. When no
auth is configured, allow the bootstrap token flow regardless of whether
DISABLE_AUTH is detected.
This lets users upgrade from legacy DISABLE_AUTH deployments by using the
bootstrap token to create their first credentials, then removing the flag.
The previous fix (a1ba915ca) correctly added customDisplayName to the WebSocket
payload and made it persist in Settings, but the main Docker tab's RESOURCE
column still showed the default name.
DockerUnifiedTable had four locations that built display names but ignored
customDisplayName:
- DockerHostGroupHeader (RESOURCE column header) - line 549
- containerMatchesToken (search/filter logic) - line 391
- serviceMatchesToken (search/filter logic) - line 472
- sortedHosts (host sorting logic) - lines 1879-1880
All four now prioritize customDisplayName first, matching the pattern used in
DockerHostSummaryTable and Settings (customDisplayName || displayName ||
hostname || id).
This ensures custom Docker host names display consistently across the entire UI.
This commit resolves the recurring temperature monitoring failures that have plagued multiple releases:
1. **Fix user mismatch (v4.27.1 regression)**:
- Changed binary default user from 'pulse-sensor' to 'pulse-sensor-proxy'
- Aligns with the user created by install-sensor-proxy.sh (line 389)
- Prevents panic when binary is run outside systemd context
- Systemd unit already uses User=pulse-sensor-proxy, so this makes manual runs work too
2. **Fix standalone node validation (v4.25.0+ regression)**:
- pvecm status exits with code 2 on standalone nodes (not in a cluster)
- This caused validation to fail, rejecting all temperature requests
- Added discoverLocalHostAddresses() helper that discovers actual host IPs/hostnames
- On standalone nodes, cluster membership list is populated with host's own addresses
- Maintains SSRF protection while allowing standalone operation
- Added comprehensive test coverage
3. **Make installer fail loudly on proxy setup failure**:
- Previously, failed proxy installation only printed a warning
- Install script then claimed "Pulse installation complete!" (confusing for users)
- Now exits with clear error message and remediation steps
- Forces operators to fix proxy issues before claiming success
- Users who skip temperature monitoring are unaffected
4. **Add test coverage to prevent future regressions**:
- Added TestDiscoverLocalHostAddresses to verify local address discovery
- Validates no loopback or link-local addresses are returned
- All existing tests pass with new changes
Pattern of failures across releases:
- v4.23.0: Missing proxy binaries in release
- v4.24.0-rc.3: AMD CPU sensor naming (Tctl vs Tdie)
- v4.25.0: Single-node pvecm status exit code
- v4.27.1: User mismatch (pulse-sensor vs pulse-sensor-proxy)
This comprehensive fix addresses the root causes rather than applying another tactical patch.
Related to #571
The diagnostic code was warning ALL deployments using /run/pulse-sensor-proxy
socket path to "remove and re-add" their configuration to use /mnt/pulse-proxy
instead. This was incorrect for Docker deployments where /run is the correct
and documented mount path (see docker-compose.yml line 15).
The warning was only meant for LXC containers where the managed mount at
/mnt/pulse-proxy is preferred over a legacy hand-crafted /run mount.
Fix: Only show the warning in non-Docker environments (check PULSE_DOCKER env).
Docker deployments correctly use /run/pulse-sensor-proxy per compose file.
Impact: Docker users were seeing confusing diagnostic warnings telling them
to reconfigure a correct setup.
This fixes two bugs that prevented temperature monitoring from working
after running install-sensor-proxy.sh on LXC deployments:
1. CRITICAL: Pulse service not restarted after systemd override
- The installer wrote PULSE_SENSOR_PROXY_SOCKET env var to systemd
drop-in and ran daemon-reload, but never restarted Pulse service
- Running Pulse instances continued using old environment variables
- Temperatures wouldn't work until manual Pulse restart
- Now: Automatically restart Pulse if running after writing override
2. Added guard to check if Pulse service exists before configuring
- Installer would write systemd override even if Pulse not installed
- Left orphaned drop-in files that confused users
- Now: Check if pulse.service exists, warn and skip if not found
3. MINOR: Fix inconsistent Docker mount instructions
- docker-compose.yml showed :ro (read-only) mount
- Installer output showed :rw (read-write) mount
- Changed installer to match compose file (:ro is correct and secure)
Impact: Users in #600 reported "socketFound=false" even after running
installer successfully. This was because Pulse never picked up the new
socket path without a restart.
Implements comprehensive mdadm RAID array monitoring for Linux hosts
via pulse-host-agent. Arrays are automatically detected and monitored
with real-time status updates, rebuild progress tracking, and automatic
alerting for degraded or failed arrays.
Key changes:
**Backend:**
- Add mdadm package for parsing mdadm --detail output
- Extend host agent report structure with RAID array data
- Integrate mdadm collection into host agent (Linux-only, best-effort)
- Add RAID array processing in monitoring system
- Implement automatic alerting:
- Critical alerts for degraded arrays or arrays with failed devices
- Warning alerts for rebuilding/resyncing arrays with progress tracking
- Auto-clear alerts when arrays return to healthy state
**Frontend:**
- Add TypeScript types for RAID arrays and devices
- Display RAID arrays in host details drawer with:
- Array status (clean/degraded/recovering) with color-coded indicators
- Device counts (active/total/failed/spare)
- Rebuild progress percentage and speed when applicable
- Green for healthy, amber for rebuilding, red for degraded
**Documentation:**
- Document mdadm monitoring feature in HOST_AGENT.md
- Explain requirements (Linux, mdadm installed, root access)
- Clarify scope (software RAID only, hardware RAID not supported)
**Testing:**
- Add comprehensive tests for mdadm output parsing
- Test parsing of healthy, degraded, and rebuilding arrays
- Verify proper extraction of device states and rebuild progress
All builds pass successfully. RAID monitoring is automatic and best-effort
- if mdadm is not installed or no arrays exist, host agent continues
reporting other metrics normally.
Related to #676
Adds build support for 32-bit Windows (windows-386) for pulse-host-agent.
Changes:
- Add windows-386 build to Dockerfile host-agent build section
- Add windows-386 binary copy and symlink to Dockerfile
- Add windows-386 build to build-release.sh
- Add windows-386 zip package to release artifacts
- Include windows-386 binary in standalone binary copies
This enables pulse-host-agent to run on 32-bit Windows systems, which are still relevant in legacy/industrial monitoring environments through late 2025.
Document the new webhook security feature that allows homelab users to configure
trusted private IP ranges for webhook targets.
Includes:
- Overview of default security behavior
- Step-by-step configuration instructions
- Security considerations and best practices
- Example CIDR configurations
- Troubleshooting guidance for common error messages
Related to #673
Adds build support for 32-bit x86 (i386/i686) and ARMv6 (older Raspberry Pi models) architectures across all agents and install scripts.
Changes:
- Add linux-386 and linux-armv6 to build-release.sh builds array
- Update Dockerfile to build docker-agent, host-agent, and sensor-proxy for new architectures
- Update all install scripts to detect and handle i386/i686 and armv6l architectures
- Add architecture normalization in router download endpoints
- Update update manager architecture mapping
- Update validate-release.sh to expect 24 binaries (was 18)
This enables Pulse agents to run on older/legacy hardware including 32-bit x86 systems and Raspberry Pi Zero/Zero W devices.
Allow homelab users to send webhooks to internal services while maintaining security defaults.
Changes:
- Add webhookAllowedPrivateCIDRs field to SystemSettings (persistent config)
- Implement CIDR parsing and validation in NotificationManager
- Convert ValidateWebhookURL to instance method to access allowlist
- Add UI controls in System Settings for configuring trusted CIDR ranges
- Maintain strict security by default (block all private IPs)
- Keep localhost, link-local, and cloud metadata services blocked regardless of allowlist
- Re-validate on both config save and webhook delivery (DNS rebinding protection)
- Add comprehensive tests for CIDR parsing and IP matching
Backend:
- UpdateAllowedPrivateCIDRs() parses comma-separated CIDRs with validation
- Support for bare IPs (auto-converts to /32 or /128)
- Thread-safe allowlist updates with RWMutex
- Logging when allowlist is updated or used
- Validation errors prevent invalid CIDRs from being saved
Frontend:
- New "Webhook Security" section in System Settings
- Input field with examples and helpful placeholder text
- Real-time unsaved changes tracking
- Loads and saves allowlist via system settings API
Security:
- Default behavior unchanged (all private IPs blocked)
- Explicit opt-in required via configuration
- Localhost (127/8) always blocked
- Link-local (169.254/16) always blocked
- Cloud metadata services always blocked
- DNS resolution checked at both save and send time
Testing:
- Tests for CIDR parsing (valid/invalid inputs)
- Tests for IP allowlist matching
- Tests for bare IP address handling
- Tests for security boundaries (localhost, link-local remain blocked)
Related to #673🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
User ZaDarkSide reported that when updates fail, the UI shows a loading
spinner indefinitely with no feedback about what went wrong. Users had to
check backend logs to understand failures like "checksum verification failed".
The infrastructure was already in place:
- UpdateStatus struct had an Error field
- Frontend already renders error details when present
- But updateStatus() never populated the Error field
Changes:
- Modified updateStatus() to accept optional error parameter
- Added sanitizeError() to cap error message length (500 chars max)
- Updated all error cases in ApplyUpdate() to pass error details:
- Temp directory creation failures
- Download failures
- Checksum verification failures (most common user complaint)
- Extraction failures
- Backup creation failures
- Apply update failures
- Also updated CheckForUpdates() error cases
Now when updates fail, users immediately see the error message in the UI's
red error panel instead of being stuck on a loading spinner.
Security: Errors are only shown to authenticated admin users with update
permissions. Error messages are capped at 500 chars to prevent extremely
long output. Current error messages don't contain sensitive data (mainly
HTTP status codes, file paths, checksum mismatches).
Updated template to hybrid format combining best of v4.27.0 and v4.25.0:
Benefits from detailed format (v4.25.0):
- 4 complete installation methods (Quick/Docker/Binary/Helm)
- Copy-pasteable commands for each method
- Explicit Downloads section listing what's available
- Better for new users and SEO
Benefits from simple format (v4.27.0):
- Consistent section ordering
- Clean, scannable structure
- Breaking Changes section always present
Changes descriptions now require context and user impact, not just
one-liners. This helps users understand if a change affects them without
clicking through to issues.
Based on Codex analysis that detailed format serves more user types better:
new users, quick upgrades, search indexing, and professional appearance.
Removed hardcoded '31 assets' requirement. Instead, checklist now says:
- Compare with recent successful releases (v4.26.5, v4.27.0)
- Investigate if count differs significantly
- Trust the build script output, not a magic number
This prevents checklist from becoming outdated if build script adds/removes
artifacts. AI can adapt to changes rather than failing on incorrect validation.
Philosophy: Define what good looks like (matches recent releases) rather than
hardcoding specific numbers that will inevitably change.
Changed philosophy from 'follow these exact commands' to 'ensure these
outcomes are true'. This allows AI to be intelligent about HOW to
accomplish goals rather than blindly following steps.
Key changes:
- Focus on WHAT must be true, not HOW to make it true
- Explain WHY each requirement matters
- Document critical constraints (checksums.txt ordering, asset count)
- Provide troubleshooting guidance instead of rigid procedures
- Trust AI to figure out optimal execution path
This approach ensures consistent, reliable releases while allowing
flexibility in execution methods.
User ZaDarkSide reported that checksums.txt was being uploaded last,
causing update failures for users who check immediately after release.
The auto-updater downloads checksums.txt first, but if it's not available
yet, the update fails with 'no checksum file found'.
Changed upload order to:
1. checksums.txt (FIRST - critical for auto-updates)
2. tarballs, zips, helm chart
3. install.sh
4. SHA256 files
This prevents the race condition where fast users get update failures.
Fixes two critical bugs in refresh_smart_cache() that prevented SMART
temperature collection from working:
1. Invalid smartctl parameter: Changed -n standby,after to -n standby
The 'after' parameter is not valid in smartctl 7.4 and causes:
"INVALID ARGUMENT TO -n: standby,after"
Valid syntax is standby[,STATUS[,STATUS2]] where STATUS must be numeric.
2. Broken process detection: Replaced exec -a with lock file approach
The original exec -a pulse-sensor-wrapper-refresh bash line replaced
the subshell with a new bash process that had no script to run, causing
the function to exit immediately without collecting any SMART data.
New approach uses a lock file ($CACHE_DIR/smart-refresh.lock) with
trap-based cleanup to prevent concurrent refresh operations.
Credits to @ZaDarkSide for identifying these issues in PR #672.
ROOT CAUSE: The onMount hook checked props.isOpen, but onMount only runs ONCE
when the component first mounts. Since UpdateProgressModal mounts when the app
loads (before the user clicks "Apply Update"), props.isOpen is false at mount
time, so polling never initializes.
When the user later clicks "Apply Update" and props.isOpen becomes true, onMount
doesn't re-run, leaving the modal in a broken state with no polling, no restart
detection, and no auto-reload - exactly what users reported (stuck for 30+ mins).
SOLUTION: Changed from onMount to createEffect watching props.isOpen. Now:
- Polling starts immediately when the modal opens (user clicks "Apply Update")
- Polling stops when the modal closes (cleanup)
- The entire update flow works as designed
This was the ACTUAL bug - the previous commits (global watcher, fallback polling)
were helpful additions but didn't fix the root cause.
After the initial fix, added multiple layers of reliability to ensure updates
ALWAYS auto-refresh, even in edge cases:
1. Fallback polling: GlobalUpdateProgressWatcher now polls /api/updates/status
every 5 seconds as a safety net in case WebSocket events are dropped, missed,
or the tab connects mid-update. This ensures tabs that join late or have
WebSocket issues still detect in-progress updates.
2. Manual reload button: Added "Reload Now" button in UpdateProgressModal that
appears after 5+ health check attempts during restart. Gives users an escape
hatch if auto-reload is delayed (slow DNS, reverse proxy issues, etc.).
3. Already protected: Modal close button only shows when update is complete,
preventing users from accidentally closing it mid-update.
These changes address all failure modes identified:
- Tabs without WebSocket: covered by polling fallback
- Tabs joining mid-update: covered by polling fallback
- Health check delays: covered by manual reload button
- User accidentally closing modal: already prevented
The combination of WebSocket events (primary), polling (fallback), health checks
(restart detection), and manual reload (escape hatch) should make this bulletproof.
Problem: When an update was triggered, only the tab that clicked "Apply Update"
would show the progress modal and auto-refresh after completion. Other open tabs
would remain on the old version indefinitely.
Root cause: The UpdateProgressModal was only shown when explicitly opened via the
UpdateBanner component. WebSocket already broadcasts update:progress events, but
no global listener existed to show the modal in all tabs.
Solution: Added GlobalUpdateProgressWatcher component in App.tsx that:
- Listens to WebSocket updateProgress events globally (in all tabs)
- Filters to only real update-in-progress states (downloading, verifying, extracting,
installing, restarting) to avoid false positives from routine update checks
- Auto-opens the progress modal when an update starts
- Allows manual dismissal after update completes
- Works independently of UpdateBanner visibility (e.g., when banner is dismissed)
The modal's existing health-check and auto-reload logic handles the page refresh
once the backend is healthy again.
Related to #670, #657
The fix in v4.26.5 (commit 59a97f2e3) attempted to resolve storage disappearing
by preferring hostnames over IPs when TLS hostname verification is required
(VerifySSL=true and no fingerprint). However, that fix was ineffective because
the cluster discovery code was populating BOTH the Host and IP fields with the
IP address.
**Root Cause:**
In internal/api/config_handlers.go, the detectPVECluster function was setting:
- endpoint.Host = schemePrefix + clusterNode.IP (when IP was available)
- endpoint.IP = clusterNode.IP
This meant both fields contained the same IP address. When the monitoring code
tried to prefer endpoint.Host for TLS validation (internal/monitoring/monitor.go:
361-368), it was still getting an IP, causing certificate validation to fail
with "certificate is valid for pve01.example.com, not 10.0.0.44".
**Solution:**
Separate the Host and IP fields properly during cluster discovery:
- endpoint.Host = hostname (e.g., "https://pve01:8006") for TLS validation
- endpoint.IP = IP address (e.g., "10.0.0.44") for DNS-free connections
The existing logic in clusterEndpointEffectiveURL() can now correctly choose
between them based on TLS requirements.
**Impact:**
Users with VerifySSL=true who upgraded to v4.26.1-v4.26.5 and lost storage
visibility should now see storage, VM disks, and backups again after this fix.
The 5-second connect timeout was too aggressive for DNS resolution in some
Proxmox LXC environments, causing "Resolving timed out after 5000 milliseconds"
errors when downloading the auto-update script from raw.githubusercontent.com.
Changes:
- Add download_auto_update_script() helper with retry logic
- Increase connect timeout from 5s to 15s for slow DNS
- Increase max time from 15s to 60s for complete transfer
- Retry up to 3 times with incremental backoff (3s, 6s delays)
- Gracefully degrade: installer continues without auto-updates if download fails
- Users can re-run with --enable-auto-updates later when connectivity improves
This change fixes backup-age alert notifications to display VM/CT names
instead of just "VMID XXX" in multi-cluster environments where backups
are stored on PBS.
Changes:
- Store all guests per VMID (not just first match) to handle VMID collisions across clusters
- Persist last-known guest names/types in metadata store for deleted VMs
- Enrich backup correlation with persisted metadata when live inventory is empty
- Update CheckBackups to handle multiple VMID matches intelligently
The fix addresses two scenarios:
1. Multiple PVE clusters with same VMID backing up to one PBS
2. VMs deleted from Proxmox but backups still exist on PBS
Backup-age alerts will now show proper VM/CT names when:
- A unique guest exists with that VMID (live or persisted)
- Multiple guests share a VMID (uses first match, consistent with current behavior)
When truly ambiguous (multiple live VMs, same VMID, no way to determine origin),
the alert gracefully falls back to showing "VMID XXX".
Proxmox VE 9.x removed support for the 'ds' parameter in RRD endpoints
(/nodes/{node}/rrddata and /nodes/{node}/lxc/{vmid}/rrddata). When Pulse
sent RRD requests with ds=memused,memavailable,etc., Proxmox responded with:
API error 400: {"errors":{"ds":"property is not defined in schema..."}}
This caused cluster nodes to be repeatedly marked unhealthy, which cascaded
into storage polling failures showing 'All cluster endpoints are unhealthy'
even though the nodes were actually healthy and reachable.
Changes:
- Added check in cluster_client.go executeWithFailover to recognize the ds
parameter error as a capability issue rather than node health failure
- Nodes with this error no longer get marked unhealthy
- Storage polling and other operations now succeed even when RRD calls fail
- The RRD data will be unavailable but core monitoring continues
This fix maintains backward compatibility with older Proxmox versions while
gracefully handling the API change in Proxmox 9.x.
The config backup export and import functions were incorrectly parsing
the CSRF token from cookies, causing "Export requires authentication"
errors even when users were properly logged in.
Two issues were fixed:
1. Cookie parsing used `.split('=')[1]` which truncated tokens containing
`=` padding characters (common in base64 tokens). Fixed by using
`.split('=').slice(1).join('=')` to preserve the full value.
2. Missing URL decoding of the cookie value. Browsers percent-encode
cookie values, so `=` becomes `%3D`. The backend then failed to match
the encoded token hash. Fixed by adding `decodeURIComponent()`.
Both fixes mirror the pattern already used in apiClient.ts.
The Pushover webhook template now honors user-defined custom fields
for sound, priority, and device. Previously, these fields were
hardcoded based on alert level, ignoring any custom values set by
users in the UI.
Changes:
- sound: Uses CustomFields.sound if provided, otherwise falls back to
level-based default (critical=siren, warning=tugboat, else=pushover)
- priority: Uses CustomFields.priority if provided, otherwise falls back
to level-based default (critical=1, warning=0, else=-1)
- device: Uses CustomFields.device if provided, otherwise falls back to
ResourceName
Updated setup instructions to document optional custom fields for sound,
priority, and device configuration.
This allows users to customize Pushover notification behavior without
editing webhook templates, consistent with Pulse's maintainability goals.
The custom display name feature added in cd627f33c had a critical bug where
the backend successfully stored custom names but the frontend never received
them, making the feature appear non-functional.
Root cause:
- DockerHost.CustomDisplayName was stored in backend state (models.go:201)
- SetDockerHostCustomDisplayName() correctly updated the field
- BUT DockerHostFrontend struct was missing customDisplayName field
- AND ToFrontend() converter didn't copy CustomDisplayName
- Result: WebSocket state broadcasts stripped out the custom name
When users edited a Docker host display name:
- API returned 200 OK ✓
- Success notification appeared ✓
- Edit state cleared ✓
- But subsequent state broadcasts lacked customDisplayName ✗
- UI continued showing original name ✗
Fix:
- Add CustomDisplayName field to DockerHostFrontend (models_frontend.go:105)
- Copy d.CustomDisplayName in ToFrontend() converter (converters.go:204)
- Now custom display names properly propagate to frontend via WebSocket
The feature now works as originally intended - custom names persist across
agent reconnections and display correctly in the UI.
The temperature collection in pulse-host-agent was broken on all Linux
distributions due to an incorrect platform check.
Root cause:
- collectTemperatures() checked `if a.platform != "linux"` at agent.go:316
- normalisePlatform() returns the raw distro name from gopsutil (debian, ubuntu, pve)
- This caused temperature collection to be skipped on ALL Linux hosts
Fix:
- Changed check to `if runtime.GOOS != "linux"` which correctly identifies Linux
- runtime.GOOS returns "linux" regardless of distribution
Also fixed documentation typo:
- Changed "Servers tab" to "Hosts tab" in HOST_AGENT.md and TEMPERATURE_MONITORING.md
- Reported by user in issue #661 comments
Testing:
- Verified build succeeds
- Confirmed runtime.GOOS returns "linux" on Linux systems
Related to #661
The setup script template had 44 %s placeholders, but the fmt.Sprintf call
arguments were out of order starting at position 15. This caused the Pulse
URL to be inserted where the token name should be, resulting in errors like:
Token ID: pulse-monitor@pam!http://192.168.0.44:7655
Instead of the correct format:
Token ID: pulse-monitor@pam!pulse-192-168-0-44-1762545916
Changes:
- Escaped %s in printf helper (line 3949) so it doesn't consume arguments
- Reordered fmt.Sprintf arguments (lines 4727-4732) to match template order
- Removed 2 extra pulseURL arguments that were causing the shift
This fix ensures all 44 placeholders receive the correct values in order.