chore: 初始提交 Fonu 1.0.2
- 公网反代:Nginx 状态卡片(版本 / 运行状态 / 配置校验 / 端口监听) - 部署设置第一、二梯队(HTTPS 开关、安全头、限流、连接数、仅中国大陆 IP) - 隧道检测兼容 frps v0.68 / v0.71(V2 与旧版端点) - 恢复被清空的 internal/frp/store.go、internal/api/ddns_handler.go、web/src/views/DdnsView.vue
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fonu/fonu/internal/auth"
|
||||
)
|
||||
|
||||
func NewRouter(deps Deps) http.Handler {
|
||||
logsHandler := NewLogsHandler(deps.Config)
|
||||
r := &Router{
|
||||
authHandler: NewAuthHandler(deps.Auth, deps.Notify),
|
||||
proxyHandler: NewProxyHandler(deps.Config, deps.Proxy, logsHandler, deps.Traffic),
|
||||
nginxHandler: NewNginxHandler(deps.Proxy),
|
||||
statusHandler: NewStatusHandler(deps.Config, deps.Proxy, deps.DDNS, deps.ACME, deps.StartedAt, deps.Config.NginxPIDFile),
|
||||
ddnsHandler: NewDDNSHandler(deps.DDNS),
|
||||
certHandler: NewCertHandler(deps.ACME),
|
||||
settingsHandler: NewSettingsHandler(deps.Settings, deps.Proxy, deps.Notify),
|
||||
chinaCIDRHandler: NewChinaCIDRHandler(deps.ChinaCIDR),
|
||||
logsHandler: logsHandler,
|
||||
backupHandler: NewBackupHandler(deps.Backup),
|
||||
discoveryHandler: NewDiscoveryHandler(deps.Discovery),
|
||||
frpmultiHandler: NewFRPMultiHandler(deps.FRPMulti),
|
||||
authSvc: deps.Auth,
|
||||
staticFS: deps.StaticFS,
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("GET /api/version", Version(deps.Config))
|
||||
mux.HandleFunc("GET /api/auth/status", r.authHandler.Status)
|
||||
mux.HandleFunc("POST /api/auth/login", r.authHandler.Login)
|
||||
mux.HandleFunc("POST /api/auth/logout", r.authHandler.Logout)
|
||||
|
||||
protect := func(pattern string, handler http.HandlerFunc) {
|
||||
mux.Handle(pattern, SessionMiddleware(deps.Auth)(RequireAuth(deps.Auth)(http.HandlerFunc(handler))))
|
||||
}
|
||||
|
||||
protect("POST /api/auth/password", r.authHandler.ChangePassword)
|
||||
protect("GET /api/status", r.statusHandler.Get)
|
||||
protect("GET /api/proxies", r.proxyHandler.List)
|
||||
protect("GET /api/proxies/traffic", r.proxyHandler.Traffic)
|
||||
protect("POST /api/proxies", r.proxyHandler.Create)
|
||||
protect("PUT /api/proxies/reorder", r.proxyHandler.Reorder)
|
||||
protect("PUT /api/proxies/{id}", r.proxyHandler.Update)
|
||||
protect("DELETE /api/proxies/{id}", r.proxyHandler.Delete)
|
||||
protect("GET /api/proxies/{id}/logs/stream", r.proxyHandler.StreamLogs)
|
||||
protect("GET /api/proxies/{id}/clients", r.proxyHandler.Clients)
|
||||
protect("GET /api/proxies/{id}/nginx", r.nginxHandler.GetRule)
|
||||
protect("PUT /api/proxies/{id}/nginx", r.nginxHandler.PutRule)
|
||||
protect("POST /api/proxies/{id}/nginx/rollback", r.nginxHandler.RollbackRule)
|
||||
protect("GET /api/settings/nginx/global", r.nginxHandler.GetGlobal)
|
||||
protect("PUT /api/settings/nginx/global", r.nginxHandler.PutGlobal)
|
||||
protect("POST /api/settings/nginx/global/rollback", r.nginxHandler.RollbackGlobal)
|
||||
protect("GET /api/ddns", r.ddnsHandler.List)
|
||||
protect("POST /api/ddns", r.ddnsHandler.Create)
|
||||
protect("PUT /api/ddns/{id}", r.ddnsHandler.Update)
|
||||
protect("DELETE /api/ddns/{id}", r.ddnsHandler.Delete)
|
||||
protect("POST /api/ddns/test", r.ddnsHandler.Test)
|
||||
protect("POST /api/ddns/update", r.ddnsHandler.UpdateAll)
|
||||
protect("POST /api/ddns/{id}/update", r.ddnsHandler.UpdateOne)
|
||||
protect("GET /api/certificates", r.certHandler.List)
|
||||
protect("GET /api/certificates/ca-options", r.certHandler.Options)
|
||||
protect("POST /api/certificates/apply", r.certHandler.Apply)
|
||||
protect("GET /api/certificates/jobs/{id}/stream", r.certHandler.ApplyJobStream)
|
||||
protect("POST /api/certificates/import", r.certHandler.Import)
|
||||
protect("POST /api/certificates/renew", r.certHandler.Renew)
|
||||
protect("GET /api/certificates/{domain}/download", r.certHandler.Download)
|
||||
protect("DELETE /api/certificates/{domain}", r.certHandler.Delete)
|
||||
notifyHandler := NewNotifyHandler(deps.Notify)
|
||||
protect("GET /api/settings", r.settingsHandler.Get)
|
||||
protect("PUT /api/settings", r.settingsHandler.Put)
|
||||
protect("POST /api/settings/notify/test", notifyHandler.Test)
|
||||
if deps.ChinaCIDR != nil {
|
||||
protect("GET /api/settings/china-cidr", r.chinaCIDRHandler.Status)
|
||||
protect("POST /api/settings/china-cidr/refresh", r.chinaCIDRHandler.Refresh)
|
||||
}
|
||||
protect("GET /api/logs/access", r.logsHandler.Access)
|
||||
protect("GET /api/logs/error", r.logsHandler.Error)
|
||||
protect("GET /api/logs/system", r.logsHandler.System)
|
||||
protect("GET /api/logs/stream", r.logsHandler.Stream)
|
||||
protect("GET /api/backup/export", r.backupHandler.Export)
|
||||
protect("POST /api/backup/restore", r.backupHandler.Restore)
|
||||
protect("GET /api/discovery/scan", r.discoveryHandler.Scan)
|
||||
if deps.FRPMulti != nil {
|
||||
frpHandler := r.frpmultiHandler
|
||||
protect("GET /api/frpmulti/status", frpHandler.Status)
|
||||
protect("GET /api/frpmulti/runtime", frpHandler.Runtime)
|
||||
protect("PUT /api/frpmulti/runtime/download-proxy", frpHandler.SetDownloadProxy)
|
||||
protect("GET /api/frpmulti/releases", frpHandler.Releases)
|
||||
protect("GET /api/frpmulti/binaries", frpHandler.Binaries)
|
||||
protect("GET /api/frpmulti/download", frpHandler.DownloadStatus)
|
||||
protect("POST /api/frpmulti/releases/{version}/download", frpHandler.DownloadRelease)
|
||||
protect("POST /api/frpmulti/binaries/{version}/activate", frpHandler.ActivateBinary)
|
||||
protect("DELETE /api/frpmulti/binaries/{version}", frpHandler.DeleteBinary)
|
||||
protect("GET /api/frpmulti/servers", frpHandler.ListServers)
|
||||
protect("GET /api/frpmulti/servers/{id}/detail", frpHandler.ServerDetail)
|
||||
protect("GET /api/frpmulti/servers/{id}/diagnostics", frpHandler.ServerDiagnostics)
|
||||
protect("GET /api/frpmulti/servers/{id}/frps-config", frpHandler.FRPSServerConfig)
|
||||
protect("POST /api/frpmulti/servers/{id}/start", frpHandler.StartServer)
|
||||
protect("POST /api/frpmulti/servers/{id}/stop", frpHandler.StopServer)
|
||||
protect("POST /api/frpmulti/servers", frpHandler.CreateServer)
|
||||
protect("PUT /api/frpmulti/servers/{id}", frpHandler.UpdateServer)
|
||||
protect("DELETE /api/frpmulti/servers/{id}", frpHandler.DeleteServer)
|
||||
protect("POST /api/frpmulti/servers/{id}/test", frpHandler.TestServer)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/probe", frpHandler.AgentProbe)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/install", frpHandler.AgentInstall)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/uninstall", frpHandler.AgentUninstall)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/ssh-diagnose", frpHandler.AgentSSHDiagnose)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/test", frpHandler.AgentTest)
|
||||
protect("GET /api/frpmulti/servers/{id}/agent/status", frpHandler.AgentStatus)
|
||||
protect("GET /api/frpmulti/servers/{id}/agent/routes", frpHandler.AgentRoutes)
|
||||
protect("GET /api/frpmulti/servers/{id}/agent/routes/health", frpHandler.AgentRouteHealth)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/nginx/install", frpHandler.AgentInstallNginx)
|
||||
protect("GET /api/frpmulti/servers/{id}/agent/routes/{proxyID}/conf", frpHandler.AgentRouteConf)
|
||||
protect("GET /api/frpmulti/servers/{id}/agent/route-candidates", frpHandler.AgentRouteCandidates)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/routes/{proxyID}", frpHandler.DeployAgentRoute)
|
||||
protect("DELETE /api/frpmulti/servers/{id}/agent/routes/{proxyID}", frpHandler.RemoveAgentRoute)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/frps-install", frpHandler.InstallFRPSOnAgent)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/frps-action", frpHandler.AgentFRPSAction)
|
||||
protect("POST /api/frpmulti/servers/{id}/agent/frps-config", frpHandler.PushFRPSConfigToAgent)
|
||||
protect("GET /api/frpmulti/proxies", frpHandler.ListProxies)
|
||||
protect("GET /api/frpmulti/proxies/traffic", frpHandler.ListProxiesTraffic)
|
||||
protect("POST /api/frpmulti/proxies", frpHandler.CreateProxy)
|
||||
protect("PUT /api/frpmulti/proxies/{id}", frpHandler.UpdateProxy)
|
||||
protect("DELETE /api/frpmulti/proxies/{id}", frpHandler.DeleteProxy)
|
||||
protect("GET /api/frpmulti/proxies/{id}/logs", frpHandler.ProxyLogs)
|
||||
protect("POST /api/frpmulti/start", frpHandler.Start)
|
||||
protect("POST /api/frpmulti/stop", frpHandler.Stop)
|
||||
protect("POST /api/frpmulti/reload", frpHandler.Reload)
|
||||
protect("GET /api/frpmulti/logs", frpHandler.Logs)
|
||||
protect("GET /api/frpmulti/config/export", frpHandler.ExportConfig)
|
||||
}
|
||||
|
||||
if deps.StaticFS != nil {
|
||||
mux.Handle("/", r.spaHandler())
|
||||
}
|
||||
|
||||
logger := deps.Logger
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
return loggingMiddleware(logger, SessionMiddleware(deps.Auth)(mux))
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
authHandler *AuthHandler
|
||||
proxyHandler *ProxyHandler
|
||||
nginxHandler *NginxHandler
|
||||
statusHandler *StatusHandler
|
||||
ddnsHandler *DDNSHandler
|
||||
certHandler *CertHandler
|
||||
settingsHandler *SettingsHandler
|
||||
chinaCIDRHandler *ChinaCIDRHandler
|
||||
logsHandler *LogsHandler
|
||||
backupHandler *BackupHandler
|
||||
discoveryHandler *DiscoveryHandler
|
||||
frpmultiHandler *FRPMultiHandler
|
||||
authSvc *auth.Service
|
||||
staticFS fs.FS
|
||||
}
|
||||
|
||||
func (r *Router) spaHandler() http.Handler {
|
||||
fileServer := http.FileServer(http.FS(r.staticFS))
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if strings.HasPrefix(req.URL.Path, "/api/") {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
path := strings.TrimPrefix(req.URL.Path, "/")
|
||||
if path == "" {
|
||||
path = "index.html"
|
||||
}
|
||||
if _, err := fs.Stat(r.staticFS, path); err != nil {
|
||||
// SPA fallback: unknown routes serve index.html
|
||||
req.URL.Path = "/index.html"
|
||||
}
|
||||
fileServer.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
type statusRecorder struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (w *statusRecorder) WriteHeader(code int) {
|
||||
w.status = code
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *statusRecorder) Flush() {
|
||||
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func loggingMiddleware(logger *slog.Logger, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
|
||||
next.ServeHTTP(rec, r)
|
||||
if strings.HasPrefix(r.URL.Path, "/api/") &&
|
||||
!strings.Contains(r.URL.Path, "/logs/stream") &&
|
||||
!strings.Contains(r.URL.Path, "/certificates/jobs/") &&
|
||||
rec.status >= 400 {
|
||||
level := slog.LevelWarn
|
||||
msg := "api request completed with client error"
|
||||
if rec.status >= 500 {
|
||||
level = slog.LevelError
|
||||
msg = "api request failed"
|
||||
}
|
||||
logger.Log(r.Context(), level, msg,
|
||||
"module", "HTTP",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"status", rec.status,
|
||||
"duration_ms", time.Since(start).Milliseconds(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user