From 527f53ee32a107d685b52908bfd0d64e2dff5d40 Mon Sep 17 00:00:00 2001 From: Pulse Monitor Date: Thu, 14 Aug 2025 22:00:12 +0000 Subject: [PATCH] chore: bump version to v4.3.8 Emergency release to fix critical issues in v4.3.7: - Install script now correctly installs binary to /opt/pulse/bin/pulse - Password changes no longer require sudo (addresses #317) These fixes restore basic functionality for new installations and Docker deployments. --- VERSION | 2 +- install.sh | 18 +++++----- internal/api/router.go | 82 +++++++++++++++++++++++++++++------------- 3 files changed, 67 insertions(+), 35 deletions(-) diff --git a/VERSION b/VERSION index 7e7f33c2e..3bcca128b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.3.7 +4.3.8 diff --git a/install.sh b/install.sh index 34dd7f21b..c6804e0d5 100755 --- a/install.sh +++ b/install.sh @@ -197,26 +197,26 @@ download_pulse() { mkdir -p "$TEMP_EXTRACT" tar -xzf pulse.tar.gz -C "$TEMP_EXTRACT" - # Ensure install directory exists before copying - mkdir -p "$INSTALL_DIR" + # Ensure install directory and bin subdirectory exist + mkdir -p "$INSTALL_DIR/bin" - # Copy Pulse binary from bin/ directory (standard structure as of v4.3.1) + # Copy Pulse binary to the correct location (/opt/pulse/bin/pulse) if [[ -f "$TEMP_EXTRACT/bin/pulse" ]]; then - cp "$TEMP_EXTRACT/bin/pulse" "$INSTALL_DIR/pulse" + cp "$TEMP_EXTRACT/bin/pulse" "$INSTALL_DIR/bin/pulse" elif [[ -f "$TEMP_EXTRACT/pulse" ]]; then # Fallback for old archives (pre-v4.3.1) - cp "$TEMP_EXTRACT/pulse" "$INSTALL_DIR/pulse" + cp "$TEMP_EXTRACT/pulse" "$INSTALL_DIR/bin/pulse" else print_error "Pulse binary not found in archive" exit 1 fi - chmod +x "$INSTALL_DIR/pulse" + chmod +x "$INSTALL_DIR/bin/pulse" chown -R pulse:pulse "$INSTALL_DIR" # Create symlink in /usr/local/bin for PATH convenience - ln -sf "$INSTALL_DIR/pulse" /usr/local/bin/pulse - print_success "Pulse binary installed to $INSTALL_DIR/pulse" + ln -sf "$INSTALL_DIR/bin/pulse" /usr/local/bin/pulse + print_success "Pulse binary installed to $INSTALL_DIR/bin/pulse" print_success "Symlink created at /usr/local/bin/pulse" # Copy VERSION file if present @@ -254,7 +254,7 @@ Type=simple User=pulse Group=pulse WorkingDirectory=$INSTALL_DIR -ExecStart=$INSTALL_DIR/pulse +ExecStart=$INSTALL_DIR/bin/pulse Restart=always RestartSec=3 StandardOutput=journal diff --git a/internal/api/router.go b/internal/api/router.go index 463492984..14d776aed 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -892,45 +892,77 @@ PULSE_AUTH_PASS='%s' }) } else { - // For systemd installations, use the existing script method - scriptPath := "/opt/pulse/scripts/change-password.sh" - cmd := exec.Command("sudo", scriptPath, hashedPassword) - _, err = cmd.CombinedOutput() - if err != nil { - log.Error().Err(err).Msg("Failed to change password via script") + // For non-Docker (systemd/manual), save to .env file + envPath := filepath.Join(r.config.ConfigPath, ".env") + if r.config.ConfigPath == "" { + envPath = "/etc/pulse/.env" + } + + // Read existing .env file to preserve other settings + envContent := "" + existingContent, err := os.ReadFile(envPath) + if err == nil { + // Parse and update existing content + scanner := bufio.NewScanner(strings.NewReader(string(existingContent))) + for scanner.Scan() { + line := scanner.Text() + if line == "" || strings.HasPrefix(line, "#") { + envContent += line + "\n" + continue + } + // Update password line, keep others + if strings.HasPrefix(line, "PULSE_AUTH_PASS=") { + envContent += fmt.Sprintf("PULSE_AUTH_PASS='%s'\n", hashedPassword) + } else { + envContent += line + "\n" + } + } + } else { + // Create new .env if doesn't exist + envContent = fmt.Sprintf(`# Auto-generated by Pulse password change +# Generated on %s +PULSE_AUTH_USER='%s' +PULSE_AUTH_PASS='%s' +`, time.Now().Format(time.RFC3339), r.config.AuthUser, hashedPassword) + + if r.config.APIToken != "" { + envContent += fmt.Sprintf("API_TOKEN='%s'\n", r.config.APIToken) + } + } + + // Try to write the .env file + if err := os.WriteFile(envPath, []byte(envContent), 0600); err != nil { + log.Error().Err(err).Str("path", envPath).Msg("Failed to write .env file") writeErrorResponse(w, http.StatusInternalServerError, "config_error", - "Failed to save new password", nil) + "Failed to save new password. You may need to update the password manually.", nil) return } - - // Update the running config with the HASHED password + + // Update the running config r.config.AuthPass = hashedPassword - + log.Info().Msg("Password changed successfully") - // Invalidate all sessions for this user (forces re-login with new password) + // Invalidate all sessions InvalidateUserSessions(r.config.AuthUser) - // Audit log password change + // Audit log LogAuditEvent("password_change", r.config.AuthUser, GetClientIP(req), req.URL.Path, true, "Password changed") - // Return success + // Detect service name for restart instructions + serviceName := "pulse" + if _, err := os.Stat("/etc/systemd/system/pulse-backend.service"); err == nil { + serviceName = "pulse-backend" + } + + // Return success with manual restart instructions w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, - "message": "Password changed successfully. Service will restart.", + "message": fmt.Sprintf("Password changed. Restart the service to apply: sudo systemctl restart %s", serviceName), + "requiresRestart": true, + "serviceName": serviceName, }) - - // Trigger service restart in background for systemd - go func() { - time.Sleep(2 * time.Second) - log.Info().Msg("Restarting service to apply new password") - // Use sudo to restart the service - cmd := exec.Command("sudo", "systemctl", "restart", "pulse-backend") - if err := cmd.Run(); err != nil { - log.Error().Err(err).Msg("Failed to restart service after password change") - } - }() } }