11use std:: process:: ExitCode ;
22
33use anyhow:: Context ;
4+ use camino:: Utf8PathBuf ;
45use clap:: Parser ;
56use figment:: Figment ;
6- use mas_config:: { ConfigurationSectionExt , DatabaseConfig } ;
7+ use mas_config:: { ConfigurationSection , ConfigurationSectionExt , DatabaseConfig , MatrixConfig } ;
78use 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 } ;
1011use tracing:: { error, warn} ;
1112
1213use crate :: util:: database_connection_from_config;
1314
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+
1421#[ derive( Parser , Debug ) ]
1522pub ( super ) struct Options {
1623 #[ command( subcommand) ]
@@ -22,11 +29,37 @@ pub(super) struct Options {
2229 /// If you want to migrate from Synapse to MAS today, please use the Node.js-based tool in the MAS repository.
2330 #[ clap( long = "i-swear-i-am-just-testing-in-a-staging-environment" ) ]
2431 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 > ,
2554}
2655
2756#[ derive( Parser , Debug ) ]
2857enum 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.
2961 Check ,
62+ /// Perform a migration. Synapse must be offline during this process.
3063 Migrate ,
3164}
3265
@@ -41,8 +74,26 @@ impl Options {
4174 return Ok ( ExitCode :: FAILURE ) ;
4275 }
4376
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" ) ?;
4697
4798 let config = DatabaseConfig :: extract_or_default ( figment) ?;
4899
@@ -57,27 +108,79 @@ impl Options {
57108 return Ok ( ExitCode :: FAILURE ) ;
58109 } ;
59110
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
60121 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+ }
62144
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 ) ) ;
67148 }
68- let mut writer = MasWriter :: new ( mas_connection, writer_mas_connections) . await ?;
69149
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+ }
73155
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." ) ;
77157
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?
80162
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+ }
82185 }
83186}
0 commit comments