summaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorivar <i@oiee.no>2026-04-04 16:34:46 +0200
committerivar <i@oiee.no>2026-04-04 16:34:46 +0200
commita6355e7a6530af3335c4cd8af05f1e9c8b978169 (patch)
treec9d920d1e996ef1c42d3455825731598df6b56c2 /cmd
parent8a093aacd162d3fd9f142b53aab9edfa061fd66a (diff)
downloadnebbet.no-a6355e7a6530af3335c4cd8af05f1e9c8b978169.tar.xz
nebbet.no-a6355e7a6530af3335c4cd8af05f1e9c8b978169.zip
.
Diffstat (limited to 'cmd')
-rw-r--r--cmd/nebbet/main.go142
1 files changed, 63 insertions, 79 deletions
diff --git a/cmd/nebbet/main.go b/cmd/nebbet/main.go
index 210ad67..356838e 100644
--- a/cmd/nebbet/main.go
+++ b/cmd/nebbet/main.go
@@ -1,34 +1,20 @@
-// Command nebbet is the CLI for the nebbet.no static site generator.
-//
-// Usage:
-//
-// nebbet build – build all pages once
-// nebbet watch – watch for changes and rebuild
-// nebbet serve [--port N] – serve with live admin UI + watch mode
-// nebbet user add <name> – add a user to the passwords file
-// nebbet user list – list users
-// nebbet user delete <name> – remove a user
-// nebbet user passwd <name> – change a user's password
package main
import (
"flag"
"fmt"
- "net/http"
+ "html/template"
"os"
-
- "github.com/gin-gonic/gin"
+ "path/filepath"
"nebbet.no/internal/admin"
"nebbet.no/internal/admin/auth"
- "nebbet.no/internal/builder"
"nebbet.no/internal/db"
+ "nebbet.no/internal/server"
)
const (
- contentDir = "content"
outputDir = "public"
- postsDir = "content/posts"
dataDir = "data"
metaDBPath = "data/meta.db"
searchDBPath = "data/search.db"
@@ -41,10 +27,6 @@ func main() {
os.Exit(1)
}
switch os.Args[1] {
- case "build":
- cmdBuild(os.Args[2:])
- case "watch":
- cmdWatch()
case "serve":
cmdServe(os.Args[2:])
case "user":
@@ -56,78 +38,82 @@ func main() {
}
}
-func cmdBuild(args []string) {
- fs := flag.NewFlagSet("build", flag.ExitOnError)
- watch := fs.Bool("watch", false, "watch for changes and rebuild")
+func cmdServe(args []string) {
+ fs := flag.NewFlagSet("serve", flag.ExitOnError)
+ port := fs.String("port", "8080", "port to listen on")
_ = fs.Parse(args)
- b := mustBuilder()
- defer b.MetaDB.Close()
- defer b.SearchDB.Close()
+ meta, search := mustOpenDBs()
+ defer meta.Close()
+ defer search.Close()
+
+ adminSrv := admin.NewServer(passwordFile, meta, search, outputDir)
+ engine := adminSrv.Engine()
- if err := b.BuildAll(); err != nil {
- fmt.Fprintln(os.Stderr, "build error:", err)
+ // Load templates for post rendering
+ tmpl, err := template.ParseGlob(filepath.Join("templates", "*.html"))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "template load error:", err)
os.Exit(1)
}
- if *watch {
- if err := b.Watch(); err != nil {
- fmt.Fprintln(os.Stderr, "watch error:", err)
- os.Exit(1)
- }
- }
-}
-func cmdWatch() {
- b := mustBuilder()
- defer b.MetaDB.Close()
- defer b.SearchDB.Close()
- if err := b.BuildAll(); err != nil {
- fmt.Fprintln(os.Stderr, "build error:", err)
- os.Exit(1)
+ // Define MIME types for each directory
+ libMIMEs := server.AllowedMIMEs{
+ ".js": "application/javascript; charset=utf-8",
+ ".css": "text/css; charset=utf-8",
+ ".wasm": "application/wasm",
}
- if err := b.Watch(); err != nil {
- fmt.Fprintln(os.Stderr, "watch error:", err)
- os.Exit(1)
+ mediaMIMEs := server.AllowedMIMEs{
+ ".jpg": "image/jpeg",
+ ".jpeg": "image/jpeg",
+ ".png": "image/png",
+ ".webp": "image/webp",
+ ".gif": "image/gif",
+ ".svg": "image/svg+xml",
+ ".mp4": "video/mp4",
+ }
+ publicMIMEs := server.AllowedMIMEs{
+ ".html": "text/html; charset=utf-8",
+ ".css": "text/css; charset=utf-8",
+ ".js": "application/javascript; charset=utf-8",
+ ".json": "application/json",
+ ".xml": "application/xml",
+ ".txt": "text/plain; charset=utf-8",
+ ".ico": "image/x-icon",
+ ".svg": "image/svg+xml",
+ ".png": "image/png",
+ ".jpg": "image/jpeg",
+ ".webp": "image/webp",
+ }
+ stylesMIMEs := server.AllowedMIMEs{
+ ".css": "text/css; charset=utf-8",
}
-}
-func cmdServe(args []string) {
- fs := flag.NewFlagSet("serve", flag.ExitOnError)
- port := fs.String("port", "8080", "port to listen on")
- _ = fs.Parse(args)
+ // Serve styles directory
+ engine.GET("/assets/styles/*filepath", server.FileHandler("assets/styles", stylesMIMEs))
- b := mustBuilder()
- defer b.MetaDB.Close()
- defer b.SearchDB.Close()
+ // Serve lib directory (JS modules)
+ engine.GET("/lib/*filepath", server.FileHandler("assets/lib", libMIMEs))
- // Initial build.
- if err := b.BuildAll(); err != nil {
- fmt.Fprintln(os.Stderr, "build error:", err)
- os.Exit(1)
- }
+ // Serve components directory (JS web components)
+ engine.GET("/assets/components/*filepath", server.FileHandler("assets/components", libMIMEs))
- // Watch in background.
- go func() {
- if err := b.Watch(); err != nil {
- fmt.Fprintln(os.Stderr, "watch error:", err)
- }
- }()
+ // Serve media directory (images, videos)
+ engine.GET("/media/*filepath", server.FileHandler("assets/media", mediaMIMEs))
- // Create admin server with Gin
- adminSrv := admin.NewServer(postsDir, passwordFile, b)
- engine := adminSrv.Engine()
+ // Dynamic frontpage with post listing, tags, and search
+ frontpage := server.NewFrontpageHandler(meta, search, tmpl)
+ engine.GET("/", frontpage.Serve)
- // Serve dependencies (lib/)
- engine.Static("/lib", "lib")
+ // Serve posts dynamically with caching (single route, dispatch by extension)
+ postHandler := server.NewPostHandler(meta, tmpl, outputDir)
+ engine.GET("/:slug", postHandler.Serve)
// Serve static site as fallback for unmatched routes
- engine.NoRoute(func(c *gin.Context) {
- http.FileServer(http.Dir(outputDir)).ServeHTTP(c.Writer, c.Request)
- })
+ engine.NoRoute(server.PublicFileHandler(outputDir, publicMIMEs))
addr := ":" + *port
fmt.Printf("listening on http://localhost%s\n", addr)
- fmt.Printf(" public site: http://localhost%s/\n", addr)
fmt.Printf(" admin UI: http://localhost%s/admin/\n", addr)
if err := engine.Run(addr); err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -184,7 +170,7 @@ func cmdUser(args []string) {
}
}
-func mustBuilder() *builder.Builder {
+func mustOpenDBs() (*db.MetaDB, *db.SearchDB) {
if err := os.MkdirAll(dataDir, 0755); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
@@ -199,7 +185,7 @@ func mustBuilder() *builder.Builder {
fmt.Fprintln(os.Stderr, "search db:", err)
os.Exit(1)
}
- return builder.New(contentDir, outputDir, meta, search)
+ return meta, search
}
func requireArg(args []string, n int, usage string) {
@@ -210,11 +196,9 @@ func requireArg(args []string, n int, usage string) {
}
func usage() {
- fmt.Fprintln(os.Stderr, `nebbet — static site generator with admin
+ fmt.Fprintln(os.Stderr, `nebbet.no
Commands:
- build [--watch] build all pages (optionally watch for changes)
- watch watch for changes and rebuild
serve [--port N] serve site + admin UI (default port 8080)
user add <name> add user to password file
user list list users