Initial Wine Desktop image and Pterodactyl Egg
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
name: Build and publish Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
tags: ['v*']
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
type=sha,format=long
|
||||||
|
type=ref,event=tag
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.env
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.log
|
||||||
|
docker-compose.override.yml
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
FROM ghcr.io/ptero-eggs/yolks:wine_latest
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="Wine Desktop" \
|
||||||
|
org.opencontainers.image.description="Wine with an XFCE desktop, VNC, and noVNC for Pterodactyl" \
|
||||||
|
org.opencontainers.image.source="https://git.lexian.dev/Lexian-droid/OpenClaw-pterodactyl-wine-gui-egg"
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
DISPLAY=:0 \
|
||||||
|
DISPLAY_WIDTH=1280 \
|
||||||
|
DISPLAY_HEIGHT=720 \
|
||||||
|
DISPLAY_DEPTH=24 \
|
||||||
|
VNC_PORT=5900 \
|
||||||
|
NOVNC_PORT=6080 \
|
||||||
|
VNC_PASSWORD= \
|
||||||
|
XVFB=0 \
|
||||||
|
WINEPREFIX=/home/container/.wine
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
xfce4 \
|
||||||
|
xfce4-terminal \
|
||||||
|
xvfb \
|
||||||
|
x11vnc \
|
||||||
|
novnc \
|
||||||
|
websockify \
|
||||||
|
dbus-x11 \
|
||||||
|
xauth \
|
||||||
|
x11-utils \
|
||||||
|
x11-xserver-utils \
|
||||||
|
libgl1 \
|
||||||
|
libgl1-mesa-dri \
|
||||||
|
libegl1 \
|
||||||
|
mesa-utils \
|
||||||
|
pulseaudio \
|
||||||
|
pulseaudio-utils \
|
||||||
|
fonts-dejavu \
|
||||||
|
fonts-liberation \
|
||||||
|
fonts-noto-core \
|
||||||
|
fonts-noto-cjk \
|
||||||
|
winbind \
|
||||||
|
cabextract \
|
||||||
|
unzip \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
|
RUN chmod 0755 /usr/local/bin/docker-entrypoint.sh \
|
||||||
|
&& mkdir -p /home/container \
|
||||||
|
&& chown -R container:container /home/container 2>/dev/null || true
|
||||||
|
|
||||||
|
EXPOSE 5900/tcp 6080/tcp
|
||||||
|
|
||||||
|
# Keep the official tini process and inherited CMD. The wrapper prepares the
|
||||||
|
# desktop and then execs the official /entrypoint.sh command unchanged.
|
||||||
|
ENTRYPOINT ["/usr/bin/tini", "-g", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||||
|
CMD ["/bin/bash", "/entrypoint.sh"]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Lexian-droid
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# Wine Desktop
|
||||||
|
|
||||||
|
Wine Desktop extends the official Pterodactyl Wine yolk with a persistent virtual XFCE desktop. It is intended for Windows GUI applications that need a display, VNC access, or browser-based noVNC access inside a Pterodactyl or Pelican server.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Based on `ghcr.io/ptero-eggs/yolks:wine_latest`.
|
||||||
|
- XFCE on Xvfb with configurable resolution.
|
||||||
|
- Direct VNC on TCP `5900` and browser VNC on TCP `6080`.
|
||||||
|
- Persistent Wine prefix at `/home/container/.wine`.
|
||||||
|
- PulseAudio, Mesa, fonts, winetricks, winbind, and common archive tools.
|
||||||
|
- Retains the official yolk's Steam update, Gecko/Mono, winetricks, and startup handling.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The image keeps the yolk's `/usr/bin/tini -g --` process and inherited command. Its entrypoint starts Xvfb, D-Bus, XFCE, x11vnc, noVNC, and the Wine prefix once, then executes the inherited `/bin/bash /entrypoint.sh`. The official entrypoint continues to expand `STARTUP`, perform optional updates and winetricks actions, and launch the application.
|
||||||
|
|
||||||
|
The Wine prefix is not deleted or rebuilt when the container restarts. Store the server data in the normal Pterodactyl volume to keep it persistent.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Build locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.lexian.dev/Lexian-droid/OpenClaw-pterodactyl-wine-gui-egg.git WineDesktop
|
||||||
|
cd WineDesktop
|
||||||
|
docker build -t wine-desktop:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
The image exposes `5900/tcp` and `6080/tcp`. Publish those ports if running Docker directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -it -p 5900:5900 -p 6080:6080 \
|
||||||
|
-e STARTUP='wine /home/container/application.exe' \
|
||||||
|
wine-desktop:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
For Pterodactyl or Pelican, import `egg-wine-desktop.json` into the nest, select the published image (or add your own registry tag), and create a server with the Wine Desktop Egg. Add allocations for ports 5900 and 6080 when your panel supports additional allocations.
|
||||||
|
|
||||||
|
## Startup and variables
|
||||||
|
|
||||||
|
The Egg's default startup is `wine {{SERVER_EXECUTABLE}}`, with `SERVER_EXECUTABLE` defaulting to `application.exe`. Set it to a path such as `MyApp/MyApp.exe` or use a custom startup command when needed. The underlying yolk continues to receive and evaluate its normal `STARTUP` value.
|
||||||
|
|
||||||
|
Useful variables:
|
||||||
|
|
||||||
|
| Variable | Default | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `SERVER_EXECUTABLE` | `application.exe` | Executable used by the Egg startup command |
|
||||||
|
| `DISPLAY_WIDTH` | `1280` | Virtual screen width |
|
||||||
|
| `DISPLAY_HEIGHT` | `720` | Virtual screen height |
|
||||||
|
| `WINEPREFIX` | `/home/container/.wine` | Persistent Wine prefix |
|
||||||
|
| `VNC_PASSWORD` | empty | Optional direct VNC password, up to 8 characters |
|
||||||
|
| `VNC_PORT` | `5900` | x11vnc port |
|
||||||
|
| `NOVNC_PORT` | `6080` | noVNC/websockify port |
|
||||||
|
|
||||||
|
## Connecting
|
||||||
|
|
||||||
|
Direct VNC clients connect to the server allocation on port `5900`. If `VNC_PASSWORD` is blank, VNC authentication is disabled; protect the allocation with your firewall or proxy.
|
||||||
|
|
||||||
|
Browser clients open `http://SERVER:6080/vnc.html` and connect to the displayed VNC endpoint. Use HTTPS or a reverse proxy when exposing noVNC beyond a trusted network.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Black screen:** confirm ports are allocated and inspect `/tmp/wine-desktop-xfce.log` and `/tmp/wine-desktop-x11vnc.log` in the container.
|
||||||
|
- **Application does not start:** run the exact command interactively and check that the executable exists under `/home/container`; the official yolk's `STARTUP` is evaluated by Bash.
|
||||||
|
- **Prefix problems:** stop the server before changing Wine architecture or winetricks settings. Back up `.wine` before manually repairing it.
|
||||||
|
- **noVNC loads but cannot connect:** ensure both 6080 and 5900 are reachable from the container network; websockify forwards 6080 to localhost:5900.
|
||||||
|
- **No audio:** applications must support PulseAudio. The image includes the client and server packages, but application-specific audio configuration may still be required.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**Does this replace the official Wine yolk?** No. It uses it as the base and invokes its original entrypoint after the desktop is ready.
|
||||||
|
|
||||||
|
**Does it support Steam server updates and winetricks?** Yes. The inherited entrypoint remains responsible for those features.
|
||||||
|
|
||||||
|
**Is the desktop secure by default?** VNC is bound for container access and is intended to be protected by Pterodactyl allocations, firewall rules, or a reverse proxy. Set `VNC_PASSWORD` for direct VNC authentication and do not expose unprotected ports publicly.
|
||||||
|
|
||||||
|
**Can I use this with Pelican?** Yes. The Egg uses the modern `PTDL_v2` format and standard Docker image/startup fields.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License. The base Wine yolk and its components retain their own licenses.
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
readonly LOG_PREFIX="[wine-desktop]"
|
||||||
|
DISPLAY="${DISPLAY:-:0}"
|
||||||
|
export DISPLAY
|
||||||
|
DISPLAY_WIDTH="${DISPLAY_WIDTH:-1280}"
|
||||||
|
DISPLAY_HEIGHT="${DISPLAY_HEIGHT:-720}"
|
||||||
|
DISPLAY_DEPTH="${DISPLAY_DEPTH:-24}"
|
||||||
|
VNC_PORT="${VNC_PORT:-5900}"
|
||||||
|
NOVNC_PORT="${NOVNC_PORT:-6080}"
|
||||||
|
WINEPREFIX="${WINEPREFIX:-/home/container/.wine}"
|
||||||
|
export DISPLAY DISPLAY_WIDTH DISPLAY_HEIGHT DISPLAY_DEPTH VNC_PORT NOVNC_PORT WINEPREFIX
|
||||||
|
|
||||||
|
children=()
|
||||||
|
cleanup() {
|
||||||
|
trap - EXIT INT TERM
|
||||||
|
for pid in "${children[@]:-}"; do
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
log() { echo "${LOG_PREFIX} $*"; }
|
||||||
|
|
||||||
|
if ! xdpyinfo -display "$DISPLAY" >/dev/null 2>&1; then
|
||||||
|
log "Starting Xvfb on ${DISPLAY} (${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}x${DISPLAY_DEPTH})"
|
||||||
|
Xvfb "$DISPLAY" -screen 0 "${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}x${DISPLAY_DEPTH}" -ac +extension GLX +render -noreset &
|
||||||
|
children+=("$!")
|
||||||
|
for _ in {1..50}; do
|
||||||
|
xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 && break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! xdpyinfo -display "$DISPLAY" >/dev/null 2>&1; then
|
||||||
|
log "X server did not become ready"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp/runtime-${USER:-container}}"
|
||||||
|
mkdir -p "$XDG_RUNTIME_DIR"
|
||||||
|
chmod 0700 "$XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
|
if command -v dbus-launch >/dev/null 2>&1; then
|
||||||
|
eval "$(dbus-launch --sh-syntax)"
|
||||||
|
children+=("${DBUS_SESSION_BUS_PID:-}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v pulseaudio >/dev/null 2>&1 && ! pgrep -x pulseaudio >/dev/null 2>&1; then
|
||||||
|
log "Starting PulseAudio"
|
||||||
|
pulseaudio --system --daemonize=yes --disallow-exit --exit-idle-time=-1 >/tmp/wine-desktop-pulseaudio.log 2>&1 || \
|
||||||
|
log "PulseAudio could not start; applications may run without audio"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pgrep -x xfce4-session >/dev/null 2>&1; then
|
||||||
|
log "Starting XFCE desktop"
|
||||||
|
startxfce4 >/tmp/wine-desktop-xfce.log 2>&1 &
|
||||||
|
children+=("$!")
|
||||||
|
fi
|
||||||
|
|
||||||
|
vnc_args=(-display "$DISPLAY" -rfbport "$VNC_PORT" -forever -shared -nopw)
|
||||||
|
if [[ -n "${VNC_PASSWORD:-}" ]]; then
|
||||||
|
password_file="${WINEPREFIX}/.vncpasswd"
|
||||||
|
mkdir -p "$WINEPREFIX"
|
||||||
|
x11vnc -storepasswd "$VNC_PASSWORD" "$password_file" >/dev/null
|
||||||
|
chmod 0600 "$password_file"
|
||||||
|
vnc_args=(-display "$DISPLAY" -rfbport "$VNC_PORT" -forever -shared -rfbauth "$password_file")
|
||||||
|
fi
|
||||||
|
log "Starting x11vnc on TCP ${VNC_PORT}"
|
||||||
|
x11vnc "${vnc_args[@]}" >/tmp/wine-desktop-x11vnc.log 2>&1 &
|
||||||
|
children+=("$!")
|
||||||
|
|
||||||
|
novnc_web="/usr/share/novnc"
|
||||||
|
[[ -d "$novnc_web" ]] || novnc_web="/usr/share/novnc/utils"
|
||||||
|
log "Starting noVNC on TCP ${NOVNC_PORT}"
|
||||||
|
websockify --web="$novnc_web" "${NOVNC_PORT}" "localhost:${VNC_PORT}" >/tmp/wine-desktop-novnc.log 2>&1 &
|
||||||
|
children+=("$!")
|
||||||
|
|
||||||
|
mkdir -p "$WINEPREFIX"
|
||||||
|
if [[ ! -f "$WINEPREFIX/system.reg" ]]; then
|
||||||
|
log "Initializing Wine prefix at ${WINEPREFIX}"
|
||||||
|
wineboot --init >/tmp/wine-desktop-wineboot.log 2>&1 || {
|
||||||
|
log "Wine prefix initialization failed; see /tmp/wine-desktop-wineboot.log"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The inherited command remains the official yolk command, normally:
|
||||||
|
# /bin/bash /entrypoint.sh. This preserves its update, winetricks, startup,
|
||||||
|
# and signal behavior while tini remains PID 1.
|
||||||
|
log "Starting inherited Wine command: $*"
|
||||||
|
exec "$@"
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO",
|
||||||
|
"meta": {
|
||||||
|
"version": "PTDL_v2",
|
||||||
|
"update_url": null
|
||||||
|
},
|
||||||
|
"exported_at": "2026-07-30T00:00:00+00:00",
|
||||||
|
"name": "Wine Desktop",
|
||||||
|
"author": "[email protected]",
|
||||||
|
"description": "Runs Windows GUI applications inside Wine using a virtual desktop.",
|
||||||
|
"features": [],
|
||||||
|
"docker_images": {
|
||||||
|
"ghcr.io/lexiandev/wine-desktop:latest": "ghcr.io/lexiandev/wine-desktop:latest",
|
||||||
|
"ghcr.io/ptero-eggs/yolks:wine_latest": "ghcr.io/ptero-eggs/yolks:wine_latest",
|
||||||
|
"wine-desktop:latest": "wine-desktop:latest"
|
||||||
|
},
|
||||||
|
"file_denylist": [],
|
||||||
|
"startup": "wine {{SERVER_EXECUTABLE}}",
|
||||||
|
"config": {
|
||||||
|
"files": "{}",
|
||||||
|
"startup": "{\"done\":\"Wine Desktop is ready.\"}",
|
||||||
|
"logs": "{}",
|
||||||
|
"stop": "^C"
|
||||||
|
},
|
||||||
|
"scripts": {},
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"name": "Server executable",
|
||||||
|
"description": "Windows executable path relative to /home/container, for example application.exe or MyApp/MyApp.exe.",
|
||||||
|
"env_variable": "SERVER_EXECUTABLE",
|
||||||
|
"default_value": "application.exe",
|
||||||
|
"user_viewable": true,
|
||||||
|
"user_editable": true,
|
||||||
|
"rules": "required|string|max:255",
|
||||||
|
"field_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Display width",
|
||||||
|
"description": "Virtual desktop width in pixels.",
|
||||||
|
"env_variable": "DISPLAY_WIDTH",
|
||||||
|
"default_value": "1280",
|
||||||
|
"user_viewable": true,
|
||||||
|
"user_editable": true,
|
||||||
|
"rules": "required|integer|min:640|max:7680",
|
||||||
|
"field_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Display height",
|
||||||
|
"description": "Virtual desktop height in pixels.",
|
||||||
|
"env_variable": "DISPLAY_HEIGHT",
|
||||||
|
"default_value": "720",
|
||||||
|
"user_viewable": true,
|
||||||
|
"user_editable": true,
|
||||||
|
"rules": "required|integer|min:480|max:4320",
|
||||||
|
"field_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VNC password",
|
||||||
|
"description": "Optional password for direct VNC access. Leave blank to disable VNC authentication.",
|
||||||
|
"env_variable": "VNC_PASSWORD",
|
||||||
|
"default_value": "",
|
||||||
|
"user_viewable": true,
|
||||||
|
"user_editable": true,
|
||||||
|
"rules": "nullable|string|max:8",
|
||||||
|
"field_type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Wine prefix",
|
||||||
|
"description": "Persistent Wine prefix directory. Keep the default unless you have a specific reason to change it.",
|
||||||
|
"env_variable": "WINEPREFIX",
|
||||||
|
"default_value": "/home/container/.wine",
|
||||||
|
"user_viewable": true,
|
||||||
|
"user_editable": true,
|
||||||
|
"rules": "required|string|max:255",
|
||||||
|
"field_type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user