From 7884ea2796c7e710bd39b73f05b69eda489a0d6e Mon Sep 17 00:00:00 2001 From: DuProcess <273172371+DuProcess@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:40:58 -0400 Subject: [PATCH] Add prebuilt update path --- Makefile | 5 +- README.md | 11 ++ SPEC.md | 2 + install.sh | 290 ++++++++++++++++++++++++++++--------- scripts/package-release.sh | 52 +++++++ src/bin/dosh-client.rs | 4 +- src/config.rs | 4 +- tests/integration_smoke.rs | 3 +- 8 files changed, 297 insertions(+), 74 deletions(-) create mode 100755 scripts/package-release.sh diff --git a/Makefile b/Makefile index 68197ca..ac7c30f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test fmt install bench-local bench-local-json bench-docker-ssh bench-docker-mosh fuzz-smoke fuzz-deep soak-local +.PHONY: build test fmt install package-release bench-local bench-local-json bench-docker-ssh bench-docker-mosh fuzz-smoke fuzz-deep soak-local build: cargo build --release @@ -12,6 +12,9 @@ fmt: install: sh packaging/install.sh +package-release: + sh scripts/package-release.sh + # Safe, self-contained local benchmark matrix (native cold auth, cached attach # ticket, local-auth) on a throwaway server bound to 127.0.0.1 on a free port in # a temp HOME. Never touches the production server or UDP port 50000. diff --git a/README.md b/README.md index 53c78dc..c288785 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,11 @@ Update an installed client later: dosh update ``` +`dosh update` first tries a release tarball named for the platform +(`dosh-macos-aarch64.tar.gz`, `dosh-linux-x86_64.tar.gz`, etc.) from the latest +Gitea/GitHub release. If that asset is not published yet, it falls back to the +source build path. + Install the client on Windows PowerShell: ```powershell @@ -266,6 +271,12 @@ Install release binaries and the user systemd service: make install ``` +Build release tarballs for upload to Gitea/GitHub releases: + +```bash +make package-release +``` + ## Performance Rules The stack is performance-driven, not fixed by taste. Rust is the default because the diff --git a/SPEC.md b/SPEC.md index beb36a0..7f39ec6 100644 --- a/SPEC.md +++ b/SPEC.md @@ -468,6 +468,8 @@ dosh_port = 50000 default_session = "new" reconnect_timeout_secs = 5 view_only = false +predict = true +predict_mode = "experimental" cache_attach_tickets = true credential_cache = "~/.local/share/dosh/credentials" ``` diff --git a/install.sh b/install.sh index 8ae7630..f150399 100755 --- a/install.sh +++ b/install.sh @@ -12,6 +12,12 @@ start_server=1 force_config=0 update_cache="${DOSH_UPDATE_CACHE:-$HOME/.cache/dosh/source}" quiet="${DOSH_UPDATE_QUIET:-0}" +use_prebuilt="${DOSH_USE_PREBUILT:-0}" +binary_url="${DOSH_BINARY_URL:-}" +binary_base="${DOSH_BINARY_BASE:-}" +binary_name="${DOSH_BINARY_NAME:-}" +binary_version="${DOSH_BINARY_VERSION:-latest}" +binary_required="${DOSH_BINARY_REQUIRED:-0}" usage() { cat <<'EOF' @@ -32,6 +38,18 @@ Options: Environment alternatives: DOSH_REPO, DOSH_ROLE, DOSH_SERVER, DOSH_HOST, DOSH_PORT, PREFIX, DOSH_UPDATE_CACHE + DOSH_USE_PREBUILT=1 + Try a release tarball before building from source + DOSH_BINARY_URL URL + Exact release tarball URL to install + DOSH_BINARY_BASE URL + Release download base; defaults to REPO/releases/latest/download + DOSH_BINARY_NAME NAME + Release tarball name; defaults to dosh-OS-ARCH.tar.gz + DOSH_BINARY_VERSION TAG + Release tag when deriving DOSH_BINARY_BASE; default latest + DOSH_BINARY_REQUIRED=1 + Fail instead of falling back to source when binary install fails EOF } @@ -112,8 +130,6 @@ ensure_cargo() { . "$HOME/.cargo/env" } -ensure_cargo - cleanup() { if [ -n "${tmpdir:-}" ]; then rm -rf "$tmpdir" @@ -121,73 +137,213 @@ cleanup() { } trap cleanup EXIT INT TERM -if [ "$from_current" -eq 1 ] || [ -f Cargo.toml ]; then - src_dir="$(pwd)" -else - if [ -z "$repo" ]; then - echo "DOSH_REPO or --repo is required when running the installer from curl" >&2 - exit 2 - fi - case "$update_cache" in - ""|"/"|"$HOME"|"$HOME/"|"$HOME/.cache") - echo "refusing unsafe update cache path: $update_cache" >&2 - exit 2 - ;; - esac - need git - mkdir -p "$(dirname "$update_cache")" - if [ -d "$update_cache/.git" ]; then - [ "$quiet" = "1" ] && echo "Updating Dosh source" - git -C "$update_cache" remote set-url origin "$repo" - if [ "$quiet" = "1" ]; then - git -C "$update_cache" fetch -q --depth 1 origin main - else - git -C "$update_cache" fetch --depth 1 origin main - fi - git -C "$update_cache" checkout -q -B main FETCH_HEAD - else - [ "$quiet" = "1" ] && echo "Downloading Dosh source" - rm -rf "$update_cache" - if [ "$quiet" = "1" ]; then - git clone -q --depth 1 --branch main "$repo" "$update_cache" >/dev/null - else - git clone --depth 1 --branch main "$repo" "$update_cache" >/dev/null - fi - fi - src_dir="$update_cache" -fi - -cd "$src_dir" -[ "$quiet" = "1" ] && echo "Building Dosh $role" -if [ "$role" = "client" ]; then - if [ "$quiet" = "1" ]; then - cargo build -q --release --bin dosh-client - else - cargo build --release --bin dosh-client - fi -else - if [ "$quiet" = "1" ]; then - cargo build -q --release - else - cargo build --release - fi -fi - bindir="$prefix/bin" config_dir="$HOME/.config/dosh" data_dir="$HOME/.local/share/dosh" systemd_user_dir="$HOME/.config/systemd/user" +src_dir="" mkdir -p "$bindir" "$config_dir" "$data_dir" -install -m 0755 target/release/dosh-client "$bindir/dosh-client" -ln -sf dosh-client "$bindir/dosh" -if [ "$role" = "server" ] || [ "$role" = "both" ]; then - install -m 0755 target/release/dosh-server "$bindir/dosh-server" - install -m 0755 target/release/dosh-auth "$bindir/dosh-auth" -fi -if [ -f target/release/dosh-bench ]; then - install -m 0755 target/release/dosh-bench "$bindir/dosh-bench" +normalize_os() { + case "$(uname -s)" in + Darwin) printf '%s\n' macos ;; + Linux) printf '%s\n' linux ;; + FreeBSD) printf '%s\n' freebsd ;; + *) uname -s | tr '[:upper:]' '[:lower:]' ;; + esac +} + +normalize_arch() { + case "$(uname -m)" in + x86_64|amd64) printf '%s\n' x86_64 ;; + arm64|aarch64) printf '%s\n' aarch64 ;; + armv7l) printf '%s\n' armv7 ;; + *) uname -m | tr '[:upper:]' '[:lower:]' ;; + esac +} + +repo_web_base() { + repo_base="$1" + case "$repo_base" in + http://*|https://*) + repo_base="${repo_base%.git}" + printf '%s\n' "${repo_base%/}" + ;; + *) + return 1 + ;; + esac +} + +release_artifact_name() { + if [ -n "$binary_name" ]; then + printf '%s\n' "$binary_name" + else + printf 'dosh-%s-%s.tar.gz\n' "$(normalize_os)" "$(normalize_arch)" + fi +} + +release_download_url() { + if [ -n "$binary_url" ]; then + printf '%s\n' "$binary_url" + return 0 + fi + if [ -n "$binary_base" ]; then + printf '%s/%s\n' "${binary_base%/}" "$(release_artifact_name)" + return 0 + fi + if [ -z "$repo" ]; then + return 1 + fi + web_base="$(repo_web_base "$repo")" || return 1 + if [ "$binary_version" = "latest" ]; then + printf '%s/releases/latest/download/%s\n' "$web_base" "$(release_artifact_name)" + else + printf '%s/releases/download/%s/%s\n' "$web_base" "$binary_version" "$(release_artifact_name)" + fi +} + +find_extracted_binary() { + find "$1" -type f -name "$2" 2>/dev/null | sed -n '1p' +} + +install_extracted_binary() { + found="$(find_extracted_binary "$1" "$2")" + if [ -z "$found" ]; then + echo "prebuilt archive missing $2" >&2 + return 1 + fi + install -m 0755 "$found" "$3" +} + +try_install_prebuilt() { + download_url="$(release_download_url)" || return 1 + tmpdir="$(mktemp -d)" + archive="$tmpdir/$(release_artifact_name)" + [ "$quiet" = "1" ] && echo "Trying Dosh prebuilt $(release_artifact_name)" + need curl + need tar + if ! curl -fL "$download_url" -o "$archive"; then + echo "prebuilt unavailable: $download_url" >&2 + return 1 + fi + mkdir -p "$tmpdir/extract" + if ! tar -xzf "$archive" -C "$tmpdir/extract"; then + echo "prebuilt archive could not be extracted: $download_url" >&2 + return 1 + fi + install_extracted_binary "$tmpdir/extract" dosh-client "$bindir/dosh-client" || return 1 + ln -sf dosh-client "$bindir/dosh" + if [ "$role" = "server" ] || [ "$role" = "both" ]; then + install_extracted_binary "$tmpdir/extract" dosh-server "$bindir/dosh-server" || return 1 + install_extracted_binary "$tmpdir/extract" dosh-auth "$bindir/dosh-auth" || return 1 + fi + if found_bench="$(find_extracted_binary "$tmpdir/extract" dosh-bench)" && [ -n "$found_bench" ]; then + install -m 0755 "$found_bench" "$bindir/dosh-bench" + fi + return 0 +} + +install_from_source() { + ensure_cargo + if [ "$from_current" -eq 1 ] || [ -f Cargo.toml ]; then + src_dir="$(pwd)" + else + if [ -z "$repo" ]; then + echo "DOSH_REPO or --repo is required when running the installer from curl" >&2 + exit 2 + fi + case "$update_cache" in + ""|"/"|"$HOME"|"$HOME/"|"$HOME/.cache") + echo "refusing unsafe update cache path: $update_cache" >&2 + exit 2 + ;; + esac + need git + mkdir -p "$(dirname "$update_cache")" + if [ -d "$update_cache/.git" ]; then + [ "$quiet" = "1" ] && echo "Updating Dosh source" + git -C "$update_cache" remote set-url origin "$repo" + if [ "$quiet" = "1" ]; then + git -C "$update_cache" fetch -q --depth 1 origin main + else + git -C "$update_cache" fetch --depth 1 origin main + fi + git -C "$update_cache" checkout -q -B main FETCH_HEAD + else + [ "$quiet" = "1" ] && echo "Downloading Dosh source" + rm -rf "$update_cache" + if [ "$quiet" = "1" ]; then + git clone -q --depth 1 --branch main "$repo" "$update_cache" >/dev/null + else + git clone --depth 1 --branch main "$repo" "$update_cache" >/dev/null + fi + fi + src_dir="$update_cache" + fi + + cd "$src_dir" + [ "$quiet" = "1" ] && echo "Building Dosh $role" + if [ "$role" = "client" ]; then + if [ "$quiet" = "1" ]; then + cargo build -q --release --bin dosh-client + else + cargo build --release --bin dosh-client + fi + else + if [ "$quiet" = "1" ]; then + cargo build -q --release + else + cargo build --release + fi + fi + + install -m 0755 target/release/dosh-client "$bindir/dosh-client" + ln -sf dosh-client "$bindir/dosh" + if [ "$role" = "server" ] || [ "$role" = "both" ]; then + install -m 0755 target/release/dosh-server "$bindir/dosh-server" + install -m 0755 target/release/dosh-auth "$bindir/dosh-auth" + fi + if [ -f target/release/dosh-bench ]; then + install -m 0755 target/release/dosh-bench "$bindir/dosh-bench" + fi +} + +write_systemd_service() { + if [ -n "$src_dir" ] && [ -f "$src_dir/packaging/systemd/dosh-server.service" ]; then + sed "s#ExecStart=%h/.local/bin/dosh-server serve#ExecStart=$bindir/dosh-server serve#" \ + "$src_dir/packaging/systemd/dosh-server.service" >"$systemd_user_dir/dosh-server.service" + else + cat >"$systemd_user_dir/dosh-server.service" </dev/null 2>&1; then mkdir -p "$systemd_user_dir" - sed "s#ExecStart=%h/.local/bin/dosh-server serve#ExecStart=$bindir/dosh-server serve#" \ - packaging/systemd/dosh-server.service >"$systemd_user_dir/dosh-server.service" + write_systemd_service if [ "$start_server" -eq 1 ] && systemctl --user daemon-reload >/dev/null 2>&1; then systemctl --user enable --now dosh-server.service fi @@ -272,7 +427,8 @@ dosh_port = $port default_session = "new" reconnect_timeout_secs = 5 view_only = false -predict = false +predict = true +predict_mode = "experimental" cache_attach_tickets = true credential_cache = "~/.local/share/dosh/credentials" auth_preference = "native,ssh" @@ -300,13 +456,13 @@ EOF # dosh_host = "palav.dev" # port = 50000 # default_command = "tm" -# predict = false +# predict = true [default] ssh = "$default_server" dosh_host = "$host_udp" port = $port -predict = false +predict = true EOF fi fi diff --git a/scripts/package-release.sh b/scripts/package-release.sh new file mode 100755 index 0000000..ece7b05 --- /dev/null +++ b/scripts/package-release.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env sh +set -eu + +repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +cd "$repo_root" + +out_dir="${DOSH_PACKAGE_DIR:-target/dosh-release}" +version="${DOSH_VERSION:-$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | sed -n '1p')}" + +normalize_os() { + case "$(uname -s)" in + Darwin) printf '%s\n' macos ;; + Linux) printf '%s\n' linux ;; + FreeBSD) printf '%s\n' freebsd ;; + *) uname -s | tr '[:upper:]' '[:lower:]' ;; + esac +} + +normalize_arch() { + case "$(uname -m)" in + x86_64|amd64) printf '%s\n' x86_64 ;; + arm64|aarch64) printf '%s\n' aarch64 ;; + armv7l) printf '%s\n' armv7 ;; + *) uname -m | tr '[:upper:]' '[:lower:]' ;; + esac +} + +os="$(normalize_os)" +arch="$(normalize_arch)" +artifact="dosh-$os-$arch.tar.gz" +versioned_artifact="dosh-$version-$os-$arch.tar.gz" +stage="$out_dir/stage/dosh" + +cargo build --release + +rm -rf "$stage" +mkdir -p "$stage/bin" "$out_dir" +for bin in dosh-client dosh-server dosh-auth dosh-bench; do + if [ -f "target/release/$bin" ]; then + install -m 0755 "target/release/$bin" "$stage/bin/$bin" + fi +done +printf '%s\n' "$version" >"$stage/VERSION" + +tar -C "$out_dir/stage" -czf "$out_dir/$artifact" dosh +cp "$out_dir/$artifact" "$out_dir/$versioned_artifact" + +cat < Result<()> { .join("dosh") .join("source"); let mut script = format!( - "curl -fsSL {} | DOSH_REPO={} DOSH_PORT={} DOSH_UPDATE_CACHE={} DOSH_UPDATE_QUIET=1 sh -s -- client", + "curl -fsSL {} | DOSH_REPO={} DOSH_PORT={} DOSH_UPDATE_CACHE={} DOSH_UPDATE_QUIET=1 DOSH_USE_PREBUILT=1 sh -s -- client", shell_word(&installer), shell_word(&repo), config.update_port.unwrap_or(config.dosh_port), @@ -1152,7 +1152,7 @@ fn run_import_ssh(config: &dosh::config::ClientConfig, aliases: &[String]) -> Re { raw.push_str(&format!("ssh_port = {port}\n")); } - raw.push_str("predict = false\n"); + raw.push_str("predict = true\n"); } if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; diff --git a/src/config.rs b/src/config.rs index c57b441..3eaa75f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -104,7 +104,7 @@ pub struct ClientConfig { pub default_session: String, pub reconnect_timeout_secs: u64, pub view_only: bool, - #[serde(default)] + #[serde(default = "default_true")] pub predict: bool, /// Prediction display policy: "off", "experimental" (adaptive, the default), /// or "always". Controls when speculative local echo is shown. @@ -193,7 +193,7 @@ impl Default for ClientConfig { default_session: "new".to_string(), reconnect_timeout_secs: 5, view_only: false, - predict: false, + predict: true, predict_mode: default_predict_mode(), cache_attach_tickets: true, credential_cache: "~/.local/share/dosh/credentials".to_string(), diff --git a/tests/integration_smoke.rs b/tests/integration_smoke.rs index e85c4a5..dd9ef02 100644 --- a/tests/integration_smoke.rs +++ b/tests/integration_smoke.rs @@ -1407,8 +1407,7 @@ fn large_tui_paint_is_delivered_in_mtu_safe_frames() { let mut text = String::new(); let mut saw_split_frame = false; let deadline = std::time::Instant::now() + Duration::from_secs(3); - while std::time::Instant::now() < deadline && text.matches("DOSH_TUI_BIG_PAINT").count() < 200 - { + while std::time::Instant::now() < deadline && text.matches("DOSH_TUI_BIG_PAINT").count() < 200 { if let Some((_header, frame)) = recv_frame(&socket, &bootstrap.session_key) { if frame.bytes.len() <= 1400 && frame.bytes.len() >= 900 { saw_split_frame = true;