fix: 修复安装/升级无法重启服务的问题
This commit is contained in:
@@ -2,8 +2,10 @@ package admin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"sub2api/internal/pkg/response"
|
"sub2api/internal/pkg/response"
|
||||||
|
"sub2api/internal/pkg/sysutil"
|
||||||
"sub2api/internal/service"
|
"sub2api/internal/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -72,10 +74,14 @@ func (h *SystemHandler) Rollback(c *gin.Context) {
|
|||||||
// RestartService restarts the systemd service
|
// RestartService restarts the systemd service
|
||||||
// POST /api/v1/admin/system/restart
|
// POST /api/v1/admin/system/restart
|
||||||
func (h *SystemHandler) RestartService(c *gin.Context) {
|
func (h *SystemHandler) RestartService(c *gin.Context) {
|
||||||
if err := h.updateSvc.RestartService(); err != nil {
|
// Schedule service restart in background after sending response
|
||||||
response.Error(c, http.StatusInternalServerError, err.Error())
|
// This ensures the client receives the success response before the service restarts
|
||||||
return
|
go func() {
|
||||||
}
|
// Wait a moment to ensure the response is sent
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
sysutil.RestartServiceAsync()
|
||||||
|
}()
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
"message": "Service restart initiated",
|
"message": "Service restart initiated",
|
||||||
})
|
})
|
||||||
|
|||||||
51
backend/internal/pkg/sysutil/restart.go
Normal file
51
backend/internal/pkg/sysutil/restart.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package sysutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
const serviceName = "sub2api"
|
||||||
|
|
||||||
|
// RestartService triggers a service restart via systemd.
|
||||||
|
//
|
||||||
|
// IMPORTANT: This function initiates the restart and returns immediately.
|
||||||
|
// The actual restart happens asynchronously - the current process will be killed
|
||||||
|
// by systemd and a new process will be started.
|
||||||
|
//
|
||||||
|
// We use Start() instead of Run() because:
|
||||||
|
// - systemctl restart will kill the current process first
|
||||||
|
// - Run() waits for completion, but the process dies before completion
|
||||||
|
// - Start() spawns the command independently, allowing systemd to handle the full cycle
|
||||||
|
//
|
||||||
|
// Prerequisites:
|
||||||
|
// - Linux OS with systemd
|
||||||
|
// - NOPASSWD sudo access configured (install.sh creates /etc/sudoers.d/sub2api)
|
||||||
|
func RestartService() error {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return fmt.Errorf("systemd restart only available on Linux")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Initiating service restart...")
|
||||||
|
|
||||||
|
// The sub2api user has NOPASSWD sudo access for systemctl commands
|
||||||
|
// (configured by install.sh in /etc/sudoers.d/sub2api).
|
||||||
|
cmd := exec.Command("sudo", "systemctl", "restart", serviceName)
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return fmt.Errorf("failed to initiate service restart: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Service restart initiated successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestartServiceAsync is a fire-and-forget version of RestartService.
|
||||||
|
// It logs errors instead of returning them, suitable for goroutine usage.
|
||||||
|
func RestartServiceAsync() {
|
||||||
|
if err := RestartService(); err != nil {
|
||||||
|
log.Printf("Service restart failed: %v", err)
|
||||||
|
log.Println("Please restart the service manually: sudo systemctl restart sub2api")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -244,23 +243,6 @@ func (s *UpdateService) Rollback() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartService triggers a service restart via systemd
|
|
||||||
func (s *UpdateService) RestartService() error {
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
return fmt.Errorf("systemd restart only available on Linux")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try direct systemctl first (works if running as root or with proper permissions)
|
|
||||||
cmd := exec.Command("systemctl", "restart", "sub2api")
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
// Try with sudo (requires NOPASSWD sudoers entry)
|
|
||||||
sudoCmd := exec.Command("sudo", "systemctl", "restart", "sub2api")
|
|
||||||
if sudoErr := sudoCmd.Run(); sudoErr != nil {
|
|
||||||
return fmt.Errorf("systemctl restart failed: %w (sudo also failed: %v)", err, sudoErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *UpdateService) fetchLatestRelease(ctx context.Context) (*UpdateInfo, error) {
|
func (s *UpdateService) fetchLatestRelease(ctx context.Context) (*UpdateInfo, error) {
|
||||||
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", githubRepo)
|
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", githubRepo)
|
||||||
|
|||||||
@@ -2,17 +2,15 @@ package setup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sub2api/internal/pkg/response"
|
"sub2api/internal/pkg/response"
|
||||||
|
"sub2api/internal/pkg/sysutil"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -346,7 +344,7 @@ func install(c *gin.Context) {
|
|||||||
go func() {
|
go func() {
|
||||||
// Wait a moment to ensure the response is sent
|
// Wait a moment to ensure the response is sent
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
triggerServiceRestart()
|
sysutil.RestartServiceAsync()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
response.Success(c, gin.H{
|
response.Success(c, gin.H{
|
||||||
@@ -355,27 +353,3 @@ func install(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// triggerServiceRestart attempts to restart the service via systemd
|
|
||||||
// This is called after setup completes to switch from setup mode to normal mode
|
|
||||||
func triggerServiceRestart() {
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
log.Println("Service restart: not on Linux, manual restart required")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Setup completed, triggering service restart...")
|
|
||||||
|
|
||||||
// Try direct systemctl first (works if running as root or with proper permissions)
|
|
||||||
cmd := exec.Command("systemctl", "restart", "sub2api")
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
// Try with sudo (requires NOPASSWD sudoers entry)
|
|
||||||
sudoCmd := exec.Command("sudo", "systemctl", "restart", "sub2api")
|
|
||||||
if sudoErr := sudoCmd.Run(); sudoErr != nil {
|
|
||||||
log.Printf("Service restart failed: %v (sudo also failed: %v)", err, sudoErr)
|
|
||||||
log.Println("Please restart the service manually: sudo systemctl restart sub2api")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Service restart initiated successfully")
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user