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:
Pulse Monitor
2025-08-12 21:08:00 +00:00
parent e21c75645f
commit 62524f040a
3 changed files with 119 additions and 63 deletions
@@ -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
View File
@@ -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)
}
+11
View File
@@ -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()
}