Fixed the Discord SSO somewhat
Fixed FS system Added TZ options
This commit is contained in:
@@ -1,89 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"greencoast/internal/api"
|
||||
"greencoast/internal/config"
|
||||
"greencoast/internal/federation"
|
||||
"greencoast/internal/index"
|
||||
"greencoast/internal/storage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfgPath := flag.String("config", "shard.yaml", "path to config")
|
||||
flag.Parse()
|
||||
|
||||
cfg, err := config.Load(*cfgPath)
|
||||
if err != nil {
|
||||
log.Fatalf("config error: %v", err)
|
||||
func getenvBool(key string, def bool) bool {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
|
||||
store, err := storage.NewFSStore(cfg.Storage.Path, cfg.Storage.MaxObjectKB)
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
log.Fatalf("storage error: %v", err)
|
||||
return def
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
dataRoot := filepath.Dir(cfg.Storage.Path)
|
||||
idx := index.New(dataRoot)
|
||||
func staticHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Same security posture as API
|
||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
|
||||
w.Header().Set("Cross-Origin-Resource-Policy", "same-site")
|
||||
w.Header().Set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), interest-cohort=(), browsing-topics=()")
|
||||
w.Header().Set("X-Frame-Options", "DENY")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("Strict-Transport-Security", "max-age=15552000; includeSubDomains; preload")
|
||||
|
||||
srv := api.New(
|
||||
store, idx,
|
||||
cfg.Privacy.RetainTimestamps == "coarse",
|
||||
cfg.Security.ZeroTrust,
|
||||
api.AuthProviders{
|
||||
SigningSecretHex: cfg.Auth.SigningSecret,
|
||||
Discord: api.DiscordProvider{
|
||||
Enabled: cfg.Auth.SSO.Discord.Enabled,
|
||||
ClientID: cfg.Auth.SSO.Discord.ClientID,
|
||||
ClientSecret: cfg.Auth.SSO.Discord.ClientSecret,
|
||||
RedirectURI: cfg.Auth.SSO.Discord.RedirectURI,
|
||||
},
|
||||
GoogleEnabled: cfg.Auth.SSO.Google.Enabled,
|
||||
FacebookEnabled: cfg.Auth.SSO.Facebook.Enabled,
|
||||
WebAuthnEnabled: cfg.Auth.TwoFactor.WebAuthnEnabled,
|
||||
TOTPEnabled: cfg.Auth.TwoFactor.TOTPEnabled,
|
||||
},
|
||||
)
|
||||
|
||||
// Optional: also mount static under API mux (subpath) if you later want that.
|
||||
// srv.MountStatic(cfg.UI.Path, "/app")
|
||||
|
||||
// Start federation mTLS (if enabled)
|
||||
if cfg.Federation.MTLSEnable {
|
||||
tlsCfg, err := federation.ServerTLSConfig(
|
||||
cfg.Federation.CertFile,
|
||||
cfg.Federation.KeyFile,
|
||||
cfg.Federation.ClientCAFile,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("federation tls config error: %v", err)
|
||||
// Basic CORS for client assets
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := srv.ListenMTLS(cfg.Federation.Listen, tlsCfg); err != nil {
|
||||
log.Fatalf("federation mTLS listener error: %v", err)
|
||||
}
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
// ---- Config via env ----
|
||||
httpAddr := os.Getenv("GC_HTTP_ADDR")
|
||||
if httpAddr == "" {
|
||||
httpAddr = ":9080" // API
|
||||
}
|
||||
|
||||
// Start FRONTEND listener (separate port) if enabled
|
||||
if cfg.UI.Enable && cfg.UI.FrontendHTTP != "" {
|
||||
go func() {
|
||||
if err := srv.ListenFrontendHTTP(cfg.UI.FrontendHTTP, cfg.UI.Path, cfg.UI.BaseURL); err != nil {
|
||||
log.Fatalf("frontend listener error: %v", err)
|
||||
}
|
||||
}()
|
||||
// Optional TLS for API
|
||||
httpsAddr := os.Getenv("GC_HTTPS_ADDR") // leave empty for HTTP
|
||||
certFile := os.Getenv("GC_TLS_CERT")
|
||||
keyFile := os.Getenv("GC_TLS_KEY")
|
||||
|
||||
dataDir := os.Getenv("GC_DATA_DIR")
|
||||
if dataDir == "" {
|
||||
dataDir = "/var/lib/greencoast"
|
||||
}
|
||||
|
||||
// Choose ONE foreground listener for API: HTTPS if enabled, else HTTP.
|
||||
if cfg.TLS.Enable && cfg.Listen.HTTPS != "" {
|
||||
log.Fatal(srv.ListenHTTPS(cfg.Listen.HTTPS, cfg.TLS.CertFile, cfg.TLS.KeyFile))
|
||||
// Static dir + port (frontend)
|
||||
staticDir := os.Getenv("GC_STATIC_DIR")
|
||||
if staticDir == "" {
|
||||
staticDir = "/opt/greencoast/client"
|
||||
}
|
||||
staticAddr := os.Getenv("GC_STATIC_ADDR")
|
||||
if staticAddr == "" {
|
||||
staticAddr = ":9082"
|
||||
}
|
||||
|
||||
coarseTS := getenvBool("GC_COARSE_TS", false)
|
||||
zeroTrust := getenvBool("GC_ZERO_TRUST", true)
|
||||
signingSecretHex := os.Getenv("GC_SIGNING_SECRET_HEX")
|
||||
|
||||
// Discord SSO
|
||||
discID := os.Getenv("GC_DISCORD_CLIENT_ID")
|
||||
discSecret := os.Getenv("GC_DISCORD_CLIENT_SECRET")
|
||||
discRedirect := os.Getenv("GC_DISCORD_REDIRECT_URI")
|
||||
|
||||
// ---- Storage ----
|
||||
store, err := storage.NewFS(dataDir)
|
||||
if err != nil {
|
||||
log.Fatalf("storage init: %v", err)
|
||||
}
|
||||
|
||||
// ---- Index ----
|
||||
ix := index.New()
|
||||
|
||||
// Optional: auto-reindex from disk on boot
|
||||
if w, ok := any(store).(interface {
|
||||
Walk(func(hash string, size int64, mod time.Time) error) error
|
||||
}); ok {
|
||||
if err := w.Walk(func(hash string, size int64, mod time.Time) error {
|
||||
return ix.Put(index.Entry{
|
||||
Hash: hash,
|
||||
Bytes: size,
|
||||
StoredAt: mod.UTC().Format(time.RFC3339Nano),
|
||||
Private: false,
|
||||
})
|
||||
}); err != nil {
|
||||
log.Printf("reindex on boot: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Auth/Providers ----
|
||||
ap := api.AuthProviders{
|
||||
SigningSecretHex: signingSecretHex,
|
||||
Discord: api.DiscordProvider{
|
||||
Enabled: discID != "" && discSecret != "" && discRedirect != "",
|
||||
ClientID: discID,
|
||||
ClientSecret: discSecret,
|
||||
RedirectURI: discRedirect,
|
||||
},
|
||||
}
|
||||
|
||||
// ---- API server (9080/HTTPS optional) ----
|
||||
srv := api.New(store, ix, coarseTS, zeroTrust, ap)
|
||||
|
||||
// Serve the static client in a goroutine on 9082
|
||||
go func() {
|
||||
if st, err := os.Stat(staticDir); err != nil || !st.IsDir() {
|
||||
log.Printf("WARN: GC_STATIC_DIR %q not found or not a dir; client may 404", staticDir)
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", http.FileServer(http.Dir(staticDir)))
|
||||
log.Printf("static listening on %s (dir=%s)", staticAddr, staticDir)
|
||||
if err := http.ListenAndServe(staticAddr, staticHeaders(mux)); err != nil {
|
||||
log.Fatalf("static server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Prefer HTTPS if configured
|
||||
if httpsAddr != "" && certFile != "" && keyFile != "" {
|
||||
log.Printf("starting HTTPS API on %s", httpsAddr)
|
||||
if err := srv.ListenHTTPS(httpsAddr, certFile, keyFile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if cfg.Listen.HTTP == "" {
|
||||
log.Fatal("no API listeners configured (set listen.http or listen.https)")
|
||||
|
||||
// Otherwise HTTP
|
||||
log.Printf("starting HTTP API on %s", httpAddr)
|
||||
if err := srv.ListenHTTP(httpAddr); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Fatal(srv.ListenHTTP(cfg.Listen.HTTP))
|
||||
|
||||
_ = time.Second
|
||||
}
|
||||
|
Reference in New Issue
Block a user