DEVELOPMENT ENVIRONMENT

~liljamo/felu

4f502ff6f53c7165f7064d30ed7d28d1a09ed328 — Jonni Liljamo 20 days ago b3145b7
feat: config rearchitect, lower qName early
M internal/components/manage.templ => internal/components/manage.templ +1 -1
@@ 4,7 4,7 @@ import "git.src.quest/~liljamo/felu/internal/config"
import "fmt"

func getDomainPattern() string {
	return fmt.Sprintf(".%s", config.FeluConfig.DNSPattern)
	return fmt.Sprintf(".%s", config.FeluConfig.Domain)
}

func getUpdateACommand() string {

M internal/config/config.go => internal/config/config.go +71 -21
@@ 8,21 8,39 @@
// Package config implements the global program config.
package config

import "git.src.quest/~liljamo/erya-go/util"
import (
	"fmt"
	"log/slog"
	"net"
	"os"
	"strconv"

	"github.com/miekg/dns"
)

// TODO: Switch back to erya-go once I've rearchitected it.
//import "git.src.quest/~liljamo/erya-go/util"

// FeluConfig is a global for accessing the global config.
var FeluConfig *config

type config struct {
	// Domain, is fqdn'd when read.
	//
	// Used for NS records and as DNS pattern.
	Domain string
	// IPv4 address to return on A queries for Domain.
	IPv4 string

	// The name displayed in the frontend.
	ServiceName string

	DDNSDomain string

	// The email to return in SOAs (e.g. admin.felu.arpa.), is fqdn'd when read.
	SOAEmail string

	// Initial email for the admin user, only used if no admin account (e.g. first boot)
	// Initial email for the admin user, used if no admin account exists (e.g. first boot).
	InitialAdminEmail string
	// Initial password for the admin user, only used if no admin account (e.g. first boot)
	// Initial password for the admin user, used if no admin account exists (e.g. first boot).
	InitialAdminPwd string

	LogLevel string


@@ 38,34 56,66 @@ type config struct {

	DNSBindIP   string
	DNSBindPort int32
	// Domain pattern, no leading dot, but with trailing dot
	DNSPattern string
}

// InitConfig initializes the global program config from environment variables.
func InitConfig() {
	ipv4 := net.ParseIP(loadEnvStr("FELU_IPV4")).To4()
	if ipv4 == nil {
		panic("Invalid IPv4")
	}

	FeluConfig = &config{
		ServiceName: util.LoadEnvStr("FELU_SERVICE_NAME", "FeluDDNS"),
		Domain: dns.Fqdn(loadEnvStr("FELU_DOMAIN")),
		IPv4:   ipv4.String(),

		ServiceName: loadEnvStrDef("FELU_SERVICE_NAME", "FeluDDNS"),

		SOAEmail: dns.Fqdn(loadEnvStr("FELU_SOA_EMAIL")),

		// FIXME: panic without this, should do same for many of these
		DDNSDomain: util.LoadEnvStr("FELU_DDNS_DOMAIN", "ddns.staging.fuckdns.org"),
		APIUrl:     util.LoadEnvStr("FELU_API_URL", "MUST_SET"),
		InitialAdminEmail: loadEnvStrDef("FELU_INITIAL_ADMIN_EMAIL", "admin@example.com"),
		InitialAdminPwd:   loadEnvStrDef("FELU_INITIAL_ADMIN_PWD", "feluadmin"),

		SOAEmail: util.LoadEnvStr("FELU_SOA_EMAIL", "admin.felu.arpa"),
		LogLevel: loadEnvStrDef("FELU_LOG_LEVEL", "info"),

		InitialAdminEmail: util.LoadEnvStr("FELU_INITIAL_ADMIN_EMAIL", "admin@example.com"),
		InitialAdminPwd:   util.LoadEnvStr("FELU_INITIAL_ADMIN_PWD", "feluadmin"),
		DataDir: loadEnvStrDef("FELU_DB_PATH", "/var/felu/"),

		LogLevel: util.LoadEnvStr("FELU_LOG_LEVEL", "info"),
		FrontendBindAddr: loadEnvStrDef("FELU_FRONTEND_BIND_ADDR", "0.0.0.0:8080"),

		DataDir: util.LoadEnvStr("FELU_DB_PATH", "/var/felu/"),
		APIUrl:      loadEnvStr("FELU_API_URL"),
		APIBindAddr: loadEnvStrDef("FELU_API_BIND_ADDR", "0.0.0.0:8081"),

		FrontendBindAddr: util.LoadEnvStr("FELU_FRONTEND_BIND_ADDR", "0.0.0.0:8080"),
		DNSBindIP:   loadEnvStrDef("FELU_DNS_BIND_IP", "0.0.0.0"),
		DNSBindPort: loadEnvInt32Def("FELU_DNS_BIND_PORT", 53),
	}
}

		APIBindAddr: util.LoadEnvStr("FELU_API_BIND_ADDR", "0.0.0.0:8081"),
func loadEnvStr(key string) string {
	value, found := os.LookupEnv(key)
	if found {
		return value
	}
	panic(fmt.Sprintf("Env var '%s' not found", key))
}

func loadEnvStrDef(key string, def string) string {
	value, found := os.LookupEnv(key)
	if found {
		return value
	}
	slog.Warn("Env var not set, using default", slog.String("key", key), slog.String("def", def))
	return def
}

		DNSBindIP:   util.LoadEnvStr("FELU_DNS_BIND_IP", "0.0.0.0"),
		DNSBindPort: util.LoadEnvInt32("FELU_DNS_BIND_PORT", 53),
		DNSPattern:  util.LoadEnvStr("FELU_DNS_PATTERN", "."),
func loadEnvInt32Def(key string, def int32) int32 {
	value, found := os.LookupEnv(key)
	if found {
		res, err := strconv.ParseInt(value, 10, 32)
		if err != nil {
			panic(fmt.Sprintf("Couldn't parse env var '%s' into an integer", key))
		}
		return int32(res)
	}
	slog.Warn("Env var not set, using default", slog.String("key", key), slog.Int("def", int(def)))
	return def
}

M internal/dns/query.go => internal/dns/query.go +15 -11
@@ 65,20 65,22 @@ func handleARecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
}

func handleNSRecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
	qName := strings.ToLower(q.Name)

	ns := &dns.NS{
		Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 86400},
		Ns:  dns.Fqdn(config.FeluConfig.DDNSDomain),
		Ns:  config.FeluConfig.Domain,
	}

	// "Root" DDNS domain NS.
	if q.Name == dns.Fqdn(config.FeluConfig.DDNSDomain) {
	// "Root" Domain NS.
	if qName == config.FeluConfig.Domain {
		m.Answer = append(m.Answer, ns)
		return
	}

	if index := strings.IndexByte(q.Name, '.'); index >= 0 {
	if index := strings.IndexByte(qName, '.'); index >= 0 {
		// FIXME: other way of checking that the domain exists
		_, err := db.FetchDomainARecord(strings.ToLower(q.Name[:index]))
		_, err := db.FetchDomainARecord(qName[:index])
		if err != nil {
			m.SetRcode(r, dns.RcodeNameError)
		} else {


@@ 90,10 92,12 @@ func handleNSRecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
}

func handleSOARecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
	qName := strings.ToLower(q.Name)

	soa := &dns.SOA{
		Hdr:     dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 0},
		Ns:      dns.Fqdn(config.FeluConfig.DDNSDomain),
		Mbox:    dns.Fqdn(config.FeluConfig.SOAEmail),
		Ns:      config.FeluConfig.Domain,
		Mbox:    config.FeluConfig.SOAEmail,
		Serial:  2024100301,
		Refresh: 86400,
		Retry:   7200,


@@ 101,15 105,15 @@ func handleSOARecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
		Minttl:  172800,
	}

	// "Root" DDNS domain SOA.
	if q.Name == dns.Fqdn(config.FeluConfig.DDNSDomain) {
	// "Root" Domain SOA.
	if qName == config.FeluConfig.Domain {
		m.Answer = append(m.Answer, soa)
		return
	}

	if index := strings.IndexByte(q.Name, '.'); index >= 0 {
	if index := strings.IndexByte(qName, '.'); index >= 0 {
		// FIXME: other way of checking that the domain exists
		_, err := db.FetchDomainARecord(strings.ToLower(q.Name[:index]))
		_, err := db.FetchDomainARecord(qName[:index])
		if err != nil {
			m.SetRcode(r, dns.RcodeNameError)
		} else {

M internal/dns/server.go => internal/dns/server.go +1 -1
@@ 15,7 15,7 @@ import (

// Run starts the DNS server.
func Run(addr string, net string) error {
	dns.HandleFunc(config.FeluConfig.DNSPattern, handleDNSRequest)
	dns.HandleFunc(config.FeluConfig.Domain, handleDNSRequest)

	server := &dns.Server{
		Addr:          addr,