diff --git a/frontend-modern/src/components/Settings/QuickSecuritySetup.tsx b/frontend-modern/src/components/Settings/QuickSecuritySetup.tsx index 8d4aad598..350208e7f 100644 --- a/frontend-modern/src/components/Settings/QuickSecuritySetup.tsx +++ b/frontend-modern/src/components/Settings/QuickSecuritySetup.tsx @@ -65,9 +65,21 @@ export const QuickSecuritySetup: Component = () => { throw new Error(error || 'Failed to setup security'); } + const result = await response.json(); setCredentials(newCredentials); setShowCredentials(true); - showSuccess('Security enabled successfully!'); + + if (result.method === 'systemd' && result.willRestart) { + showSuccess('Security enabled! Pulse will restart automatically in 2 seconds...'); + // Show countdown + setTimeout(() => { + showSuccess('Restarting now... You will need to log in with your new credentials.'); + }, 2000); + } else if (result.method === 'docker') { + showSuccess('Security configured! Please restart your Docker container with the credentials shown.'); + } else { + showSuccess('Security configured! Please restart Pulse to apply settings.'); + } } catch (error) { showError(`Failed to setup security: ${error}`); } finally { @@ -238,36 +250,13 @@ Important: -
-

- To activate these credentials: +

+

+ ✅ Security has been automatically enabled! +

+

+ Pulse will restart automatically in a few seconds. You'll need to log in with these credentials.

-
-
-

For systemd (most common):

-
-sudo systemctl edit pulse-backend
-
-# Add these lines:
-[Service]
-Environment="PULSE_AUTH_USER={credentials()!.username}"
-Environment="PULSE_AUTH_PASS={credentials()!.password}"
-Environment="API_TOKEN={credentials()!.apiToken}"
-
-# Save and exit, then:
-sudo systemctl restart pulse-backend
-
- -
-

For Docker:

-
-docker run -d \
-  -e PULSE_AUTH_USER={credentials()!.username} \
-  -e PULSE_AUTH_PASS={credentials()!.password} \
-  -e API_TOKEN={credentials()!.apiToken} \
-  rcourtman/pulse:latest
-
-
diff --git a/internal/api/router.go b/internal/api/router.go index 9d6a8bef9..c750150af 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "os" + "path/filepath" "strings" "time" @@ -261,44 +262,99 @@ func (r *Router) setupRoutes() { return } - // Create config file for systemd environment - configContent := fmt.Sprintf(`# Pulse Security Configuration -# Generated by Quick Security Setup on %s -# -# Add these to your systemd service configuration: -# sudo systemctl edit pulse-backend -# -# [Service] -# Environment="PULSE_AUTH_USER=%s" -# Environment="PULSE_AUTH_PASS=%s" -# Environment="API_TOKEN=%s" -# Environment="ENABLE_AUDIT_LOG=true" -# -# Then restart the service: -# sudo systemctl restart pulse-backend - + // Check if we're running under systemd + isSystemd := os.Getenv("INVOCATION_ID") != "" + isDocker := os.Getenv("PULSE_DOCKER") == "true" + + if isSystemd { + // We're running under systemd - create override file + overridePath := "/etc/systemd/system/pulse-backend.service.d/override.conf" + overrideDir := filepath.Dir(overridePath) + + // Create override directory + if err := os.MkdirAll(overrideDir, 0755); err != nil { + log.Error().Err(err).Msg("Failed to create systemd override directory") + http.Error(w, "Failed to create systemd configuration", http.StatusInternalServerError) + return + } + + // Create override content + overrideContent := fmt.Sprintf(`# Auto-generated by Pulse Quick Security Setup +# Generated on %s +[Service] +Environment="PULSE_AUTH_USER=%s" +Environment="PULSE_AUTH_PASS=%s" +Environment="API_TOKEN=%s" +Environment="ENABLE_AUDIT_LOG=true" +`, time.Now().Format(time.RFC3339), setupRequest.Username, setupRequest.Password, setupRequest.APIToken) + + // Write override file + if err := os.WriteFile(overridePath, []byte(overrideContent), 0644); err != nil { + log.Error().Err(err).Msg("Failed to write systemd override") + http.Error(w, "Failed to write systemd configuration", http.StatusInternalServerError) + return + } + + // Reload systemd and restart service + if err := utils.RunCommand("systemctl", "daemon-reload"); err != nil { + log.Error().Err(err).Msg("Failed to reload systemd") + } + + // Schedule restart after response (so user gets the credentials) + go func() { + time.Sleep(2 * time.Second) + log.Info().Msg("Restarting Pulse to apply security settings...") + utils.RunCommand("systemctl", "restart", "pulse-backend") + }() + + response := map[string]interface{}{ + "success": true, + "method": "systemd", + "willRestart": true, + "message": "Security enabled! Pulse will restart in 2 seconds to apply settings.", + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + + } else if isDocker { + // For Docker, we can't modify the running container + // But we can save settings and provide docker run command + response := map[string]interface{}{ + "success": true, + "method": "docker", + "requiresManualRestart": true, + "message": "Security configuration generated. Restart your Docker container with the environment variables shown.", + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + + } else { + // Development or manual installation + // Save to .env file for next restart + envPath := filepath.Join(r.config.ConfigPath, ".env") + envContent := fmt.Sprintf(`# Auto-generated by Pulse Quick Security Setup PULSE_AUTH_USER=%s PULSE_AUTH_PASS=%s API_TOKEN=%s ENABLE_AUDIT_LOG=true -`, time.Now().Format(time.RFC3339), setupRequest.Username, setupRequest.Password, setupRequest.APIToken, - setupRequest.Username, setupRequest.Password, setupRequest.APIToken) - - // Save to a temporary config file for user reference - configPath := fmt.Sprintf("%s/security-config-%d.env", r.config.DataPath, time.Now().Unix()) - if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil { - log.Error().Err(err).Msg("Failed to write security config file") +`, setupRequest.Username, setupRequest.Password, setupRequest.APIToken) + + if err := os.WriteFile(envPath, []byte(envContent), 0600); err != nil { + log.Error().Err(err).Msg("Failed to write .env file") + } + + response := map[string]interface{}{ + "success": true, + "method": "manual", + "envFile": envPath, + "message": "Security configuration saved. Restart Pulse to apply settings.", + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) } - - // Return success with instructions - response := map[string]interface{}{ - "success": true, - "configPath": configPath, - "instructions": "Security configuration has been generated. Please update your systemd service configuration with the provided environment variables and restart Pulse.", - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) } else { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } diff --git a/internal/utils/command.go b/internal/utils/command.go new file mode 100644 index 000000000..8491380fc --- /dev/null +++ b/internal/utils/command.go @@ -0,0 +1,11 @@ +package utils + +import ( + "os/exec" +) + +// RunCommand executes a system command +func RunCommand(name string, args ...string) error { + cmd := exec.Command(name, args...) + return cmd.Run() +} \ No newline at end of file