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
e2ae9fe5
Commit
e2ae9fe5
authored
Dec 18, 2025
by
shaw
Browse files
fix: release error
parent
20aee89d
Changes
4
Hide whitespace changes
Inline
Side-by-side
backend/cmd/server/main.go
View file @
e2ae9fe5
...
...
@@ -235,6 +235,18 @@ func registerRoutes(r *gin.Engine, h *handler.Handlers, s *service.Services, rep
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"status"
:
"ok"
})
})
// Setup status endpoint (always returns needs_setup: false in normal mode)
// This is used by the frontend to detect when the service has restarted after setup
r
.
GET
(
"/setup/status"
,
func
(
c
*
gin
.
Context
)
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"code"
:
0
,
"data"
:
gin
.
H
{
"needs_setup"
:
false
,
"step"
:
"completed"
,
},
})
})
// API v1
v1
:=
r
.
Group
(
"/api/v1"
)
{
...
...
backend/internal/setup/handler.go
View file @
e2ae9fe5
...
...
@@ -2,11 +2,15 @@ package setup
import
(
"fmt"
"log"
"net/http"
"net/mail"
"os/exec"
"regexp"
"runtime"
"strings"
"sync"
"time"
"sub2api/internal/pkg/response"
...
...
@@ -337,8 +341,41 @@ func install(c *gin.Context) {
return
}
// Schedule service restart in background after sending response
// This ensures the client receives the success response before the service restarts
go
func
()
{
// Wait a moment to ensure the response is sent
time
.
Sleep
(
500
*
time
.
Millisecond
)
triggerServiceRestart
()
}()
response
.
Success
(
c
,
gin
.
H
{
"message"
:
"Installation completed successfully"
,
"message"
:
"Installation completed successfully
. Service will restart automatically.
"
,
"restart"
:
true
,
})
}
// 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"
)
}
backend/internal/web/embed.go
View file @
e2ae9fe5
...
...
@@ -10,7 +10,7 @@ import (
"github.com/gin-gonic/gin"
)
//go:embed dist
/*
//go:embed
all:
dist
var
frontendFS
embed
.
FS
// ServeEmbeddedFrontend returns a Gin handler that serves embedded frontend assets
...
...
frontend/src/views/setup/SetupWizardView.vue
View file @
e2ae9fe5
...
...
@@ -214,12 +214,18 @@
<!-- Success Message -->
<div
v-if=
"installSuccess"
class=
"mt-6 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800/50 rounded-xl"
>
<div
class=
"flex items-start gap-3"
>
<svg
class=
"w-5 h-5 text-green-500 flex-shrink-0"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"1.5"
>
<svg
v-if=
"!serviceReady"
class=
"animate-spin w-5 h-5 text-green-500 flex-shrink-0"
fill=
"none"
viewBox=
"0 0 24 24"
>
<circle
class=
"opacity-25"
cx=
"12"
cy=
"12"
r=
"10"
stroke=
"currentColor"
stroke-width=
"4"
></circle>
<path
class=
"opacity-75"
fill=
"currentColor"
d=
"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<svg
v-else
class=
"w-5 h-5 text-green-500 flex-shrink-0"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"1.5"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<div>
<p
class=
"text-sm font-medium text-green-700 dark:text-green-400"
>
Installation completed!
</p>
<p
class=
"text-sm text-green-600 dark:text-green-500 mt-1"
>
Please restart the service to apply changes.
</p>
<p
class=
"text-sm text-green-600 dark:text-green-500 mt-1"
>
{{ serviceReady ? 'Redirecting to login page...' : 'Service is restarting, please wait...' }}
</p>
</div>
</div>
</div>
...
...
@@ -290,6 +296,7 @@ const dbConnected = ref(false);
const
redisConnected
=
ref
(
false
);
const
installing
=
ref
(
false
);
const
confirmPassword
=
ref
(
''
);
const
serviceReady
=
ref
(
false
);
// Get current server port from browser location (set by install.sh)
const
getCurrentPort
=
():
number
=>
{
...
...
@@ -390,6 +397,8 @@ async function performInstall() {
try
{
await
install
(
formData
);
installSuccess
.
value
=
true
;
// Start polling for service restart
waitForServiceRestart
();
}
catch
(
error
:
unknown
)
{
const
err
=
error
as
{
response
?:
{
data
?:
{
detail
?:
string
}
};
message
?:
string
};
errorMessage
.
value
=
err
.
response
?.
data
?.
detail
||
err
.
message
||
'
Installation failed
'
;
...
...
@@ -397,4 +406,52 @@ async function performInstall() {
installing
.
value
=
false
;
}
}
// Wait for service to restart and become available
async
function
waitForServiceRestart
()
{
const
maxAttempts
=
30
;
// 30 attempts, ~30 seconds max
const
interval
=
1000
;
// 1 second between attempts
// Wait a moment for the service to start restarting
await
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
2000
));
for
(
let
attempt
=
0
;
attempt
<
maxAttempts
;
attempt
++
)
{
try
{
// Try to access the health endpoint
const
response
=
await
fetch
(
'
/health
'
,
{
method
:
'
GET
'
,
cache
:
'
no-store
'
});
if
(
response
.
ok
)
{
// Service is up, check if setup is no longer needed
const
statusResponse
=
await
fetch
(
'
/setup/status
'
,
{
method
:
'
GET
'
,
cache
:
'
no-store
'
});
if
(
statusResponse
.
ok
)
{
const
data
=
await
statusResponse
.
json
();
// If needs_setup is false, service has restarted in normal mode
if
(
data
.
data
&&
!
data
.
data
.
needs_setup
)
{
serviceReady
.
value
=
true
;
// Redirect to login page after a short delay
setTimeout
(()
=>
{
window
.
location
.
href
=
'
/login
'
;
},
1500
);
return
;
}
}
}
}
catch
{
// Service not ready yet, continue polling
}
await
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
interval
));
}
// If we reach here, service didn't restart in time
// Show a message to refresh manually
errorMessage
.
value
=
'
Service restart is taking longer than expected. Please refresh the page manually.
'
;
}
</
script
>
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