Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

services/luci/internal/config/config.go

Raw
package config

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

type Config struct {
	DataDir                 string
	RepoRoots               []string
	HTTPAddr                string
	BaseURL                 string
	InboxDir                string
	PodmanSocket            string
	SSHAddr                 string
	SSHAuthorizedKeys       string
	ThemeCSS                string
	DefaultTimeout          time.Duration
	DefaultMemory           string
	DefaultCPUs             string
	CacheMaxVersions        int
	CacheMaxBytes           int64
	LogLevel                string
	PublishRegistryAuthFile string
	PublishPkgRoot          string
	PublishSiteRoot         string
}

func Load() (Config, error) {
	var cfg Config
	var err error

	if cfg.DataDir, err = required("LUCI_DATA_DIR"); err != nil {
		return Config{}, err
	}
	repoRoots, err := required("LUCI_REPO_ROOTS")
	if err != nil {
		return Config{}, err
	}
	cfg.RepoRoots = splitList(repoRoots)
	if cfg.HTTPAddr, err = required("LUCI_HTTP_ADDR"); err != nil {
		return Config{}, err
	}
	if cfg.BaseURL, err = required("LUCI_BASE_URL"); err != nil {
		return Config{}, err
	}
	if cfg.InboxDir, err = required("LUCI_INBOX_DIR"); err != nil {
		return Config{}, err
	}
	if cfg.PodmanSocket, err = required("LUCI_PODMAN_SOCKET"); err != nil {
		return Config{}, err
	}

	cfg.SSHAddr = optional("LUCI_SSH_ADDR", "")
	cfg.SSHAuthorizedKeys = optional("LUCI_SSH_AUTHORIZED_KEYS", "")
	cfg.ThemeCSS = optional("LUCI_THEME_CSS", "")
	cfg.DefaultMemory = optional("LUCI_DEFAULT_MEMORY", "2g")
	cfg.DefaultCPUs = optional("LUCI_DEFAULT_CPUS", "2")
	cfg.LogLevel = optional("LUCI_LOG_LEVEL", "info")
	cfg.PublishRegistryAuthFile = optional("LUCI_PUBLISH_REGISTRY_AUTH_FILE", "")
	cfg.PublishPkgRoot = optional("LUCI_PUBLISH_PKG_ROOT", "")
	cfg.PublishSiteRoot = optional("LUCI_PUBLISH_SITE_ROOT", "")

	timeoutText := optional("LUCI_DEFAULT_TIMEOUT", "20m")
	cfg.DefaultTimeout, err = time.ParseDuration(timeoutText)
	if err != nil {
		return Config{}, fmt.Errorf("invalid LUCI_DEFAULT_TIMEOUT: %w", err)
	}
	if versionsText := optional("LUCI_CACHE_MAX_VERSIONS", "3"); versionsText != "" {
		cfg.CacheMaxVersions, err = strconv.Atoi(versionsText)
		if err != nil || cfg.CacheMaxVersions < 1 {
			return Config{}, fmt.Errorf("invalid LUCI_CACHE_MAX_VERSIONS: %q", versionsText)
		}
	}
	if bytesText := optional("LUCI_CACHE_MAX_BYTES", "2GiB"); bytesText != "" {
		cfg.CacheMaxBytes, err = parseBytes(bytesText)
		if err != nil {
			return Config{}, fmt.Errorf("invalid LUCI_CACHE_MAX_BYTES: %w", err)
		}
	}

	return cfg, nil
}

// parseBytes accepts a plain byte count or a binary suffix (KiB, MiB, GiB,
// TiB, and their one-letter forms).
func parseBytes(text string) (int64, error) {
	units := map[string]int64{"": 1, "k": 1 << 10, "m": 1 << 20, "g": 1 << 30, "t": 1 << 40}
	trimmed := strings.ToLower(strings.TrimSpace(text))
	if trimmed == "" {
		return 0, fmt.Errorf("invalid byte size %q", text)
	}
	for _, suffix := range []string{"kib", "mib", "gib", "tib"} {
		if strings.HasSuffix(trimmed, suffix) {
			trimmed = strings.TrimSuffix(trimmed, suffix) + suffix[:1]
			break
		}
	}
	trimmed = strings.TrimSuffix(trimmed, "b")
	unit := ""
	if last := trimmed[len(trimmed)-1]; last >= 'a' && last <= 'z' {
		if _, ok := units[string(last)]; ok {
			unit = string(last)
			trimmed = trimmed[:len(trimmed)-1]
		}
	}
	value, err := strconv.ParseInt(trimmed, 10, 64)
	if err != nil || value < 0 || value > (1<<63-1)/units[unit] {
		return 0, fmt.Errorf("invalid byte size %q", text)
	}
	return value * units[unit], nil
}

func required(name string) (string, error) {
	value, ok := os.LookupEnv(name)
	if !ok || strings.TrimSpace(value) == "" {
		return "", fmt.Errorf("missing required env %s", name)
	}
	return value, nil
}

func optional(name string, fallback string) string {
	value, ok := os.LookupEnv(name)
	if !ok || strings.TrimSpace(value) == "" {
		return fallback
	}
	return value
}

func splitList(value string) []string {
	parts := strings.Split(value, ":")
	out := make([]string, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part != "" {
			out = append(out, part)
		}
	}
	return out
}