e9ecc4c1f7
CI / test (pull_request) Successful in 2m13s
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.
115 lines
2.6 KiB
Go
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)
|
|
}
|