diff options
| author | ivar <i@oiee.no> | 2026-04-07 00:23:24 +0200 |
|---|---|---|
| committer | ivar <i@oiee.no> | 2026-04-07 00:23:24 +0200 |
| commit | 85920b8c7a2696115d1f77c046f48f6f00d639f1 (patch) | |
| tree | 14ed2043796eadd6ed5b0a95c55e38e48713d638 /cmd | |
| download | iblog-85920b8c7a2696115d1f77c046f48f6f00d639f1.tar.xz iblog-85920b8c7a2696115d1f77c046f48f6f00d639f1.zip | |
Init
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/iblog/main.go | 34 | ||||
| -rw-r--r-- | cmd/iblog/serve.go | 110 | ||||
| -rw-r--r-- | cmd/iblog/user.go | 68 |
3 files changed, 212 insertions, 0 deletions
diff --git a/cmd/iblog/main.go b/cmd/iblog/main.go new file mode 100644 index 0000000..1c94f4d --- /dev/null +++ b/cmd/iblog/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + if len(os.Args) < 2 { + usage() + os.Exit(1) + } + switch os.Args[1] { + case "serve": + cmdServe(os.Args[2:]) + case "user": + cmdUser(os.Args[2:]) + default: + fmt.Fprintf(os.Stderr, "unknown command %q\n", os.Args[1]) + usage() + os.Exit(1) + } +} + +func usage() { + fmt.Fprintln(os.Stderr, `iblog + +Commands: + serve [--port N] [--root PATH] serve site + admin UI (default port 8080) + user [--root PATH] add <name> add user to password file + user [--root PATH] list list users + user [--root PATH] delete <name> remove user + user [--root PATH] passwd <name> change user password`) +} diff --git a/cmd/iblog/serve.go b/cmd/iblog/serve.go new file mode 100644 index 0000000..7636142 --- /dev/null +++ b/cmd/iblog/serve.go @@ -0,0 +1,110 @@ +package main + +import ( + "flag" + "fmt" + "html/template" + "os" + "path/filepath" + + iblog "iblog" + "iblog/internal/db" + "iblog/internal/media" + "iblog/internal/server" + + "github.com/davidbyttow/govips/v2/vips" +) + +func cmdServe(args []string) { + fs := flag.NewFlagSet("serve", flag.ExitOnError) + port := fs.String("port", "8080", "port to listen on") + root := fs.String("root", ".", "site root directory") + _ = fs.Parse(args) + + dataDir := filepath.Join(*root, "data") + dbPath := filepath.Join(dataDir, "iblog.db") + passwordFile := filepath.Join(dataDir, ".passwords") + publicMedia := filepath.Join(dataDir, "media") + outputDir := filepath.Join(*root, "public") + userComponents := filepath.Join(*root, "components") + userStyles := filepath.Join(*root, "styles") + + if err := vips.Startup(nil); err != nil { + fmt.Fprintln(os.Stderr, "govips startup:", err) + os.Exit(1) + } + + defer vips.Shutdown() + + database := mustOpenDB(dataDir, dbPath) + defer database.Close() + + if err := os.MkdirAll(publicMedia, 0755); err != nil { + fmt.Fprintln(os.Stderr, "media dir:", err) + os.Exit(1) + } + + tmpl, err := template.New("").ParseFS(iblog.SiteTemplates, "templates/*.html", "templates/**/*.html") + if err != nil { + fmt.Fprintln(os.Stderr, "site template load error:", err) + os.Exit(1) + } + + adminSrv := server.NewAdminServer(passwordFile, database, outputDir, iblog.AdminAssets, iblog.SiteAssets, tmpl) + engine := adminSrv.Engine() + + 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", + } + + mediaSrv := media.NewMediaHandler(publicMedia) + adminSrv.RegisterUploadRoute(mediaSrv.HandleUpload) + engine.GET("/media/*filepath", mediaSrv.HandleServe) + + if info, err := os.Stat(userComponents); err == nil && info.IsDir() { + engine.GET("/components/*filepath", server.FileHandler(userComponents, nil)) + } + + if info, err := os.Stat(userStyles); err == nil && info.IsDir() { + engine.GET("/styles/*filepath", server.FileHandler(userStyles, nil)) + } + + frontpage := server.NewFrontpageHandler(database) + engine.GET("/", frontpage.Serve) + + postHandler := server.NewPostHandler(database, tmpl, outputDir) + engine.GET("/:slug", postHandler.Serve) + + engine.NoRoute(server.PublicFileHandler(outputDir, publicMIMEs)) + + addr := "localhost:" + *port + fmt.Printf("listening on 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) + os.Exit(1) + } +} + +func mustOpenDB(dataDir, dbPath string) *db.DB { + if err := os.MkdirAll(dataDir, 0755); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + database, err := db.Open(dbPath) + if err != nil { + fmt.Fprintln(os.Stderr, "db:", err) + os.Exit(1) + } + return database +} diff --git a/cmd/iblog/user.go b/cmd/iblog/user.go new file mode 100644 index 0000000..f9d422a --- /dev/null +++ b/cmd/iblog/user.go @@ -0,0 +1,68 @@ +package main + +import ( + "flag" + "fmt" + auth "iblog/internal" + "os" + "path/filepath" +) + +func cmdUser(args []string) { + f := flag.NewFlagSet("user", flag.ExitOnError) + root := f.String("root", ".", "site root directory") + _ = f.Parse(args) + remaining := f.Args() + if len(remaining) == 0 { + fmt.Fprintln(os.Stderr, "usage: iblog user [--root PATH] <add|list|delete|passwd> [username]") + os.Exit(1) + } + passwordFile := filepath.Join(*root, "data", ".passwords") + a := auth.NewAuth(passwordFile) + switch remaining[0] { + case "add": + requireArg(remaining, 1, "iblog user add <username>") + if err := a.AddUser(remaining[1]); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Println("user added:", remaining[1]) + case "list": + users, err := a.ListUsers() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + if len(users) == 0 { + fmt.Println("(no users)") + return + } + for _, u := range users { + fmt.Println(u) + } + case "delete": + requireArg(remaining, 1, "iblog user delete <username>") + if err := a.DeleteUser(remaining[1]); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Println("user deleted:", remaining[1]) + case "passwd": + requireArg(remaining, 1, "iblog user passwd <username>") + if err := a.ChangePassword(remaining[1]); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Println("password updated:", remaining[1]) + default: + fmt.Fprintf(os.Stderr, "unknown user subcommand %q\n", remaining[0]) + os.Exit(1) + } +} + +func requireArg(args []string, n int, usage string) { + if len(args) <= n { + fmt.Fprintln(os.Stderr, "usage:", usage) + os.Exit(1) + } +} |
