diff --git a/CHANGELOG.md b/CHANGELOG.md index 3599478..9bc32bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,13 @@ prompt. Write them for users. versions of the app keep syncing alongside this one. - Install and restart waits for an update check that's still running, so it installs the update that check found. +- Feeds: hosts and snippets from an address your team publishes (Hosts → Add from… → Feed URL…), + read with a token. They stay in step with it on their own, every 15 minutes; your renames, keys, + tags and notes stay yours. A feed can list each server's host key, so new servers connect + without asking you to trust them, and a server with another key is refused. Its snippets are + read-only, and when the feed changes one, Run and Paste show you the new commands first. +- Two devices refreshing the same cloud source at once no longer make duplicate hosts or + "(conflict)" copies. ## 0.2.0 diff --git a/Cargo.lock b/Cargo.lock index f2ec5f1..e574ae6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3397,6 +3397,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", + "sha2 0.11.1", "tempfile", "thiserror 2.0.21", "tokio", diff --git a/README.md b/README.md index a3407be..1dc998b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ encrypted sync you can self-host. Docs: [developer docs](docs/README.md) · [sync & crypto spec](docs/sync-crypto-spec.md) · [self-host](https://morphgate.com/docs/self-hosting) · [roadmap](ROADMAP.md) · [releasing](RELEASING.md) · [changelog](CHANGELOG.md) +

+ MorphGate: the Hosts page with hosts in groups; a click on web-1 opens its session, where uptime, git log and ls run +

+ ## Install Download from the [Releases](../../releases) page: diff --git a/ROADMAP.md b/ROADMAP.md index 3c17081..c1a792c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -40,6 +40,9 @@ xterm.js in the webview avoids writing a terminal renderer. - [x] Terminal appearance: built-in themes plus themes imported from Ghostty, Alacritty, iTerm2 and Windows Terminal files (vault items, synced), a theme per host, and this device's font, size and cursor - [x] Jump hosts (ProxyJump chains via direct-tcpip, per-hop host-key pinning) +- [x] Cloud sources: hosts kept in step with Vallic Cloud (its API), AWS, Google Cloud, Azure, Hetzner Cloud and + DigitalOcean (their CLIs), and feeds: hosts and snippets from a JSON document a team publishes at a URL, + read with a token, refreshed every 15 minutes, with the host keys it lists trusted for its hosts - [x] Port forwarding: local / remote / dynamic SOCKS5 tunnels saved in the vault, start/stop from the sidebar - [x] SFTP tabs (`russh-sftp`): local + remote panes, drag and drop (also from the OS), recursive upload/download with progress, cancel and replace, mkdir/rename/chmod/delete diff --git a/apps/desktop/e2e/README.md b/apps/desktop/e2e/README.md index cfad6aa..c9827f1 100644 --- a/apps/desktop/e2e/README.md +++ b/apps/desktop/e2e/README.md @@ -4,10 +4,11 @@ These run the real AppImage, deb and rpm binaries, not a dev build, and drive th | Scenario | What it checks | | --- | --- | -| `appimage.mjs` | Creates a vault; SSHes to a throwaway local sshd and runs a command; imports two hosts and their key from a `~/.ssh/config` (one behind the other as its jump host) and connects through it; imports a PuTTY session with a new key in a `.ppk` file, a Termius-style CSV file with a group and a Royal TS document, and signs in with the PuTTY key; connects Vallic Cloud through a stand-in console (`vallic-console.mjs`: device sign-in, rotating tokens, an environment that is this sshd) and signs in to its host; signs in with a key that only has a certificate from the run's user CA (which sshd trusts), from the vault and from an agent; reaches a host by a command its jump host runs (with a login command instead of the shell), and another by a proxy command on this computer (both the relay in `crates/mg-ssh/tests/relay.py`); selects two hosts, moves them into a group and deletes them; signs in to a host with one key from an `ssh-agent` (its own socket) and forwards it (`ssh-add -l` on the host lists it); splits a tab into three panes (beside and below, one through a jump host), broadcasts a command to all of them and then types into one, drags a border and closes them; opens a `morphgate://connect` link from a second launch (which hands it over and exits) and connects from the filled-in quick connect, and refuses an unknown link; refuses a tampered update twice without touching the AppImage; installs the signed update, restarts, unlocks the same vault as the new version, and finds nothing newer | +| `appimage.mjs` | Creates a vault; SSHes to a throwaway local sshd and runs a command; imports two hosts and their key from a `~/.ssh/config` (one behind the other as its jump host) and connects through it; imports a PuTTY session with a new key in a `.ppk` file, a Termius-style CSV file with a group and a Royal TS document, and signs in with the PuTTY key; connects Vallic Cloud through a stand-in console (`vallic-console.mjs`: device sign-in, rotating tokens, an environment that is this sshd) and signs in to its host; adds AWS and DigitalOcean through stand-in CLIs (`clouds/`); adds a feed (`host-feed.mjs`) whose host connects with the host key it lists, whose wrongly listed host is refused and whose changed snippet asks before it runs; signs in with a key that only has a certificate from the run's user CA (which sshd trusts), from the vault and from an agent; reaches a host by a command its jump host runs (with a login command instead of the shell), and another by a proxy command on this computer (both the relay in `crates/mg-ssh/tests/relay.py`); selects two hosts, moves them into a group and deletes them; signs in to a host with one key from an `ssh-agent` (its own socket) and forwards it (`ssh-add -l` on the host lists it); splits a tab into three panes (beside and below, one through a jump host), broadcasts a command to all of them and then types into one, drags a border and closes them; opens a `morphgate://connect` link from a second launch (which hands it over and exits) and connects from the filled-in quick connect, and refuses an unknown link; refuses a tampered update twice without touching the AppImage; installs the signed update, restarts, unlocks the same vault as the new version, and finds nothing newer | | `package.mjs` (deb, rpm) | The binary knows how it was installed, finds the update, and warns that installing asks for an administrator password (installing needs root, so it stops there) | | `teams.mjs` | Two apps side by side (Alice and Bob) with a local reference sync server: both turn on sync; Alice creates an organization and a vault and invites Bob; Bob joins only after the owner's fingerprint matches, and Alice confirms him only after his matches; a host saved in the shared vault reaches Bob, his edit reaches Alice; Bob publishes his SSH key; Alice revokes him (the vault leaves his device) and rotates the vault key; members may create vaults; the activity log tells the story. Then Alice offboards Bob against the local sshd: removal and key rotation, his key found and removed from `authorized_keys`, and the host's shared key replaced (she still logs in) | | `layout.mjs` | Turns sync on from the offer a new vault gets, fills every page (hosts in a group, a key, a snippet, a tunnel, a team with an invite to a long address), and at 860, 1024, 1280 and 1600 px wide checks that nothing on a page overlaps or runs out of its column. Closing an editor returns to the page it was opened from, and closing a page to the session under it; with no session open, a page takes the tab bar's place | +| `demo.mjs` | Only with `E2E_ONLY=demo`: the README's demo. The AppImage's Hosts page, a click on a host and its recorded session (in a demo shell), drawn by `tools/readme-demo/demo-svg.mjs` as `demo/morphgate.svg` (copy it to `assets/demo/morphgate.svg`) | | `mobile.mjs` | The phone layout in a 390 × 844 window: imports a key and saves a host on the phone screens, connects from the host's sheet, interrupts a command with Ctrl from the extra-key row and repeats one with ↑, keeps the session open behind the host list, browses into a folder over SFTP and downloads a file to Downloads, then locks and unlocks on the phone unlock screen | ```sh @@ -24,10 +25,10 @@ Screenshots and logs go to `target/e2e/run-