From 3398ee4416d314bbba13fc7c2bcc2c7e8739eb9f Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 27 Nov 2025 11:32:27 +0100 Subject: [PATCH] cli: Add `glcli scheduler export` subcommand This allows you to take ownership of your own node. --- libs/gl-cli/.kacl.yml | 52 ++++++++++++++++++++++++++++++++ libs/gl-cli/CHANGELOG.md | 11 +++++++ libs/gl-cli/src/scheduler.rs | 57 ++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 libs/gl-cli/.kacl.yml create mode 100644 libs/gl-cli/CHANGELOG.md diff --git a/libs/gl-cli/.kacl.yml b/libs/gl-cli/.kacl.yml new file mode 100644 index 000000000..6ce4f7a36 --- /dev/null +++ b/libs/gl-cli/.kacl.yml @@ -0,0 +1,52 @@ +kacl: + file: CHANGELOG.md + allowed_header_titles: + - Changelog + - Change Log + allowed_version_sections: + - Added + - Changed + - Deprecated + - Removed + - Fixed + - Security + default_content: + - All notable changes to this project will be documented in this file. + - The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + release: + add_unreleased: True + git: + commit: False + commit_message: "[skip ci] Releasing Changelog version {new_version}" + commit_additional_files: [] + tag: False + tag_name: "v{new_version}" + tag_description: "Version v{new_version} released" + links: + auto_generate: False + compare_versions_template: '{host}/compare/{previous_version}...{version}' + unreleased_changes_template: '{host}/compare/{latest_version}...master' + initial_version_template: '{host}/tree/{version}' + extension: + post_release_version_prefix: null + issue_tracker: + jira: + host: null + username: null + password: null + issue_patterns: ["[A-Z]+-[0-9]+"] + comment_template: | + # 🚀 New version [v{new_version}]({link}) + + A new release has been created referencing this issue. Please check it out. + + ## 🚧 Changes in this version + + {changes} + + ## 🧭 Reference + + Code: [Source Code Management System]({link}) + stash: + directory: .kacl_stash + always: False diff --git a/libs/gl-cli/CHANGELOG.md b/libs/gl-cli/CHANGELOG.md new file mode 100644 index 000000000..8dae8a8ca --- /dev/null +++ b/libs/gl-cli/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Added + +- Added `scheduler export` subcommand to take control of your own node. diff --git a/libs/gl-cli/src/scheduler.rs b/libs/gl-cli/src/scheduler.rs index e8b8b113a..da37def0e 100644 --- a/libs/gl-cli/src/scheduler.rs +++ b/libs/gl-cli/src/scheduler.rs @@ -52,6 +52,8 @@ pub enum Command { )] pairing_data: String, }, + /// Export the node from Greenlight infrastructure + Export, } pub async fn command_handler>(cmd: Command, config: Config

) -> Result<()> { @@ -76,6 +78,7 @@ pub async fn command_handler>(cmd: Command, config: Config

) -> Command::ApprovePairing { pairing_data } => { approve_pairing_handler(config, &pairing_data).await } + Command::Export => export_handler(config).await, } } @@ -273,6 +276,60 @@ async fn pair_device_handler>( Err(Error::custom("Connection to scheduler has been closed")) } +async fn export_handler>(config: Config

) -> Result<()> { + let creds_path = config.data_dir.as_ref().join(CREDENTIALS_FILE_NAME); + let creds = util::read_credentials(&creds_path); + if creds.is_none() { + println!("Could not find credentials at {}", creds_path.display()); + return Err(Error::credentials_not_found(format!( + "could not read from {}", + creds_path.display() + ))); + } + + let creds = creds.unwrap(); // we checked if it is none before. + let scheduler = Scheduler::new(config.network, creds) + .await + .map_err(|e| Error::custom(format!("Failed to create scheduler: {}", e)))?; + + // Confirm export action with the user + print!( + "WARNING: Exporting your node will make it no longer schedulable on Greenlight. +After export, you will need to run the node on your own infrastructure. +This operation is idempotent and can be called multiple times. + +Are you sure you want to export your node? (y/N) " + ); + io::stdout().flush().expect("Failed to flush stdout"); + + let input = task::spawn_blocking(|| { + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .expect("Failed to read line"); + input + }) + .await + .expect("Task failed"); + + if !input.trim().eq_ignore_ascii_case("y") { + println!("Export cancelled."); + return Ok(()); + } + + // Initiate the node export + let res = scheduler + .export_node() + .await + .map_err(|e| Error::custom(format!("Failed to export node: {}", e)))?; + + println!("Node export initiated successfully!"); + println!("Download the encrypted backup from: {}", res.url); + println!("\nNote: After exporting, the node will no longer be schedulable on Greenlight."); + println!("The backup is encrypted and can only be decrypted with your node secret."); + Ok(()) +} + async fn approve_pairing_handler>( config: Config

, pairing_data: &str,