AdGuardHome/internal/home/upgrade.go
Dimitry Kolyshev c77b2a0ce5 Pull request: home: imp code
Merge in DNS/adguard-home from home-imp-code to master

Squashed commit of the following:

commit 459297e189c55393bf0340dd51ec9608d3475e55
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 10 11:42:34 2023 +0300

    home: imp code

commit ab38e1e80fed7b24fe57d4afdc57b70608f65d73
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 10 11:01:23 2023 +0300

    all: lint script

commit 7df68b128bf32172ef2e3bf7116f4f72a97baa2b
Merge: bcb482714 db52f7a3a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 10 10:59:40 2023 +0300

    Merge remote-tracking branch 'origin/master' into home-imp-code

commit bcb482714780da882e69c261be08511ea4f36f3b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 4 13:48:27 2023 +0300

    all: lint script

commit 1c017f27715202ec1f40881f069a96f11f9822e8
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 4 13:45:25 2023 +0300

    all: lint script

commit ee3d427a7d6ee7e377e67c5eb99eebc7fb1e6acc
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 4 13:44:53 2023 +0300

    home: imp code

commit bc50430469123415216e60e178bd8e30fc229300
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 4 13:12:10 2023 +0300

    home: imp code

commit fc07e416aeab2612e68cf0e3f933aaed95931115
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 4 11:42:32 2023 +0300

    aghos: service precheck

commit a68480fd9c4cd6f3c89210bee6917c53074f7a82
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu May 4 11:07:05 2023 +0300

    home: imp code

commit 61b743a340ac1564c48212452c7a9acd1808d352
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 3 17:17:21 2023 +0300

    all: lint script

commit c6fe620510c4af5b65456e90cb3424831334e004
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 3 17:16:37 2023 +0300

    home: imp code

commit 4b2fb47ea9c932054ccc72b1fd1d11793c93e39c
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 3 16:55:44 2023 +0300

    home: imp code

commit 63df3e2ab58482920a074cfd5f4188e49a0f8448
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 3 16:25:38 2023 +0300

    home: imp code

commit c7f1502f976482c2891e0c64426218b549585e83
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 3 15:54:30 2023 +0300

    home: imp code

commit c64cdaf1c82495bb70d9cdcaf7be9eeee9a7c773
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 3 14:35:04 2023 +0300

    home: imp code

commit a50436e040b3a064ef51d5f936b879fe8de72d41
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 3 14:24:02 2023 +0300

    home: imp code

commit 2b66464f472df732ea27cbbe5ac5c673a13bc14b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 3 14:11:53 2023 +0300

    home: imp code

commit 713ce2963c210887faa0a06e41e01e4ebbf96894
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 3 14:10:54 2023 +0300

    home: imp code
2023-05-10 16:30:03 +03:00

1138 lines
22 KiB
Go

package home
import (
"bytes"
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/google/renameio/maybe"
"golang.org/x/crypto/bcrypt"
yaml "gopkg.in/yaml.v3"
)
// currentSchemaVersion is the current schema version.
const currentSchemaVersion = 20
// These aliases are provided for convenience.
type (
yarr = []any
yobj = map[string]any
)
// Performs necessary upgrade operations if needed
func upgradeConfig() error {
// read a config file into an interface map, so we can manipulate values without losing any
diskConf := yobj{}
body, err := readConfigFile()
if err != nil {
return err
}
err = yaml.Unmarshal(body, &diskConf)
if err != nil {
log.Printf("parsing config file for upgrade: %s", err)
return err
}
schemaVersionInterface, ok := diskConf["schema_version"]
log.Tracef("got schema version %v", schemaVersionInterface)
if !ok {
// no schema version, set it to 0
schemaVersionInterface = 0
}
schemaVersion, ok := schemaVersionInterface.(int)
if !ok {
err = fmt.Errorf("configuration file contains non-integer schema_version, abort")
log.Println(err)
return err
}
if schemaVersion == currentSchemaVersion {
// do nothing
return nil
}
return upgradeConfigSchema(schemaVersion, diskConf)
}
// upgradeFunc is a function that upgrades a config and returns an error.
type upgradeFunc = func(diskConf yobj) (err error)
// Upgrade from oldVersion to newVersion
func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
upgrades := []upgradeFunc{
upgradeSchema0to1,
upgradeSchema1to2,
upgradeSchema2to3,
upgradeSchema3to4,
upgradeSchema4to5,
upgradeSchema5to6,
upgradeSchema6to7,
upgradeSchema7to8,
upgradeSchema8to9,
upgradeSchema9to10,
upgradeSchema10to11,
upgradeSchema11to12,
upgradeSchema12to13,
upgradeSchema13to14,
upgradeSchema14to15,
upgradeSchema15to16,
upgradeSchema16to17,
upgradeSchema17to18,
upgradeSchema18to19,
upgradeSchema19to20,
}
n := 0
for i, u := range upgrades {
if i >= oldVersion {
err = u(diskConf)
if err != nil {
return err
}
n++
}
}
if n == 0 {
return fmt.Errorf("unknown configuration schema version %d", oldVersion)
}
buf := &bytes.Buffer{}
enc := yaml.NewEncoder(buf)
enc.SetIndent(2)
err = enc.Encode(diskConf)
if err != nil {
return fmt.Errorf("generating new config: %w", err)
}
config.fileData = buf.Bytes()
confFile := config.getConfigFilename()
err = maybe.WriteFile(confFile, config.fileData, 0o644)
if err != nil {
return fmt.Errorf("writing new config: %w", err)
}
return nil
}
// The first schema upgrade:
// No more "dnsfilter.txt", filters are now kept in data/filters/
func upgradeSchema0to1(diskConf yobj) (err error) {
log.Printf("%s(): called", funcName())
dnsFilterPath := filepath.Join(Context.workDir, "dnsfilter.txt")
log.Printf("deleting %s as we don't need it anymore", dnsFilterPath)
err = os.Remove(dnsFilterPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
log.Info("warning: %s", err)
// Go on.
}
diskConf["schema_version"] = 1
return nil
}
// Second schema upgrade:
// coredns is now dns in config
// delete 'Corefile', since we don't use that anymore
func upgradeSchema1to2(diskConf yobj) (err error) {
log.Printf("%s(): called", funcName())
coreFilePath := filepath.Join(Context.workDir, "Corefile")
log.Printf("deleting %s as we don't need it anymore", coreFilePath)
err = os.Remove(coreFilePath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
log.Info("warning: %s", err)
// Go on.
}
if _, ok := diskConf["dns"]; !ok {
diskConf["dns"] = diskConf["coredns"]
delete(diskConf, "coredns")
}
diskConf["schema_version"] = 2
return nil
}
// Third schema upgrade:
// Bootstrap DNS becomes an array
func upgradeSchema2to3(diskConf yobj) error {
log.Printf("%s(): called", funcName())
// Let's read dns configuration from diskConf
dnsConfig, ok := diskConf["dns"]
if !ok {
return fmt.Errorf("no DNS configuration in config file")
}
// Convert any to yobj
newDNSConfig := make(yobj)
switch v := dnsConfig.(type) {
case yobj:
for k, v := range v {
newDNSConfig[fmt.Sprint(k)] = v
}
default:
return fmt.Errorf("unexpected type of dns: %T", dnsConfig)
}
// Replace bootstrap_dns value filed with new array contains old bootstrap_dns inside
bootstrapDNS, ok := newDNSConfig["bootstrap_dns"]
if !ok {
return fmt.Errorf("no bootstrap DNS in DNS config")
}
newBootstrapConfig := []string{fmt.Sprint(bootstrapDNS)}
newDNSConfig["bootstrap_dns"] = newBootstrapConfig
diskConf["dns"] = newDNSConfig
// Bump schema version
diskConf["schema_version"] = 3
return nil
}
// Add use_global_blocked_services=true setting for existing "clients" array
func upgradeSchema3to4(diskConf yobj) error {
log.Printf("%s(): called", funcName())
diskConf["schema_version"] = 4
clients, ok := diskConf["clients"]
if !ok {
return nil
}
switch arr := clients.(type) {
case []any:
for i := range arr {
switch c := arr[i].(type) {
case map[any]any:
c["use_global_blocked_services"] = true
default:
continue
}
}
default:
return nil
}
return nil
}
// Replace "auth_name", "auth_pass" string settings with an array:
// users:
// - name: "..."
// password: "..."
//
// ...
func upgradeSchema4to5(diskConf yobj) error {
log.Printf("%s(): called", funcName())
diskConf["schema_version"] = 5
name, ok := diskConf["auth_name"]
if !ok {
return nil
}
nameStr, ok := name.(string)
if !ok {
log.Fatal("Please use double quotes in your user name in \"auth_name\" and restart AdGuardHome")
return nil
}
pass, ok := diskConf["auth_pass"]
if !ok {
return nil
}
passStr, ok := pass.(string)
if !ok {
log.Fatal("Please use double quotes in your password in \"auth_pass\" and restart AdGuardHome")
return nil
}
if len(nameStr) == 0 {
return nil
}
hash, err := bcrypt.GenerateFromPassword([]byte(passStr), bcrypt.DefaultCost)
if err != nil {
log.Fatalf("Can't use password \"%s\": bcrypt.GenerateFromPassword: %s", passStr, err)
return nil
}
u := webUser{
Name: nameStr,
PasswordHash: string(hash),
}
users := []webUser{u}
diskConf["users"] = users
return nil
}
// upgradeSchema5to6 performs the following changes:
//
// # BEFORE:
// 'clients':
// ...
// 'ip': 127.0.0.1
// 'mac': ...
//
// # AFTER:
// 'clients':
// ...
// 'ids':
// - 127.0.0.1
// - ...
func upgradeSchema5to6(diskConf yobj) error {
log.Printf("Upgrade yaml: 5 to 6")
diskConf["schema_version"] = 6
clientsVal, ok := diskConf["clients"]
if !ok {
return nil
}
clients, ok := clientsVal.([]yobj)
if !ok {
return fmt.Errorf("unexpected type of clients: %T", clientsVal)
}
for i := range clients {
c := clients[i]
var ids []string
if ipVal, hasIP := c["ip"]; hasIP {
var ip string
if ip, ok = ipVal.(string); !ok {
return fmt.Errorf("client.ip is not a string: %v", ipVal)
}
if ip != "" {
ids = append(ids, ip)
}
}
if macVal, hasMac := c["mac"]; hasMac {
var mac string
if mac, ok = macVal.(string); !ok {
return fmt.Errorf("client.mac is not a string: %v", macVal)
}
if mac != "" {
ids = append(ids, mac)
}
}
c["ids"] = ids
}
return nil
}
// dhcp:
//
// enabled: false
// interface_name: vboxnet0
// gateway_ip: 192.168.56.1
// ...
//
// ->
//
// dhcp:
//
// enabled: false
// interface_name: vboxnet0
// dhcpv4:
// gateway_ip: 192.168.56.1
// ...
func upgradeSchema6to7(diskConf yobj) error {
log.Printf("Upgrade yaml: 6 to 7")
diskConf["schema_version"] = 7
dhcpVal, ok := diskConf["dhcp"]
if !ok {
return nil
}
switch dhcp := dhcpVal.(type) {
case map[any]any:
var str string
str, ok = dhcp["gateway_ip"].(string)
if !ok {
log.Fatalf("expecting dhcp.%s to be a string", "gateway_ip")
return nil
}
dhcpv4 := yobj{
"gateway_ip": str,
}
delete(dhcp, "gateway_ip")
str, ok = dhcp["subnet_mask"].(string)
if !ok {
log.Fatalf("expecting dhcp.%s to be a string", "subnet_mask")
return nil
}
dhcpv4["subnet_mask"] = str
delete(dhcp, "subnet_mask")
str, ok = dhcp["range_start"].(string)
if !ok {
log.Fatalf("expecting dhcp.%s to be a string", "range_start")
return nil
}
dhcpv4["range_start"] = str
delete(dhcp, "range_start")
str, ok = dhcp["range_end"].(string)
if !ok {
log.Fatalf("expecting dhcp.%s to be a string", "range_end")
return nil
}
dhcpv4["range_end"] = str
delete(dhcp, "range_end")
var n int
n, ok = dhcp["lease_duration"].(int)
if !ok {
log.Fatalf("expecting dhcp.%s to be an integer", "lease_duration")
return nil
}
dhcpv4["lease_duration"] = n
delete(dhcp, "lease_duration")
n, ok = dhcp["icmp_timeout_msec"].(int)
if !ok {
log.Fatalf("expecting dhcp.%s to be an integer", "icmp_timeout_msec")
return nil
}
dhcpv4["icmp_timeout_msec"] = n
delete(dhcp, "icmp_timeout_msec")
dhcp["dhcpv4"] = dhcpv4
default:
return nil
}
return nil
}
// upgradeSchema7to8 performs the following changes:
//
// # BEFORE:
// 'dns':
// 'bind_host': '127.0.0.1'
//
// # AFTER:
// 'dns':
// 'bind_hosts':
// - '127.0.0.1'
func upgradeSchema7to8(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 7 to 8")
diskConf["schema_version"] = 8
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
dns, ok := dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
bindHostVal := dns["bind_host"]
bindHost, ok := bindHostVal.(string)
if !ok {
return fmt.Errorf("unexpected type of dns.bind_host: %T", bindHostVal)
}
delete(dns, "bind_host")
dns["bind_hosts"] = yarr{bindHost}
return nil
}
// upgradeSchema8to9 performs the following changes:
//
// # BEFORE:
// 'dns':
// 'autohost_tld': 'lan'
//
// # AFTER:
// 'dns':
// 'local_domain_name': 'lan'
func upgradeSchema8to9(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 8 to 9")
diskConf["schema_version"] = 9
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
dns, ok := dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
autohostTLDVal, ok := dns["autohost_tld"]
if !ok {
// This happens when upgrading directly from v0.105.2, because
// dns.autohost_tld was never set to any value. Go on and leave
// it that way.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2988.
return nil
}
autohostTLD, ok := autohostTLDVal.(string)
if !ok {
return fmt.Errorf("unexpected type of dns.autohost_tld: %T", autohostTLDVal)
}
delete(dns, "autohost_tld")
dns["local_domain_name"] = autohostTLD
return nil
}
// addQUICPort inserts a port into QUIC upstream's hostname if it is missing.
func addQUICPort(ups string, port int) (withPort string) {
if ups == "" || ups[0] == '#' {
return ups
}
var doms string
withPort = ups
if strings.HasPrefix(ups, "[/") {
domsAndUps := strings.Split(strings.TrimPrefix(ups, "[/"), "/]")
if len(domsAndUps) != 2 {
return ups
}
doms, withPort = "[/"+domsAndUps[0]+"/]", domsAndUps[1]
}
if !strings.Contains(withPort, "://") {
return ups
}
upsURL, err := url.Parse(withPort)
if err != nil || upsURL.Scheme != "quic" {
return ups
}
var host string
host, err = netutil.SplitHost(upsURL.Host)
if err != nil || host != upsURL.Host {
return ups
}
upsURL.Host = strings.Join([]string{host, strconv.Itoa(port)}, ":")
return doms + upsURL.String()
}
// upgradeSchema9to10 performs the following changes:
//
// # BEFORE:
// 'dns':
// 'upstream_dns':
// - 'quic://some-upstream.com'
//
// # AFTER:
// 'dns':
// 'upstream_dns':
// - 'quic://some-upstream.com:784'
func upgradeSchema9to10(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 9 to 10")
diskConf["schema_version"] = 10
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
var dns yobj
dns, ok = dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
const quicPort = 784
for _, upsField := range []string{
"upstream_dns",
"local_ptr_upstreams",
} {
var upsVal any
upsVal, ok = dns[upsField]
if !ok {
continue
}
var ups yarr
ups, ok = upsVal.(yarr)
if !ok {
return fmt.Errorf("unexpected type of dns.%s: %T", upsField, upsVal)
}
var u string
for i, uVal := range ups {
u, ok = uVal.(string)
if !ok {
return fmt.Errorf("unexpected type of upstream field: %T", uVal)
}
ups[i] = addQUICPort(u, quicPort)
}
dns[upsField] = ups
}
return nil
}
// upgradeSchema10to11 performs the following changes:
//
// # BEFORE:
// 'rlimit_nofile': 42
//
// # AFTER:
// 'os':
// 'group': ''
// 'rlimit_nofile': 42
// 'user': ''
func upgradeSchema10to11(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 10 to 11")
diskConf["schema_version"] = 11
rlimit := 0
rlimitVal, ok := diskConf["rlimit_nofile"]
if ok {
rlimit, ok = rlimitVal.(int)
if !ok {
return fmt.Errorf("unexpected type of rlimit_nofile: %T", rlimitVal)
}
}
delete(diskConf, "rlimit_nofile")
diskConf["os"] = yobj{
"group": "",
"rlimit_nofile": rlimit,
"user": "",
}
return nil
}
// upgradeSchema11to12 performs the following changes:
//
// # BEFORE:
// 'querylog_interval': 90
//
// # AFTER:
// 'querylog_interval': '2160h'
func upgradeSchema11to12(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 11 to 12")
diskConf["schema_version"] = 12
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
var dns yobj
dns, ok = dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
const field = "querylog_interval"
// Set the initial value from home.initConfig function.
qlogIvl := 90
qlogIvlVal, ok := dns[field]
if ok {
qlogIvl, ok = qlogIvlVal.(int)
if !ok {
return fmt.Errorf("unexpected type of %s: %T", field, qlogIvlVal)
}
}
dns[field] = timeutil.Duration{Duration: time.Duration(qlogIvl) * timeutil.Day}
return nil
}
// upgradeSchema12to13 performs the following changes:
//
// # BEFORE:
// 'dns':
// # …
// 'local_domain_name': 'lan'
//
// # AFTER:
// 'dhcp':
// # …
// 'local_domain_name': 'lan'
func upgradeSchema12to13(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 12 to 13")
diskConf["schema_version"] = 13
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
var dns yobj
dns, ok = dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
dhcpVal, ok := diskConf["dhcp"]
if !ok {
return nil
}
var dhcp yobj
dhcp, ok = dhcpVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dhcp: %T", dhcpVal)
}
const field = "local_domain_name"
dhcp[field] = dns[field]
delete(dns, field)
return nil
}
// upgradeSchema13to14 performs the following changes:
//
// # BEFORE:
// 'clients':
// - 'name': 'client-name'
// # …
//
// # AFTER:
// 'clients':
// 'persistent':
// - 'name': 'client-name'
// # …
// 'runtime_sources':
// 'whois': true
// 'arp': true
// 'rdns': true
// 'dhcp': true
// 'hosts': true
func upgradeSchema13to14(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 13 to 14")
diskConf["schema_version"] = 14
clientsVal, ok := diskConf["clients"]
if !ok {
clientsVal = yarr{}
}
var rdnsSrc bool
if dnsVal, dok := diskConf["dns"]; dok {
var dnsSettings yobj
dnsSettings, ok = dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
var rdnsSrcVal any
rdnsSrcVal, ok = dnsSettings["resolve_clients"]
if ok {
rdnsSrc, ok = rdnsSrcVal.(bool)
if !ok {
return fmt.Errorf("unexpected type of resolve_clients: %T", rdnsSrcVal)
}
delete(dnsSettings, "resolve_clients")
}
}
diskConf["clients"] = yobj{
"persistent": clientsVal,
"runtime_sources": &clientSourcesConfig{
WHOIS: true,
ARP: true,
RDNS: rdnsSrc,
DHCP: true,
HostsFile: true,
},
}
return nil
}
// upgradeSchema14to15 performs the following changes:
//
// # BEFORE:
// 'dns':
// 'querylog_enabled': true
// 'querylog_file_enabled': true
// 'querylog_interval': '2160h'
// 'querylog_size_memory': 1000
//
// # AFTER:
// 'querylog':
// 'enabled': true
// 'file_enabled': true
// 'interval': '2160h'
// 'size_memory': 1000
// 'ignored': []
func upgradeSchema14to15(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 14 to 15")
diskConf["schema_version"] = 15
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
dns, ok := dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
type temp struct {
val any
from string
to string
}
replaces := []temp{
{from: "querylog_enabled", to: "enabled", val: true},
{from: "querylog_file_enabled", to: "file_enabled", val: true},
{from: "querylog_interval", to: "interval", val: "2160h"},
{from: "querylog_size_memory", to: "size_memory", val: 1000},
}
qlog := map[string]any{
"ignored": []any{},
}
for _, r := range replaces {
v, has := dns[r.from]
if !has {
v = r.val
}
delete(dns, r.from)
qlog[r.to] = v
}
diskConf["querylog"] = qlog
return nil
}
// upgradeSchema15to16 performs the following changes:
//
// # BEFORE:
// 'dns':
// 'statistics_interval': 1
//
// # AFTER:
// 'statistics':
// 'enabled': true
// 'interval': 1
// 'ignored': []
//
// If statistics were disabled:
//
// # BEFORE:
// 'dns':
// 'statistics_interval': 0
//
// # AFTER:
// 'statistics':
// 'enabled': false
// 'interval': 1
// 'ignored': []
func upgradeSchema15to16(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 15 to 16")
diskConf["schema_version"] = 16
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
dns, ok := dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
stats := map[string]any{
"enabled": true,
"interval": 1,
"ignored": []any{},
}
const field = "statistics_interval"
statsIvlVal, has := dns[field]
if has {
var statsIvl int
statsIvl, ok = statsIvlVal.(int)
if !ok {
return fmt.Errorf("unexpected type of dns.statistics_interval: %T", statsIvlVal)
}
if statsIvl == 0 {
// Set the interval to the default value of one day to make sure
// that it passes the validations.
stats["interval"] = 1
stats["enabled"] = false
} else {
stats["interval"] = statsIvl
stats["enabled"] = true
}
}
delete(dns, field)
diskConf["statistics"] = stats
return nil
}
// upgradeSchema16to17 performs the following changes:
//
// # BEFORE:
// 'dns':
// 'edns_client_subnet': false
//
// # AFTER:
// 'dns':
// 'edns_client_subnet':
// 'enabled': false
// 'use_custom': false
// 'custom_ip': ""
func upgradeSchema16to17(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 16 to 17")
diskConf["schema_version"] = 17
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
dns, ok := dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
const field = "edns_client_subnet"
dns[field] = map[string]any{
"enabled": dns[field] == true,
"use_custom": false,
"custom_ip": "",
}
return nil
}
// upgradeSchema17to18 performs the following changes:
//
// # BEFORE:
// 'dns':
// 'safesearch_enabled': true
//
// # AFTER:
// 'dns':
// 'safe_search':
// 'enabled': true
// 'bing': true
// 'duckduckgo': true
// 'google': true
// 'pixabay': true
// 'yandex': true
// 'youtube': true
func upgradeSchema17to18(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 17 to 18")
diskConf["schema_version"] = 18
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
dns, ok := dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
safeSearch := yobj{
"enabled": true,
"bing": true,
"duckduckgo": true,
"google": true,
"pixabay": true,
"yandex": true,
"youtube": true,
}
const safeSearchKey = "safesearch_enabled"
v, has := dns[safeSearchKey]
if has {
safeSearch["enabled"] = v
}
delete(dns, safeSearchKey)
dns["safe_search"] = safeSearch
return nil
}
// upgradeSchema18to19 performs the following changes:
//
// # BEFORE:
// 'clients':
// 'persistent':
// - 'name': 'client-name'
// 'safesearch_enabled': true
//
// # AFTER:
// 'clients':
// 'persistent':
// - 'name': 'client-name'
// 'safe_search':
// 'enabled': true
// 'bing': true
// 'duckduckgo': true
// 'google': true
// 'pixabay': true
// 'yandex': true
// 'youtube': true
func upgradeSchema18to19(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 18 to 19")
diskConf["schema_version"] = 19
clientsVal, ok := diskConf["clients"]
if !ok {
return nil
}
clients, ok := clientsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of clients: %T", clientsVal)
}
persistent, ok := clients["persistent"].([]yobj)
if !ok {
return nil
}
const safeSearchKey = "safesearch_enabled"
for i := range persistent {
c := persistent[i]
safeSearch := yobj{
"enabled": true,
"bing": true,
"duckduckgo": true,
"google": true,
"pixabay": true,
"yandex": true,
"youtube": true,
}
v, has := c[safeSearchKey]
if has {
safeSearch["enabled"] = v
}
delete(c, safeSearchKey)
c["safe_search"] = safeSearch
}
return nil
}
// upgradeSchema19to20 performs the following changes:
//
// # BEFORE:
// 'statistics':
// 'interval': 1
//
// # AFTER:
// 'statistics':
// 'interval': 24h
func upgradeSchema19to20(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 19 to 20")
diskConf["schema_version"] = 20
statsVal, ok := diskConf["statistics"]
if !ok {
return nil
}
var stats yobj
stats, ok = statsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of stats: %T", statsVal)
}
const field = "interval"
// Set the initial value from the global configuration structure.
statsIvl := 1
statsIvlVal, ok := stats[field]
if ok {
statsIvl, ok = statsIvlVal.(int)
if !ok {
return fmt.Errorf("unexpected type of %s: %T", field, statsIvlVal)
}
// The initial version of upgradeSchema16to17 did not set the zero
// interval to a non-zero one. So, reset it now.
if statsIvl == 0 {
statsIvl = 1
}
}
stats[field] = timeutil.Duration{Duration: time.Duration(statsIvl) * timeutil.Day}
return nil
}
// TODO(a.garipov): Replace with log.Output when we port it to our logging
// package.
func funcName() string {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return path.Base(f.Name())
}