chore(release): 发布 v1.9.2
CI / verify (push) Canceled after 0s
Docker Release / build-and-push (amd64, linux/amd64) (push) Canceled after 0s
Docker Release / build-and-push (arm64, linux/arm64) (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Docker Release / create-manifest (push) Canceled after 0s

- 「公网服务端」新增 Nginx 启停控制(Agent 0.15.2,停止带二次确认)
- frps 启动失败不再误报成功,并增强启动诊断
- frps vhost 端口禁止 80/443,避免与 VPS 上 Nginx 冲突
- 旧服务端配置的 tcp_mux / login_fail_exit 默认值补齐
- 新增安全审查、页面布局方案与场景操作文档
This commit is contained in:
2026-10-07 19:26:02 +08:00
parent a7d71752e3
commit 2a0740a60f
55 changed files with 5065 additions and 467 deletions
+47 -1
View File
@@ -6,9 +6,11 @@ import (
"io"
"io/fs"
"log/slog"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/havline/havline/internal/acme"
@@ -152,6 +154,9 @@ func New(cfg config.Config, staticFS fs.FS, migrationsDir string) (*App, error)
StaticFS: staticFS,
StartedAt: startedAt,
})
if prefix := strings.TrimSpace(cfg.GatewayPrefix); prefix != "" {
handler = stripPrefixIfPresent(prefix, handler)
}
server := &http.Server{
Addr: cfg.ListenAddr,
Handler: handler,
@@ -243,7 +248,32 @@ func (a *App) bootstrapNginx(ctx context.Context, proxySvc *service.ProxyService
}
func (a *App) Run() error {
return a.server.ListenAndServe()
if strings.TrimSpace(a.cfg.GatewaySocket) == "" {
return a.server.ListenAndServe()
}
// 先准备两端监听,任何一端失败都不留下半启动的服务。
tcp, err := net.Listen("tcp", a.server.Addr)
if err != nil {
return err
}
defer tcp.Close()
socket := strings.TrimSpace(a.cfg.GatewaySocket)
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
return err
}
listener, err := net.Listen("unix", socket)
if err != nil {
return err
}
defer listener.Close()
defer os.Remove(socket)
if err := os.Chmod(socket, 0o660); err != nil {
return err
}
errCh := make(chan error, 2)
go func() { errCh <- a.server.Serve(tcp) }()
go func() { errCh <- a.server.Serve(listener) }()
return <-errCh
}
func (a *App) Shutdown(ctx context.Context) error {
@@ -254,6 +284,9 @@ func (a *App) Shutdown(ctx context.Context) error {
if err := a.server.Shutdown(ctx); err != nil {
return err
}
if socket := strings.TrimSpace(a.cfg.GatewaySocket); socket != "" {
_ = os.Remove(socket)
}
if err := a.nginx.Stop(ctx); err != nil {
a.logger.Error("nginx stop failed", "module", "NGINX", "error", err.Error())
}
@@ -268,6 +301,19 @@ func (a *App) Shutdown(ctx context.Context) error {
return nil
}
func stripPrefixIfPresent(prefix string, next http.Handler) http.Handler {
prefix = strings.TrimRight(prefix, "/")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == prefix || strings.HasPrefix(r.URL.Path, prefix+"/") {
r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix)
if r.URL.Path == "" {
r.URL.Path = "/"
}
}
next.ServeHTTP(w, r)
})
}
func ResolveMigrationsDir() (string, error) {
candidates := []string{
"migrations",