Public Access
Package the Linux desktop head as an RPM, and publish a nightly one
The third desktop platform shipped as nothing: a checkout somebody built.
VelopackUpdateChannel already named the two platforms it serves rather than
negating the one it does not, so that a Linux package would be an addition
there rather than something that had silently half-happened. This is it, and
it is deliberately not a Velopack channel.
There is no updater in the package at all. On this platform dnf is already an
updater the user trusts, audits and controls, and a second one answering to
this project instead is the capability ADR 0013 rule 4 spends a page keeping
unsettable. The cost is real and is written down rather than glossed: nothing
tells a Linux user that a newer build exists, because there is no repository
to tell them.
Rule 3 is untouched. The build job packages the release-shaped RPM on every
main build and every tag and uploads nothing, exactly as it does for Windows
and macOS; the desktop job packages the nightly-shaped one and puts it on the
rolling nightly-desktop release beside the Windows feed. Two RPMs with
different names owning different paths cannot replace one another, so rule 9's
separations come almost free here — the profile split still belongs to the
build, through DodoChannel.
vpk verifies that what it packs actually calls VelopackApp.Build().Run(), and
nothing here does that for us, so scripts/ci-linux-rpm.sh makes its own
assertions: it asks the finished package what it requires, what it provides,
what it holds and what modes those carry. Two of those checks exist because
the first realistic payload tripped them.
- .NET's libmscordbi.so links libmscordaccore.so. With bundled provides
excluded — which they must be, or this package would advertise libcoreclr
to the whole system — that produced a dependency no host on earth can
satisfy. The requires exclusion is computed from the payload rather than
listed, because a list of Microsoft's internal file names goes stale in
the direction that yields an uninstallable package.
- cp -a inherits the publish directory's modes, and off the wrong filesystem
that meant a world-writable /opt/DodoSSH: any local account able to
replace the SSH client every other account runs. For an application
holding a team's credentials in plaintext that is the trust model walked
around rather than attacked. Every mode is now set absolutely.
Verified as far as a runner can: both packages install side by side on Fedora
44, resolve webkit2gtk4.1 2.52.5 — the backend MainWindow records as measured
— keep separate launchers, icons and profiles, and dnf remove takes one tree
and leaves the other. 0.0.1~alpha.0.143 sorts below both 0.0.1 and the next
height. What no runner can answer is Phase 19, which is new and unwalked.
RHEL 9 is deliberately not claimed. It ships WebKitGTK's 4.0 line under
libwebkit2gtk-4.0.so.37, and an or-branch for it was written and then removed:
nobody has run the head there, and dnf refusing with "nothing provides
libwebkit2gtk-4.1.so.0" is a better answer than an install that draws a blank
terminal. Same sentence, and the same reason, as the missing Intel Mac.
x86_64 only, for that reason and not a different one.
This commit is contained in:
1 parent
1970e20e56
commit
1bf79c7c26
12 files changed
+1015
-5
No files matched your search
@@ -316,6 +316,66 @@ preferences screen, which is the screen somebody reads when asked which nightly
|
||||
across the floor holds: heights rise within a floored version, and the first real tag moves past all of
|
||||
them. The release script gets no floor and must not have one; there, `0.0.0` should be refused.
|
||||
|
||||
### 11. Linux is a third desktop platform, and it is a package rather than an updater
|
||||
|
||||
The Linux head existed and shipped as nothing: a checkout somebody built. `VelopackUpdateChannel` already
|
||||
named the two platforms it serves rather than negating the one it does not, on the reasoning that "a future
|
||||
AppImage should be an addition here rather than a thing that silently already half-happened". This is that
|
||||
addition, and it is not an AppImage.
|
||||
|
||||
**The package is an RPM and there is no updater in it.** That is the decision, and everything else follows
|
||||
from it. Velopack does have a Linux path, so the shape of decisions 1 through 9 could have been repeated a
|
||||
third time — one more channel, one more index, one more client polling the forge. It should not be, because
|
||||
this platform already has the mechanism that whole apparatus is a substitute for. `dnf` is an updater the
|
||||
user already trusts, already audits and already controls, and it updates the runtime's own dependencies
|
||||
besides. Adding a second one that answers to this project instead would be building the capability decision
|
||||
4 spends a page keeping unsettable, on the one platform where nobody had to.
|
||||
|
||||
The honest cost, recorded because it is the part that will be argued with: **discovery is worse here than on
|
||||
either other desktop.** Windows and macOS clients learn about a new build within six hours without anybody
|
||||
deciding to look. A Linux user learns when their repository does, and this project publishes no repository,
|
||||
so today they learn when they next go and look. That is a real regression against the sharpest thing this
|
||||
ADR bought, accepted because the alternative regression — a second update path with a different authority
|
||||
behind it — is the one the trust model cannot afford. A package repository is the fix and is not built.
|
||||
|
||||
**CI publishes it, and only to the nightly channel.** Rule 3 is untouched and applies with its full force:
|
||||
the token that writes a release is the capability to ship somebody a build, and it stays off runners. What
|
||||
changes is nothing, because rule 9 already established a channel CI may publish to and this is a second
|
||||
artefact on it. The release-shaped package is built on every `main` build and on every tag and uploaded
|
||||
nowhere, exactly as the Windows and macOS packages are, which proves the `linux-x64` restore graph and the
|
||||
packaging without putting an installer anybody has run in a place that invites passing it on.
|
||||
|
||||
Note what CI's check is and is not. `vpk` verifies that the main executable actually calls
|
||||
`VelopackApp.Build().Run()` — a real assertion about the application, made for free by the packager. There
|
||||
is no equivalent here, so `scripts/ci-linux-rpm.sh` makes its own: it asks the finished package what it
|
||||
requires, what it provides, what files it holds and what modes they carry, and fails on each way a package
|
||||
can build cleanly and then be uninstallable or unsafe. Two of those checks exist because the first realistic
|
||||
payload tripped them — .NET's `libmscordbi.so` links `libmscordaccore.so`, which with bundled provides
|
||||
excluded produces a dependency no host can satisfy; and `cp -a` off the wrong filesystem produced a
|
||||
world-writable `/opt/DodoSSH`, which is a local privilege escalation against an application holding
|
||||
plaintext credentials.
|
||||
|
||||
**The separations of rule 9 survive translation, and cost less here.** Two RPMs with different names owning
|
||||
different paths cannot replace one another and `dnf` says so, where on Windows the same property had to be
|
||||
built out of pack ids. The profile split is unchanged and still belongs to the build: `DodoChannel=nightly`
|
||||
is what `ClientPaths` reads, and the packaging must not answer that question a second time.
|
||||
|
||||
**Two things this platform does not have, and they are stated rather than left to be discovered.** There is
|
||||
no hardware-backed device key store — `DesktopDeviceKeyStores` returns `UnavailableDeviceKeyStore`, so the
|
||||
passphrase is asked for at every unlock, where Windows has a TPM and a Mac has the Secure Enclave. And there
|
||||
is no signature on the package, which is not decision 8's "unsigned for now": nothing on this platform
|
||||
refuses or warns about an unsigned RPM, so there is no dialog to buy a certificate to remove. What stands in
|
||||
for one is the same thing that stands in for it on the forge — TLS, and a project that publishes from one
|
||||
address. An RPM repository would change that, because repository metadata is signed and a repository is a
|
||||
thing users add once and then trust; that is the point at which a key becomes necessary rather than optional,
|
||||
and it is the reason building one is a decision and not a chore.
|
||||
|
||||
**x86_64 only, for decision 10's reason and not a different one.** aarch64 is one argument to the publish
|
||||
and one to the script. What is missing is somebody to run Phase 19 on such a machine, and this project does
|
||||
not ship desktop builds nobody has run. The same sentence rules out claiming RHEL 9, which ships WebKitGTK's
|
||||
4.0 API line under a soname the package deliberately does not accept: `dnf` refusing with *nothing provides
|
||||
libwebkit2gtk-4.1.so.0* is a better answer than an install that draws a blank terminal.
|
||||
|
||||
## Consequences
|
||||
|
||||
**The desktop gets what ADR 0011 had to refuse the phone.** Discovery is still manual — somebody has to be
|
||||
@@ -369,6 +429,10 @@ ADR 0011 changes.
|
||||
genuinely the nicest onboarding available and will be proposed again.
|
||||
- **Conditioning updates on `MinClientVersion`.** See decision 5. It would create the attack rather than
|
||||
inherit it.
|
||||
- **A Velopack channel for Linux.** The mechanism exists — Velopack builds an AppImage — and repeating
|
||||
decisions 1 through 9 a third time would have been the consistent-looking move. Declined in decision 11
|
||||
because this is the one desktop platform that already has an updater the user controls, and a second one
|
||||
answering to this project is the capability decision 4 exists to make unsettable.
|
||||
- **Azure Trusted Signing**, for now, on custody grounds rather than on mechanics. See decision 8.
|
||||
- **A hand-edited version property.** Considered seriously against MinVer, and it loses on the thing that
|
||||
matters here: the tag is already the version of record for the container image, and two places to write
|
||||
|
||||
@@ -2841,3 +2841,112 @@ Register a device, sync something, then remove the application.
|
||||
**Failure means:** an empty directory is the pack id having been changed to `DodoSSH`, which puts Velopack's
|
||||
install root on top of `ClientPaths.DataDirectory` and makes an uninstall delete a user's un-synced work.
|
||||
This is the single reason the bundle is named `DodoSSH.Desktop.app`.
|
||||
|
||||
## Phase 19 — Installing the Linux client, and the things dnf does not check
|
||||
|
||||
The Linux counterpart of phases 16 and 18, and the shortest of the three because the platform takes two of
|
||||
their questions off the table. There is no updater here, so nothing in this phase asks whether an update
|
||||
arrives; and there is no signing, because nothing on this platform refuses an unsigned package — a distinct
|
||||
statement from "unsigned is fine", and `build/linux/dodossh.spec` says what stands in for a signature: the
|
||||
package comes from the project's own forge over TLS and from nowhere else.
|
||||
|
||||
What that leaves is the set nobody else can answer. **CI builds this package and installs nothing**, and
|
||||
every check in `scripts/ci-linux-rpm.sh` is a check on what the package *says* — its dependencies, its file
|
||||
list, its modes. None of them start the application, and the failures below are all of the shape "installs
|
||||
perfectly and then does not work".
|
||||
|
||||
Needs a Fedora 44 machine or newer with a graphical session. Build the package first:
|
||||
|
||||
```bash
|
||||
dotnet publish src/DodoSSH.Client.App/DodoSSH.Client.App.csproj \
|
||||
--configuration Release --runtime linux-x64 --self-contained true \
|
||||
-p:RestoreLockedMode=false --output /tmp/linux-x64
|
||||
scripts/ci-linux-rpm.sh --payload /tmp/linux-x64 --version 1.2.3 --out /tmp/rpm
|
||||
sudo dnf install /tmp/rpm/dodossh-1.2.3-1.x86_64.rpm
|
||||
```
|
||||
|
||||
### 19.1 It launches from the menu, and not only from a shell · **do this one first**
|
||||
|
||||
Log out and back in, or wait for the desktop to notice the new `.desktop` file, then start DodoSSH from the
|
||||
applications menu rather than from a terminal.
|
||||
|
||||
**Pass:** the window opens, and the running application's icon in the dock or task list is the same entry
|
||||
that launched it — one icon, not two.
|
||||
|
||||
**Failure means:** a second unlabelled icon appearing beside the launcher is `StartupWMClass` not matching
|
||||
what Avalonia sets `WM_CLASS` to. The spec sets it to `DodoSSH`, which is the assembly name and is the same
|
||||
for both channels; if Avalonia's answer has changed, `xprop WM_CLASS` on the window is what to compare
|
||||
against. Nothing at all in the menu is the desktop database not having been rebuilt — `%post` runs
|
||||
`update-desktop-database` and swallows its failure on purpose, so try it by hand before concluding anything.
|
||||
|
||||
### 19.2 The terminal draws, which is the WebKitGTK question
|
||||
|
||||
Connect to a host and use the shell: type, run something that scrolls, resize the window.
|
||||
|
||||
**Pass:** the terminal attaches within a second or two and behaves as it does on Windows.
|
||||
|
||||
**Failure means:** **a blank pane where everything else works is the known one**, and it is the reason
|
||||
`MainWindow.OnTerminalEnvironmentRequested` exists — the GTK WebView adapter in its default mode can only
|
||||
open a window of its own, so a control asked to host it in place has nothing to draw. If that method has
|
||||
stopped setting `ExperimentalOffscreen`, or the vendor has withdrawn the flag, this is what it looks like.
|
||||
A pane that reports a renderer timeout instead is the loopback WebSocket rather than the backend.
|
||||
|
||||
### 19.3 The icon is the mark, at the sizes the desktop actually asks for
|
||||
|
||||
Look at it in the applications menu, in the dock, and in the window switcher.
|
||||
|
||||
**Pass:** the accent tile and the `>_` mark, filling its square, legible at the small end.
|
||||
|
||||
**Failure means:** a generic icon is `Icon=` in the desktop entry not matching an installed file name — both
|
||||
are the package's binary name, so they can only disagree if one was edited. A blurry small icon is the
|
||||
48-pixel file missing and the 256 being scaled down; `dodossh-icon.ps1` draws four sizes and the spec
|
||||
installs four.
|
||||
|
||||
### 19.4 The passphrase is asked for, and no device key is offered
|
||||
|
||||
Open the security settings page.
|
||||
|
||||
**Pass:** registering a device key is **not** offered, and unlocking asks for the passphrase every time.
|
||||
|
||||
**Failure means:** being offered it is `DesktopDeviceKeyStores.ForThisMachine` having returned something
|
||||
other than `UnavailableDeviceKeyStore` on a platform with nothing to back it. Accepting the offer would put
|
||||
a wrap on the server that nothing can ever open.
|
||||
|
||||
### 19.5 The nightly installs beside the release rather than over it
|
||||
|
||||
With the release package installed, install the nightly one too.
|
||||
|
||||
**Pass:** `dnf` installs it as a second package rather than as an upgrade. Both entries appear in the menu,
|
||||
named *DodoSSH* and *DodoSSH Nightly*. Both run at once. Signing into the nightly asks for the passphrase
|
||||
again and appears on the account as a separate device.
|
||||
|
||||
**Failure means:** `dnf` offering to *upgrade* rather than install is the two packages sharing a name. A
|
||||
nightly that opens the release's vault already unlocked is the profile separation gone — that comes from
|
||||
`DodoChannel=nightly` reaching `ClientPaths`, not from the packaging, so check
|
||||
`~/.local/share/dodossh.nightly` exists rather than looking at the spec.
|
||||
|
||||
### 19.6 Nothing in the installed tree is writable by anyone but root
|
||||
|
||||
```bash
|
||||
find /opt/DodoSSH ! -user root -o -perm -o+w
|
||||
```
|
||||
|
||||
**Pass:** no output.
|
||||
|
||||
**Failure means:** any line here is a local account being able to replace the SSH client every other account
|
||||
on the machine runs, which for an application holding a team's credentials in plaintext is the whole trust
|
||||
model walked around rather than attacked. `ci-linux-rpm.sh` refuses to produce such a package and the spec
|
||||
sets every mode absolutely, so a hit here means one of those two was changed — it cannot arrive by accident
|
||||
from a publish directory.
|
||||
|
||||
### 19.7 Removing it leaves the vault, and leaves the other channel alone
|
||||
|
||||
Register a device, sync something, then `sudo dnf remove dodossh`.
|
||||
|
||||
**Pass:** `~/.local/share/dodossh` still holds the cache and the outbox. `/opt/DodoSSH` is gone entirely.
|
||||
`/opt/DodoSSH.Nightly` and its menu entry are untouched.
|
||||
|
||||
**Failure means:** a missing cache is `ClientPaths` having been pointed somewhere under `/opt`, which would
|
||||
make an uninstall delete a user's un-synced work — the Linux spelling of the mistake ADR 0013 decision 2
|
||||
exists to prevent. Files left behind under `/opt/DodoSSH` are files the package did not own, which means
|
||||
something wrote into the install directory at runtime and would survive a reinstall.
|
||||
Reference in new issue
Block a user