Skip to content

Commit 4bf2c6d

Browse files
committed
Initial commit
0 parents  commit 4bf2c6d

10 files changed

Lines changed: 676 additions & 0 deletions

File tree

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* text=auto eol=lf
2+
*.sh text eol=lf

.github/workflows/build.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Build and Push
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
tags:
8+
- 'v*'
9+
10+
jobs:
11+
build:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
packages: write
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v5
20+
21+
- name: Set up QEMU
22+
uses: docker/setup-qemu-action@v4
23+
24+
- name: Set up Docker Buildx
25+
uses: docker/setup-buildx-action@v4
26+
27+
- name: Log in to GHCR
28+
uses: docker/login-action@v4
29+
with:
30+
registry: ghcr.io
31+
username: ${{ github.actor }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
33+
34+
- name: Extract metadata
35+
id: meta
36+
uses: docker/metadata-action@v6
37+
with:
38+
images: ghcr.io/lostsynapse/jnlp-anywhere
39+
tags: |
40+
type=ref,event=branch
41+
type=semver,pattern={{version}}
42+
type=semver,pattern={{major}}.{{minor}}
43+
type=raw,value=latest,enable={{is_default_branch}}
44+
45+
- name: Build and push
46+
uses: docker/build-push-action@v7
47+
with:
48+
context: .
49+
platforms: linux/amd64,linux/arm64
50+
push: true
51+
tags: ${{ steps.meta.outputs.tags }}
52+
labels: ${{ steps.meta.outputs.labels }}
53+
cache-from: type=gha
54+
cache-to: type=gha,mode=max
55+
build-args: |
56+
VERSION=${{ steps.meta.outputs.version }}

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Downloaded jars (fetched at runtime, never committed)
2+
*.jar
3+
*.jnlp
4+
5+
# Local testing artifacts
6+
.env
7+
docker-compose.override.yml
8+
9+
# OS
10+
.DS_Store
11+
Thumbs.db
12+
13+
# Editor
14+
.vscode/
15+
.idea/
16+
*.swp

Dockerfile

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
FROM ubuntu:22.04
2+
3+
ENV DEBIAN_FRONTEND=noninteractive
4+
5+
ARG VERSION=dev
6+
ENV JNLP_ANYWHERE_VERSION=${VERSION}
7+
8+
RUN apt-get update && apt-get install -y \
9+
curl \
10+
wget \
11+
gnupg \
12+
&& wget -O /usr/share/keyrings/xpra.asc https://xpra.org/xpra.asc \
13+
&& wget -P /etc/apt/sources.list.d \
14+
https://raw.githubusercontent.com/Xpra-org/xpra/master/packaging/repos/jammy/xpra.sources \
15+
&& apt-get update && apt-get install -y \
16+
xpra \
17+
xpra-html5 \
18+
xserver-xorg-video-dummy \
19+
openjdk-8-jre \
20+
python3-xlib \
21+
xkb-data \
22+
libx11-6 \
23+
libx11-dev \
24+
libxkbfile1 \
25+
xmlstarlet \
26+
ncurses-bin \
27+
dbus \
28+
&& rm -rf /var/lib/apt/lists/*
29+
30+
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libX11.so.6
31+
ENV XPRA_ZEROCOPY=0
32+
ENV XPRA_OPENGL=0
33+
ENV TERM=xterm-256color
34+
35+
RUN ldconfig
36+
37+
RUN useradd -m -s /bin/bash xpra-user \
38+
&& mkdir -p /app /etc/xpra /run/dbus /etc/xdg/menus /run/user/1000/xpra /run/xpra \
39+
&& touch /etc/xpra/password \
40+
&& chmod 700 /run/user/1000/xpra \
41+
&& chmod 775 /run/xpra \
42+
&& chown -R xpra-user:xpra-user /app /etc/xpra /run/dbus /etc/xdg/menus /run/user/1000/xpra /run/xpra
43+
44+
RUN echo '<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd"><Menu><Name>Debian</Name></Menu>' \
45+
> /etc/xdg/menus/debian-menu.menu
46+
47+
COPY entrypoint.sh /entrypoint.sh
48+
RUN chmod +x /entrypoint.sh
49+
50+
USER xpra-user
51+
52+
EXPOSE 14500
53+
54+
ENTRYPOINT ["/entrypoint.sh"]

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 LostSynapse
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.

README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# jnlp-anywhere
2+
3+
Java Web Start was a raging dumpster fire — arbitrary code execution from a URL, signed with certificates that could be compromised, running with permissions the user probably didn't intend to grant.
4+
5+
Since it was officially killed in Java 11, and banished from the browsers long before that, it is no small task to access these applications with modern software.
6+
7+
jnlp-anywhere is a container that solves the access problem without solving it on your workstation. It fetches the JNLP file from the target device at runtime, parses it, downloads whatever jars the application needs, and runs it inside an Xpra session accessible from any browser. No Java on your machine. No plugin. No Web Start. Just a URL.
8+
9+
Running these applications carries inherent risk that exists regardless of how you access them. jnlp-anywhere contains the execution environment but does not sanitize or inspect the application being run. The security posture of the application itself is your problem, not this container's.
10+
11+
---
12+
13+
## Project Status
14+
15+
This has only been tested on x86_64 via the Quick start command line with an ISY-994 as target.
16+
17+
Container is not optimized or hardened; There are no protections against naughty webstart applications.
18+
19+
**No lifeguards are present. Swim at your own risk.**
20+
21+
---
22+
23+
## How it works
24+
25+
Point it at a JNLP URL. Everything else is derived automatically. Hopefully.
26+
27+
```
28+
JNLP_URL=http://192.168.1.x/admin.jnlp
29+
```
30+
31+
The container fetches the JNLP file, parses the codebase, downloads all declared jars, constructs the classpath, extracts the main class and any application arguments, and launches the application under Xpra. The HTML5 client is served on the same port. Open a browser, get a window.
32+
33+
The native Xpra client also works on the same port. However, inclusion is incidental.
34+
35+
---
36+
37+
## Requirements
38+
39+
- A container platform - Docker, Kubernetes, Podman, whatever (maybe)
40+
- A device serving a JNLP file on a reachable URL with no authentication
41+
- Java 8 compatible application (most legacy JNLP applications are)
42+
43+
---
44+
45+
## Quick start
46+
47+
```bash
48+
docker run -e JNLP_URL=http://192.168.1.x/admin.jnlp \
49+
-p 14500:14500 \
50+
ghcr.io/lostsynapse/jnlp-anywhere:latest
51+
```
52+
53+
Open `http://localhost:14500` in a browser.
54+
55+
---
56+
57+
## Authentication
58+
59+
jnlp-anywhere supports four authentication modes ranging from no authentication to layered proxy and application-level auth. The right choice depends on your network topology and threat model — that decision is yours to make.
60+
61+
See the [Traefik + Authentik integration guide](docs/traefik-authentik.md) for one complete deployment pattern. Orchestration examples for Swarm and k3s are in the [examples](examples/) directory.
62+
63+
---
64+
65+
## Environment Variables
66+
67+
| Variable | Required | Description |
68+
| --- | --- | --- |
69+
| `JNLP_URL` | Yes | Full URL to the JNLP file |
70+
| `XPRA_PASSWORD` | No | Enables password authentication on the Xpra session. If unset, the session is unauthenticated. |
71+
| `XPRA_PRESEED_PASSWORD` | No | Set to `true` to write the password into the HTML5 client's default settings, pre-filling the connect dialog. Requires `XPRA_PASSWORD`. Do not use if the port is directly exposed without a reverse proxy — the password will be served to any browser that can reach it. |
72+
| `XPRA_AUDIO` | No | Set to `true` to enable audio forwarding via PulseAudio. Disabled by default. May be supported by JNLP applications that use audio output such as IPMI SOL viewers. (untested) |
73+
| `XPRA_DEBUG` | No | Set to `1` for Xpra verbose logging. Set to `2` for shell trace. Set to `3` for both. |
74+
75+
---
76+
77+
## Session lifecycle
78+
79+
Understanding how the container behaves across different exit scenarios is important for choosing the right deployment model.
80+
81+
**Application exits normally** — the user closes the application window. The Xpra session ends, the container exits, and the orchestrator restarts it per the configured restart policy. The next connection fetches a fresh JNLP and starts clean. This is the expected cluster behavior.
82+
83+
**Application crashes** — same path as a normal exit. If the crash is reproducible the orchestrator will restart with backoff and eventually mark the service as failed. Worth monitoring if the target application is known to be unstable.
84+
85+
**Browser disconnects** — Xpra keeps the application session alive when the browser disconnects. The Java application continues running inside the container. Reconnecting from any browser resumes the existing session. The container does not exit on browser disconnect.
86+
87+
**Container running with no active connection** — the application runs idle until the user reconnects, the application itself times out, or the service is manually disabled. For occasional management tasks this is acceptable. For resource-constrained clusters it is worth considering.
88+
89+
**Recommended cluster behavior** — deploy with a restart policy so the container recovers from application exits and crashes automatically. Disable the service manually when the management task is complete rather than relying on automatic teardown. This gives full control over when the resource is consumed without requiring intervention for transient failures.
90+
91+
**Quick access without an orchestrator** — the `docker run` one-liner in the quick start section is the right tool for a single session. The container exits when the application closes and does not restart. No cleanup required.
92+
93+
---
94+
95+
## Known limitations
96+
97+
**Java version:** The container runs OpenJDK 8. Applications requiring a newer or older Java version are not supported.
98+
99+
**Multi-jar applications:** All jars declared in the JNLP resources block are downloaded and added to the classpath. Applications that dynamically load additional jars at runtime outside the JNLP declaration may not work correctly.
100+
101+
**Signed jars and security dialogs:** Some applications present security dialogs during launch. These will appear in the Xpra session and must be accepted manually on first run.
102+
103+
**Session persistence:** The container runs a single Xpra session tied to the application lifecycle. When the application exits, the container exits. This is intentional — the container is not designed as a persistent desktop.
104+
105+
**Native client through a proxy:** The native Xpra client cannot complete an OIDC authentication flow. It connects directly to port 14500 only. See the auth documentation for details.
106+
107+
**App windows misbehave:** Some independent windows that should be hidden at start are not, may be layered incorrectly or differently than you expect, program is ugly, etc.
108+
109+
110+
---
111+
112+
## License
113+
114+
MIT. Do what you want with it.

0 commit comments

Comments
 (0)