Skip to content

Commit eb46704

Browse files
committed
replace irrelevant link, added server id to status output
1 parent 1631a35 commit eb46704

2 files changed

Lines changed: 97 additions & 3 deletions

File tree

src/console/commands/cli/ssh_key.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ fn default_backup_ssh_dir() -> PathBuf {
232232
.unwrap_or_else(|| PathBuf::from("stacker").join("ssh"))
233233
}
234234

235+
pub fn local_backup_private_key_path(server_id: i32) -> PathBuf {
236+
backup_key_paths_for_server(server_id, &default_backup_ssh_dir()).0
237+
}
238+
235239
fn backup_key_paths_for_server(server_id: i32, ssh_dir: &Path) -> (PathBuf, PathBuf) {
236240
let private_key_path = ssh_dir.join(format!("server-{}_ed25519", server_id));
237241
let public_key_path = PathBuf::from(format!("{}.pub", private_key_path.display()));
@@ -341,7 +345,7 @@ fn set_private_file_permissions(_path: &Path) -> Result<(), CliError> {
341345
Ok(())
342346
}
343347

344-
fn format_ssh_command(private_key_path: &Path, user: &str, host: &str, port: u16) -> String {
348+
pub fn format_ssh_command(private_key_path: &Path, user: &str, host: &str, port: u16) -> String {
345349
format!(
346350
"ssh -i {} -p {} {}@{}",
347351
private_key_path.display(),

src/console/commands/cli/status.rs

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::cli::error::CliError;
66
use crate::cli::install_runner::{CommandExecutor, CommandOutput, ShellExecutor};
77
use crate::cli::local_compose::resolve_local_compose_path;
88
use crate::cli::stacker_client::{self, DeploymentStatusInfo, ServerInfo, StackerClient};
9+
use crate::console::commands::cli::ssh_key::{format_ssh_command, local_backup_private_key_path};
910
use crate::console::commands::CallableTrait;
1011

1112
const DEFAULT_CONFIG_FILE: &str = "stacker.yml";
@@ -79,6 +80,23 @@ struct StatusContext<'a> {
7980
live_containers: Option<&'a [serde_json::Value]>,
8081
}
8182

83+
fn emergency_ssh_command(server: &ServerInfo) -> Option<String> {
84+
let ip = server.srv_ip.as_deref()?;
85+
let private_key_path = local_backup_private_key_path(server.id);
86+
if !private_key_path.exists() {
87+
return None;
88+
}
89+
90+
let ssh_user = server.ssh_user.as_deref().unwrap_or("root");
91+
let ssh_port = server.ssh_port.unwrap_or(22) as u16;
92+
Some(format_ssh_command(
93+
&private_key_path,
94+
ssh_user,
95+
ip,
96+
ssh_port,
97+
))
98+
}
99+
82100
/// Pretty-print a deployment status with optional server/config context.
83101
fn print_deployment_status_rich(info: &DeploymentStatusInfo, json: bool, ctx: &StatusContext<'_>) {
84102
if json {
@@ -120,7 +138,9 @@ fn print_deployment_status_rich(info: &DeploymentStatusInfo, json: bool, ctx: &S
120138
if let Some(srv) = ctx.server {
121139
println!("\n── Server ─────────────────────────────────");
122140
if let Some(ref name) = srv.name {
123-
println!(" Name: {}", name);
141+
println!(" Name: {} (id={})", name, srv.id);
142+
} else {
143+
println!(" ID: {}", srv.id);
124144
}
125145
if let Some(ref ip) = srv.srv_ip {
126146
println!(" IP: {}", ip);
@@ -138,6 +158,9 @@ fn print_deployment_status_rich(info: &DeploymentStatusInfo, json: bool, ctx: &S
138158
if let Some(ref region) = srv.region {
139159
println!(" Region: {}", region);
140160
}
161+
if let Some(command) = emergency_ssh_command(srv) {
162+
println!(" Emergency SSH: {}", command);
163+
}
141164
}
142165

143166
if let Some(containers) = ctx.live_containers {
@@ -216,7 +239,9 @@ fn print_deployment_status_rich(info: &DeploymentStatusInfo, json: bool, ctx: &S
216239
config.deploy.target
217240
);
218241
println!("\n── Documentation ──────────────────────────");
219-
println!(" https://try.direct/docs");
242+
println!(
243+
" https://github.com/trydirect/stacker/blob/main/docs/STACKER_YML_REFERENCE.md"
244+
);
220245
}
221246
}
222247

@@ -555,6 +580,7 @@ impl CallableTrait for StatusCommand {
555580
mod tests {
556581
use super::*;
557582
use crate::cli::deployment_lock::DeploymentLock;
583+
use crate::cli::stacker_client::ServerInfo;
558584
use chrono::{Duration, Utc};
559585

560586
#[test]
@@ -789,6 +815,70 @@ deploy:
789815
assert_eq!(resolve_stacker_base_url(&creds), "https://api.try.direct");
790816
}
791817

818+
#[test]
819+
fn test_emergency_ssh_command_uses_local_backup_key_when_present() {
820+
let temp_home = tempfile::TempDir::new().unwrap();
821+
std::env::set_var("XDG_CONFIG_HOME", temp_home.path());
822+
823+
let ssh_dir = temp_home.path().join("stacker/ssh");
824+
std::fs::create_dir_all(&ssh_dir).unwrap();
825+
let private_key_path = ssh_dir.join("server-92_ed25519");
826+
std::fs::write(&private_key_path, "PRIVATE KEY").unwrap();
827+
828+
let server = ServerInfo {
829+
id: 92,
830+
user_id: "user".to_string(),
831+
project_id: 7,
832+
cloud_id: None,
833+
cloud: Some("hetzner".to_string()),
834+
region: Some("fsn1".to_string()),
835+
zone: None,
836+
server: Some("cx22".to_string()),
837+
os: None,
838+
disk_type: None,
839+
srv_ip: Some("178.105.133.10".to_string()),
840+
ssh_port: Some(22),
841+
ssh_user: Some("root".to_string()),
842+
name: Some("status-web".to_string()),
843+
vault_key_path: None,
844+
connection_mode: "ssh".to_string(),
845+
key_status: "active".to_string(),
846+
};
847+
848+
let command = emergency_ssh_command(&server).expect("ssh command should be available");
849+
assert!(command.contains("server-92_ed25519"));
850+
assert!(command.contains("root@178.105.133.10"));
851+
assert!(command.contains(" -p 22 "));
852+
}
853+
854+
#[test]
855+
fn test_emergency_ssh_command_is_absent_without_local_backup_key() {
856+
let temp_home = tempfile::TempDir::new().unwrap();
857+
std::env::set_var("XDG_CONFIG_HOME", temp_home.path());
858+
859+
let server = ServerInfo {
860+
id: 93,
861+
user_id: "user".to_string(),
862+
project_id: 7,
863+
cloud_id: None,
864+
cloud: Some("hetzner".to_string()),
865+
region: Some("fsn1".to_string()),
866+
zone: None,
867+
server: Some("cx22".to_string()),
868+
os: None,
869+
disk_type: None,
870+
srv_ip: Some("178.105.133.11".to_string()),
871+
ssh_port: Some(22),
872+
ssh_user: Some("root".to_string()),
873+
name: Some("status-web".to_string()),
874+
vault_key_path: None,
875+
connection_mode: "ssh".to_string(),
876+
key_status: "active".to_string(),
877+
};
878+
879+
assert!(emergency_ssh_command(&server).is_none());
880+
}
881+
792882
#[test]
793883
fn test_missing_remote_project_reason_mentions_active_stacker_api() {
794884
let reason = missing_remote_project_reason(

0 commit comments

Comments
 (0)