mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-19 07:50:43 +01:00
Resend is already used by the license server and finance workflows, so using it here avoids a second provider. The admin magic-link endpoint now accepts send_email to deliver the link directly via Resend. Env var: POSTMARK_SERVER_TOKEN -> RESEND_API_KEY
109 lines
3.2 KiB
Go
109 lines
3.2 KiB
Go
package cloudcp
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/joho/godotenv"
|
|
)
|
|
|
|
// CPConfig holds all configuration for the control plane.
|
|
type CPConfig struct {
|
|
DataDir string
|
|
BindAddress string
|
|
Port int
|
|
AdminKey string
|
|
BaseURL string
|
|
PulseImage string
|
|
DockerNetwork string
|
|
TenantMemoryLimit int64 // bytes
|
|
TenantCPUShares int64
|
|
StripeWebhookSecret string
|
|
StripeAPIKey string
|
|
ResendAPIKey string // Resend API key (optional — if empty, emails are logged)
|
|
EmailFrom string // Sender email address (e.g. "noreply@pulserelay.pro")
|
|
}
|
|
|
|
// TenantsDir returns the directory where per-tenant data is stored.
|
|
func (c *CPConfig) TenantsDir() string {
|
|
return filepath.Join(c.DataDir, "tenants")
|
|
}
|
|
|
|
// ControlPlaneDir returns the directory for control plane's own data (registry DB, etc).
|
|
func (c *CPConfig) ControlPlaneDir() string {
|
|
return filepath.Join(c.DataDir, "control-plane")
|
|
}
|
|
|
|
// LoadConfig loads control plane configuration from environment variables.
|
|
// A .env file is loaded if present but not required.
|
|
func LoadConfig() (*CPConfig, error) {
|
|
// Best-effort .env loading (not required)
|
|
_ = godotenv.Load()
|
|
|
|
cfg := &CPConfig{
|
|
DataDir: envOrDefault("CP_DATA_DIR", "/data"),
|
|
BindAddress: envOrDefault("CP_BIND_ADDRESS", "0.0.0.0"),
|
|
Port: envOrDefaultInt("CP_PORT", 8443),
|
|
AdminKey: strings.TrimSpace(os.Getenv("CP_ADMIN_KEY")),
|
|
BaseURL: strings.TrimSpace(os.Getenv("CP_BASE_URL")),
|
|
PulseImage: envOrDefault("CP_PULSE_IMAGE", "ghcr.io/rcourtman/pulse:latest"),
|
|
DockerNetwork: envOrDefault("CP_DOCKER_NETWORK", "pulse-cloud"),
|
|
TenantMemoryLimit: envOrDefaultInt64("CP_TENANT_MEMORY_LIMIT", 512*1024*1024), // 512 MiB
|
|
TenantCPUShares: envOrDefaultInt64("CP_TENANT_CPU_SHARES", 256),
|
|
StripeWebhookSecret: strings.TrimSpace(os.Getenv("STRIPE_WEBHOOK_SECRET")),
|
|
StripeAPIKey: strings.TrimSpace(os.Getenv("STRIPE_API_KEY")),
|
|
ResendAPIKey: strings.TrimSpace(os.Getenv("RESEND_API_KEY")),
|
|
EmailFrom: envOrDefault("PULSE_EMAIL_FROM", "noreply@pulserelay.pro"),
|
|
}
|
|
|
|
if err := cfg.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (c *CPConfig) validate() error {
|
|
var missing []string
|
|
if c.AdminKey == "" {
|
|
missing = append(missing, "CP_ADMIN_KEY")
|
|
}
|
|
if c.BaseURL == "" {
|
|
missing = append(missing, "CP_BASE_URL")
|
|
}
|
|
if c.StripeWebhookSecret == "" {
|
|
missing = append(missing, "STRIPE_WEBHOOK_SECRET")
|
|
}
|
|
if len(missing) > 0 {
|
|
return fmt.Errorf("missing required environment variables: %s", strings.Join(missing, ", "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func envOrDefault(key, fallback string) string {
|
|
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envOrDefaultInt(key string, fallback int) int {
|
|
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
return n
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envOrDefaultInt64(key string, fallback int64) int64 {
|
|
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
|
|
if n, err := strconv.ParseInt(v, 10, 64); err == nil {
|
|
return n
|
|
}
|
|
}
|
|
return fallback
|
|
}
|