Fix terminal resize handling and cached updates
This commit is contained in:
+26
-5
@@ -10,6 +10,7 @@ prefix="${PREFIX:-$HOME/.local}"
|
|||||||
from_current=0
|
from_current=0
|
||||||
start_server=1
|
start_server=1
|
||||||
force_config=0
|
force_config=0
|
||||||
|
update_cache="${DOSH_UPDATE_CACHE:-$HOME/.cache/dosh/source}"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -22,11 +23,14 @@ Options:
|
|||||||
--dosh-host HOST UDP host for Dosh packets when different from SSH target
|
--dosh-host HOST UDP host for Dosh packets when different from SSH target
|
||||||
--port PORT Dosh UDP port; default 50000
|
--port PORT Dosh UDP port; default 50000
|
||||||
--prefix DIR Install prefix; default ~/.local
|
--prefix DIR Install prefix; default ~/.local
|
||||||
|
--update-cache DIR
|
||||||
|
Persistent source checkout for incremental updates
|
||||||
--no-start Install server but do not start it
|
--no-start Install server but do not start it
|
||||||
--force-config Rewrite existing ~/.config/dosh/*.toml files
|
--force-config Rewrite existing ~/.config/dosh/*.toml files
|
||||||
|
|
||||||
Environment alternatives:
|
Environment alternatives:
|
||||||
DOSH_REPO, DOSH_ROLE, DOSH_SERVER, DOSH_HOST, DOSH_PORT, PREFIX
|
DOSH_REPO, DOSH_ROLE, DOSH_SERVER, DOSH_HOST, DOSH_PORT, PREFIX,
|
||||||
|
DOSH_UPDATE_CACHE
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +59,10 @@ while [ "$#" -gt 0 ]; do
|
|||||||
prefix="$2"
|
prefix="$2"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--update-cache)
|
||||||
|
update_cache="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--from-current)
|
--from-current)
|
||||||
from_current=1
|
from_current=1
|
||||||
;;
|
;;
|
||||||
@@ -119,15 +127,28 @@ else
|
|||||||
echo "DOSH_REPO or --repo is required when running the installer from curl" >&2
|
echo "DOSH_REPO or --repo is required when running the installer from curl" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
case "$update_cache" in
|
||||||
|
""|"/"|"$HOME"|"$HOME/"|"$HOME/.cache")
|
||||||
|
echo "refusing unsafe update cache path: $update_cache" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
need git
|
need git
|
||||||
tmpdir="$(mktemp -d)"
|
mkdir -p "$(dirname "$update_cache")"
|
||||||
git clone --depth 1 "$repo" "$tmpdir/dosh" >/dev/null
|
if [ -d "$update_cache/.git" ]; then
|
||||||
src_dir="$tmpdir/dosh"
|
git -C "$update_cache" remote set-url origin "$repo"
|
||||||
|
git -C "$update_cache" fetch --depth 1 origin main
|
||||||
|
git -C "$update_cache" checkout -q -B main FETCH_HEAD
|
||||||
|
else
|
||||||
|
rm -rf "$update_cache"
|
||||||
|
git clone --depth 1 --branch main "$repo" "$update_cache" >/dev/null
|
||||||
|
fi
|
||||||
|
src_dir="$update_cache"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd "$src_dir"
|
cd "$src_dir"
|
||||||
if [ "$role" = "client" ]; then
|
if [ "$role" = "client" ]; then
|
||||||
cargo build --release --bin dosh-client --bin dosh-bench
|
cargo build --release --bin dosh-client
|
||||||
else
|
else
|
||||||
cargo build --release
|
cargo build --release
|
||||||
fi
|
fi
|
||||||
|
|||||||
+66
-5
@@ -9,7 +9,7 @@ use dosh::auth::{
|
|||||||
use dosh::config::{expand_tilde, load_client_config, load_server_config};
|
use dosh::config::{expand_tilde, load_client_config, load_server_config};
|
||||||
use dosh::crypto;
|
use dosh::crypto;
|
||||||
use dosh::protocol::{
|
use dosh::protocol::{
|
||||||
self, AttachOk, BootstrapAttachRequest, CLIENT_TO_SERVER, Frame, Input, PacketKind,
|
self, AttachOk, BootstrapAttachRequest, CLIENT_TO_SERVER, Frame, Input, PacketKind, Resize,
|
||||||
ResumeRequest, SERVER_TO_CLIENT, TicketAttachBody, TicketAttachEnvelope,
|
ResumeRequest, SERVER_TO_CLIENT, TicketAttachBody, TicketAttachEnvelope,
|
||||||
TicketAttachOkEnvelope,
|
TicketAttachOkEnvelope,
|
||||||
};
|
};
|
||||||
@@ -218,11 +218,16 @@ fn run_update(config: &dosh::config::ClientConfig) -> Result<()> {
|
|||||||
.unwrap_or_else(|| "https://git.palav.dev/Palav/dosh.git".to_string());
|
.unwrap_or_else(|| "https://git.palav.dev/Palav/dosh.git".to_string());
|
||||||
let raw_base = raw_base_from_repo(&repo);
|
let raw_base = raw_base_from_repo(&repo);
|
||||||
let installer = format!("{raw_base}/raw/branch/main/install.sh");
|
let installer = format!("{raw_base}/raw/branch/main/install.sh");
|
||||||
|
let update_cache = dirs::cache_dir()
|
||||||
|
.unwrap_or_else(|| expand_tilde("~/.cache"))
|
||||||
|
.join("dosh")
|
||||||
|
.join("source");
|
||||||
let mut script = format!(
|
let mut script = format!(
|
||||||
"curl -fsSL {} | DOSH_REPO={} DOSH_PORT={} sh -s -- client",
|
"curl -fsSL {} | DOSH_REPO={} DOSH_PORT={} DOSH_UPDATE_CACHE={} sh -s -- client",
|
||||||
shell_word(&installer),
|
shell_word(&installer),
|
||||||
shell_word(&repo),
|
shell_word(&repo),
|
||||||
config.update_port.unwrap_or(config.dosh_port)
|
config.update_port.unwrap_or(config.dosh_port),
|
||||||
|
shell_word(&update_cache.display().to_string())
|
||||||
);
|
);
|
||||||
if config.server != "user@example.com" {
|
if config.server != "user@example.com" {
|
||||||
script.push_str(&format!(" --server {}", shell_word(&config.server)));
|
script.push_str(&format!(" --server {}", shell_word(&config.server)));
|
||||||
@@ -230,9 +235,8 @@ fn run_update(config: &dosh::config::ClientConfig) -> Result<()> {
|
|||||||
if let Some(host) = &config.dosh_host {
|
if let Some(host) = &config.dosh_host {
|
||||||
script.push_str(&format!(" --dosh-host {}", shell_word(host)));
|
script.push_str(&format!(" --dosh-host {}", shell_word(host)));
|
||||||
}
|
}
|
||||||
script.push_str(" --force-config");
|
|
||||||
let status = Command::new("sh")
|
let status = Command::new("sh")
|
||||||
.arg("-lc")
|
.arg("-c")
|
||||||
.arg(script)
|
.arg(script)
|
||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
.status()
|
.status()
|
||||||
@@ -549,6 +553,8 @@ async fn run_terminal(
|
|||||||
let mut last_packet_at = Instant::now();
|
let mut last_packet_at = Instant::now();
|
||||||
let mut status_visible = false;
|
let mut status_visible = false;
|
||||||
let mut status_tick = tokio::time::interval(Duration::from_secs(1));
|
let mut status_tick = tokio::time::interval(Duration::from_secs(1));
|
||||||
|
let mut resize_tick = tokio::time::interval(Duration::from_millis(250));
|
||||||
|
let mut last_size = size().unwrap_or((80, 24));
|
||||||
if let Some(frame) = first_frame {
|
if let Some(frame) = first_frame {
|
||||||
render_frame(&frame)?;
|
render_frame(&frame)?;
|
||||||
if frame.closed {
|
if frame.closed {
|
||||||
@@ -597,6 +603,16 @@ async fn run_terminal(
|
|||||||
socket.send_to(&packet, addr).await?;
|
socket.send_to(&packet, addr).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ = resize_tick.tick() => {
|
||||||
|
if let Ok((cols, rows)) = size()
|
||||||
|
&& (cols, rows) != last_size
|
||||||
|
{
|
||||||
|
last_size = (cols, rows);
|
||||||
|
if cred.mode != "view-only" {
|
||||||
|
send_resize(&socket, addr, &cred, &mut send_seq, cols, rows).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
recv = socket.recv_from(&mut recv_buf) => {
|
recv = socket.recv_from(&mut recv_buf) => {
|
||||||
let (n, _) = recv?;
|
let (n, _) = recv?;
|
||||||
last_packet_at = Instant::now();
|
last_packet_at = Instant::now();
|
||||||
@@ -647,6 +663,29 @@ async fn run_terminal(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn send_resize(
|
||||||
|
socket: &UdpSocket,
|
||||||
|
addr: SocketAddr,
|
||||||
|
cred: &CachedCredential,
|
||||||
|
send_seq: &mut u64,
|
||||||
|
cols: u16,
|
||||||
|
rows: u16,
|
||||||
|
) -> Result<()> {
|
||||||
|
let body = protocol::to_body(&Resize { cols, rows })?;
|
||||||
|
let packet = protocol::encode_encrypted(
|
||||||
|
PacketKind::Resize,
|
||||||
|
cred.client_id,
|
||||||
|
*send_seq,
|
||||||
|
cred.last_rendered_seq,
|
||||||
|
&cred.session_key,
|
||||||
|
CLIENT_TO_SERVER,
|
||||||
|
&body,
|
||||||
|
)?;
|
||||||
|
*send_seq += 1;
|
||||||
|
socket.send_to(&packet, addr).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn send_ack(
|
async fn send_ack(
|
||||||
socket: &UdpSocket,
|
socket: &UdpSocket,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
@@ -733,12 +772,34 @@ struct RawMode;
|
|||||||
impl RawMode {
|
impl RawMode {
|
||||||
fn enter() -> Result<Self> {
|
fn enter() -> Result<Self> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
stdout.write_all(TERMINAL_CLEANUP)?;
|
||||||
|
stdout.flush()?;
|
||||||
Ok(Self)
|
Ok(Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RawMode {
|
impl Drop for RawMode {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
let _ = stdout.write_all(TERMINAL_CLEANUP);
|
||||||
|
let _ = stdout.flush();
|
||||||
let _ = disable_raw_mode();
|
let _ = disable_raw_mode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TERMINAL_CLEANUP: &[u8] = concat!(
|
||||||
|
"\x1b[?1l",
|
||||||
|
"\x1b[0m",
|
||||||
|
"\x1b[?25h",
|
||||||
|
"\x1b[?1003l",
|
||||||
|
"\x1b[?1002l",
|
||||||
|
"\x1b[?1001l",
|
||||||
|
"\x1b[?1000l",
|
||||||
|
"\x1b[?1015l",
|
||||||
|
"\x1b[?1006l",
|
||||||
|
"\x1b[?1005l",
|
||||||
|
"\x1b[?2004l",
|
||||||
|
"\x1b[?1049l"
|
||||||
|
)
|
||||||
|
.as_bytes();
|
||||||
|
|||||||
@@ -258,6 +258,10 @@ async fn handle_bootstrap_attach(
|
|||||||
.sessions
|
.sessions
|
||||||
.get_mut(&req.bootstrap.session)
|
.get_mut(&req.bootstrap.session)
|
||||||
.expect("session exists");
|
.expect("session exists");
|
||||||
|
if req.bootstrap.mode != "view-only" {
|
||||||
|
session.pty.resize(req.cols, req.rows)?;
|
||||||
|
session.parser.set_size(req.rows, req.cols);
|
||||||
|
}
|
||||||
let client_id = crypto::random_16();
|
let client_id = crypto::random_16();
|
||||||
let snapshot = session.parser.screen().state_formatted();
|
let snapshot = session.parser.screen().state_formatted();
|
||||||
let screen = session.parser.screen().clone();
|
let screen = session.parser.screen().clone();
|
||||||
@@ -362,6 +366,10 @@ async fn handle_ticket_attach(
|
|||||||
.sessions
|
.sessions
|
||||||
.get_mut(&req.session)
|
.get_mut(&req.session)
|
||||||
.expect("session exists");
|
.expect("session exists");
|
||||||
|
if req.mode != "view-only" {
|
||||||
|
session.pty.resize(req.cols, req.rows)?;
|
||||||
|
session.parser.set_size(req.rows, req.cols);
|
||||||
|
}
|
||||||
let client_id = crypto::random_16();
|
let client_id = crypto::random_16();
|
||||||
let snapshot = session.parser.screen().state_formatted();
|
let snapshot = session.parser.screen().state_formatted();
|
||||||
let screen = session.parser.screen().clone();
|
let screen = session.parser.screen().clone();
|
||||||
@@ -456,6 +464,10 @@ async fn handle_resume(
|
|||||||
client.rows = req.rows;
|
client.rows = req.rows;
|
||||||
client.last_seen = Instant::now();
|
client.last_seen = Instant::now();
|
||||||
client.send_seq += 1;
|
client.send_seq += 1;
|
||||||
|
if client.mode != "view-only" {
|
||||||
|
session.pty.resize(req.cols, req.rows)?;
|
||||||
|
session.parser.set_size(req.rows, req.cols);
|
||||||
|
}
|
||||||
let snapshot = session.parser.screen().state_formatted();
|
let snapshot = session.parser.screen().state_formatted();
|
||||||
client.last_screen = Some(session.parser.screen().clone());
|
client.last_screen = Some(session.parser.screen().clone());
|
||||||
(client.send_seq, session.output_seq, snapshot)
|
(client.send_seq, session.output_seq, snapshot)
|
||||||
|
|||||||
Reference in New Issue
Block a user