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

119
main.go Normal file
View File

@@ -0,0 +1,119 @@
package main
import (
"embed"
"fmt"
"io/fs"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/imbytecat/voicepaste/internal/asr"
"github.com/imbytecat/voicepaste/internal/config"
"github.com/imbytecat/voicepaste/internal/paste"
"github.com/imbytecat/voicepaste/internal/server"
"github.com/imbytecat/voicepaste/internal/ws"
)
//go:embed all:web
var webFS embed.FS
var version = "dev"
func main() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
})))
slog.Info("VoicePaste", "version", version)
// Load config
cfg, err := config.Load("")
if err != nil {
slog.Error("failed to load config", "error", err)
os.Exit(1)
}
// Start config hot-reload watcher
config.WatchAndReload("")
// Initialize clipboard
if err := paste.Init(); err != nil {
slog.Warn("clipboard init failed, paste will be unavailable", "err", err)
}
// Detect LAN IP
lanIP, err := server.GetLANIP()
if err != nil {
slog.Error("failed to detect LAN IP", "error", err)
os.Exit(1)
}
// Generate auth token
token := server.GenerateToken()
// Build URL
scheme := "https"
if !cfg.Server.TLSAuto {
scheme = "http"
}
url := fmt.Sprintf("%s://%s:%d/?token=%s", scheme, lanIP, cfg.Server.Port, token)
// Print connection info
fmt.Println()
fmt.Println("╔══════════════════════════════════════╗")
fmt.Println("║ VoicePaste Ready ║")
fmt.Println("╚══════════════════════════════════════╝")
fmt.Println()
fmt.Printf(" URL: %s\n", url)
fmt.Println()
printQRCode(url)
fmt.Println()
fmt.Println(" Scan QR code with your phone to connect.")
fmt.Println(" Press Ctrl+C to stop.")
fmt.Println()
// Create and start server
webContent, _ := fs.Sub(webFS, "web")
srv := server.New(token, lanIP, webContent)
// Build ASR factory from config
asrCfg := asr.Config{
AppKey: cfg.Doubao.AppKey,
AccessKey: cfg.Doubao.AccessKey,
ResourceID: cfg.Doubao.ResourceID,
}
asrFactory := func(resultCh chan<- ws.ServerMsg) (func([]byte), func(), error) {
client, err := asr.Dial(asrCfg, resultCh)
if err != nil {
return nil, nil, err
}
sendAudio := func(pcm []byte) {
if err := client.SendAudio(pcm, false); err != nil {
slog.Warn("send audio to asr", "err", err)
}
}
cleanup := func() {
// Send last empty frame to signal end
_ = client.SendAudio(nil, true)
client.Close()
}
return sendAudio, cleanup, nil
}
// Register WebSocket handler
wsHandler := ws.NewHandler(token, paste.Paste, asrFactory)
wsHandler.Register(srv.App())
// Graceful shutdown
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
slog.Info("shutting down...")
srv.Shutdown()
}()
if err := srv.Start(); err != nil {
slog.Error("server error", "error", err)
os.Exit(1)
}
}