Add CLI flags for setup and server configuration

- Add --setup flag to create admin user
- Add --admin-user and --admin-password for setup
- Generate cryptographically random passwords for --setup
- Add --bind and --port flags for server binding
- Add BIND env var support
- Add SMTP email service for password reset
- Add password reset token storage
- Add auth header to queue/logs frontend pages
- Fix journalctl timestamp format
- Fix logs to return empty array instead of error when no entries
- Fix queue handler to return proper error message
- Hide Users/Aliases nav links when no domain selected
This commit is contained in:
Christoph Haas 2026-03-22 01:17:43 +01:00
parent 2c3a4c3daa
commit 53ce3b06ae
14 changed files with 435 additions and 69 deletions

View file

@ -1,6 +1,9 @@
package main
import (
"crypto/rand"
"flag"
"fmt"
"log"
"net/http"
"os"
@ -13,8 +16,22 @@ import (
)
func main() {
setup := flag.Bool("setup", false, "Create admin user and exit")
adminUser := flag.String("admin-user", "admin", "Admin username for setup")
adminPassword := flag.String("admin-password", "", "Admin password for setup (required with --setup)")
bind := flag.String("bind", "", "IP address to bind to (default: 0.0.0.0)")
port := flag.String("port", "", "Port to listen on (default: 8080)")
flag.Parse()
cfg := config.Load()
if *bind != "" {
cfg.Bind = *bind
}
if *port != "" {
cfg.Port = *port
}
database, err := db.Connect(cfg)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
@ -24,6 +41,28 @@ func main() {
log.Printf("Warning: Could not initialize schema: %v", err)
}
if *setup {
password := *adminPassword
if password == "" {
password = generateRandomPassword(16)
fmt.Printf("Generated random password: %s\n", password)
}
hash, err := auth.HashPassword(password)
if err != nil {
log.Fatalf("Failed to hash password: %v", err)
}
if err := database.EnsureAdminUser(*adminUser, hash); err != nil {
log.Fatalf("Failed to create admin user: %v", err)
}
fmt.Printf("Admin user '%s' created successfully.\n", *adminUser)
fmt.Printf("You can now login with username: %s\n", *adminUser)
fmt.Printf("Password: %s\n", password)
return
}
if cfg.AdminUser != "" && cfg.AdminPassword != "" {
hash, err := auth.HashPassword(cfg.AdminPassword)
if err != nil {
@ -38,7 +77,6 @@ func main() {
}
useEmbedded := os.Getenv("USE_EMBEDDED") != "false"
port := cfg.Port
frontendFS := FrontendFileSystem()
@ -62,18 +100,32 @@ func main() {
log.Println("Using filesystem frontend")
}
log.Printf("Server starting on http://localhost:%s", port)
log.Fatal(http.ListenAndServe(":"+port, router))
addr := fmt.Sprintf("%s:%s", cfg.Bind, cfg.Port)
log.Printf("Server starting on http://%s", addr)
log.Fatal(http.ListenAndServe(addr, router))
}
func generateRandomPassword(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
result := make([]byte, length)
if _, err := rand.Read(result); err != nil {
for i := range result {
result[i] = charset[i%len(charset)]
}
return string(result)
}
for i := range result {
result[i] = charset[int(result[i])%len(charset)]
}
return string(result)
}
func serveStaticPrefixed(fsys http.FileSystem, prefix string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
path := prefix + r.URL.Path
log.Printf("Static file requested: /_app/%s -> %s", r.URL.Path, path)
file, err := fsys.Open(path)
if err != nil {
log.Printf("Error opening file %s: %v", path, err)
http.NotFound(w, r)
return
}
@ -98,11 +150,8 @@ func serveStaticPrefixed(fsys http.FileSystem, prefix string) http.HandlerFunc {
func serveStaticFile(fsys http.FileSystem, filename string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Static file requested: /%s", filename)
file, err := fsys.Open(filename)
if err != nil {
log.Printf("Error opening file %s: %v", filename, err)
http.NotFound(w, r)
return
}
@ -129,11 +178,7 @@ func serveSPA(fsys http.FileSystem) http.HandlerFunc {
return
}
if path == "/" || path == "" {
path = "embed/index.html"
} else {
path = "embed/index.html"
}
path = "embed/index.html"
file, err := fsys.Open(path)
if err != nil {