Files
nib-v1/cmd/serve.go
lerko e9ecc4c1f7
CI / test (pull_request) Successful in 2m13s
fix: address code review findings across backend and frontend
Fix goroutine-unsafe ULID entropy by wrapping in LockedMonotonicReader.
Move PRAGMA foreign_keys outside transaction in v3 migration where
SQLite was silently ignoring it. Escape LIKE wildcards in link
resolution to prevent false matches. Add non-localhost binding warning,
log writeJSON encoder errors, add ?permanent=true for explicit hard
delete, preserve title/description during absorb, use millisecond
backup timestamps, add path.Clean to spaHandler. Frontend gains
checkedJSON() for resp.ok validation, consistent stopPropagation, and
shared renderCardSections() to eliminate duplicate rendering.
2026-05-21 16:02:57 -04:00

115 lines
2.6 KiB
Go

package cmd
import (
"context"
"fmt"
"io/fs"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/lerko/nib/internal/api"
"github.com/spf13/cobra"
)
var WebFS fs.FS
var (
servePort int
serveHost string
serveDev bool
tlsCert string
tlsKey string
)
var serveCmd = &cobra.Command{
Use: "serve",
Short: "start the HTTP server",
RunE: runServe,
}
func init() {
serveCmd.Flags().IntVar(&servePort, "port", 0, "port to listen on (default 4444, or 4443 with TLS)")
serveCmd.Flags().StringVar(&serveHost, "host", "127.0.0.1", "address to bind to (default localhost only)")
serveCmd.Flags().BoolVar(&serveDev, "dev", false, "enable CORS for development")
serveCmd.Flags().StringVar(&tlsCert, "tls-cert", "", "path to TLS certificate file")
serveCmd.Flags().StringVar(&tlsKey, "tls-key", "", "path to TLS private key file")
rootCmd.AddCommand(serveCmd)
}
func runServe(_ *cobra.Command, _ []string) error {
useTLS := tlsCert != "" && tlsKey != ""
if (tlsCert != "") != (tlsKey != "") {
return fmt.Errorf("both --tls-cert and --tls-key are required for TLS")
}
port := servePort
if port == 0 {
if envPort := os.Getenv("NIB_PORT"); envPort != "" {
p, err := strconv.Atoi(envPort)
if err != nil {
return fmt.Errorf("invalid NIB_PORT: %w", err)
}
port = p
} else if useTLS {
port = 4443
} else {
port = 4444
}
}
store, err := openStore()
if err != nil {
return err
}
defer store.Close()
var router = api.NewRouter(store, serveDev)
if WebFS != nil {
router = api.NewRouter(store, serveDev, WebFS)
}
addr := fmt.Sprintf("%s:%d", serveHost, port)
srv := &http.Server{
Addr: addr,
Handler: router,
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
if useTLS {
fmt.Printf("nib serving on https://%s\n", addr)
} else {
fmt.Printf("nib serving on http://%s\n", addr)
}
if serveDev {
fmt.Println(" CORS enabled (dev mode)")
}
if serveHost != "127.0.0.1" && serveHost != "localhost" && serveHost != "::1" {
fmt.Fprintln(os.Stderr, " WARNING: binding to non-localhost with no authentication — API is open to the network")
}
var listenErr error
if useTLS {
listenErr = srv.ListenAndServeTLS(tlsCert, tlsKey)
} else {
listenErr = srv.ListenAndServe()
}
if listenErr != nil && listenErr != http.ErrServerClosed {
fmt.Fprintf(os.Stderr, "server error: %v\n", listenErr)
}
}()
<-ctx.Done()
fmt.Println("\nshutting down...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return srv.Shutdown(shutdownCtx)
}