mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-22 03:04:03 +00:00
feat: make security setup fully automatic for systemd
- One-click security that actually applies immediately - Creates systemd override file automatically - Auto-restarts service after 2 seconds - No manual command line steps needed - Shows clear success message about auto-restart - Still provides manual instructions for Docker users
This commit is contained in:
@@ -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:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
|
||||
<p class="text-xs text-blue-700 dark:text-blue-300 font-semibold mb-2">
|
||||
To activate these credentials:
|
||||
<div class="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-3">
|
||||
<p class="text-sm font-semibold text-green-800 dark:text-green-200 mb-2">
|
||||
✅ Security has been automatically enabled!
|
||||
</p>
|
||||
<p class="text-xs text-green-700 dark:text-green-300">
|
||||
Pulse will restart automatically in a few seconds. You'll need to log in with these credentials.
|
||||
</p>
|
||||
<div class="text-xs text-blue-700 dark:text-blue-300 space-y-3">
|
||||
<div>
|
||||
<p class="font-semibold">For systemd (most common):</p>
|
||||
<pre class="bg-blue-100 dark:bg-blue-900 p-2 rounded mt-1 overflow-x-auto">
|
||||
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</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-semibold">For Docker:</p>
|
||||
<pre class="bg-blue-100 dark:bg-blue-900 p-2 rounded mt-1 overflow-x-auto">
|
||||
docker run -d \
|
||||
-e PULSE_AUTH_USER={credentials()!.username} \
|
||||
-e PULSE_AUTH_PASS={credentials()!.password} \
|
||||
-e API_TOKEN={credentials()!.apiToken} \
|
||||
rcourtman/pulse:latest</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
+89
-33
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user