feat: add Fiber HTTPS server with embedded static files

This commit is contained in:
2026-03-01 03:03:15 +08:00
parent 044a8aa166
commit 4ebc9226ed
4 changed files with 262 additions and 0 deletions

34
internal/server/net.go Normal file
View File

@@ -0,0 +1,34 @@
package server
import (
"crypto/rand"
"encoding/hex"
"fmt"
"log/slog"
"net"
)
// GetLANIP returns the first non-loopback IPv4 address.
func GetLANIP() (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil {
return ipNet.IP.String(), nil
}
}
return "", fmt.Errorf("no LAN IP found")
}
// GenerateToken creates a cryptographically random token for WS auth.
func GenerateToken() string {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
slog.Error("failed to generate token", "error", err)
// Fallback to a less secure but functional token
return "voicepaste-fallback-token"
}
return hex.EncodeToString(b)
}

91
internal/server/server.go Normal file
View File

@@ -0,0 +1,91 @@
package server
import (
"crypto/tls"
"fmt"
"io/fs"
"log/slog"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/static"
"github.com/imbytecat/voicepaste/internal/config"
vpTLS "github.com/imbytecat/voicepaste/internal/tls"
)
// Server holds the Fiber app and related state.
type Server struct {
app *fiber.App
token string
lanIP string
webFS fs.FS
}
// New creates a new Server instance.
func New(token, lanIP string, webFS fs.FS) *Server {
app := fiber.New(fiber.Config{
AppName: "VoicePaste",
})
s := &Server{
app: app,
token: token,
lanIP: lanIP,
webFS: webFS,
}
s.setupRoutes()
return s
}
// setupRoutes configures HTTP routes.
func (s *Server) setupRoutes() {
// Health check
s.app.Get("/health", func(c fiber.Ctx) error {
return c.SendString("ok")
})
// Static files from embedded FS
s.app.Use("/", static.New("", static.Config{
FS: s.webFS,
Browse: false,
}))
}
// App returns the underlying Fiber app for WebSocket route registration.
func (s *Server) App() *fiber.App {
return s.app
}
// Token returns the auth token.
func (s *Server) Token() string {
return s.token
}
// Start starts the HTTPS server.
func (s *Server) Start() error {
cfg := config.Get()
addr := fmt.Sprintf(":%d", cfg.Server.Port)
if cfg.Server.TLSAuto {
tlsCfg, err := vpTLS.GetTLSConfig(s.lanIP)
if err != nil {
return fmt.Errorf("TLS setup failed: %w", err)
}
slog.Info("starting HTTPS server", "addr", addr)
return s.app.Listen(addr, fiber.ListenConfig{
TLSConfig: &tls.Config{
Certificates: tlsCfg.Certificates,
MinVersion: tls.VersionTLS12,
},
})
}
slog.Info("starting HTTP server (no TLS)", "addr", addr)
return s.app.Listen(addr)
}
// Shutdown gracefully shuts down the server.
func (s *Server) Shutdown() error {
return s.app.Shutdown()
}