Signed-off-by: Rodrigo Kellermann <kellermann@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com>
99 lines
2.5 KiB
Go
99 lines
2.5 KiB
Go
// Hoppscotch Webapp Server
|
|
//
|
|
// Builds a signed bundle from frontend assets and serves it over HTTP.
|
|
// The bundle is zstd-compressed and signed with ed25519 so clients can verify integrity.
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"hoppscotch-selfhost-web/webapp-server/internal/bundle"
|
|
"hoppscotch-selfhost-web/webapp-server/internal/config"
|
|
"hoppscotch-selfhost-web/webapp-server/internal/crypto"
|
|
"hoppscotch-selfhost-web/webapp-server/internal/server"
|
|
)
|
|
|
|
func main() {
|
|
log.Println("Initializing Hoppscotch Web Static Server")
|
|
|
|
cfg := config.Load()
|
|
|
|
// NOTE: key generation handles persistence internally
|
|
// it'll try env vars first, then disk, then generate new
|
|
keyPair, err := crypto.GenerateKeyPair()
|
|
if err != nil {
|
|
log.Fatalf("Failed to generate key pair: %v", err)
|
|
}
|
|
|
|
builder, err := bundle.NewBuilder()
|
|
if err != nil {
|
|
log.Fatalf("Failed to create bundle builder: %v", err)
|
|
}
|
|
|
|
// this walks the frontend dir and creates a zstd-compressed zip
|
|
content, files, err := builder.Build(cfg.FrontendPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to build bundle: %v", err)
|
|
}
|
|
|
|
// manager holds the bundle in memory and handles signing
|
|
bundleManager, err := bundle.NewManager(
|
|
content,
|
|
files,
|
|
keyPair.SigningKey,
|
|
keyPair.VerifyingKey,
|
|
bundle.DefaultMaxSize,
|
|
)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create bundle manager: %v", err)
|
|
}
|
|
|
|
srv := server.New(bundleManager)
|
|
mux := http.NewServeMux()
|
|
srv.RegisterRoutes(mux)
|
|
|
|
addr := fmt.Sprintf(":%d", cfg.Port)
|
|
|
|
// NOTE: timeouts default to conservative values but can be overridden
|
|
// via WEBAPP_SERVER_READ_TIMEOUT, WEBAPP_SERVER_WRITE_TIMEOUT, WEBAPP_SERVER_IDLE_TIMEOUT
|
|
// (Go duration strings, e.g. "30s", "2m", "0" to disable)
|
|
httpServer := &http.Server{
|
|
Addr: addr,
|
|
Handler: mux,
|
|
ReadTimeout: cfg.ReadTimeout,
|
|
WriteTimeout: cfg.WriteTimeout,
|
|
IdleTimeout: cfg.IdleTimeout,
|
|
}
|
|
|
|
go func() {
|
|
log.Printf("Server starting on %s", addr)
|
|
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("Server failed: %v", err)
|
|
}
|
|
}()
|
|
|
|
// wait for shutdown signal
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
<-quit
|
|
|
|
log.Println("Server shutting down...")
|
|
|
|
// give in-flight requests 30s to finish
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
if err := httpServer.Shutdown(ctx); err != nil {
|
|
log.Printf("Server forced to shutdown: %v", err)
|
|
}
|
|
|
|
log.Println("Server stopped")
|
|
}
|