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()
}