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