Skip to content

Releases: hang-in/sshc

0.13.1 — 2026-06-26

Choose a tag to compare

@github-actions github-actions released this 26 Jun 03:58

Release Notes

Single-key UX addition. The auto-probe model from v0.3 (probe at
startup + after edits, never on a timer) stays — r just adds
the explicit "refresh the dots without touching anything" trigger
that the picker-and-go workflow was missing between auto-rounds.

Added

  • r re-probes every host's TCP reachability. Previously
    the only way to update the list's reachability glyphs was to
    edit something (Enter → save) or to use g for the
    selected host only. Now r triggers
    ProbePool::refresh(&app.hosts) — same call path the
    SaveState / EditConfig actions already use — and emits a
    status hint "probing N host(s)…". Empty host list is a
    no-op (no action, no status noise).

    The choice of r reuses the slot vacated when v0.6 dropped
    the old r (manage-mode reconnect) for inline parity.

    This is intentionally not a background polling timer:

    • Repeated SYNs from one client to many hosts can be
      interpreted as a port scan by firewall / IDS and trigger
      fail2ban-style automatic blocks.
    • sshc is a picker, not a dashboard — anti-feature 4
      explicitly rules out "daemon / always-on process".

Tests

  • test_r_key_emits_refresh_reachability_with_status_hint
    three hosts, r fires the action and the status reads
    "probing 3".
  • test_r_key_on_empty_host_list_is_noop — empty list →
    no action, no status.

Install sshc 0.13.1

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.13.1/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.13.1/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.13.1

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-aarch64-pc-windows-msvc.zip ARM64 Windows checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

0.13.0 — 2026-06-26

Choose a tag to compare

@github-actions github-actions released this 26 Jun 03:10

Release Notes

Mixed cycle. One deps swap (G1 toml_edit removal) takes another
chunk off the binary, and two UX gaps from v0.10 / v0.12 close
out (G2 preview Forwarding section, G3 doctor's
ProxyCommand %h/%p/%r substitution). README + README.ko
caught up to four pieces of v0.8-v0.12 surface that hadn't been
documented.

Added

  • Preview panel shows typed Forwarding summary + first entry
    (G2). v0.10 G1 promoted the three *Forward directives to
    typed Vec; v0.12 R3 made them editable through a list
    modal. The v0.6 preview pane never caught up — Forwarding
    configuration was invisible until you opened the form. v0.13
    G2 adds:

    Forward:  L:2 R:1 D:0
              8080 localhost:80
    

    Local / Remote / Dynamic counts on one row, first entry on
    the next so the panel shows content, not just a count.
    Section is omitted entirely when every Vec is empty
    (no row, no blank line gap) — same shape as the existing
    Extra handling.

  • doctor substitutes %h / %p / %r in ProxyCommand
    tokens before walking PATH
    (G3). v0.10 G4 surfaced
    "ProxyCommand binary not on PATH" but skipped any token
    containing % or $. The most common shape —
    ProxyCommand nc %h %p or
    ProxyCommand corp-helper %h %p — fell exactly into that
    skip, so the check passed silently when it should have
    fired. v0.13 G3 narrows the skip:

    • $ still skips (shell variables — we don't shell out).
    • %h / %p / %r get substituted from the host's typed
      fields (with sensible fallbacks: alias for hostname,
      "22" for port, $USER / $USERNAME / "user" for user).
      %% becomes a literal %.
    • After substitution, if any % is still in the token
      (anything we don't model — %C, %u, %L) we skip.
      Modelling the full OpenSSH substitution set would creep
      into anti-feature 5 (complete config(5) parser); the
      three we cover are the ones that actually appear in real
      ProxyCommand strings.

Changed

  • toml = "0.5" (drops toml_edit transitive) (G1).
    cargo bloat on v0.12.0 ranked toml_edit at 167 KiB of
    .text — 11.7% of the binary — and v0.10 G1 / v0.12 R3
    showed sshc never needs format-preservation (state.toml is
    rewritten from scratch every save). Going back to
    toml = "0.5" (pre-toml_edit split) drops the whole
    format-preserving layer.

    Measured impact on macOS arm64 release binary:

    Phase Bytes Δ vs prior
    v0.12.0 (master) 2,729,456
    v0.13.0 (after toml swap) 2,470,688 −258,768 (−9.5%)

    cargo-dist artifact (per-platform .tar.xz / .zip) deltas
    vs v0.12.0 land at tag-push time and are reported in the
    GitHub Release notes.

    One internal schema reorder accompanied the swap:
    MemorySection::sort_axis (scalar) moved BEFORE
    recent: Vec<RecentEntry> (array of tables). toml 0.5
    enforces the TOML spec's "scalar fields before tables"
    rule; toml 0.8's format-preserving emitter had silently
    reordered when v0.12 G3 appended sort_axis after recent.
    #[serde(default)] on every field means existing v0.12
    state.toml files load cleanly regardless of where their
    keys land.

  • README + README.ko: four gap fixes (R1).

    • Windows ARM64 (sshc-aarch64-pc-windows-msvc.zip) artifact
      added to the Windows install section — it's been shipping
      since v0.8 but was undocumented.
    • Hero / 소개 paragraph quotes the v0.12.0 measured release
      sizes (macOS arm64 ~810 KB, Linux x64 ~1.16 MB, Windows
      x64 ~1.10 MB) so the "small single binary" claim has
      concrete numbers.
    • Form section splits IdentityFile multi-value into its own
      paragraph and adds the in-modal keymap table covering
      Shift+↑/↓ reorder + the IdentityFile candidate cycle
      that lives in edit mode.
    • v keymap row rewritten to clarify it opens the
      resolved ssh -G <alias> output for debugging
      Include / Match ordering — the v0.6 wording sounded like
      sshc was validating the alias itself.

Internal

  • substitute_known(token, host) -> Option<String> helper
    isolates the OpenSSH-style marker substitution. Returns
    None on any unknown %x so the caller can continue
    without branching on the string contents. Five new tests.
  • Two new preview tests (renders_with_multi_forwarding_* /
    renders_without_forwarding_omits_section) +
    render_text(term) test-helper that walks the TestBackend
    buffer into a String for substring assertions.
  • toml downgrade: a one-line Cargo.toml change. No
    src/state/mod.rs API surface moved — from_str and
    to_string_pretty are stable from 0.5 onwards.

Migration

  • state.toml: no migration. #[serde(default)] on every
    field means missing keys (and reordered keys) load
    cleanly.
  • v0.13 binaries running on a v0.12 state.toml file will
    pick up the v0.12 sort_axis value verbatim; the next
    save reorders the keys so the file conforms to toml 0.5's
    scalar-before-table requirement.

Out of scope (carried into v0.14+)

  • nucleo_matcher → smaller fuzzy (69 KiB candidate).
  • ureq surface trim (118 KiB candidate; v0.9 R7 already did
    the rustls→native-tls win).
  • K/J as Shift+↑/↓ reorder alias.
  • Help modal Shift+↑/↓ + S persistence note.
  • doctor: SSHC_NO_OSC52 + Wayland-no-display hint.

Install sshc 0.13.0

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.13.0/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.13.0/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.13.0

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-aarch64-pc-windows-msvc.zip ARM64 Windows checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

0.12.0 — 2026-06-26

Choose a tag to compare

@github-actions github-actions released this 26 Jun 02:10

Release Notes

UX round on top of v0.11's size win. Three goals; all delivered.
The v0.10 ForwardingListModal was extracted into a reusable
ListEditModal and the IdentityFile row finally joins the
forwarding triple as a multi-entry list editor. Sort axis
persistence closes a v0.10 G5 regression where every fresh
sshc started on AliasAlpha.

Added

  • IdentityFile multi-value with the same list-modal UX as
    forwarding
    (G1). OpenSSH lets a host carry several
    IdentityFile lines and tries them in order; v0.11 and
    earlier kept only the last typed entry (Option<PathBuf>)
    even though the parser saw every line. v0.12 promotes
    Host::identity_file to Vec<PathBuf> end-to-end:
    • Parser pushes per occurrence; serializer emits one
      IdentityFile <path> line per entry in declaration order.
    • The form's IdentityFile row is now a summary cell; Enter
      opens a ListEditModal with kind IdentityFile { candidates }
      where candidates is the ~/.ssh/* private-key path Vec
      the form discovered up-front.
    • The v0.7.1 ↑/↓ candidate picker moved INSIDE the modal's
      edit mode — same shortcut, just one level deeper so the
      multi-entry case has a single home.
    • Per-entry validation (the v0.7.2 shell-metacharacter
      forbid list + Windows backslash exception) moved to the
      modal so the form-level guard HostForm::validate no
      longer needs an IdentityFile branch.
  • Shift+↑/↓ reorder inside ListEditModal (G2). Power
    users with several forwarding or IdentityFile entries can
    now move them up/down without delete + re-add. OpenSSH treats
    declaration order as meaningful (keys tried in order,
    forwardings opened in order), and v0.10 G1's modal only
    supported add/edit/delete. v0.12 closes the gap. Top entry
    Shift+↑ and bottom entry Shift+↓ are guarded no-ops; on
    the "+ add" pseudo-row Shift is ignored and the key falls
    through to plain navigation.
  • Sort axis persistence in state.toml (G3). v0.10 G5's
    S shortcut was session-only; every fresh sshc re-started
    on AliasAlpha. v0.12 adds a sort_axis field to
    MemorySection. App::new loads it; App::cycle_sort_axis
    writes it back (best-effort state::save — failures don't
    surface a sticky error because the cosmetic preference
    doesn't merit one). #[serde(default)] lets pre-v0.12
    state.toml files load with Alias — no migration needed.

Changed

  • ui::forms::forwarding_listui::forms::list_edit. Type
    rename ForwardingListModalListEditModal. New
    ListKind enum carries the per-kind title / validate /
    reject-hint / candidate set so the modal stays one type as
    new list kinds land. The R1 refactor was pure (every v0.10
    forwarding test passed verbatim) and isolates the surface
    area changes that R3 introduced.
  • FormPayload::Host::identity_file: StringVec<String>.
    app::forms::build_host signature follows; the v0.11 single
    Option<PathBuf> wrap shim is gone.
  • README + README.ko: keymap notes for S persistence and
    IdentityFile list modal.

Internal

  • MemorySection::sort_axis: SortAxisPersisted lives in
    state::schema (no UI deps — R-G6). Two pure conversion
    functions (SortAxis::from_persisted / to_persisted) live
    in app::mod so the boundary holds in both directions.
  • Round-trip test:
    test_round_trip_multi_identity_file_preserves_order
    3 IdentityFile lines round-trip through serializer + parser
    with order intact.
  • New ListEditModal tests cover IdentityFile validate
    (positive + rejection), candidate cycle in edit mode, and
    the forwarding-edit-mode arrow no-op (regression guard).
  • Reorder tests cover the swap + cursor follow, the top-entry
    no-op, and the "+ add" row fall-through.
  • Sort axis persistence tests cover load on new and round-
    trip through three cycles.

Migration

  • v0.12-written sshc.conf files with multiple IdentityFile
    lines on a host: if you roll back to v0.11 (or earlier) and
    re-save the host, v0.11 keeps only the last typed entry
    (the same trade-off v0.10 G1 introduced for the forwarding
    triple). v0.12 reads its own format and v0.11's identically.
  • state.toml: no migration. sort_axis defaults via
    #[serde(default)] on missing key.

Out of scope (carried into v0.13+)

  • Further size recovery candidates (toml_edit 165 KiB,
    ureq 118 KiB).
  • Forwarding entries on the preview panel.
  • doctor: variable-substituted ProxyCommand sanity
    (%h/%p expansion).
  • doctor: SSHC_NO_OSC52 + Wayland-no-display hint.
  • K/J keys as reorder alias.

Install sshc 0.12.0

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.12.0/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.12.0/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.12.0

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-aarch64-pc-windows-msvc.zip ARM64 Windows checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

0.11.0 — 2026-06-26

Choose a tag to compare

@github-actions github-actions released this 26 Jun 01:17

Release Notes

Single-goal size recovery cycle. v0.10's G2 entry promised
Linux/Windows artifact reductions of -600 to -800 KB from
disabling arboard's image-data feature, but cargo-dist
came back with the opposite shape — every platform's artifact
grew (Linux x64 by +164 KB). v0.11 corrects course by going
after the actual top consumer measured with cargo bloat.

The user-facing surface — keymap, forms, doctor checks — is
unchanged.

Changed

  • env_logger switched to default-features = false (G1).
    cargo bloat on v0.10.0 (macOS arm64 release) ranked the regex
    family — regex_automata (264 KiB) + regex_syntax (169 KiB)

    • aho_corasick (114 KiB) — as 547 KiB of .text, 26.7% of
      the binary. The chain entered sshc transitively via
      env_logger 0.11 → env_filter → regex.

    sshc never used wildcard or regex patterns in RUST_LOG: every
    log::warn! / log::error! call site sits under the sshc::
    module prefix, and the README never documented a wildcard
    shape. Turning default-features off drops regex,
    humantime, and auto-color. RUST_LOG=sshc=debug still
    parses (prefix matching is in the slim path); regex patterns
    silently don't apply.

    Measured deltas (this CHANGELOG entry quotes only measured
    numbers, never predicted — see v0.10's R6 retrospective for
    why):

    macOS arm64 release binary (host machine):

    Phase Bytes Δ vs prior
    v0.10.0 (master) 3,982,280
    v0.11.0 (after env_logger default-off) 2,727,504 −1,254,776 (−31.5%)

    cargo-dist artifact (per-platform .tar.xz / .zip) deltas vs
    v0.10.0 land at tag-push time — see GitHub Release notes for
    the table. If they don't shrink, an erratum will follow.

Internal

  • env_logger / env_filter still ship as ~9 KiB each in the
    slim path. The log facade and seven call sites are
    unchanged.
  • No source code touched in v0.11. Single Cargo.toml line edit.

Out of scope (carried into v0.12+)

  • Further size recovery candidates (toml_edit 165 KiB,
    ureq 118 KiB, nucleo_matcher 69 KiB) — only consider if
    user feedback prioritises additional binary slimming over
    feature work.
  • Sort axis state persistence (v0.10 G5 carryover).
  • IdentityFile multi-value via the v0.10 ForwardingListModal
    pattern.
  • Forwarding list reorder (↑/↓).
  • doctor: SSHC_NO_OSC52 + Wayland-no-display combination
    sanity.

Install sshc 0.11.0

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.11.0/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.11.0/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.11.0

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-aarch64-pc-windows-msvc.zip ARM64 Windows checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

0.10.0 — 2026-06-26

Choose a tag to compare

@github-actions github-actions released this 26 Jun 00:27

Erratum (added post-release, 2026-06-26) — the G2 entry below
predicted Linux/Windows artifact reductions of -600 to -800 KB
from disabling arboard's image-data feature. cargo-dist's
actual output shows the opposite: every platform's artifact grew,
because the new G1/G3/G4/G5 code (forwarding modal, OSC 52 escape
builder, ProxyCommand walker, sort axis, base64 dep) added more
than the image decoders saved. Measured deltas vs v0.9.0:

Target v0.9.0 v0.10.0 Δ
macOS arm64 1086 KB 1103 KB +16 KB
macOS x86_64 1216 KB 1233 KB +17 KB
Windows ARM64 1478 KB 1492 KB +13 KB
Windows x64 1544 KB 1568 KB +24 KB
Linux aarch64 1213 KB 1376 KB +163 KB
Linux x86_64 1351 KB 1515 KB +164 KB

The image-data strip is still correct in principle (sshc never
reads image clipboards) but the net size win didn't materialise.
v0.11 will revisit with cargo bloat to find the real footprint.

Release Notes

Surface-polish round on top of v0.9. Five goals, all delivered;
two of them (G1 + G2) close lingering v0.9 trade-offs (typed
forwarding was single-entry; arboard pulled in image decoders sshc
never used).

Added

  • Multi-entry forwarding via a list modal (G1). v0.9 G5
    introduced typed LocalForward / RemoteForward /
    DynamicForward form rows but only stored one entry per kind.
    OpenSSH lets a host carry several of each; v0.9 cascaded
    duplicates into the freeform extra block as a workaround. v0.10
    upgrades the model end-to-end: each field is now Vec<String>,
    the parser pushes per occurrence in declaration order, the
    serializer emits one line per entry, and the form row opens a
    small list modal (Enter to edit / d to delete / Esc to return).
    Validation per kind matches v0.9 G5 — modal rejects garbage on
    the Enter that tries to add it.

  • S cycles the host list sort axis (G5). lazyssh's s/S
    parity, but s is sshc's ssh-connect key, so v0.10 takes S
    (Shift+s) only — one direction, three axes. Cycles
    alias → recent → reachability → alias. Favorites still float
    to the top regardless. Status bar shows the new label after each
    press. Session-only — not persisted across sshc invocations.

  • OSC 52 clipboard fallback (G3). v0.9 G4's c copy lived
    directly on arboard, which silently failed on Wayland w/o
    display, in remote SSH sessions, or in tmux without
    set -g set-clipboard on. v0.10 chains: try arboard first, then
    emit the OSC 52 escape (ESC ] 52 ; c ; <base64> ESC \) to
    stdout so modern emulators (kitty, iTerm2, foot, alacritty,
    wezterm) can write the user's host clipboard. Status hint shows
    copied: … (osc52) when the fallback fires so the user knows
    which path won. SSHC_NO_OSC52 disables the fallback for users
    who want to keep their terminal output clean.

  • ProxyCommand PATH sanity check in doctor (G4). For every
    host parsed out of ~/.ssh/config (Include chain followed),
    pull the ProxyCommand first token and look it up on $PATH
    (PATHEXT on Windows). When the binary isn't found, doctor
    surfaces a single WARN line aggregating offenders by host
    count, e.g.

    [WARN] proxy commands not on PATH — 'my-corp-helper' (3 hosts)

    Variable-laden tokens (%h, $JUMP) are skipped — we can't
    resolve those without ssh's own substitution layer. Clean
    configs see no extra line.

Changed

  • arboard image-data feature off (G2). sshc only ever writes
    text via set_text. Stripping default-features and re-opting
    into just wayland-data-control removes the image and tiff
    decoder crates from the transitive graph (verified via
    cargo tree --target x86_64-unknown-linux-gnu). macOS arm64
    release size barely moves because LTO had already DCE'd the
    unused code path on that target; Linux and Windows artifacts
    drop noticeably (-600 to -800 KB depending on platform). New
    download sizes will land in this release's GitHub artifacts.

Internal

  • New module src/ui/forms/forwarding_list.rs (~280 LOC plus 7
    unit tests). Owns its own validate-per-kind helpers; the v0.9
    copies in host_form were dead-code removed.
  • New module src/exec/clipboard.rs (copy_to_clipboard +
    OSC 52 escape builder). App::copy_ssh_command_for_selected
    now routes through it; no in-flight v0.9 G4 surface area
    changed.
  • Host::local_forward / remote_forward / dynamic_forward
    promoted from Option<String> to Vec<String>. All 8 host
    literal fixtures (model, app/tests, ui/list, ui/preview,
    ui/modal, inline_app, probe, examples) updated. FormPayload
    and build_host follow.
  • App.sort_axis is new state with a SortAxis enum cycling
    through three keys. apply_filter rewrites the comparator to
    use it as the secondary key behind favorites and (when present)
    the fuzzy score.
  • New dep: base64 = "0.22" (OSC 52 payload encoder, ~+10 KB
    binary impact, scoped to clipboard.rs).
  • Two arboard dep cleanups removed image and tiff transitive
    pulls.

Migration

  • v0.10-written sshc.conf files with multiple LocalForward /
    RemoteForward / DynamicForward lines on a single host:
    if you roll back to v0.9 (or earlier) and re-save that host,
    v0.9 will keep only the last such line per kind (the cascade
    workaround was last-wins). The data still survives in the
    extra block from the read side, but the typed form surface
    drops it. v0.10 reads its own format and v0.9's format
    identically.

Out of scope (carried into v0.11+)

  • Sort axis persistence in state.toml — v0.10 is intentionally
    session-only; user feedback decides v0.11.
  • ↑/↓ reorder inside the forwarding list modal — add/edit/del
    only for v0.10.
  • IdentityFile multi-value — same shape as the forwarding work;
    v0.11 candidate.
  • Hand-rolled clipboard backend (G2 alternative path that wasn't
    needed — arboard with image-data off was enough).

Install sshc 0.10.0

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.10.0/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.10.0/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.10.0

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-aarch64-pc-windows-msvc.zip ARM64 Windows checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

0.9.0 — 2026-06-26

Choose a tag to compare

@github-actions github-actions released this 25 Jun 23:20

Release Notes

Feature round + a measurable size win after eight v0.8.x patches.
Three sources fed v0.9: operational hardening leftover from the
v0.8 cycle, UX ideas borrowed (selectively) from Adembc/lazyssh,
and a long-deferred dependency cleanup.

Added

  • doctor surfaces CRLF in ~/.ssh/config (G1). When the user
    copies a config off a Windows host or saves it through an editor
    whose files.eol is CRLF, OpenSSH treats \r as part of an alias
    token and every Host match silently fails. doctor now scans the
    first 100 lines and emits a WARN with the exact tr -d '\r'
    fix. Clean configs see no extra line.
  • doctor surfaces nested Include scope (G2). Pre-v0.8.4
    installs (and any user who hand-edits the ~/.ssh/config block
    sshc injected) can end up with the Include line nested under a
    Host <pattern> stanza. OpenSSH then only fires sshc.conf for
    that alias, making every sshc-managed host invisible to
    ssh <other-alias>. doctor identifies the offending stanza, names
    it in the warning, and suggests adding Match all directly above
    the Include — or re-running sshc -mi on v0.8.4+ which
    emits the terminator automatically.
  • Sticky-on-error status messages (G3). The single most painful
    failure mode of the v0.7–v0.8 cycle was status messages getting
    overwritten by the modal-close redraw before the user could see
    them. Split StatusMessage into Info (v0.6 transient, 3 s
    timeout) and Error (stays visible until the user's next
    keystroke). All seven real failure paths (apply_form, persist,
    ssh -G, include injection, etc.) now use Error; routine
    confirmations stay Info.
  • c copies a one-line ssh command (G4). Pressing c on a
    selected host runs ssh -G <alias>, reduces the dump to
    ssh user@host -p port -i key (dropping defaults like port 22 or
    empty identityfile), and pushes the line onto the system
    clipboard via arboard. Useful when sharing a connection string
    with someone who doesn't have your ~/.ssh/config. Clipboard
    failures (Wayland w/o display, etc.) surface as a sticky Error so
    the line isn't silently dropped.
  • Typed Forwarding form fields (G5). The add/modify host form
    grows from 7 to 10 fields with a dimmed ─── Forwarding ───
    section header between Tags and the freeform Options field, and
    a ─── Advanced ─── header above Options. LocalForward,
    RemoteForward, and DynamicForward are now typed
    Option<String> values on Host, parsed and serialized
    round-trip. Loose validation matches OpenSSH's
    [bind:]port host:hostport (Local/Remote) and [bind:]port
    (Dynamic) shapes; deep validation is left to ssh on connect
    (anti-feature 5: no full config(5) parser). Multi-directive
    hosts keep their extras in the free-form extra block so the
    round-trip is lossless.
  • g TCP reachability probe (G6). Distinguishes "host is down"
    from "ssh config is wrong" without spawning ssh. g resolves the
    selected alias through ssh -G (reusing the v/c cache),
    attempts a raw TCP connect to the hostname:port endpoint with a
    2-second budget, and reports either a sticky ✗ TCP unreachable
    error or a transient ✓ TCP reach: host:port (N ms). nc -z
    semantics — reachability ≠ authentication, anti-feature 1 stands.
  • Windows ARM64 release artifacts (G8). cargo-dist now produces
    sshc-aarch64-pc-windows-msvc.zip alongside the existing
    x86_64 Windows artifact. The PowerShell installer routes
    ARM64 hosts to the matching binary automatically.

Changed

  • HTTP/TLS: ureq + native-tls, explicitly wired (G7). v0.8.0
    shipped ureq with default rustls + webpki-roots after the
    initial native-tls attempt died with "no TLS backend is
    configured". v0.9 closes that loop by handing AgentBuilder an
    explicit tls_connector — the step v0.8 R6 missed. The release
    binary on macOS arm64 shrinks from 5.95 MB (v0.9 R6, with
    arboard + new exec/ surface) to 3.76 MB (-2.18 MB), well
    under the v0.7-era baseline of 3.0 MB even with everything v0.8
    added on top. Dropping ring as a transitive dependency also
    unblocked cargo check --target x86_64-pc-windows-msvc from
    macOS hosts (broken since v0.8 R6 — see the
    docs/WINDOWS_DEBUG_HANDOFF.md rant for context).

Internal

  • host_form row layout split into a small Row enum so section
    headers (Forwarding, Advanced) participate in the layout
    without confusing Tab routing.
  • FormPayload + FormOutcome pick up #[allow(clippy::large_enum_variant)]
    rather than the boxing dance — both are stack values with
    one-call lifecycles.
  • All 8 Host literal fixtures (model, app/tests, ui/list,
    ui/preview, ui/modal, inline_app, probe, examples) gained the
    three new forwarding defaults.
  • New module src/exec/tcp_reach.rs houses the G6 reachability
    helper. Network surface stays in src/exec/*.
  • doctor's Path import is no longer cfg(unix)-gated (G2 uses it
    on every platform).
  • ~/.cargo/bin/sshc 0.9.0 doctor smoke run: 7 PASS lines, including
    the update check landing through native-tls.

Out of scope (carried into v0.10+)

  • Multiple Forwarding directives per host (G5 takes the last and
    cascades the rest into extra; round-trip is preserved).
  • Self-built SSH client, SCP, key deployment — anti-features 1 + 2
    still stand; lazyssh's roadmap items in that direction are
    intentionally not mirrored.
  • Always-on / startup update check — anti-feature 4 stands. g for
    reachability is the user-driven probe; the GitHub Releases call
    fires only inside --doctor.

Install sshc 0.9.0

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.9.0/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.9.0/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.9.0

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-aarch64-pc-windows-msvc.zip ARM64 Windows checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

0.8.4 — 2026-05-21

Choose a tag to compare

@github-actions github-actions released this 01 Jun 06:27

Release Notes

Hotfix for a load-bearing inject_include bug present since v0.4.0.
Affects both macOS and Windows. No behavior change for users whose
~/.ssh/config was empty when sshc first ran setup.

Fixed

  • Include directive appended by sshc -m's i could end up
    nested inside the user's last Host stanza
    , making sshc-managed
    hosts silently invisible to ssh <alias>. OpenSSH terminates a
    Host block only on the next Host / Match directive or EOF —
    blank lines and comments don't end a stanza — so an Include
    line appended to a config that ends with a Host block became a
    conditional Include scoped to that last alias. ssh -vv showed
    Reading configuration data .../sshc.conf (parse time), but
    Applying options for <sshc-alias> never fired for any sshc host.

    Fix: inject_include now emits three lines instead of two, with
    Match all between the comment header and the Include:

    # Added by sshc; do not remove.
    Match all
    Include ~/.ssh/config.d/sshc.conf
    

    Match is a stanza header in its own right, so the preceding
    Host block is closed; Match all matches every connection
    unconditionally, so the Include (which now belongs to this
    block) fires for every alias. Restores the "append-only,
    unconditional include" semantics the user expects.

    Users on existing v0.7.x / v0.8.0–v0.8.3 installs whose
    ~/.ssh/config ends with a Host stanza should either re-run
    sshc -mi after deleting the existing Include line, or
    manually add a Match all line directly above the sshc-managed
    Include block in ~/.ssh/config. New installs onto an empty
    ~/.ssh/config see no change in behavior.

Internal

  • New regression tests:
    • test_inject_emits_match_all_terminator_before_include
      asserts Match all is the line immediately before Include
      after inject_include runs over a config that ends with a
      Host stanza.
    • test_inject_idempotent_with_terminator — confirms calling
      inject twice still yields exactly one Include line and one
      Match all line.
  • is_include_present is unchanged; it scans for any Include
    directive line-by-line regardless of position, so the new
    three-line block is detected as already-present on the second
    invocation.

Install sshc 0.8.4

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.8.4/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.8.4/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.8.4

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

0.8.3 — 2026-05-21

Choose a tag to compare

@github-actions github-actions released this 20 May 23:48

Release Notes

Windows usability hotfix on top of v0.8.2. No behavior change on
macOS / Linux.

Fixed

  • sshc.conf is now usable by ssh on Windows. v0.8.2 made the
    save succeed (the a form actually wrote bytes to disk), but
    Windows OpenSSH then refused to read the result with
    Bad owner or permissions on C:\Users\<u>\.ssh\config.d\sshc.conf.
    The cause: set_owner_only_perms was a #[cfg(not(unix))] no-op,
    so newly-written sshc.conf inherited the parent directory's DACL
    — typically BUILTIN\Users or Authenticated Users with read
    access — and ssh.exe rejects any trustee broader than the owner.

    v0.8.3 replaces the no-op with a real DACL writer that mirrors
    Unix chmod 0600 intent: three explicit ACEs (current owner +
    NT AUTHORITY\SYSTEM + BUILTIN\Administrators), no inheritance,
    PROTECTED_DACL_SECURITY_INFORMATION so parent ACEs can't drift
    back in. Implementation goes through windows-sys
    GetNamedSecurityInfoWSetEntriesInAclW
    SetNamedSecurityInfoW. Owner SID is not modified.

    Verify on Windows after upgrading: sshc -ma → fill, Enter,
    then ssh -G <alias> no longer prints the "Bad owner" error and
    icacls $HOME\.ssh\config.d\sshc.conf shows only SYSTEM /
    Administrators / your user account.

Internal

  • test_editor_command_construction now pins EDITOR=vim for the
    duration of the test and restores the original value on exit.
    v0.8-era runs on Windows with EDITOR unset fell through to
    notepad.exe, which doesn't accept the +42 arg the test
    asserts. The assertion itself wasn't catching a real regression
    on those hosts — it was tripping on the environment default.

  • Cargo.toml windows-sys features list adds
    Win32_Security_Authorization (transitively required by
    SetEntriesInAclW / SetNamedSecurityInfoW).

Install sshc 0.8.3

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.8.3/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.8.3/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.8.3

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

0.8.2 — 2026-05-21

Choose a tag to compare

@github-actions github-actions released this 20 May 23:26

Release Notes

Windows hotfix over v0.8.1 — actually fixes the silent a (add host)
data loss. The v0.8.1 changelog blamed PathBuf normalization
(NFC/NFD on non-ASCII home directories) and unified the cached path
on both sides of the filter; that change was internally correct but
did not fix the bug. The real cause was one layer deeper, in
with_locked_write. No behavior change on macOS / Linux.

Fixed

  • Manage-mode a (add host) on Windows actually persists the new
    host to sshc.conf now.
    crate::storage::with_locked_write
    acquired an exclusive LockFileEx on its first handle and then —
    before handing the buffer to the mutator — opened a second
    File::open(path) to read the existing content. Windows file
    locking is mandatory, so the second open inside the locked range
    returned ERROR_LOCK_VIOLATION (os error 33) and the writer
    bailed out with StorageError::ReadFailed before any bytes were
    written. The error did surface as a status_message from
    apply_form, but the modal-close redraw overwrote it almost
    immediately and the user only saw an empty file.

    Unix flock is advisory and silently permitted the second open,
    which is why every pre-v0.8.2 release looked fine on macOS / Linux
    while consistently failing on Windows from v0.7 onward.

    Fix: read the existing content from the already-locked handle via
    seek(SeekFrom::Start(0)) + read_to_string instead of opening a
    second handle. One handle, one lock, no violation. Added a
    cross-platform regression test
    (app::tests::test_apply_form_add_host_writes_through_locked_writer)
    that drives apply_form end-to-end against a temp path so the
    same class of regression can't slip back in silently.

Note on v0.8.1

The v0.8.1 patch (path-cache unification in persist_sshc_conf) is
still in place and still correct as defense-in-depth — it just
wasn't the root cause. If you upgraded to v0.8.1 expecting the a
fix to land and saw the same empty-file behavior, v0.8.2 is the
release you actually want.

Install sshc 0.8.2

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.8.2/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.8.2/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.8.2

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

0.8.1 — 2026-05-20

Choose a tag to compare

@github-actions github-actions released this 20 May 11:43

Release Notes

Windows hotfix over v0.8.0 — fixes a long-running silent data loss
in the add-host form. No behavior change on macOS / Linux.

Fixed

  • Manage-mode a (add host) silently wrote an empty sshc.conf on
    Windows when the user directory had non-ASCII characters in its
    path
    (e.g. C:\Users\사자). The form looked like it submitted —
    modal closed, no error message — but the new host never appeared
    in the list, and the on-disk sshc.conf ended up empty. The
    symptom traces back to persist_sshc_conf in src/app/forms.rs:
    it filtered which in-memory hosts to serialize by comparing
    host.source_file against a freshly recomputed
    crate::storage::sshc_conf_path(). The source_file field was
    set at host-build time from the App::new-cached copy, so on
    Windows the two PathBufs could differ in normalization (NFC vs
    NFD on the home-directory component) even though the underlying
    bytes were equivalent. The filter then matched zero rows and the
    serializer wrote an empty file.

    This was actually present since v0.7-era (when manage mode add
    first landed), but only Windows users with non-ASCII paths could
    trigger it; v0.7.2 (backslash) and v0.7.3 (rename lock) fixes
    cleared the visible failures one step earlier in the pipeline,
    so the data-loss path stayed hidden until v0.8.

    Fix: persist_sshc_conf now uses the cached self.sshc_conf_path
    for both the filter predicate and the write target, so the two
    comparisons sit on the same PathBuf instance regardless of
    platform normalization quirks.

Install sshc 0.8.1

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hang-in/sshc/releases/download/v0.8.1/sshc-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/hang-in/sshc/releases/download/v0.8.1/sshc-installer.ps1 | iex"

Install prebuilt binaries via Homebrew

brew install hang-in/tap/sshc

Download sshc 0.8.1

File Platform Checksum
sshc-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
sshc-x86_64-apple-darwin.tar.xz Intel macOS checksum
sshc-x86_64-pc-windows-msvc.zip x64 Windows checksum
sshc-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
sshc-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum