1
1
use std:: process:: ExitCode ;
2
2
3
3
use anyhow:: Context ;
4
+ use camino:: Utf8PathBuf ;
4
5
use clap:: Parser ;
5
6
use figment:: Figment ;
6
- use mas_config:: { ConfigurationSectionExt , DatabaseConfig } ;
7
+ use mas_config:: { ConfigurationSection , ConfigurationSectionExt , DatabaseConfig , MatrixConfig } ;
7
8
use rand:: thread_rng;
8
- use sqlx:: { Connection , Either , PgConnection } ;
9
- use syn2mas:: { LockedMasDatabase , MasWriter , SynapseReader } ;
9
+ use sqlx:: { postgres :: PgConnectOptions , Connection , Either , PgConnection } ;
10
+ use syn2mas:: { synapse_config , LockedMasDatabase , MasWriter , SynapseReader } ;
10
11
use tracing:: { error, warn} ;
11
12
12
13
use crate :: util:: database_connection_from_config;
13
14
15
+ /// The exit code used by `syn2mas check` and `syn2mas migrate` when there are errors preventing migration.
16
+ const EXIT_CODE_CHECK_ERRORS : u8 = 10 ;
17
+
18
+ /// The exit code used by `syn2mas check` when there are warnings which should be considered prior to migration.
19
+ const EXIT_CODE_CHECK_WARNINGS : u8 = 11 ;
20
+
14
21
#[ derive( Parser , Debug ) ]
15
22
pub ( super ) struct Options {
16
23
#[ command( subcommand) ]
@@ -22,11 +29,37 @@ pub(super) struct Options {
22
29
/// If you want to migrate from Synapse to MAS today, please use the Node.js-based tool in the MAS repository.
23
30
#[ clap( long = "i-swear-i-am-just-testing-in-a-staging-environment" ) ]
24
31
experimental_accepted : bool ,
32
+
33
+ /// Path to the Synapse configuration (in YAML format).
34
+ /// May be specified multiple times if multiple Synapse configuration files are in use.
35
+ #[ clap( long = "synapse-config" ) ]
36
+ synapse_configuration_files : Vec < Utf8PathBuf > ,
37
+
38
+ /// Override the Synapse database URI.
39
+ /// syn2mas normally loads the Synapse database connection details from the Synapse configuration.
40
+ /// However, it may sometimes be necessary to override the database URI and in that case this flag can be used.
41
+ ///
42
+ /// Should be a connection URI of the following general form:
43
+ /// ```text
44
+ /// postgresql://[user[:password]@][host][:port][/dbname][?param1=value1&...]
45
+ /// ```
46
+ /// To use a UNIX socket at a custom path, the host should be a path to a socket, but in the URI string
47
+ /// it must be URI-encoded by replacing `/` with `%2F`.
48
+ ///
49
+ /// Finally, any missing values will be loaded from the libpq-compatible environment variables
50
+ /// `PGHOST`, `PGPORT`, `PGUSER`, `PGDATABASE`, `PGPASSWORD`, etc.
51
+ /// It is valid to specify the URL `postgresql:` and configure all values through those environment variables.
52
+ #[ clap( long = "synapse-database-uri" ) ]
53
+ synapse_database_uri : Option < PgConnectOptions > ,
25
54
}
26
55
27
56
#[ derive( Parser , Debug ) ]
28
57
enum Subcommand {
58
+ /// Check the setup for potential problems before running a migration.
59
+ ///
60
+ /// It is OK for Synapse to be online during these checks.
29
61
Check ,
62
+ /// Perform a migration. Synapse must be offline during this process.
30
63
Migrate ,
31
64
}
32
65
@@ -41,8 +74,26 @@ impl Options {
41
74
return Ok ( ExitCode :: FAILURE ) ;
42
75
}
43
76
44
- // TODO allow configuring the synapse database location
45
- let mut syn_conn = PgConnection :: connect ( "postgres:///fakesyn" ) . await . unwrap ( ) ;
77
+ if self . synapse_configuration_files . is_empty ( ) {
78
+ error ! ( "Please specify the path to the Synapse configuration file(s)." ) ;
79
+ return Ok ( ExitCode :: FAILURE ) ;
80
+ }
81
+
82
+ let synapse_config = synapse_config:: Config :: load ( & self . synapse_configuration_files )
83
+ . context ( "Failed to load Synapse configuration" ) ?;
84
+
85
+ // Establish a connection to Synapse's Postgres database
86
+ let syn_connection_options = if let Some ( db_override) = self . synapse_database_uri {
87
+ db_override
88
+ } else {
89
+ synapse_config
90
+ . database
91
+ . to_sqlx_postgres ( )
92
+ . context ( "Synapse configuration does not use Postgres, cannot migrate." ) ?
93
+ } ;
94
+ let mut syn_conn = PgConnection :: connect_with ( & syn_connection_options)
95
+ . await
96
+ . context ( "could not connect to Synapse Postgres database" ) ?;
46
97
47
98
let config = DatabaseConfig :: extract_or_default ( figment) ?;
48
99
@@ -57,27 +108,79 @@ impl Options {
57
108
return Ok ( ExitCode :: FAILURE ) ;
58
109
} ;
59
110
111
+ // Check configuration
112
+ let ( mut check_warnings, mut check_errors) = syn2mas:: synapse_config_check ( & synapse_config) ;
113
+ {
114
+ let ( extra_warnings, extra_errors) =
115
+ syn2mas:: synapse_config_check_against_mas_config ( & synapse_config, figment) . await ?;
116
+ check_warnings. extend ( extra_warnings) ;
117
+ check_errors. extend ( extra_errors) ;
118
+ }
119
+
120
+ // Check databases
60
121
syn2mas:: mas_pre_migration_checks ( & mut mas_connection) . await ?;
61
- syn2mas:: synapse_pre_migration_checks ( & mut syn_conn) . await ?;
122
+ {
123
+ let ( extra_warnings, extra_errors) =
124
+ syn2mas:: synapse_database_check ( & mut syn_conn, & synapse_config, figment) . await ?;
125
+ check_warnings. extend ( extra_warnings) ;
126
+ check_errors. extend ( extra_errors) ;
127
+ }
128
+
129
+ // Display errors and warnings
130
+ if !check_errors. is_empty ( ) {
131
+ eprintln ! ( "===== Errors =====" ) ;
132
+ eprintln ! ( "These issues prevent migrating from Synapse to MAS right now:\n " ) ;
133
+ for error in & check_errors {
134
+ eprintln ! ( "• {error}\n " ) ;
135
+ }
136
+ }
137
+ if !check_warnings. is_empty ( ) {
138
+ eprintln ! ( "===== Warnings =====" ) ;
139
+ eprintln ! ( "These potential issues should be considered before migrating from Synapse to MAS right now:\n " ) ;
140
+ for warning in & check_warnings {
141
+ eprintln ! ( "• {warning}\n " ) ;
142
+ }
143
+ }
62
144
63
- let mut reader = SynapseReader :: new ( & mut syn_conn, true ) . await ?;
64
- let mut writer_mas_connections = Vec :: with_capacity ( NUM_WRITER_CONNECTIONS ) ;
65
- for _ in 0 ..NUM_WRITER_CONNECTIONS {
66
- writer_mas_connections. push ( database_connection_from_config ( & config) . await ?) ;
145
+ // Do not proceed if there are any errors
146
+ if !check_errors. is_empty ( ) {
147
+ return Ok ( ExitCode :: from ( EXIT_CODE_CHECK_ERRORS ) ) ;
67
148
}
68
- let mut writer = MasWriter :: new ( mas_connection, writer_mas_connections) . await ?;
69
149
70
- // TODO is this rng ok?
71
- #[ allow( clippy:: disallowed_methods) ]
72
- let mut rng = thread_rng ( ) ;
150
+ match self . subcommand {
151
+ Subcommand :: Check => {
152
+ if !check_warnings. is_empty ( ) {
153
+ return Ok ( ExitCode :: from ( EXIT_CODE_CHECK_WARNINGS ) ) ;
154
+ }
73
155
74
- // TODO progress reporting
75
- // TODO allow configuring the server name
76
- syn2mas:: migrate ( & mut reader, & mut writer, "matrix.org" , & mut rng) . await ?;
156
+ println ! ( "Check completed successfully with no errors or warnings." ) ;
77
157
78
- reader. finish ( ) . await ?;
79
- writer. finish ( ) . await ?;
158
+ Ok ( ExitCode :: SUCCESS )
159
+ }
160
+ Subcommand :: Migrate => {
161
+ // TODO how should we handle warnings at this stage?
80
162
81
- Ok ( ExitCode :: SUCCESS )
163
+ let mut reader = SynapseReader :: new ( & mut syn_conn, true ) . await ?;
164
+ let mut writer_mas_connections = Vec :: with_capacity ( NUM_WRITER_CONNECTIONS ) ;
165
+ for _ in 0 ..NUM_WRITER_CONNECTIONS {
166
+ writer_mas_connections. push ( database_connection_from_config ( & config) . await ?) ;
167
+ }
168
+ let mut writer = MasWriter :: new ( mas_connection, writer_mas_connections) . await ?;
169
+
170
+ // TODO is this rng ok?
171
+ #[ allow( clippy:: disallowed_methods) ]
172
+ let mut rng = thread_rng ( ) ;
173
+
174
+ // TODO progress reporting
175
+ let mas_matrix = MatrixConfig :: extract ( figment) ?;
176
+ syn2mas:: migrate ( & mut reader, & mut writer, & mas_matrix. homeserver , & mut rng)
177
+ . await ?;
178
+
179
+ reader. finish ( ) . await ?;
180
+ writer. finish ( ) . await ?;
181
+
182
+ Ok ( ExitCode :: SUCCESS )
183
+ }
184
+ }
82
185
}
83
186
}
0 commit comments