elsewindow 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- elsewindow-0.1.0/.version +1 -0
- elsewindow-0.1.0/CHANGELOG.md +18 -0
- elsewindow-0.1.0/LICENSE +21 -0
- elsewindow-0.1.0/MANIFEST.in +17 -0
- elsewindow-0.1.0/PKG-INFO +176 -0
- elsewindow-0.1.0/README.md +127 -0
- elsewindow-0.1.0/elsewindow/__init__.py +33 -0
- elsewindow-0.1.0/elsewindow/__main__.py +8 -0
- elsewindow-0.1.0/elsewindow/cli.py +204 -0
- elsewindow-0.1.0/elsewindow/config.py +152 -0
- elsewindow-0.1.0/elsewindow/live-cli.yml +94 -0
- elsewindow-0.1.0/elsewindow/live_config.py +429 -0
- elsewindow-0.1.0/elsewindow/profiles.yml +41 -0
- elsewindow-0.1.0/elsewindow/py.typed +1 -0
- elsewindow-0.1.0/elsewindow/session.py +788 -0
- elsewindow-0.1.0/elsewindow.egg-info/PKG-INFO +176 -0
- elsewindow-0.1.0/elsewindow.egg-info/SOURCES.txt +21 -0
- elsewindow-0.1.0/elsewindow.egg-info/dependency_links.txt +1 -0
- elsewindow-0.1.0/elsewindow.egg-info/entry_points.txt +2 -0
- elsewindow-0.1.0/elsewindow.egg-info/requires.txt +25 -0
- elsewindow-0.1.0/elsewindow.egg-info/top_level.txt +1 -0
- elsewindow-0.1.0/pyproject.toml +109 -0
- elsewindow-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.0
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- Copyright (c) 2026 kogeler. SPDX-License-Identifier: MIT. -->
|
|
2
|
+
|
|
3
|
+
# Changelog
|
|
4
|
+
|
|
5
|
+
All notable changes to Elsewindow are documented here.
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-08-30
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Established Elsewindow as an independent Linux project with one public
|
|
12
|
+
distribution, import package, command, and repository identity.
|
|
13
|
+
- Preserved the one-authentication OpenSSH lifecycle, mux-only Xpra channels,
|
|
14
|
+
heartbeat-supervised remote process group, and selective cleanup behavior.
|
|
15
|
+
- Shipped reviewed Xpra command and network profiles as package data and kept
|
|
16
|
+
release-backed package installation and container acceptance tooling.
|
|
17
|
+
- Added CPython 3.13 and 3.14 governance, deterministic Python distributions,
|
|
18
|
+
Linux standalone artifacts, and fail-closed publication policy.
|
elsewindow-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kogeler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
include .version
|
|
2
|
+
include CHANGELOG.md
|
|
3
|
+
include LICENSE
|
|
4
|
+
include MANIFEST.in
|
|
5
|
+
include README.md
|
|
6
|
+
include pyproject.toml
|
|
7
|
+
recursive-include elsewindow *.py *.yml py.typed
|
|
8
|
+
prune .github
|
|
9
|
+
prune bin
|
|
10
|
+
prune containers
|
|
11
|
+
prune doc
|
|
12
|
+
prune tests
|
|
13
|
+
prune tmp
|
|
14
|
+
prune tools
|
|
15
|
+
global-exclude .DS_Store
|
|
16
|
+
global-exclude *.py[cod]
|
|
17
|
+
global-exclude __pycache__
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: elsewindow
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Run one remote Linux GUI application through Xpra over one owned OpenSSH master
|
|
5
|
+
Author: kogeler
|
|
6
|
+
Maintainer: kogeler
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://kogeler.github.io/elsewindow/
|
|
9
|
+
Project-URL: Documentation, https://kogeler.github.io/elsewindow/
|
|
10
|
+
Project-URL: Repository, https://github.com/kogeler/elsewindow
|
|
11
|
+
Project-URL: Issues, https://github.com/kogeler/elsewindow/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/kogeler/elsewindow/blob/main/CHANGELOG.md
|
|
13
|
+
Keywords: xpra,openssh,ssh,wayland,remote-desktop
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: X11 Applications
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: System Administrators
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
|
+
Classifier: Topic :: System :: Networking
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: <3.15,>=3.13
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: ssh-wrapper==0.1.0
|
|
29
|
+
Provides-Extra: quality
|
|
30
|
+
Requires-Dist: bandit[toml]==1.9.4; extra == "quality"
|
|
31
|
+
Requires-Dist: mypy==2.3.1; extra == "quality"
|
|
32
|
+
Requires-Dist: pip-audit==2.10.1; extra == "quality"
|
|
33
|
+
Requires-Dist: pip-licenses==5.5.5; extra == "quality"
|
|
34
|
+
Requires-Dist: ruff==0.16.5; extra == "quality"
|
|
35
|
+
Provides-Extra: test
|
|
36
|
+
Requires-Dist: pytest==9.1.1; extra == "test"
|
|
37
|
+
Requires-Dist: pytest-asyncio==1.4.0; extra == "test"
|
|
38
|
+
Requires-Dist: pytest-cov==7.1.0; extra == "test"
|
|
39
|
+
Requires-Dist: pytest-xdist==3.8.0; extra == "test"
|
|
40
|
+
Provides-Extra: package
|
|
41
|
+
Requires-Dist: build==1.6.0; extra == "package"
|
|
42
|
+
Requires-Dist: setuptools==84.0.0; extra == "package"
|
|
43
|
+
Requires-Dist: wheel==0.48.0; extra == "package"
|
|
44
|
+
Provides-Extra: standalone
|
|
45
|
+
Requires-Dist: pyinstaller==6.22.2; extra == "standalone"
|
|
46
|
+
Provides-Extra: docs
|
|
47
|
+
Requires-Dist: mkdocs-material==9.7.7; extra == "docs"
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# Elsewindow
|
|
51
|
+
|
|
52
|
+
`elsewindow` starts one GUI application on a remote Linux host and shows
|
|
53
|
+
its windows through Xpra. One owned OpenSSH ControlMaster carries every Xpra
|
|
54
|
+
control channel; the command opens no forwarding or Xpra TCP listener and
|
|
55
|
+
cleans only the server, application process group, sockets, and runtime state
|
|
56
|
+
created by that invocation.
|
|
57
|
+
|
|
58
|
+
The application renders through the remote Wayland compositor and remote GPU.
|
|
59
|
+
Only Xpra picture, input, and control traffic crosses SSH.
|
|
60
|
+
|
|
61
|
+
## Install And Run
|
|
62
|
+
|
|
63
|
+
The Python distribution requires Linux, CPython 3.13 or 3.14, OpenSSH,
|
|
64
|
+
`false`, a local graphical session, and compatible Xpra packages on both
|
|
65
|
+
systems. Install with pip or pipx after the first release:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python3.14 -m pip install "elsewindow==0.1.0"
|
|
69
|
+
# or: pipx install "elsewindow==0.1.0"
|
|
70
|
+
elsewindow --ssh-alias agents-a -- xterm
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Check packaged versions, profile digests, Linux support, and local commands
|
|
74
|
+
without opening a connection:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
elsewindow --diagnose
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The published distribution resolves the exact reviewed
|
|
81
|
+
[`ssh-wrapper==0.1.0`](https://pypi.org/project/ssh-wrapper/0.1.0/) dependency.
|
|
82
|
+
For a source checkout, prepare the hash-locked runtime and use the repository
|
|
83
|
+
launcher from any working directory:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
make runtime-venv
|
|
87
|
+
./bin/elsewindow --help
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use a direct authority when an OpenSSH alias is not appropriate:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
elsewindow \
|
|
94
|
+
--host host.example \
|
|
95
|
+
--user desktop-user \
|
|
96
|
+
--port 2222 \
|
|
97
|
+
-- /opt/application/bin/application
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The default encoding and network profiles are `rgb` and `gigabit_lan`.
|
|
101
|
+
Production arguments come exclusively from the reviewed YAML mirrors shipped
|
|
102
|
+
inside the Python package. See [the Xpra guide](doc/xpra.md) for profile,
|
|
103
|
+
hardware, application, and lifecycle details.
|
|
104
|
+
|
|
105
|
+
## Installing The Maintained Xpra Build
|
|
106
|
+
|
|
107
|
+
[`tools/install_xpra_release.py`](tools/install_xpra_release.py) is a standalone
|
|
108
|
+
standard-library installer for Debian 13 and Ubuntu 26.04. It resolves the
|
|
109
|
+
newest canonical package release from `kogeler/xpra:develop`, verifies archive
|
|
110
|
+
and DEB contents before mutation, prints the exact installed Xpra inventory,
|
|
111
|
+
and requires interactive confirmation before purging that inventory. It uses
|
|
112
|
+
APT for the validated local packages and dependencies.
|
|
113
|
+
|
|
114
|
+
Elsewindow deliberately uses this maintained fork rather than treating a
|
|
115
|
+
generic upstream Xpra build as interchangeable. Project development uncovered
|
|
116
|
+
dozens of blocking Xpra defects: some fixes were accepted as direct upstream
|
|
117
|
+
contributions, some were implemented by the upstream maintainer after issue
|
|
118
|
+
reports, and some were not accepted after technical disagreements. The last
|
|
119
|
+
category requires continued maintenance of additional fork patches. The
|
|
120
|
+
[Xpra guide](doc/xpra.md#why-the-maintained-xpra-fork-is-required) explains
|
|
121
|
+
the resulting user compatibility boundary.
|
|
122
|
+
|
|
123
|
+
Review the installer and the [security contract](doc/security.md). The streamed
|
|
124
|
+
route below is pending until the first reviewed `main` push; it must not be
|
|
125
|
+
treated as available before then:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
( set -o pipefail; curl --proto '=https' --tlsv1.2 -fsSL 'https://raw.githubusercontent.com/kogeler/elsewindow/main/tools/install_xpra_release.py' | /usr/bin/python3 - )
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Do not prefix the pipeline or Python process with `sudo`. When privilege is
|
|
132
|
+
needed, the reviewed script reads purge confirmation from the controlling
|
|
133
|
+
terminal and invokes `/usr/bin/sudo` itself only for its root-owned staging and
|
|
134
|
+
APT transaction.
|
|
135
|
+
|
|
136
|
+
## Standalone Linux Executables
|
|
137
|
+
|
|
138
|
+
After the first release, GitHub Releases provide `elsewindow-linux-amd64` and
|
|
139
|
+
`elsewindow-linux-arm64`. These native one-file ELF executables bundle
|
|
140
|
+
Elsewindow, CPython, `ssh-wrapper`, version metadata, and the reviewed YAML
|
|
141
|
+
profiles. They do not bundle OpenSSH, Xpra, Podman, GPU drivers, VA-API, or
|
|
142
|
+
distribution packages. Verify the release's `SHA256SUMS.txt`, make the selected
|
|
143
|
+
file executable, and run `./elsewindow-linux-amd64 --diagnose` (or the arm64
|
|
144
|
+
equivalent) before starting a session.
|
|
145
|
+
|
|
146
|
+
## Documentation
|
|
147
|
+
|
|
148
|
+
- [Rendered documentation site](https://kogeler.github.io/elsewindow/)
|
|
149
|
+
(published by the first reviewed direct `main` push)
|
|
150
|
+
- [Getting started](doc/getting-started.md)
|
|
151
|
+
- [Complete Xpra behavior and options](doc/xpra.md)
|
|
152
|
+
- [Security model](doc/security.md)
|
|
153
|
+
- [Architecture](doc/architecture.md)
|
|
154
|
+
- [Development and validation](doc/development.md)
|
|
155
|
+
- [Contributing](doc/contributing.md)
|
|
156
|
+
- [Maintainer CI contract](doc/maintenance/CI.md)
|
|
157
|
+
- [Dependency maintenance](doc/maintenance/DEPENDENCIES.md)
|
|
158
|
+
- [Release maintenance](doc/maintenance/RELEASES.md)
|
|
159
|
+
|
|
160
|
+
The Pages workflow renders these same maintained Markdown files and validates
|
|
161
|
+
every generated route, local link, anchor, canonical URL, sitemap, crawler
|
|
162
|
+
file, and advertised `llms.txt` route without network access.
|
|
163
|
+
|
|
164
|
+
## Automatic Live Validation
|
|
165
|
+
|
|
166
|
+
`make live-test` resolves the newest fork release, builds checksum-bound Ubuntu
|
|
167
|
+
target and Debian client images, and runs detach and abrupt-master-loss cases
|
|
168
|
+
on a private rootless Podman network. The payload excludes `elsewindow/` source
|
|
169
|
+
and clean-installs the already verified wheel. The harness generates a fresh
|
|
170
|
+
Ed25519 key, performs exactly one authentication per case, verifies a visible
|
|
171
|
+
window and picture updates through the production profile assembler, preserves
|
|
172
|
+
an unrelated session, and removes only its labelled containers and network.
|
|
173
|
+
All wheel, test, and key material enters through validated tar streams; the
|
|
174
|
+
test uses no host bind, data volume, or copy channel.
|
|
175
|
+
|
|
176
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Elsewindow
|
|
2
|
+
|
|
3
|
+
`elsewindow` starts one GUI application on a remote Linux host and shows
|
|
4
|
+
its windows through Xpra. One owned OpenSSH ControlMaster carries every Xpra
|
|
5
|
+
control channel; the command opens no forwarding or Xpra TCP listener and
|
|
6
|
+
cleans only the server, application process group, sockets, and runtime state
|
|
7
|
+
created by that invocation.
|
|
8
|
+
|
|
9
|
+
The application renders through the remote Wayland compositor and remote GPU.
|
|
10
|
+
Only Xpra picture, input, and control traffic crosses SSH.
|
|
11
|
+
|
|
12
|
+
## Install And Run
|
|
13
|
+
|
|
14
|
+
The Python distribution requires Linux, CPython 3.13 or 3.14, OpenSSH,
|
|
15
|
+
`false`, a local graphical session, and compatible Xpra packages on both
|
|
16
|
+
systems. Install with pip or pipx after the first release:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
python3.14 -m pip install "elsewindow==0.1.0"
|
|
20
|
+
# or: pipx install "elsewindow==0.1.0"
|
|
21
|
+
elsewindow --ssh-alias agents-a -- xterm
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Check packaged versions, profile digests, Linux support, and local commands
|
|
25
|
+
without opening a connection:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
elsewindow --diagnose
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The published distribution resolves the exact reviewed
|
|
32
|
+
[`ssh-wrapper==0.1.0`](https://pypi.org/project/ssh-wrapper/0.1.0/) dependency.
|
|
33
|
+
For a source checkout, prepare the hash-locked runtime and use the repository
|
|
34
|
+
launcher from any working directory:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
make runtime-venv
|
|
38
|
+
./bin/elsewindow --help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Use a direct authority when an OpenSSH alias is not appropriate:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
elsewindow \
|
|
45
|
+
--host host.example \
|
|
46
|
+
--user desktop-user \
|
|
47
|
+
--port 2222 \
|
|
48
|
+
-- /opt/application/bin/application
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The default encoding and network profiles are `rgb` and `gigabit_lan`.
|
|
52
|
+
Production arguments come exclusively from the reviewed YAML mirrors shipped
|
|
53
|
+
inside the Python package. See [the Xpra guide](doc/xpra.md) for profile,
|
|
54
|
+
hardware, application, and lifecycle details.
|
|
55
|
+
|
|
56
|
+
## Installing The Maintained Xpra Build
|
|
57
|
+
|
|
58
|
+
[`tools/install_xpra_release.py`](tools/install_xpra_release.py) is a standalone
|
|
59
|
+
standard-library installer for Debian 13 and Ubuntu 26.04. It resolves the
|
|
60
|
+
newest canonical package release from `kogeler/xpra:develop`, verifies archive
|
|
61
|
+
and DEB contents before mutation, prints the exact installed Xpra inventory,
|
|
62
|
+
and requires interactive confirmation before purging that inventory. It uses
|
|
63
|
+
APT for the validated local packages and dependencies.
|
|
64
|
+
|
|
65
|
+
Elsewindow deliberately uses this maintained fork rather than treating a
|
|
66
|
+
generic upstream Xpra build as interchangeable. Project development uncovered
|
|
67
|
+
dozens of blocking Xpra defects: some fixes were accepted as direct upstream
|
|
68
|
+
contributions, some were implemented by the upstream maintainer after issue
|
|
69
|
+
reports, and some were not accepted after technical disagreements. The last
|
|
70
|
+
category requires continued maintenance of additional fork patches. The
|
|
71
|
+
[Xpra guide](doc/xpra.md#why-the-maintained-xpra-fork-is-required) explains
|
|
72
|
+
the resulting user compatibility boundary.
|
|
73
|
+
|
|
74
|
+
Review the installer and the [security contract](doc/security.md). The streamed
|
|
75
|
+
route below is pending until the first reviewed `main` push; it must not be
|
|
76
|
+
treated as available before then:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
( set -o pipefail; curl --proto '=https' --tlsv1.2 -fsSL 'https://raw.githubusercontent.com/kogeler/elsewindow/main/tools/install_xpra_release.py' | /usr/bin/python3 - )
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Do not prefix the pipeline or Python process with `sudo`. When privilege is
|
|
83
|
+
needed, the reviewed script reads purge confirmation from the controlling
|
|
84
|
+
terminal and invokes `/usr/bin/sudo` itself only for its root-owned staging and
|
|
85
|
+
APT transaction.
|
|
86
|
+
|
|
87
|
+
## Standalone Linux Executables
|
|
88
|
+
|
|
89
|
+
After the first release, GitHub Releases provide `elsewindow-linux-amd64` and
|
|
90
|
+
`elsewindow-linux-arm64`. These native one-file ELF executables bundle
|
|
91
|
+
Elsewindow, CPython, `ssh-wrapper`, version metadata, and the reviewed YAML
|
|
92
|
+
profiles. They do not bundle OpenSSH, Xpra, Podman, GPU drivers, VA-API, or
|
|
93
|
+
distribution packages. Verify the release's `SHA256SUMS.txt`, make the selected
|
|
94
|
+
file executable, and run `./elsewindow-linux-amd64 --diagnose` (or the arm64
|
|
95
|
+
equivalent) before starting a session.
|
|
96
|
+
|
|
97
|
+
## Documentation
|
|
98
|
+
|
|
99
|
+
- [Rendered documentation site](https://kogeler.github.io/elsewindow/)
|
|
100
|
+
(published by the first reviewed direct `main` push)
|
|
101
|
+
- [Getting started](doc/getting-started.md)
|
|
102
|
+
- [Complete Xpra behavior and options](doc/xpra.md)
|
|
103
|
+
- [Security model](doc/security.md)
|
|
104
|
+
- [Architecture](doc/architecture.md)
|
|
105
|
+
- [Development and validation](doc/development.md)
|
|
106
|
+
- [Contributing](doc/contributing.md)
|
|
107
|
+
- [Maintainer CI contract](doc/maintenance/CI.md)
|
|
108
|
+
- [Dependency maintenance](doc/maintenance/DEPENDENCIES.md)
|
|
109
|
+
- [Release maintenance](doc/maintenance/RELEASES.md)
|
|
110
|
+
|
|
111
|
+
The Pages workflow renders these same maintained Markdown files and validates
|
|
112
|
+
every generated route, local link, anchor, canonical URL, sitemap, crawler
|
|
113
|
+
file, and advertised `llms.txt` route without network access.
|
|
114
|
+
|
|
115
|
+
## Automatic Live Validation
|
|
116
|
+
|
|
117
|
+
`make live-test` resolves the newest fork release, builds checksum-bound Ubuntu
|
|
118
|
+
target and Debian client images, and runs detach and abrupt-master-loss cases
|
|
119
|
+
on a private rootless Podman network. The payload excludes `elsewindow/` source
|
|
120
|
+
and clean-installs the already verified wheel. The harness generates a fresh
|
|
121
|
+
Ed25519 key, performs exactly one authentication per case, verifies a visible
|
|
122
|
+
window and picture updates through the production profile assembler, preserves
|
|
123
|
+
an unrelated session, and removes only its labelled containers and network.
|
|
124
|
+
All wheel, test, and key material enters through validated tar streams; the
|
|
125
|
+
test uses no host bind, data volume, or copy channel.
|
|
126
|
+
|
|
127
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright (c) 2026 kogeler
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""Xpra application runner over one owned SSH master."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _resolve_version() -> str:
|
|
10
|
+
"""Resolve the source-tree or installed distribution version."""
|
|
11
|
+
try:
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
root = Path(__file__).resolve().parent.parent
|
|
15
|
+
if (root / "pyproject.toml").is_file():
|
|
16
|
+
return (root / ".version").read_text(encoding="utf-8").strip()
|
|
17
|
+
bundled = Path(__file__).resolve().parent / ".version"
|
|
18
|
+
if bundled.is_file():
|
|
19
|
+
return bundled.read_text(encoding="utf-8").strip()
|
|
20
|
+
except OSError:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
25
|
+
|
|
26
|
+
return version("elsewindow")
|
|
27
|
+
except PackageNotFoundError:
|
|
28
|
+
return "0.0.0+unknown"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__version__ = _resolve_version()
|
|
32
|
+
|
|
33
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Copyright (c) 2026 kogeler
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""Command-line interface for one owned remote Xpra application."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import asyncio
|
|
10
|
+
import hashlib
|
|
11
|
+
import shutil
|
|
12
|
+
import signal
|
|
13
|
+
import sys
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from contextlib import suppress
|
|
16
|
+
from importlib.metadata import PackageNotFoundError
|
|
17
|
+
from importlib.metadata import version as distribution_version
|
|
18
|
+
|
|
19
|
+
from ssh_wrapper.errors import SSHError
|
|
20
|
+
|
|
21
|
+
from . import __version__
|
|
22
|
+
from .config import (
|
|
23
|
+
DEFAULT_CONNECT_TIMEOUT,
|
|
24
|
+
DEFAULT_ENCODING_PROFILE,
|
|
25
|
+
DEFAULT_GRACE_TIMEOUT,
|
|
26
|
+
DEFAULT_HEARTBEAT_INTERVAL,
|
|
27
|
+
DEFAULT_LEASE_TIMEOUT,
|
|
28
|
+
DEFAULT_NETWORK_PROFILE,
|
|
29
|
+
DEFAULT_POLL_INTERVAL,
|
|
30
|
+
DEFAULT_PROBE_TIMEOUT,
|
|
31
|
+
DEFAULT_READY_TIMEOUT,
|
|
32
|
+
SUPPORTED_ENCODING_PROFILES,
|
|
33
|
+
SUPPORTED_NETWORK_PROFILES,
|
|
34
|
+
XpraConfig,
|
|
35
|
+
)
|
|
36
|
+
from .session import XpraSession
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
40
|
+
"""Build the public command-line contract."""
|
|
41
|
+
parser = argparse.ArgumentParser(
|
|
42
|
+
prog="elsewindow",
|
|
43
|
+
description="Run one new remote GUI application through Xpra over owned SSH.",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--version", action="version", version=f"%(prog)s {__version__}"
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--diagnose",
|
|
50
|
+
action="store_true",
|
|
51
|
+
help="report bundled versions, profile digests, and local prerequisites",
|
|
52
|
+
)
|
|
53
|
+
authority = parser.add_mutually_exclusive_group()
|
|
54
|
+
authority.add_argument("--ssh-alias", help="trusted OpenSSH host alias")
|
|
55
|
+
authority.add_argument("--host", help="direct SSH host or address")
|
|
56
|
+
parser.add_argument("--user", help="remote user required with --host")
|
|
57
|
+
parser.add_argument("--port", type=int, default=22, help="direct SSH port")
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--encoding-profile",
|
|
60
|
+
choices=SUPPORTED_ENCODING_PROFILES,
|
|
61
|
+
default=DEFAULT_ENCODING_PROFILE,
|
|
62
|
+
help=(
|
|
63
|
+
"reviewed pixel transport profile; h264 uses native VA-API, "
|
|
64
|
+
"libyuv, OpenGL, and alpha-capable fallback"
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--network-profile",
|
|
69
|
+
choices=SUPPORTED_NETWORK_PROFILES,
|
|
70
|
+
default=DEFAULT_NETWORK_PROFILE,
|
|
71
|
+
help="reviewed client quality/network profile (default: %(default)s)",
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--connect-timeout", type=float, default=DEFAULT_CONNECT_TIMEOUT
|
|
75
|
+
)
|
|
76
|
+
parser.add_argument("--ready-timeout", type=float, default=DEFAULT_READY_TIMEOUT)
|
|
77
|
+
parser.add_argument("--probe-timeout", type=float, default=DEFAULT_PROBE_TIMEOUT)
|
|
78
|
+
parser.add_argument("--poll-interval", type=float, default=DEFAULT_POLL_INTERVAL)
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--heartbeat-interval", type=float, default=DEFAULT_HEARTBEAT_INTERVAL
|
|
81
|
+
)
|
|
82
|
+
parser.add_argument("--lease-timeout", type=float, default=DEFAULT_LEASE_TIMEOUT)
|
|
83
|
+
parser.add_argument("--cleanup-grace", type=float, default=DEFAULT_GRACE_TIMEOUT)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"application",
|
|
86
|
+
nargs=argparse.REMAINDER,
|
|
87
|
+
metavar="APP [ARG ...]",
|
|
88
|
+
help="application argv after --",
|
|
89
|
+
)
|
|
90
|
+
return parser
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _diagnose() -> int:
|
|
94
|
+
"""Report immutable bundled inputs and actionable host prerequisites."""
|
|
95
|
+
from .live_config import LIVE_CLI_PATH, NETWORK_PROFILES_PATH
|
|
96
|
+
|
|
97
|
+
failed = False
|
|
98
|
+
print(f"elsewindow: {__version__}")
|
|
99
|
+
try:
|
|
100
|
+
wrapper_version = distribution_version("ssh-wrapper")
|
|
101
|
+
except PackageNotFoundError:
|
|
102
|
+
wrapper_version = "missing"
|
|
103
|
+
failed = True
|
|
104
|
+
print(f"ssh-wrapper: {wrapper_version}")
|
|
105
|
+
for label, path in (
|
|
106
|
+
("live-cli.yml", LIVE_CLI_PATH),
|
|
107
|
+
("profiles.yml", NETWORK_PROFILES_PATH),
|
|
108
|
+
):
|
|
109
|
+
try:
|
|
110
|
+
digest = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
111
|
+
except OSError as error:
|
|
112
|
+
print(f"elsewindow: invalid_resource: {label}: {error}", file=sys.stderr)
|
|
113
|
+
failed = True
|
|
114
|
+
else:
|
|
115
|
+
print(f"{label}: sha256:{digest}")
|
|
116
|
+
if not sys.platform.startswith("linux"):
|
|
117
|
+
print(
|
|
118
|
+
f"elsewindow: unsupported_platform: Linux is required, found {sys.platform}",
|
|
119
|
+
file=sys.stderr,
|
|
120
|
+
)
|
|
121
|
+
failed = True
|
|
122
|
+
for command in ("ssh", "false", "xpra"):
|
|
123
|
+
resolved = shutil.which(command)
|
|
124
|
+
if resolved is None:
|
|
125
|
+
print(
|
|
126
|
+
"elsewindow: missing_dependency: "
|
|
127
|
+
f"required command not found on PATH: {command}",
|
|
128
|
+
file=sys.stderr,
|
|
129
|
+
)
|
|
130
|
+
failed = True
|
|
131
|
+
else:
|
|
132
|
+
print(f"{command}: {resolved}")
|
|
133
|
+
return int(failed)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def _run_with_signals(config: XpraConfig) -> int:
|
|
137
|
+
session = XpraSession(config)
|
|
138
|
+
loop = asyncio.get_running_loop()
|
|
139
|
+
interrupted: asyncio.Future[int] = loop.create_future()
|
|
140
|
+
|
|
141
|
+
def stop(exit_code: int) -> None:
|
|
142
|
+
if not interrupted.done():
|
|
143
|
+
interrupted.set_result(exit_code)
|
|
144
|
+
|
|
145
|
+
installed: list[signal.Signals] = []
|
|
146
|
+
for selected in (signal.SIGINT, signal.SIGTERM):
|
|
147
|
+
try:
|
|
148
|
+
loop.add_signal_handler(selected, stop, 128 + selected)
|
|
149
|
+
except NotImplementedError:
|
|
150
|
+
continue
|
|
151
|
+
installed.append(selected)
|
|
152
|
+
|
|
153
|
+
run_task = asyncio.create_task(session.run())
|
|
154
|
+
try:
|
|
155
|
+
done, _pending = await asyncio.wait(
|
|
156
|
+
{run_task, interrupted}, return_when=asyncio.FIRST_COMPLETED
|
|
157
|
+
)
|
|
158
|
+
if interrupted in done:
|
|
159
|
+
run_task.cancel()
|
|
160
|
+
with suppress(asyncio.CancelledError):
|
|
161
|
+
await run_task
|
|
162
|
+
return interrupted.result()
|
|
163
|
+
return run_task.result()
|
|
164
|
+
finally:
|
|
165
|
+
if not interrupted.done():
|
|
166
|
+
interrupted.cancel()
|
|
167
|
+
for selected in installed:
|
|
168
|
+
loop.remove_signal_handler(selected)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
172
|
+
"""Validate configuration, run one session, and return a stable exit code."""
|
|
173
|
+
parser = build_parser()
|
|
174
|
+
args = parser.parse_args(argv)
|
|
175
|
+
if args.diagnose:
|
|
176
|
+
if (
|
|
177
|
+
args.ssh_alias is not None
|
|
178
|
+
or args.host is not None
|
|
179
|
+
or args.user is not None
|
|
180
|
+
or args.port != 22
|
|
181
|
+
or args.application
|
|
182
|
+
):
|
|
183
|
+
parser.error("--diagnose cannot be combined with a session authority")
|
|
184
|
+
return _diagnose()
|
|
185
|
+
if args.ssh_alias is None and args.host is None:
|
|
186
|
+
parser.error("provide either --ssh-alias or --host")
|
|
187
|
+
try:
|
|
188
|
+
config = XpraConfig.from_namespace(args)
|
|
189
|
+
except SSHError as error:
|
|
190
|
+
parser.error(error.message)
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
return asyncio.run(_run_with_signals(config))
|
|
194
|
+
except KeyboardInterrupt:
|
|
195
|
+
return 130
|
|
196
|
+
except SSHError as error:
|
|
197
|
+
print(f"elsewindow: {error.code}: {error.message}", file=sys.stderr)
|
|
198
|
+
return 1
|
|
199
|
+
except Exception as error: # noqa: BLE001 - public diagnostics stay sanitized.
|
|
200
|
+
print(
|
|
201
|
+
f"elsewindow: session_failed: {type(error).__name__}",
|
|
202
|
+
file=sys.stderr,
|
|
203
|
+
)
|
|
204
|
+
return 1
|