1
This commit is contained in:
@@ -44,9 +44,13 @@ impl AuthedClient {
|
||||
}
|
||||
let pub_openssh = priv_key.public_key().to_openssh().context("encode pubkey")?;
|
||||
|
||||
let (ws, _) = tokio_tungstenite::connect_async(&cfg.backend_url)
|
||||
let ws_url = format!(
|
||||
"{}/ws/op",
|
||||
cfg.backend_url.trim_end_matches('/').trim_end_matches("/ws/op")
|
||||
);
|
||||
let (ws, _) = tokio_tungstenite::connect_async(&ws_url)
|
||||
.await
|
||||
.with_context(|| format!("ws connect {}", cfg.backend_url))?;
|
||||
.with_context(|| format!("ws connect {ws_url}"))?;
|
||||
let (mut sink, mut stream) = ws.split();
|
||||
|
||||
send_msg(&mut sink, &OpMsg::AuthInit { pubkey_openssh: pub_openssh }).await?;
|
||||
|
||||
@@ -48,6 +48,7 @@ pub async fn run(
|
||||
.req_stream(OpReq::Attach {
|
||||
session: session.clone(),
|
||||
connection_id: Some(target.connection_id),
|
||||
shell_id: None,
|
||||
pty,
|
||||
cols,
|
||||
rows,
|
||||
@@ -68,7 +69,7 @@ pub async fn run(
|
||||
}
|
||||
ui::print_info(&format!(
|
||||
"attached to #{} ({}@{}) — Ctrl-] to detach",
|
||||
target.connection_id, target.info.user, target.info.hostname
|
||||
target.connection_id, target.info.user, target.info.hostname,
|
||||
));
|
||||
|
||||
if pty {
|
||||
@@ -83,7 +84,7 @@ pub async fn run(
|
||||
result
|
||||
}
|
||||
|
||||
async fn pump(
|
||||
pub async fn pump(
|
||||
client: &AuthedClient,
|
||||
resps: &mut tokio::sync::mpsc::Receiver<OpResp>,
|
||||
pty: bool,
|
||||
@@ -145,7 +146,7 @@ async fn pump(
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_resize_watch(tx: tokio::sync::mpsc::Sender<(u16, u16)>) {
|
||||
pub fn spawn_resize_watch(tx: tokio::sync::mpsc::Sender<(u16, u16)>) {
|
||||
tokio::spawn(async move {
|
||||
let mut last = terminal::size().unwrap_or((80, 24));
|
||||
loop {
|
||||
|
||||
@@ -3,3 +3,4 @@ pub mod connection;
|
||||
pub mod connect;
|
||||
pub mod keys;
|
||||
pub mod watch;
|
||||
pub mod shell;
|
||||
|
||||
99
crates/rshc/src/cmd/shell.rs
Normal file
99
crates/rshc/src/cmd/shell.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use crate::auth::AuthedClient;
|
||||
use crate::cmd::connect;
|
||||
use crate::cmd::connection;
|
||||
use crate::ui;
|
||||
use anyhow::{anyhow, Result};
|
||||
use crossterm::terminal;
|
||||
use rsh_types::{OpReq, OpResp};
|
||||
|
||||
pub async fn run(
|
||||
client: &AuthedClient,
|
||||
session: String,
|
||||
connection_id: Option<u64>,
|
||||
shell: Option<String>,
|
||||
no_pty: bool,
|
||||
) -> Result<()> {
|
||||
let conns = connection::fetch(client, Some(session.clone())).await?;
|
||||
if conns.is_empty() {
|
||||
return Err(anyhow!("no connections for session '{session}'"));
|
||||
}
|
||||
let conn_id = match connection_id {
|
||||
Some(id) => {
|
||||
conns
|
||||
.iter()
|
||||
.find(|c| c.connection_id == id)
|
||||
.ok_or_else(|| anyhow!("no connection {id} in session '{session}'"))?
|
||||
.connection_id
|
||||
}
|
||||
None => {
|
||||
if conns.len() == 1 {
|
||||
conns[0].connection_id
|
||||
} else {
|
||||
let labels: Vec<String> = conns
|
||||
.iter()
|
||||
.map(|c| format!("#{} {}@{} ({})", c.connection_id, c.info.user, c.info.hostname, ui::fmt_time(c.connected_at)))
|
||||
.collect();
|
||||
let pick = inquire::Select::new("select connection:", labels.clone())
|
||||
.prompt()
|
||||
.map_err(|e| anyhow!("prompt: {e}"))?;
|
||||
let idx = labels.iter().position(|l| l == &pick).unwrap();
|
||||
conns[idx].connection_id
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (cols, rows) = terminal::size().unwrap_or((80, 24));
|
||||
let pty = !no_pty;
|
||||
|
||||
let spawned = client
|
||||
.req(OpReq::SpawnShell {
|
||||
session: session.clone(),
|
||||
connection_id: Some(conn_id),
|
||||
shell,
|
||||
pty,
|
||||
cols,
|
||||
rows,
|
||||
})
|
||||
.await?;
|
||||
let shell_id = match spawned {
|
||||
OpResp::ShellSpawned { shell_id, .. } => shell_id,
|
||||
OpResp::Err(e) => return Err(anyhow!(e)),
|
||||
other => return Err(anyhow!("unexpected: {other:?}")),
|
||||
};
|
||||
|
||||
let (attach_id, mut resps) = client
|
||||
.req_stream(OpReq::Attach {
|
||||
session: session.clone(),
|
||||
connection_id: Some(conn_id),
|
||||
shell_id: Some(shell_id),
|
||||
pty,
|
||||
cols,
|
||||
rows,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let ready = resps.recv().await.ok_or_else(|| anyhow!("attach: no response"))?;
|
||||
match ready {
|
||||
OpResp::AttachReady { .. } => {}
|
||||
OpResp::Err(e) => {
|
||||
client.drop_stream(attach_id).await;
|
||||
return Err(anyhow!(e));
|
||||
}
|
||||
other => {
|
||||
client.drop_stream(attach_id).await;
|
||||
return Err(anyhow!("unexpected: {other:?}"));
|
||||
}
|
||||
}
|
||||
ui::print_info(&format!("spawned shell in #{conn_id} — Ctrl-] to detach"));
|
||||
|
||||
if pty {
|
||||
terminal::enable_raw_mode().ok();
|
||||
}
|
||||
let result = connect::pump(client, &mut resps, pty).await;
|
||||
if pty {
|
||||
terminal::disable_raw_mode().ok();
|
||||
}
|
||||
client.drop_stream(attach_id).await;
|
||||
println!();
|
||||
result
|
||||
}
|
||||
@@ -24,7 +24,7 @@ impl Config {
|
||||
std::fs::create_dir_all(parent).ok();
|
||||
}
|
||||
let stub = Config {
|
||||
backend_url: "wss://example.invalid/ws/op".into(),
|
||||
backend_url: "wss://example.invalid".into(),
|
||||
ssh_key_file: "~/.ssh/id_ed25519".into(),
|
||||
};
|
||||
let txt = serde_yaml::to_string(&stub)?;
|
||||
|
||||
@@ -24,6 +24,8 @@ enum Cmd {
|
||||
Connection(ConnectionCmd),
|
||||
#[command(alias = "c")]
|
||||
Connect(ConnectArgs),
|
||||
#[command(alias = "sh")]
|
||||
Shell(ShellArgs),
|
||||
Keys(KeysCmd),
|
||||
}
|
||||
|
||||
@@ -84,6 +86,17 @@ struct ConnectArgs {
|
||||
no_pty: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct ShellArgs {
|
||||
session: String,
|
||||
#[arg(long)]
|
||||
connection: Option<u64>,
|
||||
#[arg(long)]
|
||||
shell: Option<String>,
|
||||
#[arg(long)]
|
||||
no_pty: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct KeysCmd {
|
||||
#[command(subcommand)]
|
||||
@@ -146,6 +159,7 @@ async fn run() -> Result<()> {
|
||||
ConnectionSub::List { session } => cmd::connection::list(&client, session).await,
|
||||
},
|
||||
Cmd::Connect(a) => cmd::connect::run(&client, a.session, a.connection_id, a.no_pty).await,
|
||||
Cmd::Shell(a) => cmd::shell::run(&client, a.session, a.connection, a.shell, a.no_pty).await,
|
||||
Cmd::Keys(k) => match k.sub {
|
||||
KeysSub::Append { key, file, url } => cmd::keys::append(&client, key, file, url).await,
|
||||
KeysSub::Rm { key, file, url } => cmd::keys::remove(&client, key, file, url).await,
|
||||
|
||||
Reference in New Issue
Block a user