7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 1 | use anyhow::Result; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 2 | use clap::Parser; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 3 | use futures_util::{StreamExt, pin_mut}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 4 | use matrix_sdk::{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 5 | Client, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 6 | config::SyncSettings, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 7 | encryption::{backups::BackupState, secret_storage::SecretStore}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 8 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 9 | use url::Url; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 10 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 11 | /// A command line example showcasing how to resume backups by importing the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 12 | /// backup key from secret storage. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 13 | #[derive(Parser, Debug)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 14 | struct Cli { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 15 | /// The homeserver to connect to. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 16 | #[clap(value_parser)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 17 | homeserver: Url, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 18 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 19 | /// The user ID that should be used to restore the session. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 20 | #[clap(value_parser)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 21 | user_name: String, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 22 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 23 | /// The password that should be used for the login. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 24 | #[clap(value_parser)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 25 | password: String, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 26 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 27 | /// Set the proxy that should be used for the connection. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 28 | #[clap(short, long)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 29 | proxy: Option<Url>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 30 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 31 | /// Enable verbose logging output. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 32 | #[clap(short, long, action)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 33 | verbose: bool, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 34 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 35 | /// The secret storage key, this key will be used to open the secret-store. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 36 | #[clap(long, action)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 37 | secret_store_key: String, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 38 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 39 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 40 | async fn import_known_secrets(client: &Client, secret_store: SecretStore) -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 41 | secret_store.import_secrets().await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 42 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 43 | let status = client |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 44 | .encryption() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 45 | .cross_signing_status() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 46 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 47 | .expect("We should be able to get our cross-signing status"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 48 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 49 | if status.is_complete() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 50 | println!("Successfully imported all the cross-signing keys"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 51 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 52 | eprintln!("Couldn't import all the cross-signing keys: {status:?}"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 53 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 54 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 55 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 56 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 57 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 58 | async fn login(cli: &Cli) -> Result<Client> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 59 | let builder = Client::builder().homeserver_url(&cli.homeserver); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 60 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 61 | let builder = if let Some(proxy) = &cli.proxy { builder.proxy(proxy) } else { builder }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 62 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 63 | let client = builder.build().await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 64 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 65 | client |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 66 | .matrix_auth() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 67 | .login_username(&cli.user_name, &cli.password) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 68 | .initial_device_display_name("rust-sdk") |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 69 | .await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 70 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 71 | Ok(client) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 72 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 73 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 74 | async fn listen_for_backup_state_changes(client: Client) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 75 | let stream = client.encryption().backups().state_stream(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 76 | pin_mut!(stream); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 77 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 78 | while let Some(state) = stream.next().await { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 79 | let Ok(state) = state else { panic!("Error while receiving backup state updates") }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 80 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 81 | match state { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 82 | BackupState::Unknown => (), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 83 | BackupState::Enabling => println!("Trying to enable backups"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 84 | BackupState::Resuming => println!("Trying to resume backups"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 85 | BackupState::Enabled => println!("Backups have been successfully enabled"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 86 | BackupState::Downloading => println!("Downloading the room keys from the backup"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 87 | BackupState::Disabling => println!("Disabling the backup"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 88 | BackupState::Creating => println!("Trying to create a new backup"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 89 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 90 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 91 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 92 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 93 | #[tokio::main] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 94 | async fn main() -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 95 | let cli = Cli::parse(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 96 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 97 | if cli.verbose { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 98 | tracing_subscriber::fmt::init(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 99 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 100 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 101 | let client = login(&cli).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 102 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 103 | client.sync_once(Default::default()).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 104 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 105 | let secret_store = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 106 | client.encryption().secret_storage().open_secret_store(&cli.secret_store_key).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 107 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 108 | let _task = tokio::spawn({ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 109 | let client = client.clone(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 110 | async move { listen_for_backup_state_changes(client).await } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 111 | }); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 112 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 113 | import_known_secrets(&client, secret_store).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 114 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 115 | loop { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 116 | if let Err(e) = client.sync(SyncSettings::new()).await { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 117 | eprintln!("Error syncing: {e:?}") |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 118 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 119 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 120 | } |