feat: The Other Dude v9.0.1 — full-featured email system

ci: add GitHub Pages deployment workflow for docs site

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-08 17:46:37 -05:00
commit b840047e19
511 changed files with 106948 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
// Package config loads poller configuration from environment variables.
package config
import (
"encoding/base64"
"fmt"
"log/slog"
"os"
"strconv"
)
// Config holds all runtime configuration for the poller service.
type Config struct {
// Environment is the deployment environment (dev, staging, production).
// Controls startup validation of security-sensitive defaults.
Environment string
// DatabaseURL is the PostgreSQL connection string for the poller_user role.
// Example: postgres://poller_user:poller_password@localhost:5432/mikrotik
DatabaseURL string
// RedisURL is the Redis connection URL.
RedisURL string
// NatsURL is the NATS server URL.
NatsURL string
// CredentialEncryptionKey is the 32-byte AES key decoded from base64.
// MUST match the Python backend CREDENTIAL_ENCRYPTION_KEY environment variable.
// OPTIONAL when OpenBao Transit is configured (OPENBAO_ADDR set).
CredentialEncryptionKey []byte
// OpenBaoAddr is the OpenBao server address for Transit API calls.
// Example: http://openbao:8200
OpenBaoAddr string
// OpenBaoToken is the authentication token for OpenBao API calls.
OpenBaoToken string
// PollIntervalSeconds is how often each device is polled.
PollIntervalSeconds int
// DeviceRefreshSeconds is how often the DB is queried for new/removed devices.
DeviceRefreshSeconds int
// ConnectionTimeoutSeconds is the TLS connection timeout per device.
ConnectionTimeoutSeconds int
// LogLevel controls log verbosity (debug, info, warn, error).
LogLevel string
// CircuitBreakerMaxFailures is the number of consecutive connection failures
// before the circuit breaker enters backoff mode for a device.
CircuitBreakerMaxFailures int
// CircuitBreakerBaseBackoffSeconds is the base backoff duration in seconds.
// Actual backoff is exponential: base * 2^(failures-1), capped at max.
CircuitBreakerBaseBackoffSeconds int
// CircuitBreakerMaxBackoffSeconds is the maximum backoff duration in seconds.
CircuitBreakerMaxBackoffSeconds int
// CommandTimeoutSeconds is the per-command timeout for RouterOS API calls.
// Each API call (DetectVersion, CollectInterfaces, etc.) is wrapped with
// this timeout to prevent indefinite blocking on unresponsive devices.
CommandTimeoutSeconds int
}
// knownInsecureEncryptionKey is the base64-encoded dev default encryption key.
// Production environments MUST NOT use this value.
const knownInsecureEncryptionKey = "LLLjnfBZTSycvL2U07HDSxUeTtLxb9cZzryQl0R9E4w="
// Load reads configuration from environment variables, applying defaults where appropriate.
// Returns an error if any required variable is missing or invalid.
func Load() (*Config, error) {
cfg := &Config{
Environment: getEnv("ENVIRONMENT", "dev"),
DatabaseURL: getEnv("DATABASE_URL", ""),
RedisURL: getEnv("REDIS_URL", "redis://localhost:6379/0"),
NatsURL: getEnv("NATS_URL", "nats://localhost:4222"),
LogLevel: getEnv("LOG_LEVEL", "info"),
PollIntervalSeconds: getEnvInt("POLL_INTERVAL_SECONDS", 60),
DeviceRefreshSeconds: getEnvInt("DEVICE_REFRESH_SECONDS", 60),
ConnectionTimeoutSeconds: getEnvInt("CONNECTION_TIMEOUT_SECONDS", 10),
CircuitBreakerMaxFailures: getEnvInt("CIRCUIT_BREAKER_MAX_FAILURES", 5),
CircuitBreakerBaseBackoffSeconds: getEnvInt("CIRCUIT_BREAKER_BASE_BACKOFF_SECONDS", 30),
CircuitBreakerMaxBackoffSeconds: getEnvInt("CIRCUIT_BREAKER_MAX_BACKOFF_SECONDS", 900),
CommandTimeoutSeconds: getEnvInt("COMMAND_TIMEOUT_SECONDS", 30),
}
if cfg.DatabaseURL == "" {
return nil, fmt.Errorf("DATABASE_URL environment variable is required")
}
// OpenBao Transit configuration (optional -- required for Phase 29+ envelope encryption)
cfg.OpenBaoAddr = getEnv("OPENBAO_ADDR", "")
cfg.OpenBaoToken = getEnv("OPENBAO_TOKEN", "")
if cfg.OpenBaoAddr != "" && cfg.OpenBaoToken == "" {
return nil, fmt.Errorf("OPENBAO_TOKEN is required when OPENBAO_ADDR is set")
}
// Decode the AES-256-GCM encryption key from base64.
// Must use StdEncoding (NOT URLEncoding) to match Python's base64.b64encode output.
// OPTIONAL when OpenBao Transit is configured (OPENBAO_ADDR set).
keyB64 := getEnv("CREDENTIAL_ENCRYPTION_KEY", "")
if keyB64 == "" {
if cfg.OpenBaoAddr == "" {
return nil, fmt.Errorf("CREDENTIAL_ENCRYPTION_KEY environment variable is required (or configure OPENBAO_ADDR for Transit encryption)")
}
// OpenBao configured without legacy key -- OK for post-migration
slog.Info("CREDENTIAL_ENCRYPTION_KEY not set; OpenBao Transit will handle all credential decryption")
} else {
// Validate production safety BEFORE decode: reject known insecure defaults in non-dev environments.
// This runs first so placeholder values like "CHANGE_ME_IN_PRODUCTION" get a clear security
// error instead of a confusing "not valid base64" error.
if cfg.Environment != "dev" {
if keyB64 == knownInsecureEncryptionKey || keyB64 == "CHANGE_ME_IN_PRODUCTION" {
return nil, fmt.Errorf(
"FATAL: CREDENTIAL_ENCRYPTION_KEY uses a known insecure default in '%s' environment. "+
"Generate a secure key for production: "+
"python -c \"import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())\"",
cfg.Environment,
)
}
}
key, err := base64.StdEncoding.DecodeString(keyB64)
if err != nil {
return nil, fmt.Errorf("CREDENTIAL_ENCRYPTION_KEY is not valid base64: %w", err)
}
if len(key) != 32 {
return nil, fmt.Errorf("CREDENTIAL_ENCRYPTION_KEY must decode to exactly 32 bytes, got %d", len(key))
}
cfg.CredentialEncryptionKey = key
}
return cfg, nil
}
// getEnv returns the value of an environment variable, or the defaultValue if not set.
func getEnv(key, defaultValue string) string {
if val := os.Getenv(key); val != "" {
return val
}
return defaultValue
}
// getEnvInt returns the integer value of an environment variable, or the defaultValue if not set or invalid.
func getEnvInt(key string, defaultValue int) int {
val := os.Getenv(key)
if val == "" {
return defaultValue
}
n, err := strconv.Atoi(val)
if err != nil {
return defaultValue
}
return n
}

View File

@@ -0,0 +1,79 @@
package config
import (
"os"
"strings"
"testing"
)
func TestProductionValidationRejectsInsecureKey(t *testing.T) {
// Save and restore env
origEnv := os.Getenv("ENVIRONMENT")
origDB := os.Getenv("DATABASE_URL")
origKey := os.Getenv("CREDENTIAL_ENCRYPTION_KEY")
defer func() {
os.Setenv("ENVIRONMENT", origEnv)
os.Setenv("DATABASE_URL", origDB)
os.Setenv("CREDENTIAL_ENCRYPTION_KEY", origKey)
}()
os.Setenv("DATABASE_URL", "postgres://test:test@localhost:5432/test")
// Test: production with known insecure default key should fail
os.Setenv("ENVIRONMENT", "production")
os.Setenv("CREDENTIAL_ENCRYPTION_KEY", "LLLjnfBZTSycvL2U07HDSxUeTtLxb9cZzryQl0R9E4w=")
_, err := Load()
if err == nil {
t.Fatal("expected error for insecure key in production, got nil")
}
if !strings.Contains(err.Error(), "FATAL") {
t.Fatalf("expected FATAL in error message, got: %s", err.Error())
}
}
func TestProductionValidationRejectsPlaceholder(t *testing.T) {
origEnv := os.Getenv("ENVIRONMENT")
origDB := os.Getenv("DATABASE_URL")
origKey := os.Getenv("CREDENTIAL_ENCRYPTION_KEY")
defer func() {
os.Setenv("ENVIRONMENT", origEnv)
os.Setenv("DATABASE_URL", origDB)
os.Setenv("CREDENTIAL_ENCRYPTION_KEY", origKey)
}()
os.Setenv("DATABASE_URL", "postgres://test:test@localhost:5432/test")
os.Setenv("ENVIRONMENT", "production")
os.Setenv("CREDENTIAL_ENCRYPTION_KEY", "CHANGE_ME_IN_PRODUCTION")
_, err := Load()
if err == nil {
t.Fatal("expected error for CHANGE_ME_IN_PRODUCTION in production, got nil")
}
if !strings.Contains(err.Error(), "FATAL") {
t.Fatalf("expected FATAL in error message for placeholder, got: %s", err.Error())
}
}
func TestDevModeAcceptsInsecureDefaults(t *testing.T) {
origEnv := os.Getenv("ENVIRONMENT")
origDB := os.Getenv("DATABASE_URL")
origKey := os.Getenv("CREDENTIAL_ENCRYPTION_KEY")
defer func() {
os.Setenv("ENVIRONMENT", origEnv)
os.Setenv("DATABASE_URL", origDB)
os.Setenv("CREDENTIAL_ENCRYPTION_KEY", origKey)
}()
os.Setenv("ENVIRONMENT", "dev")
os.Setenv("DATABASE_URL", "postgres://test:test@localhost:5432/test")
os.Setenv("CREDENTIAL_ENCRYPTION_KEY", "LLLjnfBZTSycvL2U07HDSxUeTtLxb9cZzryQl0R9E4w=")
cfg, err := Load()
if err != nil {
t.Fatalf("dev mode should accept insecure defaults, got: %s", err.Error())
}
if cfg.Environment != "dev" {
t.Fatalf("expected Environment=dev, got %s", cfg.Environment)
}
}

View File

@@ -0,0 +1,104 @@
package config
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoad_RequiredDatabaseURL(t *testing.T) {
// Clear DATABASE_URL to trigger required field error
t.Setenv("DATABASE_URL", "")
t.Setenv("CREDENTIAL_ENCRYPTION_KEY", base64.StdEncoding.EncodeToString(make([]byte, 32)))
_, err := Load()
require.Error(t, err)
assert.Contains(t, err.Error(), "DATABASE_URL")
}
func TestLoad_RequiredEncryptionKey(t *testing.T) {
t.Setenv("DATABASE_URL", "postgres://user:pass@localhost/db")
t.Setenv("CREDENTIAL_ENCRYPTION_KEY", "")
_, err := Load()
require.Error(t, err)
assert.Contains(t, err.Error(), "CREDENTIAL_ENCRYPTION_KEY")
}
func TestLoad_InvalidBase64Key(t *testing.T) {
t.Setenv("DATABASE_URL", "postgres://user:pass@localhost/db")
t.Setenv("CREDENTIAL_ENCRYPTION_KEY", "not-valid-base64!!!")
_, err := Load()
require.Error(t, err)
assert.Contains(t, err.Error(), "base64")
}
func TestLoad_WrongKeyLength(t *testing.T) {
// Encode a 16-byte key (too short -- must be 32)
t.Setenv("DATABASE_URL", "postgres://user:pass@localhost/db")
t.Setenv("CREDENTIAL_ENCRYPTION_KEY", base64.StdEncoding.EncodeToString(make([]byte, 16)))
_, err := Load()
require.Error(t, err)
assert.Contains(t, err.Error(), "32 bytes")
}
func TestLoad_DefaultValues(t *testing.T) {
t.Setenv("DATABASE_URL", "postgres://user:pass@localhost/db")
t.Setenv("CREDENTIAL_ENCRYPTION_KEY", base64.StdEncoding.EncodeToString(make([]byte, 32)))
// Clear optional vars to test defaults
t.Setenv("REDIS_URL", "")
t.Setenv("NATS_URL", "")
t.Setenv("LOG_LEVEL", "")
t.Setenv("POLL_INTERVAL_SECONDS", "")
t.Setenv("DEVICE_REFRESH_SECONDS", "")
t.Setenv("CONNECTION_TIMEOUT_SECONDS", "")
cfg, err := Load()
require.NoError(t, err)
assert.Equal(t, "redis://localhost:6379/0", cfg.RedisURL)
assert.Equal(t, "nats://localhost:4222", cfg.NatsURL)
assert.Equal(t, "info", cfg.LogLevel)
assert.Equal(t, 60, cfg.PollIntervalSeconds)
assert.Equal(t, 60, cfg.DeviceRefreshSeconds)
assert.Equal(t, 10, cfg.ConnectionTimeoutSeconds)
}
func TestLoad_CustomValues(t *testing.T) {
t.Setenv("DATABASE_URL", "postgres://custom:pass@db:5432/mydb")
t.Setenv("CREDENTIAL_ENCRYPTION_KEY", base64.StdEncoding.EncodeToString(make([]byte, 32)))
t.Setenv("REDIS_URL", "redis://custom-redis:6380/1")
t.Setenv("NATS_URL", "nats://custom-nats:4223")
t.Setenv("LOG_LEVEL", "debug")
t.Setenv("POLL_INTERVAL_SECONDS", "30")
t.Setenv("DEVICE_REFRESH_SECONDS", "120")
t.Setenv("CONNECTION_TIMEOUT_SECONDS", "5")
cfg, err := Load()
require.NoError(t, err)
assert.Equal(t, "postgres://custom:pass@db:5432/mydb", cfg.DatabaseURL)
assert.Equal(t, "redis://custom-redis:6380/1", cfg.RedisURL)
assert.Equal(t, "nats://custom-nats:4223", cfg.NatsURL)
assert.Equal(t, "debug", cfg.LogLevel)
assert.Equal(t, 30, cfg.PollIntervalSeconds)
assert.Equal(t, 120, cfg.DeviceRefreshSeconds)
assert.Equal(t, 5, cfg.ConnectionTimeoutSeconds)
}
func TestLoad_ValidEncryptionKey(t *testing.T) {
key := make([]byte, 32)
for i := range key {
key[i] = byte(i) // deterministic test key
}
t.Setenv("DATABASE_URL", "postgres://user:pass@localhost/db")
t.Setenv("CREDENTIAL_ENCRYPTION_KEY", base64.StdEncoding.EncodeToString(key))
cfg, err := Load()
require.NoError(t, err)
assert.Equal(t, key, cfg.CredentialEncryptionKey)
}