From 465fa4aa163ffb5781606a0fabf5378cef963749 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 7 Nov 2021 22:31:22 +0200 Subject: [PATCH] Switch to go-yaml v3 and add config updater. Fixes #243 --- config/bridge.go | 10 +- config/config.go | 36 ++---- config/upgrade.go | 176 +++++++++++++++++++++++++ config/upgradehelper.go | 276 ++++++++++++++++++++++++++++++++++++++++ example-config.yaml | 28 ++-- go.mod | 3 +- go.sum | 3 +- main.go | 70 ++++++---- 8 files changed, 527 insertions(+), 75 deletions(-) create mode 100644 config/upgrade.go create mode 100644 config/upgradehelper.go diff --git a/config/bridge.go b/config/bridge.go index 446b4e2..da794b3 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -54,7 +54,6 @@ type BridgeConfig struct { DoublePuppetServerMap map[string]string `yaml:"double_puppet_server_map"` DoublePuppetAllowDiscovery bool `yaml:"double_puppet_allow_discovery"` LoginSharedSecretMap map[string]string `yaml:"login_shared_secret_map"` - LegacyLoginSharedSecret string `yaml:"login_shared_secret"` PrivateChatPortalMeta bool `yaml:"private_chat_portal_meta"` BridgeNotices bool `yaml:"bridge_notices"` @@ -65,12 +64,9 @@ type BridgeConfig struct { TagOnlyOnCreate bool `yaml:"tag_only_on_create"` MarkReadOnlyOnCreate bool `yaml:"mark_read_only_on_create"` EnableStatusBroadcast bool `yaml:"enable_status_broadcast"` - - WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"` - - AllowUserInvite bool `yaml:"allow_user_invite"` - - FederateRooms bool `yaml:"federate_rooms"` + WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"` + AllowUserInvite bool `yaml:"allow_user_invite"` + FederateRooms bool `yaml:"federate_rooms"` CommandPrefix string `yaml:"command_prefix"` diff --git a/config/config.go b/config/config.go index 9953e8e..c43f191 100644 --- a/config/config.go +++ b/config/config.go @@ -18,13 +18,11 @@ package config import ( "fmt" - "os" - "gopkg.in/yaml.v2" - - "maunium.net/go/mautrix/id" + "gopkg.in/yaml.v3" "maunium.net/go/mautrix/appservice" + "maunium.net/go/mautrix/id" ) var ExampleConfig string @@ -99,37 +97,23 @@ func (config *Config) CanDoublePuppetBackfill(userID id.UserID) bool { return true } -func Load(path string) (*Config, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - +func Load(data []byte, upgraded bool) (*Config, error) { var config = &Config{} - err = yaml.UnmarshalStrict([]byte(ExampleConfig), config) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal example config: %w", err) + if !upgraded { + // Fallback: if config upgrading failed, load example config for base values + err := yaml.Unmarshal([]byte(ExampleConfig), config) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal example config: %w", err) + } } - err = yaml.Unmarshal(data, config) + err := yaml.Unmarshal(data, config) if err != nil { return nil, err } - if len(config.Bridge.LegacyLoginSharedSecret) > 0 { - config.Bridge.LoginSharedSecretMap[config.Homeserver.Domain] = config.Bridge.LegacyLoginSharedSecret - } - return config, err } -func (config *Config) Save(path string) error { - data, err := yaml.Marshal(config) - if err != nil { - return err - } - return os.WriteFile(path, data, 0600) -} - func (config *Config) MakeAppService() (*appservice.AppService, error) { as := appservice.Create() as.HomeserverDomain = config.Homeserver.Domain diff --git a/config/upgrade.go b/config/upgrade.go new file mode 100644 index 0000000..efd03bf --- /dev/null +++ b/config/upgrade.go @@ -0,0 +1,176 @@ +// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge. +// Copyright (C) 2021 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package config + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" + + "maunium.net/go/mautrix/appservice" +) + +func (helper *UpgradeHelper) TempMethod() { + helper.doUpgrade() +} + +func (helper *UpgradeHelper) doUpgrade() { + helper.Copy(Str, "homeserver", "address") + helper.Copy(Str, "homeserver", "domain") + helper.Copy(Bool, "homeserver", "asmux") + helper.Copy(Str|Null, "homeserver", "status_endpoint") + + helper.Copy(Str, "appservice", "address") + helper.Copy(Str, "appservice", "hostname") + helper.Copy(Int, "appservice", "port") + helper.Copy(Str, "appservice", "database", "type") + helper.Copy(Str, "appservice", "database", "uri") + helper.Copy(Int, "appservice", "database", "max_open_conns") + helper.Copy(Int, "appservice", "database", "max_idle_conns") + helper.Copy(Str, "appservice", "provisioning", "prefix") + if secret, ok := helper.Get(Str, "appservice", "provisioning", "shared_secret"); !ok || secret == "generate" { + sharedSecret := appservice.RandomString(64) + helper.Set(Str, sharedSecret, "appservice", "provisioning", "shared_secret") + } else { + helper.Copy(Str, "appservice", "provisioning", "shared_secret") + } + helper.Copy(Str, "appservice", "id") + helper.Copy(Str, "appservice", "bot", "username") + helper.Copy(Str, "appservice", "bot", "displayname") + helper.Copy(Str, "appservice", "bot", "avatar") + helper.Copy(Str, "appservice", "as_token") + helper.Copy(Str, "appservice", "hs_token") + + helper.Copy(Bool, "metrics", "enabled") + helper.Copy(Str, "metrics", "listen") + + helper.Copy(Str, "whatsapp", "os_name") + helper.Copy(Str, "whatsapp", "browser_name") + + helper.Copy(Str, "bridge", "username_template") + helper.Copy(Str, "bridge", "displayname_template") + helper.Copy(Bool, "bridge", "delivery_receipts") + helper.Copy(Int, "bridge", "portal_message_buffer") + helper.Copy(Bool, "bridge", "call_start_notices") + helper.Copy(Bool, "bridge", "history_sync", "create_portals") + helper.Copy(Int, "bridge", "history_sync", "max_age") + helper.Copy(Bool, "bridge", "history_sync", "backfill") + helper.Copy(Bool, "bridge", "history_sync", "double_puppet_backfill") + helper.Copy(Bool, "bridge", "history_sync", "request_full_sync") + helper.Copy(Bool, "bridge", "user_avatar_sync") + helper.Copy(Bool, "bridge", "bridge_matrix_leave") + helper.Copy(Bool, "bridge", "sync_with_custom_puppets") + helper.Copy(Bool, "bridge", "sync_direct_chat_list") + helper.Copy(Bool, "bridge", "default_bridge_receipts") + helper.Copy(Bool, "bridge", "default_bridge_presence") + helper.Copy(Map, "bridge", "double_puppet_server_map") + helper.Copy(Bool, "bridge", "double_puppet_allow_discovery") + if legacySecret, ok := helper.Get(Str, "bridge", "login_shared_secret"); ok && len(legacySecret) > 0 { + baseNode := helper.GetBaseNode("bridge", "login_shared_secret_map") + baseNode.Map[helper.GetBase("homeserver", "domain")] = YAMLNode{Node: makeStringNode(legacySecret)} + baseNode.Content = baseNode.Map.toNodes() + } else { + helper.Copy(Map, "bridge", "login_shared_secret_map") + } + helper.Copy(Bool, "bridge", "private_chat_portal_meta") + helper.Copy(Bool, "bridge", "bridge_notices") + helper.Copy(Bool, "bridge", "resend_bridge_info") + helper.Copy(Bool, "bridge", "mute_bridging") + helper.Copy(Str|Null, "bridge", "archive_tag") + helper.Copy(Str|Null, "bridge", "pinned_tag") + helper.Copy(Bool, "bridge", "tag_only_on_create") + helper.Copy(Bool, "bridge", "enable_status_broadcast") + helper.Copy(Bool, "bridge", "whatsapp_thumbnail") + helper.Copy(Bool, "bridge", "allow_user_invite") + helper.Copy(Str, "bridge", "command_prefix") + helper.Copy(Bool, "bridge", "federate_rooms") + helper.Copy(Str, "bridge", "management_room_text", "welcome") + helper.Copy(Str, "bridge", "management_room_text", "welcome_connected") + helper.Copy(Str, "bridge", "management_room_text", "welcome_unconnected") + helper.Copy(Str|Null, "bridge", "management_room_text", "additional_help") + helper.Copy(Bool, "bridge", "encryption", "allow") + helper.Copy(Bool, "bridge", "encryption", "default") + helper.Copy(Bool, "bridge", "encryption", "key_sharing", "allow") + helper.Copy(Bool, "bridge", "encryption", "key_sharing", "require_cross_signing") + helper.Copy(Bool, "bridge", "encryption", "key_sharing", "require_verification") + helper.Copy(Map, "bridge", "permissions") + helper.Copy(Bool, "bridge", "relay", "enabled") + helper.Copy(Bool, "bridge", "relay", "admin_only") + helper.Copy(Map, "bridge", "relay", "message_formats") + + helper.Copy(Str, "logging", "directory") + helper.Copy(Str|Null, "logging", "file_name_format") + helper.Copy(Str|Timestamp, "logging", "file_date_format") + helper.Copy(Int, "logging", "file_mode") + helper.Copy(Str|Timestamp, "logging", "timestamp_format") + helper.Copy(Str, "logging", "print_level") +} + +func Mutate(path string, mutate func(helper *UpgradeHelper)) error { + _, _, err := upgrade(path, true, mutate) + return err +} + +func Upgrade(path string, save bool) ([]byte, bool, error) { + return upgrade(path, save, nil) +} + +func upgrade(path string, save bool, mutate func(helper *UpgradeHelper)) ([]byte, bool, error) { + sourceData, err := os.ReadFile(path) + if err != nil { + return nil, false, fmt.Errorf("failed to read config: %w", err) + } + var base, cfg yaml.Node + err = yaml.Unmarshal([]byte(ExampleConfig), &base) + if err != nil { + return sourceData, false, fmt.Errorf("failed to unmarshal example config: %w", err) + } + err = yaml.Unmarshal(sourceData, &cfg) + if err != nil { + return sourceData, false, fmt.Errorf("failed to unmarshal config: %w", err) + } + + helper := NewUpgradeHelper(&base, &cfg) + helper.doUpgrade() + if mutate != nil { + mutate(helper) + } + + output, err := yaml.Marshal(&base) + if err != nil { + return sourceData, false, fmt.Errorf("failed to marshal updated config: %w", err) + } + if save { + var tempFile *os.File + tempFile, err = os.CreateTemp("", "wa-config-*.yaml") + if err != nil { + return output, true, fmt.Errorf("failed to create temp file for writing config: %w", err) + } + _, err = tempFile.Write(output) + if err != nil { + _ = os.Remove(tempFile.Name()) + return output, true, fmt.Errorf("failed to write updated config to temp file: %w", err) + } + err = os.Rename(tempFile.Name(), path) + if err != nil { + _ = os.Remove(tempFile.Name()) + return output, true, fmt.Errorf("failed to override current config with temp file: %w", err) + } + } + return output, true, nil +} diff --git a/config/upgradehelper.go b/config/upgradehelper.go new file mode 100644 index 0000000..3739533 --- /dev/null +++ b/config/upgradehelper.go @@ -0,0 +1,276 @@ +// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge. +// Copyright (C) 2021 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package config + +import ( + "fmt" + "os" + "strings" + + "gopkg.in/yaml.v3" +) + +type YAMLMap map[string]YAMLNode +type YAMLList []YAMLNode + +type YAMLNode struct { + *yaml.Node + Map YAMLMap + List YAMLList +} + +type YAMLType uint32 + +const ( + Null YAMLType = 1 << iota + Bool + Str + Int + Float + Timestamp + List + Map + Binary +) + +func (t YAMLType) String() string { + switch t { + case Null: + return NullTag + case Bool: + return BoolTag + case Str: + return StrTag + case Int: + return IntTag + case Float: + return FloatTag + case Timestamp: + return TimestampTag + case List: + return SeqTag + case Map: + return MapTag + case Binary: + return BinaryTag + default: + panic(fmt.Errorf("can't convert type %d to string", t)) + } +} + +func tagToType(tag string) YAMLType { + switch tag { + case NullTag: + return Null + case BoolTag: + return Bool + case StrTag: + return Str + case IntTag: + return Int + case FloatTag: + return Float + case TimestampTag: + return Timestamp + case SeqTag: + return List + case MapTag: + return Map + case BinaryTag: + return Binary + default: + return 0 + } +} + +const ( + NullTag = "!!null" + BoolTag = "!!bool" + StrTag = "!!str" + IntTag = "!!int" + FloatTag = "!!float" + TimestampTag = "!!timestamp" + SeqTag = "!!seq" + MapTag = "!!map" + BinaryTag = "!!binary" +) + +func fromNode(node *yaml.Node) YAMLNode { + switch node.Kind { + case yaml.DocumentNode: + return fromNode(node.Content[0]) + case yaml.AliasNode: + return fromNode(node.Alias) + case yaml.MappingNode: + return YAMLNode{ + Node: node, + Map: parseYAMLMap(node), + } + case yaml.SequenceNode: + return YAMLNode{ + Node: node, + List: parseYAMLList(node), + } + default: + return YAMLNode{Node: node} + } +} + +func (yn *YAMLNode) toNode() *yaml.Node { + switch { + case yn.Map != nil && yn.Node.Kind == yaml.MappingNode: + yn.Content = yn.Map.toNodes() + case yn.List != nil && yn.Node.Kind == yaml.SequenceNode: + yn.Content = yn.List.toNodes() + } + return yn.Node +} + +func parseYAMLList(node *yaml.Node) YAMLList { + data := make(YAMLList, len(node.Content)) + for i, item := range node.Content { + data[i] = fromNode(item) + } + return data +} + +func (yl YAMLList) toNodes() []*yaml.Node { + nodes := make([]*yaml.Node, len(yl)) + for i, item := range yl { + nodes[i] = item.toNode() + } + return nodes +} + +func parseYAMLMap(node *yaml.Node) YAMLMap { + if len(node.Content)%2 != 0 { + panic(fmt.Errorf("uneven number of items in YAML map (%d)", len(node.Content))) + } + data := make(YAMLMap, len(node.Content)/2) + for i := 0; i < len(node.Content); i += 2 { + key := node.Content[i] + value := node.Content[i+1] + if key.Kind == yaml.ScalarNode { + data[key.Value] = fromNode(value) + } + } + return data +} + +func (ym YAMLMap) toNodes() []*yaml.Node { + nodes := make([]*yaml.Node, len(ym)*2) + i := 0 + for key, value := range ym { + nodes[i] = makeStringNode(key) + nodes[i+1] = value.toNode() + i += 2 + } + return nodes +} + +func makeStringNode(val string) *yaml.Node { + var node yaml.Node + node.SetString(val) + return &node +} + +type UpgradeHelper struct { + base YAMLNode + cfg YAMLNode +} + +func NewUpgradeHelper(base, cfg *yaml.Node) *UpgradeHelper { + return &UpgradeHelper{ + base: fromNode(base), + cfg: fromNode(cfg), + } +} + +func (helper *UpgradeHelper) Copy(allowedTypes YAMLType, path ...string) { + base, cfg := helper.base, helper.cfg + var ok bool + for _, item := range path { + base = base.Map[item] + cfg, ok = cfg.Map[item] + if !ok { + return + } + } + if allowedTypes&tagToType(cfg.Tag) == 0 { + _, _ = fmt.Fprintf(os.Stderr, "Ignoring incorrect config field type %s at %s\n", cfg.Tag, strings.Join(path, "->")) + return + } + base.Tag = cfg.Tag + base.Style = cfg.Style + switch base.Kind { + case yaml.ScalarNode: + base.Value = cfg.Value + case yaml.SequenceNode, yaml.MappingNode: + base.Content = cfg.Content + } +} + +func getNode(cfg YAMLNode, path []string) *YAMLNode { + var ok bool + for _, item := range path { + cfg, ok = cfg.Map[item] + if !ok { + return nil + } + } + return &cfg +} + +func (helper *UpgradeHelper) GetNode(path ...string) *YAMLNode { + return getNode(helper.cfg, path) +} + +func (helper *UpgradeHelper) GetBaseNode(path ...string) *YAMLNode { + return getNode(helper.base, path) +} + +func (helper *UpgradeHelper) Get(tag YAMLType, path ...string) (string, bool) { + node := helper.GetNode(path...) + if node == nil || node.Kind != yaml.ScalarNode || tag&tagToType(node.Tag) == 0 { + return "", false + } + return node.Value, true +} + +func (helper *UpgradeHelper) GetBase(path ...string) string { + return helper.GetBaseNode(path...).Value +} + +func (helper *UpgradeHelper) Set(tag YAMLType, value string, path ...string) { + base := helper.base + for _, item := range path { + base = base.Map[item] + } + base.Tag = tag.String() + base.Value = value +} + +func (helper *UpgradeHelper) SetMap(value YAMLMap, path ...string) { + base := helper.base + for _, item := range path { + base = base.Map[item] + } + if base.Tag != MapTag || base.Kind != yaml.MappingNode { + panic(fmt.Errorf("invalid target for SetMap(%+v): tag:%s, kind:%d", path, base.Tag, base.Kind)) + } + base.Content = value.toNodes() +} diff --git a/example-config.yaml b/example-config.yaml index 7486a42..0dfa57d 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -5,6 +5,8 @@ homeserver: # The domain of the homeserver (for MXIDs, etc). domain: example.com + # Is the homeserver actually mautrix-asmux? + asmux: false # The URL to push real-time bridge status to. # If set, the bridge will make POST requests to this URL whenever a user's whatsapp connection state changes. # The bridge will use the appservice as_token to authorize requests. @@ -37,8 +39,9 @@ appservice: provisioning: # Prefix for the provisioning API paths. prefix: /_matrix/provision/v1 - # Shared secret for authentication. If set to "disable", the provisioning API will be disabled. - shared_secret: disable + # Shared secret for authentication. If set to "generate", a random secret will be generated, + # or if set to "disable", the provisioning API will be disabled. + shared_secret: generate # The unique ID of this appservice. id: whatsapp @@ -55,12 +58,14 @@ appservice: as_token: "This value is generated when generating the registration" hs_token: "This value is generated when generating the registration" +# Prometheus config. metrics: # Enable prometheus metrics? enabled: false # IP and port where the metrics listener should be. The path is always /metrics listen: 127.0.0.1:8001 +# Config for things that are directly sent to WhatsApp. whatsapp: # Device name that's shown in the "WhatsApp Web" section in the mobile app. os_name: Mautrix-WhatsApp bridge @@ -87,6 +92,7 @@ bridge: delivery_receipts: false # Should incoming calls send a message to the Matrix room? call_start_notices: true + portal_message_buffer: 128 # Settings for handling history sync payloads. These settings only apply right after login, # because the phone only sends the history sync data once, and there's no way to re-request it @@ -158,22 +164,19 @@ bridge: # Should WhatsApp status messages be bridged into a Matrix room? # Disabling this won't affect already created status broadcast rooms. enable_status_broadcast: true - # Should the bridge use thumbnails from WhatsApp? # They're disabled by default due to very low resolution. whatsapp_thumbnail: false - # Allow invite permission for user. User can invite any bots to room with whatsapp # users (private chat and groups) allow_user_invite: false - - # The prefix for commands. Only required in non-management rooms. - command_prefix: "!wa" - # Whether or not created rooms should have federation enabled. # If false, created portal rooms will never be federated. federate_rooms: true + # The prefix for commands. Only required in non-management rooms. + command_prefix: "!wa" + # Messages sent upon joining a management room. # Markdown is supported. The defaults are listed below. management_room_text: @@ -184,7 +187,7 @@ bridge: # Sent when joining a management room and the user is not logged in. welcome_unconnected: "Use `help` for help or `login` to log in." # Optional extra text sent when joining a management room. - # additional_help: "This would be some additional text in case you need it." + additional_help: "" # End-to-bridge encryption support options. # @@ -223,6 +226,7 @@ bridge: "example.com": user "@admin:example.com": admin + # Settings for relay mode relay: # Whether relay mode should be allowed. If allowed, `!wa set-relay` can be used to turn any # authenticated user into a relaybot for that chat. @@ -248,11 +252,11 @@ logging: # Set this to null to disable logging to file. file_name_format: "{{.Date}}-{{.Index}}.log" # Date format for file names in the Go time format: https://golang.org/pkg/time/#pkg-constants - file_date_format: 2006-01-02 + file_date_format: "2006-01-02" # Log file permissions. - file_mode: 0600 + file_mode: 0o600 # Timestamp format for log entries in the Go time format. - timestamp_format: Jan _2, 2006 15:04:05 + timestamp_format: "Jan _2, 2006 15:04:05" # Minimum severity for log messages printed to stdout/stderr. This doesn't affect the log file. # Options: debug, info, warn, error, fatal print_level: debug diff --git a/go.mod b/go.mod index 9f7e71a..2d58ea6 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( go.mau.fi/whatsmeow v0.0.0-20211105153256-c8ed00a3615d golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d google.golang.org/protobuf v1.27.1 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b maunium.net/go/mauflag v1.0.0 maunium.net/go/maulogger/v2 v2.3.1 maunium.net/go/mautrix v0.10.1 @@ -37,4 +37,5 @@ require ( golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 8989f2f..625d82f 100644 --- a/go.sum +++ b/go.sum @@ -213,8 +213,9 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= maunium.net/go/maulogger/v2 v2.3.1 h1:fwBYJne0pHvJrrIPHK+TAPfyxxbBEz46oVGez2x0ODE= diff --git a/main.go b/main.go index 0b4f1b0..7d09cfa 100644 --- a/main.go +++ b/main.go @@ -97,8 +97,7 @@ func init() { } var configPath = flag.MakeFull("c", "config", "The path to your config file.", "config.yaml").String() - -//var baseConfigPath = flag.MakeFull("b", "base-config", "The path to the example config file.", "example-config.yaml").String() +var dontSaveConfig = flag.MakeFull("n", "no-update", "Don't save updated config to disk.", "false").Bool() var registrationPath = flag.MakeFull("r", "registration", "The path where to save the appservice registration.", "registration.yaml").String() var generateRegistration = flag.MakeFull("g", "generate-registration", "Generate registration and quit.", "false").Bool() var version = flag.MakeFull("v", "version", "View bridge version and quit.", "false").Bool() @@ -107,6 +106,11 @@ var migrateFrom = flag.Make().LongKey("migrate-db").Usage("Source database type var wantHelp, _ = flag.MakeHelpFlag() func (bridge *Bridge) GenerateRegistration() { + if *dontSaveConfig { + // We need to save the generated as_token and hs_token in the config + _, _ = fmt.Fprintln(os.Stderr, "--no-update is not compatible with --generate-registration") + os.Exit(5) + } reg, err := bridge.Config.NewRegistration() if err != nil { _, _ = fmt.Fprintln(os.Stderr, "Failed to generate registration:", err) @@ -119,7 +123,10 @@ func (bridge *Bridge) GenerateRegistration() { os.Exit(21) } - err = bridge.Config.Save(*configPath) + err = config.Mutate(*configPath, func(helper *config.UpgradeHelper) { + helper.Set(config.Str, bridge.Config.AppService.ASToken, "appservice", "as_token") + helper.Set(config.Str, bridge.Config.AppService.HSToken, "appservice", "hs_token") + }) if err != nil { _, _ = fmt.Fprintln(os.Stderr, "Failed to save config:", err) os.Exit(22) @@ -193,26 +200,6 @@ type Crypto interface { Stop() } -func NewBridge() *Bridge { - bridge := &Bridge{ - usersByMXID: make(map[id.UserID]*User), - usersByUsername: make(map[string]*User), - managementRooms: make(map[id.RoomID]*User), - portalsByMXID: make(map[id.RoomID]*Portal), - portalsByJID: make(map[database.PortalKey]*Portal), - puppets: make(map[types.JID]*Puppet), - puppetsByCustomMXID: make(map[id.UserID]*Puppet), - } - - var err error - bridge.Config, err = config.Load(*configPath) - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, "Failed to load config:", err) - os.Exit(10) - } - return bridge -} - func (bridge *Bridge) ensureConnection() { for { resp, err := bridge.Bot.Whoami() @@ -344,10 +331,15 @@ func (bridge *Bridge) Start() { } func (bridge *Bridge) ResendBridgeInfo() { - bridge.Config.Bridge.ResendBridgeInfo = false - err := bridge.Config.Save(*configPath) - if err != nil { - bridge.Log.Errorln("Failed to save config after setting resend_bridge_info to false:", err) + if *dontSaveConfig { + bridge.Log.Warnln("Not setting resend_bridge_info to false in config due to --no-update flag") + } else { + err := config.Mutate(*configPath, func(helper *config.UpgradeHelper) { + helper.Set(config.Bool, "false", "bridge", "resend_bridge_info") + }) + if err != nil { + bridge.Log.Errorln("Failed to save config after setting resend_bridge_info to false:", err) + } } bridge.Log.Infoln("Re-sending bridge info state event to all portals") for _, portal := range bridge.GetAllPortals() { @@ -426,6 +418,20 @@ func (bridge *Bridge) Stop() { } func (bridge *Bridge) Main() { + configData, upgraded, err := config.Upgrade(*configPath, !*dontSaveConfig) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Error updating config:", err) + if configData == nil { + os.Exit(10) + } + } + + bridge.Config, err = config.Load(configData, upgraded) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to parse config:", err) + os.Exit(10) + } + if *generateRegistration { bridge.GenerateRegistration() return @@ -466,5 +472,13 @@ func main() { return } - NewBridge().Main() + (&Bridge{ + usersByMXID: make(map[id.UserID]*User), + usersByUsername: make(map[string]*User), + managementRooms: make(map[id.RoomID]*User), + portalsByMXID: make(map[id.RoomID]*Portal), + portalsByJID: make(map[database.PortalKey]*Portal), + puppets: make(map[types.JID]*Puppet), + puppetsByCustomMXID: make(map[id.UserID]*Puppet), + }).Main() }