~bigbes/core-go

ref: e359a4d558add23973e3ba292e7af627a8b88c88 core-go/config/config.go -rw-r--r-- 4.2 KiB
e359a4d5 — Drew DeVault gofmt 9 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package config

import (
	"fmt"
	"io"
	"io/fs"
	"log"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/vaughan0/go-ini"
)

// osFS implement the fs.FS interface for the host OS filesystem.
type osFS struct{}

func (osFS) Open(name string) (fs.File, error) { return os.Open(filepath.FromSlash(name)) }

func (osFS) Glob(pattern string) ([]string, error) { return filepath.Glob(filepath.FromSlash(pattern)) }

var (
	// FS is the filesystem to operate on. Can be exchanged for testing or embedding.
	FS            fs.GlobFS = osFS{}
	internalIPNet []net.IPNet
)

// Loads the application configuration.
func LoadConfig() ini.File {
	var (
		config ini.File
		err    error
	)

	// Only loads from one of these locations
	for _, path := range []string{
		"config.ini",
		"../config.ini",
		"/etc/sr.ht/config.ini",
		"/etc/sr.ht/*.ini",
	} {
		matches, err_ := FS.Glob(path)
		if err_ != nil {
			panic(err) // only happens on bad input
		}
		for _, f := range matches {
			var r io.Reader
			r, err = FS.Open(f)
			if err != nil {
				break
			}
			if config == nil {
				config, err = ini.Load(r)
			} else {
				err = config.Load(r)
			}
			if err != nil {
				break
			}
		}
		if len(matches) > 0 {
			break
		}
	}
	if err != nil {
		log.Fatalf("Failed to load config file: %v", err)
	}

	nets, ok := config.Get("sr.ht", "internal-ipnet")
	if !ok {
		nets = "127.0.0.0/8,::1/128," + // Loopback
			"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7," + // Private
			"169.254.0.0/16,fe80::/64" // Link local
	}
	for _, n := range strings.Split(nets, ",") {
		_, net, err := net.ParseCIDR(n)
		if err != nil {
			panic(fmt.Errorf("[sr.ht]internal-ipnet: %w", err))
		}
		internalIPNet = append(internalIPNet, *net)
	}

	return config
}

// Returns the URL (scheme, host, port) at which the web application for a
// given service can be found.
//
// Set "external" to true if the URL will be accessed by an external user (i.e.
// outside of the LAN).
func GetOrigin(conf ini.File, svc string, external bool) string {
	if external {
		origin, _ := conf.Get(svc, "origin")
		return origin
	}
	origin, ok := conf.Get(svc, "internal-origin")
	if ok {
		return origin
	}
	origin, _ = conf.Get(svc, "origin")
	return origin
}

// Returns the URL (scheme, host, port) at which the API for a given service
// can be found. Does not include the /query path!
//
// Set "external" to true if the URL will be accessed by an external user (i.e.
// outside of the LAN).
func GetAPI(conf ini.File, svc string, external bool) string {
	var candidates []string

	if external {
		candidates = []string{
			"api-origin",
			"origin",
		}
	} else {
		candidates = []string{
			"api-internal-origin",
			"internal-origin",
			"api-origin",
			"origin",
		}
	}

	for _, name := range candidates {
		origin, ok := conf.Get(svc, name)
		if ok {
			return origin
		}
	}

	panic(fmt.Errorf("No suitable origin configured for requested API"))
}

const DefaultQueueSize = 512

// Returns the configured integer value for the given section and variable name.
// If nothing is configured, the default value will be returned.
// If an invalid integer is configured, this function will panic.
func GetInt(conf ini.File, section, name string, defValue int) int {
	value := defValue
	if s, ok := conf.Get(section, name); ok {
		var err error
		if value, err = strconv.Atoi(s); err != nil {
			panic(fmt.Errorf("[%s]%s: %w", section, name, err))
		}
	}
	return value
}

// Returns the configured boolean value for the given section and variable name.
// If nothing is configured, the default value will be returned.
// If an invalid boolean is configured, this function will panic.
func GetBool(conf ini.File, section, name string, defValue bool) bool {
	value := defValue
	if s, ok := conf.Get(section, name); ok {
		switch s {
		case "true", "yes", "on", "1":
			value = true
		case "false", "no", "off", "0":
			value = false
		default:
			panic(fmt.Errorf("[%s]%s: invalid boolean value", section, name))
		}
	}
	return value
}

// Returns true if the given IP address is part of the internal networks as
// per the configuration of [sr.ht]internal-ipnet.
func IsInternalIP(ip net.IP) bool {
	for _, net := range internalIPNet {
		if net.Contains(ip) {
			return true
		}
	}
	return false
}