Add Windows client packaging and recovery tools
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
This commit is contained in:
@@ -1,403 +1,154 @@
|
||||
# dosh - Dormant Shell
|
||||
|
||||
dosh is a low-latency remote terminal designed around fast attach and fast reconnect.
|
||||
It is mosh-shaped, but not a mosh clone: the server is a resident daemon, terminal
|
||||
sessions stay hot, and repeat connects try encrypted UDP before starting SSH.
|
||||
|
||||
The core target is simple:
|
||||
|
||||
- First secure trust establishment uses SSH.
|
||||
- Existing sessions attach in one encrypted UDP exchange whenever cached credentials allow it.
|
||||
- Reconnect after sleep, roaming, or network change resumes in one encrypted UDP exchange.
|
||||
- Cold SSH fallback stays competitive with plain `ssh` by doing less after auth.
|
||||
|
||||
## Why not just mosh?
|
||||
|
||||
mosh is excellent at roaming and high-latency interactivity. Its startup path still
|
||||
has work dosh can avoid:
|
||||
|
||||
1. SSH connects to the host.
|
||||
2. SSH starts `mosh-server`.
|
||||
3. The client receives connection material over SSH.
|
||||
4. SSH exits and the mosh UDP session begins.
|
||||
|
||||
dosh keeps `dosh-server` running before the client arrives. Named PTY sessions can
|
||||
also be prewarmed, so attaching to `default` does not need to spawn a daemon, create
|
||||
a PTY, or start a shell on the user's critical path.
|
||||
|
||||
This is not an encryption argument against mosh. dosh also encrypts its UDP data
|
||||
channel; the speed difference comes from keeping the server and session hot.
|
||||
|
||||
## Fast Path Order
|
||||
|
||||
The client always tries the cheapest valid path first:
|
||||
|
||||
1. **UDP resume:** existing `ClientId` and session key. No SSH. One encrypted UDP
|
||||
request, one encrypted UDP reply.
|
||||
2. **UDP attach ticket:** cached server-issued attach ticket for the same
|
||||
host/user/session/mode. No SSH. One encrypted UDP request, one encrypted UDP
|
||||
reply.
|
||||
3. **SSH bootstrap:** `ssh -T user@host ~/.local/bin/dosh-auth ...`, then one encrypted UDP
|
||||
attach.
|
||||
4. **New session:** same as attach, but the server must create the PTY/shell unless
|
||||
the session was prewarmed.
|
||||
|
||||
The fastest path is not a custom SSH replacement. SSH remains the first trust root;
|
||||
dosh removes SSH from repeat attaches when the server has already issued valid
|
||||
credentials.
|
||||
|
||||
Attach tickets are implemented because they are the way a fresh client process can
|
||||
skip SSH after a recent successful bootstrap.
|
||||
|
||||
## Connection Speed Contract
|
||||
|
||||
dosh is measured by terminal-ready time: elapsed time from running `dosh host` to the
|
||||
first usable terminal screen.
|
||||
|
||||
- UDP resume: <= one measured UDP RTT + local render time.
|
||||
- UDP attach ticket: <= one measured UDP RTT + local render time.
|
||||
- Warm attach with ControlMaster: <= `ssh host true` over the existing master + one
|
||||
measured UDP RTT.
|
||||
- Cold attach without ControlMaster: <= cold `ssh host` terminal-ready time + one
|
||||
measured UDP RTT.
|
||||
- New session: measured separately because it may need PTY and shell creation.
|
||||
|
||||
Server-side client resume state is kept for a day by default, so a sleeping laptop
|
||||
can resume the same encrypted client association without depending on a still-valid
|
||||
attach ticket. Attach tickets remain the fast path for fresh client processes.
|
||||
|
||||
The client emits timing spans for credential lookup, SSH bootstrap, UDP resume,
|
||||
UDP ticket attach, and terminal-ready time.
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
dosh-server
|
||||
UDP socket on one configurable port
|
||||
session table keyed by name
|
||||
one PTY per named session
|
||||
optional prewarmed sessions, default ["default"]
|
||||
terminal parser/screen state per session
|
||||
client table per session
|
||||
encrypted UDP protocol
|
||||
tiny SSH-invoked dosh-auth helper mode
|
||||
persistent sessions (persist_sessions, currently opt-in): each shell runs in a
|
||||
detached per-session holder process; the server adopts its PTY master fd via
|
||||
SCM_RIGHTS and re-adopts it after a restart, so sessions survive crash/upgrade
|
||||
|
||||
dosh-client
|
||||
terminal raw mode
|
||||
local credential cache
|
||||
UDP resume/attach first
|
||||
SSH bootstrap fallback
|
||||
PTY input/output forwarding
|
||||
reconnect and roaming state machine
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
Default UDP port: `50000`. This is intentionally inside the common forwarded range
|
||||
`50000-52000/udp`.
|
||||
|
||||
Install on each Linux server you want to attach to:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.palav.dev/Palav/dosh/raw/branch/main/install.sh \
|
||||
| DOSH_REPO=https://git.palav.dev/Palav/dosh.git DOSH_PORT=50000 sh -s -- server
|
||||
```
|
||||
|
||||
Install the client on macOS:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.palav.dev/Palav/dosh/raw/branch/main/install.sh \
|
||||
| DOSH_REPO=https://git.palav.dev/Palav/dosh.git DOSH_PORT=50000 sh -s -- client
|
||||
```
|
||||
|
||||
Update an installed client later:
|
||||
|
||||
```bash
|
||||
dosh update
|
||||
dosh update --check
|
||||
dosh --version
|
||||
```
|
||||
|
||||
`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. Gitea's concrete tag download route is detected when
|
||||
`/releases/latest/download/...` is not supported. If that asset is not published
|
||||
yet, it falls back to the source build path. If the release also publishes
|
||||
`<artifact>.sha256`, the installer verifies the archive before installing it.
|
||||
|
||||
Install the client on Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:DOSH_REPO="https://git.palav.dev/Palav/dosh.git"; $env:DOSH_PORT="50000"; irm https://git.palav.dev/Palav/dosh/raw/branch/main/install.ps1 | iex
|
||||
```
|
||||
|
||||
Attach:
|
||||
|
||||
```bash
|
||||
dosh user@server.example.com
|
||||
```
|
||||
|
||||
First-time setup for an existing SSH alias:
|
||||
|
||||
```bash
|
||||
dosh setup homelab
|
||||
dosh selftest homelab
|
||||
dosh homelab
|
||||
```
|
||||
|
||||
`dosh setup <ssh-alias>` imports the OpenSSH alias into
|
||||
`~/.config/dosh/hosts.toml`, pins the Dosh host key over SSH, runs `dosh doctor`,
|
||||
and prints next commands. `dosh selftest <host>` checks local terminal overlay
|
||||
safety, forwarding syntax, and the remote doctor path.
|
||||
|
||||
Plain `dosh user@server.example.com` opens a fresh terminal session. Use named
|
||||
sessions when you want to reattach to the same persistent terminal from multiple
|
||||
clients:
|
||||
|
||||
```bash
|
||||
dosh --session work user@server.example.com
|
||||
dosh --session logs user@server.example.com
|
||||
```
|
||||
|
||||
Press `Ctrl-]` to detach the current client while leaving the server session alive.
|
||||
Typing `exit` in the remote shell closes that Dosh session and returns the client.
|
||||
If UDP packets stop arriving, Dosh keeps the terminal open, sends keepalive pings,
|
||||
and attempts ticket-based reconnect. While the link is silent it shows a single,
|
||||
non-destructive status line on the bottom screen row (`[dosh] last contact Ns ago
|
||||
— reconnecting…`), drawn with save/restore cursor so it never moves the app cursor
|
||||
or corrupts a full-screen TUI, and cleared the instant packets resume. (The old
|
||||
in-band overlay wrote over the active line and could corrupt TUIs; this draws only
|
||||
the last row.) Disable it with `disconnect_status = false` in `client.toml` or
|
||||
`DOSH_DISCONNECT_STATUS=0`.
|
||||
|
||||
If SSH and UDP use different public names, specify the UDP address:
|
||||
|
||||
```bash
|
||||
dosh-client --dosh-host public.example.com --dosh-port 50000 user@host
|
||||
```
|
||||
|
||||
Forwarding can use SSH-shaped flags directly or the forward-only wrapper:
|
||||
|
||||
```bash
|
||||
dosh -N -L 8080:127.0.0.1:80 homelab
|
||||
dosh forward homelab -L 8080:127.0.0.1:80 -D 1080
|
||||
```
|
||||
|
||||
Dosh already lets OpenSSH handle SSH aliases, users, keys, ports, known-hosts,
|
||||
ProxyJump, and other SSH config during bootstrap. It also runs `ssh -G` to infer the
|
||||
UDP host from an SSH alias when `dosh_host` is not configured. To make that explicit
|
||||
in Dosh's host config:
|
||||
|
||||
```bash
|
||||
dosh import-ssh homelab
|
||||
dosh homelab
|
||||
```
|
||||
|
||||
Optional command extensions are just config-side startup shortcuts; Dosh has no
|
||||
compile-time dependency on the tools they run. For example, to make a separately
|
||||
installed server-side `tm` dashboard easy to open:
|
||||
|
||||
```toml
|
||||
# ~/.config/dosh/client.toml
|
||||
[extensions.tm]
|
||||
command = "tm {args}"
|
||||
description = "Open the server-side tmux dashboard"
|
||||
```
|
||||
|
||||
Then `dosh homelab tm` sends `tm`, and `dosh homelab tm dosh` sends `tm 'dosh'`.
|
||||
Remove that table to remove the integration. Hosts can override or opt out:
|
||||
|
||||
```toml
|
||||
# ~/.config/dosh/hosts.toml
|
||||
[homelab.extensions.tm]
|
||||
command = "/opt/tm/bin/tm {args}"
|
||||
|
||||
[other-host.extensions.tm]
|
||||
disabled = true
|
||||
```
|
||||
|
||||
## Develop
|
||||
|
||||
Build:
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
Attach locally, using local bootstrap instead of SSH:
|
||||
|
||||
```bash
|
||||
target/debug/dosh-client --local-auth --no-cache local
|
||||
```
|
||||
|
||||
Benchmark local attach:
|
||||
|
||||
```bash
|
||||
target/debug/dosh-bench --local-auth --server local --iterations 5
|
||||
```
|
||||
|
||||
Benchmark a remote host over SSH bootstrap:
|
||||
|
||||
```bash
|
||||
target/release/dosh-bench --server user@host --ssh-port 22 --iterations 3
|
||||
```
|
||||
|
||||
Benchmark the ControlMaster-backed SSH bootstrap path:
|
||||
|
||||
```bash
|
||||
target/release/dosh-bench --server user@host --controlmaster --iterations 3
|
||||
```
|
||||
|
||||
Run the Docker OpenSSH benchmark gate used by CI. It checks cold SSH bootstrap,
|
||||
ControlMaster-backed SSH bootstrap, native cold auth after one-time `dosh trust`,
|
||||
and cached attach against a containerized `sshd` plus resident `dosh-server`:
|
||||
|
||||
```bash
|
||||
make bench-docker-ssh
|
||||
```
|
||||
|
||||
Run the same Docker comparison with Mosh installed in the benchmark container:
|
||||
|
||||
```bash
|
||||
make bench-docker-mosh
|
||||
```
|
||||
|
||||
That prints `ssh_true_ms`, `dosh_attach_ms`, `dosh_cold_native_ms`,
|
||||
`dosh_cached_attach_ms`, and, for the Mosh target, `mosh_start_true_ms` under the
|
||||
same container, key, DNS, and network path. `dosh_cached_attach_ms` is the real Dosh
|
||||
fast path after the first authentication has issued an attach ticket. See
|
||||
`docs/PUBLIC_READINESS.md` before using the numbers publicly; Dosh's current
|
||||
strongest claim is fast attach/reconnect plus native encrypted forwarding on
|
||||
Dosh-installed servers, not generic SSH compatibility.
|
||||
|
||||
The latest local release evidence is in
|
||||
`docs/RELEASE_EVIDENCE_2026-06-20.md`.
|
||||
|
||||
Generate a publishable Markdown benchmark report:
|
||||
|
||||
```bash
|
||||
make bench-report
|
||||
```
|
||||
|
||||
Set `DOSH_BENCH_SERVER`, `DOSH_BENCH_ITERS`, `DOSH_BENCH_ARGS`, and
|
||||
`DOSH_BENCH_REPORT` to target a real host and choose the output path.
|
||||
|
||||
Run the explicit pre-launch soak and fuzz gates:
|
||||
|
||||
```bash
|
||||
make soak-local # 30-minute sleep/roaming gate by default
|
||||
make fuzz-deep # 5 minutes per fuzz target by default
|
||||
```
|
||||
|
||||
Both are configurable for shorter local shakedowns:
|
||||
|
||||
```bash
|
||||
DOSH_SOAK_SECONDS=30 make soak-local
|
||||
DOSH_FUZZ_SECONDS=60 make fuzz-deep
|
||||
```
|
||||
|
||||
The CI workflow includes an optional remote benchmark job. It runs when
|
||||
`DOSH_BENCH_HOST`, `DOSH_BENCH_USER`, and `DOSH_BENCH_SSH_KEY` repository secrets are
|
||||
configured.
|
||||
|
||||
Install release binaries and the user systemd service:
|
||||
|
||||
```bash
|
||||
make install
|
||||
```
|
||||
|
||||
Build release tarballs for upload to Gitea/GitHub releases:
|
||||
|
||||
```bash
|
||||
make package-release
|
||||
GITEA_TOKEN=... scripts/upload-gitea-release.sh v0.1.0 target/dosh-release/dosh-*
|
||||
```
|
||||
|
||||
## Performance Rules
|
||||
|
||||
The stack is performance-driven, not fixed by taste. Rust is the default because the
|
||||
likely bottlenecks are network RTT, SSH startup/auth, PTY/shell creation, packet
|
||||
size, and terminal rendering. Change language or runtime only if measurements show
|
||||
they are the bottleneck.
|
||||
|
||||
Hot-path rules:
|
||||
|
||||
- Custom UDP protocol with AEAD for v0; no QUIC handshake on attach.
|
||||
- Fixed binary packet headers for terminal traffic; no JSON on the protocol path.
|
||||
- Preallocated buffers; avoid per-packet heap churn.
|
||||
- Single-thread event loop is preferred for the hot path.
|
||||
- No PTY allocation, shell spawn, shell rc files, or MOTD on attach to an existing
|
||||
session.
|
||||
- Initial snapshot should be sent in the first UDP reply when it fits under the
|
||||
packet budget.
|
||||
|
||||
## Goals
|
||||
|
||||
- Connection speed as specified above.
|
||||
- UDP roaming and reconnect.
|
||||
- Encrypted terminal data.
|
||||
- Reuse SSH pubkeys for first trust establishment.
|
||||
- Named persistent sessions.
|
||||
- Multiple clients attached to one session.
|
||||
- Optional view-only clients.
|
||||
- Single server port, not one port per session.
|
||||
- Static server and client binaries where practical.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Replacing SSH as the first public-key trust mechanism.
|
||||
- Multi-user access control.
|
||||
- Windows server/full parity in v0; the client installer supports prebuilt Windows
|
||||
client artifacts when published.
|
||||
- Full mosh compatibility.
|
||||
- Perfect predictive local echo in the first MVP.
|
||||
# Dosh
|
||||
|
||||
Dosh is a fast encrypted remote terminal. It keeps the good parts of SSH and
|
||||
Mosh for day-to-day shell work: quick attach, roaming over UDP, reconnect after
|
||||
network changes, persistent server-side terminals, optional predictive typing,
|
||||
and TCP forwarding.
|
||||
|
||||
Dosh uses SSH-compatible bootstrap for first setup and can use its native
|
||||
cached attach path after trust is established. The goal is simple: type
|
||||
`dosh my-server` and land in a usable terminal about as fast as the network can
|
||||
allow, then keep that terminal alive when Wi-Fi, sleep, or the client process
|
||||
gets messy.
|
||||
|
||||
## Status
|
||||
|
||||
Rust implementation is present in this repository. It contains `dosh-server`,
|
||||
`dosh-client`, `dosh-auth`, `dosh-bench`, shared auth/crypto/protocol modules, a
|
||||
resident PTY server, encrypted UDP bootstrap attach, UDP resume, sealed UDP attach
|
||||
tickets, client ACKs, server retransmit bookkeeping, sliding replay protection,
|
||||
server-side `vt100` screen snapshots/diffs, a hardened user systemd unit, an install
|
||||
script, Docker SSH benchmark gates, CI, and protocol/integration tests.
|
||||
- Clients: macOS, Linux, and Windows.
|
||||
- Server: Unix-like hosts with PTY support.
|
||||
- Windows: client-only; install with PowerShell and connect to a Unix Dosh
|
||||
server.
|
||||
- Security: encrypted transport, pinned Dosh host keys, replay-resistant attach
|
||||
tickets, and SSH fallback for bootstrap/recovery.
|
||||
- Not included: X11 forwarding, SFTP/SCP replacement, or a Windows server.
|
||||
|
||||
### Native v1
|
||||
## Install
|
||||
|
||||
Beyond the SSH-bootstrap core, native v1 (`docs/NATIVE_V1_SPEC.md`) is substantially
|
||||
implemented and aims to replace the day-to-day `ssh host` workflow on Dosh-installed
|
||||
servers:
|
||||
Unix/macOS server or client:
|
||||
|
||||
- **Native UDP auth** with X25519 key exchange; transcript-bound Ed25519, ECDSA
|
||||
P-256, and RSA-SHA2 user auth via ssh-agent or OpenSSH identity files;
|
||||
ChaCha20-Poly1305 transport; and `authorized_keys` policy enforcement (`from=`,
|
||||
`no-port-forwarding`, `permitopen=`; unsupported options fail closed).
|
||||
- **Dosh host-key trust**: pinned `known_hosts`, `dosh trust [--remove|--replace]`,
|
||||
TOFU only when explicitly enabled, and hard-fail on host-key mismatch.
|
||||
- **TCP forwarding**: local `-L`, remote `-R` (loopback bind by default), dynamic
|
||||
SOCKS5 `-D`, forward-only `-N`, and background `-f`, with per-stream flow control so
|
||||
bulk transfers do not lag the terminal.
|
||||
- **Agent forwarding** (opt-in, security-gated): `-A` / `forward_agent` exports your
|
||||
local `SSH_AUTH_SOCK` to a per-session proxy socket on the server (private dir,
|
||||
mode 0600) and tunnels each connection back to your agent over a Dosh stream. Only
|
||||
active on explicit client opt-in **and** server `allow_agent_forwarding = true`; it
|
||||
applies to freshly spawned shells (not an already-running attached/prewarmed
|
||||
session, the same constraint ssh/mosh have).
|
||||
- **Diagnostics**: `dosh doctor host` for config/auth/UDP/forwarding-policy checks.
|
||||
```sh
|
||||
curl -fsSL https://git.palav.dev/Palav/dosh/raw/branch/main/install.sh | sh -s -- both --repo https://git.palav.dev/Palav/dosh.git --port 50000
|
||||
```
|
||||
|
||||
Native auth is **opt-in alongside SSH fallback** (`auth_preference = "native,ssh"`):
|
||||
the native authenticated path is tried first and falls back to SSH bootstrap
|
||||
explicitly when native auth is disabled, unavailable, or rejected. It never silently
|
||||
degrades to an unauthenticated mode.
|
||||
Unix/macOS client only:
|
||||
|
||||
Native v1 is **not externally audited yet**. Local 30-minute sleep/roaming soak,
|
||||
fuzz-smoke, and Docker SSH/Mosh benchmark evidence is captured in
|
||||
`docs/RELEASE_EVIDENCE_2026-06-20.md`. See `docs/THREAT_MODEL.md` for the
|
||||
published threat model and accepted residual risks, `docs/PROTOCOL_VERSIONING.md`
|
||||
for the v1 versioning policy, `docs/AUDIT_PACKET.md` for the external security
|
||||
review handoff, and the "Native v1 verification checklist status" table in
|
||||
`docs/PUBLIC_READINESS.md` for the item-by-item state. Dosh does not claim generic
|
||||
SSH compatibility; its defensible claim is fast encrypted native attach/reconnect
|
||||
and forwarding on Dosh-installed servers with SSH bootstrap fallback.
|
||||
```sh
|
||||
curl -fsSL https://git.palav.dev/Palav/dosh/raw/branch/main/install.sh | sh -s -- client --repo https://git.palav.dev/Palav/dosh.git
|
||||
```
|
||||
|
||||
Windows client:
|
||||
|
||||
```powershell
|
||||
irm https://git.palav.dev/Palav/dosh/raw/branch/main/install.ps1 | iex
|
||||
```
|
||||
|
||||
The installers prefer release binaries and fall back to a source build when
|
||||
needed.
|
||||
|
||||
## Use
|
||||
|
||||
Set up a host from your SSH config:
|
||||
|
||||
```sh
|
||||
dosh setup my-server
|
||||
```
|
||||
|
||||
Connect:
|
||||
|
||||
```sh
|
||||
dosh my-server
|
||||
```
|
||||
|
||||
Run a command on connect:
|
||||
|
||||
```sh
|
||||
dosh my-server tm
|
||||
```
|
||||
|
||||
Check the install and network path:
|
||||
|
||||
```sh
|
||||
dosh doctor my-server
|
||||
dosh selftest my-server
|
||||
```
|
||||
|
||||
Repair local cached attach state without changing host trust:
|
||||
|
||||
```sh
|
||||
dosh recover my-server
|
||||
```
|
||||
|
||||
Update:
|
||||
|
||||
```sh
|
||||
dosh update
|
||||
dosh update --check
|
||||
```
|
||||
|
||||
## Forwarding
|
||||
|
||||
Local forward:
|
||||
|
||||
```sh
|
||||
dosh forward my-server -L 8080:127.0.0.1:80
|
||||
```
|
||||
|
||||
Dynamic SOCKS forward:
|
||||
|
||||
```sh
|
||||
dosh forward my-server -D 1080
|
||||
```
|
||||
|
||||
Remote forward:
|
||||
|
||||
```sh
|
||||
dosh forward my-server -R 2222:127.0.0.1:22
|
||||
```
|
||||
|
||||
Agent forwarding is opt-in with `-A` and must also be allowed by the server.
|
||||
|
||||
## Config
|
||||
|
||||
Client config lives at:
|
||||
|
||||
```text
|
||||
~/.config/dosh/client.toml
|
||||
~/.config/dosh/hosts.toml
|
||||
```
|
||||
|
||||
Server config lives at:
|
||||
|
||||
```text
|
||||
~/.config/dosh/server.toml
|
||||
```
|
||||
|
||||
Useful client defaults:
|
||||
|
||||
```toml
|
||||
default_session = "new"
|
||||
auth_preference = "native,ssh"
|
||||
predict = true
|
||||
predict_mode = "experimental"
|
||||
cache_attach_tickets = true
|
||||
disconnect_status = true
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Build and test:
|
||||
|
||||
```sh
|
||||
cargo test
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Run the terminal/TUI regression harness:
|
||||
|
||||
```sh
|
||||
make tui-harness
|
||||
```
|
||||
|
||||
Package release artifacts:
|
||||
|
||||
```sh
|
||||
sh scripts/package-release.sh
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user