Public Access
A server Spelunk manages runs Spelunk's agent, which keeps a connection open
to Spelunk. A host can now be marked "Connect through Spelunk": a connect
asks Spelunk for a ticket, opens a WebSocket, and the agent connects to the
SSH server on the machine's own loopback, so the server needs no open SSH
port. Shells and file transfers both go this way.
Client, in Client.Session:
- SpelunkTunnel implements Spelunk's contract: POST
{api}/api/servers/{id}/tunnel-tickets with the API key as a bearer token
and {"port":N}, then a WebSocket to the API's own origin at the path
Spelunk returns, offering spelunk.tunnel.v1 and ticket.<ticket>. After the
upgrade the relay's RelayBridge carries the bytes unchanged. Every refusal
is a sentence: 401 key, 403 role (with Spelunk's detail), 404 server, 503
agent not connected, 401/503 on the upgrade, and close codes 4502 (agent
could not reach the port, or is too old: "Unknown stream kind") and 4503
(agent disconnected). Only a path is taken from the ticket response, so it
cannot send the ticket to another host.
- IConnectionRoute is what RelayRoute and SpelunkTunnelRoute have in common,
so the connect path has one shape for both.
Host model: HostSecret.Spelunk is a SpelunkRoute (normalised API base,
server id, and the id of the credential holding the API key), at payload
schema version 8. It is written last and only when present, so every other
host keeps its bytes and its version; an older build reads a Spelunk host
as read-only rather than dropping the route. A half-written route makes the
item unreadable instead of dialled. It merges as one value and is exclusive
with RelayEnabled. The API address must be https, or http to loopback.
The API key is an ordinary keychain credential whose password is the key,
referenced by id, so it is encrypted, synced, merged and rotated like every
other secret, one credential serves every server in the organisation, and
the used-by list and deletion warning count it.
Routing: OpenRouteAsync is the one decision for both transports. A Spelunk
host is never dialled directly, never probed after a failure, and a route
that hung up with a reason replaces SSH.NET's transport error.
UI: a "Connect through Spelunk" card beside the relay card in the desktop
drawer and the phone's editor; ticking one unticks the other.
dodossh://spelunk/connect?api=&server=&name=&host= links:
- SpelunkLink parses them strictly and never reads a credential from one.
- Program reads a link from the arguments; a launch that finds DodoSSH
running passes it down the doorbell as "dodossh.open-link <link>", which
the running instance parses again, bounded, and otherwise ignores whole.
The plain come-forward ring is unchanged.
- A saved host routed to that server on that Spelunk is connected to;
otherwise the editor opens on a new, unsaved Spelunk host, with the key
pre-chosen only when another host already sends it to that Spelunk. A link
that arrives while locked waits for the unlock.
- Windows registers the scheme under HKCU from Velopack's install and update
hooks and removes it on uninstall only if it still points at this
executable; the macOS plist template declares CFBundleURLTypes and the app
takes the URL from Avalonia's activation event; the Linux desktop entry
gets %u and MimeType=x-scheme-handler/dodossh.
145 lines
6.3 KiB
XML
145 lines
6.3 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!--
|
|
The Info.plist for the macOS bundle, with the version left as a placeholder.
|
|
|
|
◆ A TEMPLATE RATHER THAN A FILE, BECAUSE vpk COPIES A CUSTOM PLIST VERBATIM.
|
|
|
|
Measured, not assumed: `vpk [osx] bundle --plist` performs no substitution of any kind. It logs
|
|
"Bundle using provided Info.plist" and copies the bytes. That is also why it refuses --plist and
|
|
--bundleId together — with a plist supplied, every key is the caller's problem.
|
|
|
|
So a committed Info.plist would carry whatever version it was written with into every release
|
|
afterwards, and the failure is quiet in the worst way: Velopack's own release index would carry the
|
|
right version, the updater would compare correctly and update correctly, and only the About window,
|
|
Finder's Get Info panel and any crash report would claim the build was something else. Nobody
|
|
reads those on the day of a release. scripts/release-macos.sh substitutes @VERSION@ into a copy
|
|
and passes that.
|
|
|
|
◆ WHY A CUSTOM PLIST AT ALL, WHEN vpk WRITES A PERFECTLY GOOD ONE.
|
|
|
|
Three keys it does not write, each of which is a real defect without it:
|
|
|
|
CFBundleDisplayName The bundle on disk is DodoSSH.Desktop.app, because the pack id must not
|
|
be DodoSSH — see scripts/release-macos.sh for the directory collision
|
|
that rule prevents. On Windows the pack id is invisible; on macOS it
|
|
names the thing in /Applications and in the Dock. This key is what puts
|
|
"DodoSSH" back in front of a person while the bundle keeps the id.
|
|
|
|
LSMinimumSystemVersion Without it macOS will happily launch this on a release the runtime was
|
|
never built for, and the user gets a dyld crash rather than a sentence.
|
|
|
|
NSHumanReadableCopyright Shown in the About panel. Absent, the panel shows a blank line.
|
|
-->
|
|
<plist version="1.0">
|
|
<dict>
|
|
<!--
|
|
CFBundleName is what the menu bar shows and is capped at 15 characters by convention;
|
|
CFBundleDisplayName is what Finder and the Dock show. Both say DodoSSH, and the bundle
|
|
directory does not. See the note above.
|
|
-->
|
|
<key>CFBundleName</key>
|
|
<string>DodoSSH</string>
|
|
|
|
<key>CFBundleDisplayName</key>
|
|
<string>DodoSSH</string>
|
|
|
|
<!--
|
|
Reverse-DNS under the domain this project actually controls. It is the identity Gatekeeper,
|
|
the notary service and the keychain all key off, so it is as irreversible as the Windows pack
|
|
id: changing it makes an update a different application, and it orphans anything the previous
|
|
identifier stored — including the Secure Enclave key MacDeviceKeyStore holds, which is scoped
|
|
to this identifier and cannot be migrated because its whole point is that it never leaves the
|
|
enclave.
|
|
-->
|
|
<key>CFBundleIdentifier</key>
|
|
<string>dev.dodotech.dodossh</string>
|
|
|
|
<!--
|
|
The apphost the publish produced, named for the product by <AssemblyName> in the csproj rather
|
|
than for the project. Must match --mainExe or the bundle launches nothing.
|
|
-->
|
|
<key>CFBundleExecutable</key>
|
|
<string>DodoSSH</string>
|
|
|
|
<!--
|
|
Both version keys take the numeric core only — 1.2.3 and never 1.2.3-rc.1 — because Apple
|
|
defines them as one to three dot-separated integers and notarization rejects what it cannot
|
|
parse. The full version, prerelease suffix and all, is in Velopack's release index, and that
|
|
is the one the updater compares. These two are for Finder and for Gatekeeper.
|
|
|
|
They are the same value rather than the usual marketing/build split, because there is no build
|
|
counter here that a release does not already bump.
|
|
-->
|
|
<key>CFBundleShortVersionString</key>
|
|
<string>@VERSION@</string>
|
|
|
|
<key>CFBundleVersion</key>
|
|
<string>@VERSION@</string>
|
|
|
|
<!--
|
|
The file name inside Contents/Resources, which is where --icon puts it. With a custom plist
|
|
nothing rewrites this key, so a rename of the asset that forgets this line produces a bundle
|
|
showing the generic application icon and no error anywhere.
|
|
-->
|
|
<key>CFBundleIconFile</key>
|
|
<string>dodossh.icns</string>
|
|
|
|
<key>CFBundlePackageType</key>
|
|
<string>APPL</string>
|
|
|
|
<!--
|
|
12.0, and it is read off the binaries rather than off a support matrix. The apphost and
|
|
libcoreclr.dylib in a net10.0 osx-arm64 publish both carry LC_BUILD_VERSION with minos 12.0.0,
|
|
so 12.0 is the oldest release these bytes are built to load on.
|
|
|
|
Microsoft's *support* statement for .NET 10 is higher than this, and that difference is
|
|
deliberate rather than overlooked: this key decides whether macOS refuses to launch the app at
|
|
all, and refusing on a release where it would in fact have run is the worse of the two errors.
|
|
A user on an unsupported-but-working macOS gets the application; the support matrix governs
|
|
what gets fixed if it misbehaves there, which is a different question.
|
|
-->
|
|
<key>LSMinimumSystemVersion</key>
|
|
<string>12.0</string>
|
|
|
|
<!--
|
|
Without this the window is drawn at 1x and scaled up, which on a Retina display turns the
|
|
terminal — the one surface in this application that is nothing but small text — into a blur.
|
|
-->
|
|
<key>NSHighResolutionCapable</key>
|
|
<true/>
|
|
|
|
<key>NSPrincipalClass</key>
|
|
<string>NSApplication</string>
|
|
|
|
<!--
|
|
False, and stated rather than left out. An agent application has no Dock icon and no menu bar;
|
|
this one is an ordinary windowed application and the default is already false, but the key
|
|
being absent is indistinguishable from somebody having removed it.
|
|
-->
|
|
<key>LSUIElement</key>
|
|
<false/>
|
|
|
|
<!--
|
|
The dodossh:// scheme, which is how Spelunk's "Open in DodoSSH" reaches the application. macOS
|
|
does not pass the link as an argument: Launch Services sends an "open URL" event to the running
|
|
application, or to the one it has just started, and DodoSshApp.ListenForOpenedLinks takes it from
|
|
Avalonia's activation event. Registered when the bundle is first seen by Launch Services, which
|
|
for a Velopack install is on first launch from /Applications.
|
|
-->
|
|
<key>CFBundleURLTypes</key>
|
|
<array>
|
|
<dict>
|
|
<key>CFBundleURLName</key>
|
|
<string>dev.dodotech.dodossh.link</string>
|
|
<key>CFBundleURLSchemes</key>
|
|
<array>
|
|
<string>dodossh</string>
|
|
</array>
|
|
</dict>
|
|
</array>
|
|
|
|
<key>NSHumanReadableCopyright</key>
|
|
<string>© DodoTech. MIT licensed.</string>
|
|
</dict>
|
|
</plist>
|