Files
rs_website/cmd/server/main.go
2026-04-13 03:49:24 -05:00

129 lines
3.6 KiB
Go

package main
import (
"bytes"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"time"
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/styles"
"ridgwaysystems.org/website/internal/blog"
"ridgwaysystems.org/website/internal/checker"
"ridgwaysystems.org/website/internal/handler"
"ridgwaysystems.org/website/internal/newsletter"
)
func main() {
port := getenv("PORT", "8080")
contentDir := getenv("CONTENT_DIR", "content")
dataDir := getenv("DATA_DIR", "data")
// Generate syntax.css at startup (light + dark via media query)
if err := generateSyntaxCSS("static/css/syntax.css"); err != nil {
log.Printf("warning: could not generate syntax.css: %v", err)
}
store, err := blog.NewStore(contentDir + "/posts")
if err != nil {
log.Fatal("store:", err)
}
news, err := newsletter.NewStore(filepath.Join(dataDir, "subscribers.json"))
if err != nil {
log.Fatal("newsletter store:", err)
}
h := handler.New(store, news, dataDir)
// Start service auto-checker
checkInterval := checkerInterval()
checker.Start(dataDir, checkInterval)
log.Printf("service checker running every %s", checkInterval)
// Start Twitch live status poller
h.StartTwitch(0)
mux := http.NewServeMux()
// Static files
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// robots.txt at root
mux.HandleFunc("GET /robots.txt", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/robots.txt")
})
// Public routes
mux.HandleFunc("GET /{$}", h.Index)
mux.HandleFunc("GET /blog", h.BlogList)
mux.HandleFunc("GET /blog/feed.xml", h.Feed)
mux.HandleFunc("GET /blog/{slug}", h.BlogPost)
mux.HandleFunc("GET /infrastructure", h.Infrastructure)
mux.HandleFunc("GET /status", h.Status)
mux.HandleFunc("GET /about", h.About)
mux.HandleFunc("GET /uses", h.Uses)
mux.HandleFunc("GET /projects", h.Projects)
mux.HandleFunc("GET /hire", h.Hire)
mux.HandleFunc("POST /hire", h.HirePost)
mux.HandleFunc("GET /resume", h.Resume)
mux.HandleFunc("POST /newsletter", h.NewsletterPost)
mux.HandleFunc("GET /changelog", h.Changelog)
mux.HandleFunc("GET /stream", h.Stream)
mux.HandleFunc("GET /sitemap.xml", h.Sitemap)
// Admin routes (auth handled per-handler)
mux.HandleFunc("/admin", h.AdminDashboard)
mux.HandleFunc("/admin/", h.AdminRouter)
srv := handler.Chain(mux,
handler.LoggingMiddleware,
handler.SecurityHeadersMiddleware,
)
log.Printf("ridgwaysystems.org starting on :%s (DEV=%s)", port, os.Getenv("DEV"))
log.Fatal(http.ListenAndServe(":"+port, srv))
}
// checkerInterval returns the status check interval from CHECK_INTERVAL env (minutes).
func checkerInterval() time.Duration {
if s := os.Getenv("CHECK_INTERVAL"); s != "" {
if n, err := strconv.Atoi(s); err == nil && n > 0 {
return time.Duration(n) * time.Minute
}
}
return 5 * time.Minute
}
// generateSyntaxCSS writes a chroma CSS file with light and dark themes.
func generateSyntaxCSS(path string) error {
formatter := chromahtml.New(chromahtml.WithClasses(true))
var buf bytes.Buffer
buf.WriteString("/* Auto-generated by chroma at server startup. Do not edit. */\n\n")
if err := formatter.WriteCSS(&buf, styles.Get("github")); err != nil {
return err
}
buf.WriteString("\n@media (prefers-color-scheme: dark) {\n")
var dark bytes.Buffer
if err := formatter.WriteCSS(&dark, styles.Get("github-dark")); err != nil {
return err
}
buf.Write(dark.Bytes())
buf.WriteString("}\n")
return os.WriteFile(path, buf.Bytes(), 0644)
}
func getenv(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}