Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
8e4bd42e
Commit
8e4bd42e
authored
Dec 18, 2025
by
shaw
Browse files
fix: 修复安装/升级无法重启服务的问题
parent
ef3199f0
Changes
4
Show whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/system_handler.go
View file @
8e4bd42e
...
@@ -2,8 +2,10 @@ package admin
...
@@ -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) {
...
@@ -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"
,
})
})
...
...
backend/internal/pkg/sysutil/restart.go
0 → 100644
View file @
8e4bd42e
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"
)
}
}
backend/internal/service/update_service.go
View file @
8e4bd42e
...
@@ -13,7 +13,6 @@ import (
...
@@ -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 {
...
@@ -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
)
...
...
backend/internal/setup/handler.go
View file @
8e4bd42e
...
@@ -2,17 +2,15 @@ package setup
...
@@ -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) {
...
@@ -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) {
...
@@ -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"
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment