90 lines
2.5 KiB
Go
90 lines
2.5 KiB
Go
// Package config loads he-dydns configuration from a TOML file.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
const DefaultIntervalMinutes = 5
|
|
|
|
// RecordType identifies which DNS record family an entry updates. Hurricane
|
|
// Electric's dynamic DNS API cannot infer this from the submitted address
|
|
// alone, so it must be configured explicitly per entry.
|
|
type RecordType string
|
|
|
|
const (
|
|
RecordTypeA RecordType = "A"
|
|
RecordTypeAAAA RecordType = "AAAA"
|
|
)
|
|
|
|
// Entry describes a single Hurricane Electric dynamic DNS hostname to keep updated.
|
|
type Entry struct {
|
|
Hostname string `toml:"hostname"`
|
|
Token string `toml:"token"`
|
|
// RecordType selects whether this entry updates the A (IPv4) or AAAA
|
|
// (IPv6) record. Defaults to A when omitted.
|
|
RecordType RecordType `toml:"record_type"`
|
|
}
|
|
|
|
// Config is the top-level he-dydns configuration.
|
|
type Config struct {
|
|
// Interface, if set, restricts address discovery to this network
|
|
// interface instead of guessing the public IP via an external service.
|
|
Interface string `toml:"interface"`
|
|
// IntervalMinutes controls how often updates run in daemon mode.
|
|
IntervalMinutes int `toml:"interval_minutes"`
|
|
Entries []Entry `toml:"entries"`
|
|
}
|
|
|
|
// DefaultPath returns $HOME/.config/he-dydns/config.toml.
|
|
func DefaultPath() (string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("determining home directory: %w", err)
|
|
}
|
|
return filepath.Join(home, ".config", "he-dydns", "config.toml"), nil
|
|
}
|
|
|
|
// Load reads and validates the configuration file at path.
|
|
func Load(path string) (*Config, error) {
|
|
var cfg Config
|
|
if _, err := toml.DecodeFile(path, &cfg); err != nil {
|
|
return nil, fmt.Errorf("reading config %s: %w", path, err)
|
|
}
|
|
|
|
if len(cfg.Entries) == 0 {
|
|
return nil, fmt.Errorf("config %s: no entries defined", path)
|
|
}
|
|
for i := range cfg.Entries {
|
|
e := &cfg.Entries[i]
|
|
if e.Hostname == "" {
|
|
return nil, fmt.Errorf("config %s: entries[%d] missing hostname", path, i)
|
|
}
|
|
if e.Token == "" {
|
|
return nil, fmt.Errorf("config %s: entries[%d] missing token", path, i)
|
|
}
|
|
|
|
switch RecordType(strings.ToUpper(string(e.RecordType))) {
|
|
case "":
|
|
e.RecordType = RecordTypeA
|
|
case RecordTypeA:
|
|
e.RecordType = RecordTypeA
|
|
case RecordTypeAAAA:
|
|
e.RecordType = RecordTypeAAAA
|
|
default:
|
|
return nil, fmt.Errorf("config %s: entries[%d] invalid record_type %q (must be \"A\" or \"AAAA\")", path, i, e.RecordType)
|
|
}
|
|
}
|
|
|
|
if cfg.IntervalMinutes <= 0 {
|
|
cfg.IntervalMinutes = DefaultIntervalMinutes
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|