182 lines
5.2 KiB
Go
182 lines
5.2 KiB
Go
package config
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const defaultAddr = ":8080"
|
|
const defaultDataDir = ".platform-data"
|
|
const defaultStorageBackend = "file"
|
|
const defaultBuilderDockerBinary = "docker"
|
|
|
|
// defaultBuilderImage names an explicit toolchain version rather than a
|
|
// floating tag such as latest. Operators who want digest pinning override
|
|
// PLATFORM_BUILDER_IMAGE with an image@sha256:... reference.
|
|
const defaultBuilderImage = "browser-platform-distribution-builder:1.0.0"
|
|
const defaultBuilderTimeoutSeconds = 1800
|
|
|
|
type Config struct {
|
|
Addr string
|
|
StorageBackend string
|
|
MySQLDSN string
|
|
DataDir string
|
|
MetadataPath string
|
|
LogDir string
|
|
ArtifactDir string
|
|
LogBodyBackend string
|
|
BootstrapAdminEmail string
|
|
BootstrapAdminPassword string
|
|
SecretEnvelopeKey string
|
|
AIProviderMode string
|
|
BuilderDockerBinary string
|
|
BuilderImage string
|
|
BuilderSourceDir string
|
|
BuilderWorkspaceDir string
|
|
BuilderTimeoutSeconds int
|
|
}
|
|
|
|
func Load() Config {
|
|
loadLocalEnvFiles()
|
|
|
|
addr := os.Getenv("PLATFORM_ADDR")
|
|
if addr == "" {
|
|
addr = defaultAddr
|
|
}
|
|
dataDir := strings.TrimSpace(os.Getenv("PLATFORM_DATA_DIR"))
|
|
if dataDir == "" {
|
|
dataDir = defaultDataDir
|
|
}
|
|
metadataPath := strings.TrimSpace(os.Getenv("PLATFORM_METADATA_PATH"))
|
|
if metadataPath == "" {
|
|
metadataPath = filepath.Join(dataDir, "metadata.json")
|
|
}
|
|
logDir := strings.TrimSpace(os.Getenv("PLATFORM_LOG_DIR"))
|
|
if logDir == "" {
|
|
logDir = filepath.Join(dataDir, "logs")
|
|
}
|
|
artifactDir := strings.TrimSpace(os.Getenv("PLATFORM_ARTIFACT_DIR"))
|
|
if artifactDir == "" {
|
|
artifactDir = filepath.Join(dataDir, "artifacts")
|
|
}
|
|
storageBackend := strings.TrimSpace(os.Getenv("PLATFORM_STORAGE_BACKEND"))
|
|
if storageBackend == "" {
|
|
storageBackend = defaultStorageBackend
|
|
}
|
|
logBodyBackend := strings.TrimSpace(os.Getenv("PLATFORM_LOG_BODY_BACKEND"))
|
|
builderWorkspaceDir := strings.TrimSpace(os.Getenv("PLATFORM_BUILDER_WORKSPACE_DIR"))
|
|
if builderWorkspaceDir == "" {
|
|
builderWorkspaceDir = filepath.Join(dataDir, "distribution-builds")
|
|
}
|
|
|
|
return Config{
|
|
Addr: addr,
|
|
StorageBackend: storageBackend,
|
|
MySQLDSN: strings.TrimSpace(os.Getenv("PLATFORM_MYSQL_DSN")),
|
|
DataDir: dataDir,
|
|
MetadataPath: metadataPath,
|
|
LogDir: logDir,
|
|
ArtifactDir: artifactDir,
|
|
LogBodyBackend: logBodyBackend,
|
|
BootstrapAdminEmail: strings.TrimSpace(os.Getenv("PLATFORM_BOOTSTRAP_ADMIN_EMAIL")),
|
|
BootstrapAdminPassword: os.Getenv("PLATFORM_BOOTSTRAP_ADMIN_PASSWORD"),
|
|
SecretEnvelopeKey: os.Getenv("PLATFORM_SECRET_ENVELOPE_KEY"),
|
|
AIProviderMode: defaultString(strings.TrimSpace(os.Getenv("PLATFORM_AI_PROVIDER_MODE")), "live"),
|
|
BuilderDockerBinary: defaultString(strings.TrimSpace(os.Getenv("PLATFORM_BUILDER_DOCKER_BINARY")), defaultBuilderDockerBinary),
|
|
BuilderImage: defaultString(strings.TrimSpace(os.Getenv("PLATFORM_BUILDER_IMAGE")), defaultBuilderImage),
|
|
BuilderSourceDir: strings.TrimSpace(os.Getenv("PLATFORM_BUILDER_SOURCE_DIR")),
|
|
BuilderWorkspaceDir: builderWorkspaceDir,
|
|
BuilderTimeoutSeconds: defaultPositiveInt(strings.TrimSpace(os.Getenv("PLATFORM_BUILDER_TIMEOUT_SECONDS")), defaultBuilderTimeoutSeconds),
|
|
}
|
|
}
|
|
|
|
func defaultPositiveInt(value string, fallback int) int {
|
|
parsed, err := strconv.Atoi(value)
|
|
if err != nil || parsed <= 0 {
|
|
return fallback
|
|
}
|
|
return parsed
|
|
}
|
|
|
|
func defaultString(value, fallback string) string {
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
return value
|
|
}
|
|
|
|
func loadLocalEnvFiles() {
|
|
candidates := []string{".env", filepath.Join("platform", ".env")}
|
|
for _, path := range candidates {
|
|
loadEnvFile(path)
|
|
}
|
|
}
|
|
|
|
func loadEnvFile(path string) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
key, value, ok := parseEnvLine(scanner.Text())
|
|
if !ok {
|
|
continue
|
|
}
|
|
if _, exists := os.LookupEnv(key); exists {
|
|
continue
|
|
}
|
|
_ = os.Setenv(key, value)
|
|
}
|
|
}
|
|
|
|
func parseEnvLine(line string) (string, string, bool) {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
return "", "", false
|
|
}
|
|
line = strings.TrimSpace(strings.TrimPrefix(line, "export "))
|
|
key, value, found := strings.Cut(line, "=")
|
|
if !found {
|
|
return "", "", false
|
|
}
|
|
key = strings.TrimSpace(key)
|
|
if key == "" || strings.ContainsAny(key, " \t") {
|
|
return "", "", false
|
|
}
|
|
value = strings.TrimSpace(stripInlineComment(strings.TrimSpace(value)))
|
|
if len(value) >= 2 {
|
|
if (value[0] == '"' && value[len(value)-1] == '"') || (value[0] == '\'' && value[len(value)-1] == '\'') {
|
|
value = value[1 : len(value)-1]
|
|
}
|
|
}
|
|
return key, value, true
|
|
}
|
|
|
|
func stripInlineComment(value string) string {
|
|
inSingleQuote := false
|
|
inDoubleQuote := false
|
|
for index, char := range value {
|
|
switch char {
|
|
case '\'':
|
|
if !inDoubleQuote {
|
|
inSingleQuote = !inSingleQuote
|
|
}
|
|
case '"':
|
|
if !inSingleQuote {
|
|
inDoubleQuote = !inDoubleQuote
|
|
}
|
|
case '#':
|
|
if !inSingleQuote && !inDoubleQuote && index > 0 && value[index-1] == ' ' {
|
|
return strings.TrimSpace(value[:index])
|
|
}
|
|
}
|
|
}
|
|
return value
|
|
}
|