/
docker-wine
executable file
·629 lines (547 loc) · 21 KB
/
docker-wine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
#!/usr/bin/env bash
print_help () {
echo "Usage: $0 [OPTION[=VALUE]]... [COMMAND [ARGS]...]..."
echo
echo "Run the docker-wine container with behaviour determined by the following"
echo "OPTIONS:"
echo " --cache Use the cached image pulled from Docker Hub and don't"
echo " attempt to pull the latest version"
echo " --local Use locally built docker-wine image instead of pulling"
echo " image from Docker Hub"
echo " --local=VALUE Specify an alternate locally built image and use instead"
echo " of pulling image from Docker Hub"
echo " --rm Start the container in non-persistent mode. i.e. Without"
echo " mounting the user's home to a volume or path on the"
echo " host"
echo " --tag=VALUE Specify an image tag to use (default is latest)"
echo " --name=VALUE Name of the docker container instantiated"
echo " (default is 'wine')"
echo " --arm64 Start the containter using linux/arm64 platform for ARM"
echo " and Apple Silicon (M1) native image"
echo " --as-root Start the container as root"
echo " --as-me Start the container using your current username, UID and"
echo " GID (default when alternate --home value specified)"
echo " --notty Start container attached with no tty"
echo " --nordp Use a version of the docker-wine image that does not"
echo " include RDP server packages for a reduced image size"
echo " --rdp Shortcut for --rdp=interactive"
echo " --rdp=OPTION Runs docker-wine container with Remote Desktop Protocol"
echo " server"
echo " Valid values for OPTION are:"
echo " no Don't use RDP server (default)"
echo " start Start the RDP server as a detached"
echo " daemon"
echo " stop Stop the detached RDP server by"
echo " stopping the container"
echo " restart Restart the detached RDP server by"
echo " stopping and starting the container"
echo " interactive Start the RDP server and also run an"
echo " interactive bash session"
echo " --rdp-port=VALUE Bind RDP to a different TCP port (default is 3389)"
echo " --shm-size=VALUE Set the shared memory size (default is '1g' for 1 GB)"
echo " --xvfb[=OPTIONAL] Start xvfb"
echo " OPTIONAL consists of comma separated values of:"
echo " SERVER_NR Server number to use, eg. :1"
echo " SCREEN Screen number to use, eg. 0"
echo " RESOLUTION Screen resolution, eg. 320x240x8"
echo " If OPTIONAL is left blank, defaults to:"
echo " :95,0,320x240x8"
echo " --sound=OPTION Select a pulseaudio configuration for sound output when"
echo " running in X11 forwarding mode"
echo " Valid values for OPTION are:"
echo " default Use the default pulseaudio config for"
echo " host OS (unix is default for Linux,"
echo " dummy is default for macOS)"
echo " unix Use UNIX socket '/tmp/pulse-socket' to"
echo " connect to the host machine's"
echo " pulseaudio server (Linux only)"
echo " dummy Run pulseaudio server in container with"
echo " dummy (null) output"
echo " none Alias for dummy"
echo " --home-volume=VALUE Use an alternate volume to winehome for storing"
echo " persistent user data. Valid values can specify either"
echo " a docker volume or local path"
echo " e.g."
echo " --home=my_new_volume"
echo " --home=/tmp/my_user"
echo " --home=VALUE Specify an alternate path for the user's home within the"
echo " container (default is /home/\$USER)"
echo " --force-owner Allow the user to take ownership of a home volume that"
echo " belongs to another user (NOTE: Use with caution!)"
echo " --nosudo Disable sudo for the user"
echo " --password=VALUE Specify a password for the user in plain text (default"
echo " is the user's username)"
echo " --password-prompt Prompt to set a password for the user"
echo " --secure-password=VALUE Provide an encrypted password for the user"
echo " --device=VALUE Bind device(s) to container. Uses standard docker"
echo " syntax and multiple statements are allowed"
echo " --env=VALUE Specify additional environment variable(s) to be passed"
echo " to the container. Uses standard docker syntax and"
echo " multiple statements are allowed"
echo " --mount=VALUE Specify additional directory bindings. Uses"
echo " standard docker syntax and multiple statements are"
echo " allowed"
echo " --network=VALUE Specify the network to connect the container to. Uses"
echo " standard docker syntax"
echo " --volume=VALUE Specify additional volume(s) to be mounted. Uses"
echo " standard docker syntax and multiple statements are"
echo " allowed"
echo " --workdir=VALUE Specify alternate WORKDIR (default is \$HOME)"
echo " --help Display this help screen and exit"
echo
echo "e.g."
echo " $0"
echo " $0 wine notepad"
echo " $0 wineboot --init"
echo " $0 --local --volume=my_vol:/some/path:ro"
echo " $0 --local --as-me wine notepad"
echo " $0 --as-root --rdp"
echo " $0 --rdp=start --password=pa55w0rd"
}
add_run_arg () {
RUN_ARGS+=("$1")
}
add_run_args_for_as_me () {
USER_HOME="${HOME}"
WORKDIR="${USER_HOME}"
add_run_arg --env="USER_NAME=$(whoami)"
add_run_arg --env="USER_UID=$(id -u)"
add_run_arg --env="USER_GID=$(id -g)"
add_run_arg --env="USER_HOME=${USER_HOME}"
}
encrypt_password () {
local password="$1"
local encrypted_password
if [ -z "${password}" ]; then
echo "ERROR: Password cannot be left blank"
exit 1
fi
encrypted_password="$(openssl passwd -1 -salt "$(openssl rand -base64 6)" "${password}")"
# Add encrypted password to run args
add_run_arg --env="USER_PASSWD=${encrypted_password}"
}
add_run_arg_timezone () {
local tz
if [ -f "/etc/timezone" ]; then
tz="$(cat /etc/timezone)"
elif [ -f "/etc/localtime" ]; then
tz="$(readlink /etc/localtime | awk -F/ '{print $(NF-1)"/"$NF}')"
else
tz="UTC"
fi
add_run_arg --env="TZ=${tz}"
}
configure_xquartz () {
# Return 0 (true) if this function makes any changes
local changes_made=1
# Check XQuartz installed
if ! [ -f /opt/X11/bin/xquartz ]; then
local answer
local attempts
local max_attempts=5
# Prompt to allow install
echo "XQuartz needs to be installed for X11 forwarding to operate. If necessary, Homebrew will also be installed to perform the installation of XQuartz."
for (( attempts = 0; attempts < max_attempts; attempts++ )); do
read -r -p "Do you want to continue? [y/N] " answer
# Default is No
[ -z "${answer}" ] && answer="n"
case "${answer}" in
[Yy]|[Yy][Ee][Ss])
install_xquartz || exit 1
changes_made=0
break
;;
[Nn]|[Nn][Oo])
echo "Unable to start container with X11 forwarding. Please install XQuartz or alternatively use Remote Desktop. e.g. $0 --rdp"
exit 0
;;
*)
echo "Invalid response. Please use y or n"
;;
esac
done
# Fail after too many attempts
if [ "${attempts}" -ge "${max_attempts}" ]; then
echo "ERROR: Too many invalid responses"
exit 1
fi
fi
# Configure XQuartz
if [ -e ~/Library/Preferences/org.xquartz.X11.plist ] ; then
xquartz_properties=org.xquartz.X11
else xquartz_properties=org.macosforge.xquartz.X11; fi
if [ "$(defaults read $xquartz_properties app_to_run)" != "/usr/bin/true" ]; then
defaults write $xquartz_properties app_to_run /usr/bin/true
changes_made=0
fi
if [ "$(defaults read $xquartz_properties nolisten_tcp)" != "0" ]; then
defaults write $xquartz_properties nolisten_tcp 0
changes_made=0
fi
if [ "$(defaults read $xquartz_properties enable_iglx)" != "1" ]; then
# Enable GLX (OpenGL)
defaults write $xquartz_properties enable_iglx -bool true
changes_made=0
fi
return $changes_made
}
install_xquartz() {
# Return 0 if XQuartz is successfully installed
local installed=1
# Install Homebrew
if ! command -v brew >/dev/null 2>&1; then
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
# Confirm installed
if ! command -v brew >/dev/null 2>&1; then
echo "ERROR: Failed to install Homebrew, unable to proceed with XQuartz installation"
exit 1
fi
fi
# Install XQuartz
if ! [ -f /opt/X11/bin/xquartz ]; then
brew install --cask xquartz
# Confirm installed
[ -f /opt/X11/bin/xquartz ] && installed=0
fi
return $installed
}
add_x11_key () {
local display="$1"
# Check for .Xauthority which is required for authenticating as the current user on the host's X11 server
if [ -z "${XAUTHORITY:-${HOME}/.Xauthority}" ]; then
echo "ERROR: No valid .Xauthority file found for X11"
exit 1
fi
# Get the hex key for the display from host user's .Xauthority file and store in ~/.docker-wine.Xkey
xauth list "${display}" | head -n1 | awk '{print $3}' > ~/.docker-wine.Xkey
# Lock down permissions
chmod 600 ~/.docker-wine.Xkey
# Add .Xkey to the run args
add_run_arg --volume="${HOME}/.docker-wine.Xkey:/root/.Xkey:ro"
}
configure_sound () {
local os="$1"
case "${os}" in
linux)
if [ "${SOUND}" == "default" ]; then
SOUND="unix"
fi
;;
macos)
if [ "${SOUND}" == "default" ]; then
SOUND="dummy"
fi
;;
*)
echo "ERROR: '${os}' is not a valid OS string for configuring sound"
exit 1
;;
esac
case "${SOUND}" in
unix)
configure_pulseaudio_unix_socket
;;
dummy|none)
add_run_arg --env="DUMMY_PULSEAUDIO=yes"
;;
*)
echo "ERROR: '${SOUND}' is not a valid option for configuring sound"
exit 1
;;
esac
}
configure_pulseaudio_unix_socket () {
# Use audio if pulseaudio is installed
if command -v pulseaudio >/dev/null 2>&1; then
# One-off setup for creation of UNIX socket for pulseaudio to allow access for other users
if [ ! -f "${HOME}/.config/pulse/default.pa" ]; then
echo "INFO: Creating pulseaudio config file ${HOME}/.config/pulse/default.pa"
mkdir -p "${HOME}/.config/pulse"
echo -e ".include /etc/pulse/default.pa\nload-module module-native-protocol-unix auth-anonymous=1 socket=/tmp/pulse-socket" > "${HOME}/.config/pulse/default.pa"
fi
# Restart pulseaudio daemon to create the UNIX socket
if [ ! -e "/tmp/pulse-socket" ]; then
echo "INFO: No socket found for pulseaudio so restarting service..."
pulseaudio -k
pulseaudio --start
sleep 1
fi
# Add the pulseaudio UNIX socket to run args
if [ -e "/tmp/pulse-socket" ]; then
add_run_arg --volume="/tmp/pulse-socket:/tmp/pulse-socket"
else
echo "INFO: pulseaudio socket /tmp/pulse-socket doesn't exist, so sound will not function"
fi
else
echo "INFO: pulseaudio not installed so running without sound"
fi
}
run_container () {
local mode
case "$1" in
interactive)
mode="-it"
;;
detached)
mode="--detach"
;;
*)
echo "ERROR: '$1' is not a valid container run mode"
exit 1
;;
esac
# Add common docker run args
add_run_arg --rm
add_run_arg --hostname="$(hostname)"
add_run_arg --name="${CONTAINER_NAME}"
add_run_arg --shm-size="${SHM_SIZE}"
add_run_arg --workdir="${WORKDIR}"
add_run_arg_timezone
# Append -nordp to image tag if NO_RDP is set to 'yes'
if [ "${NO_RDP}" == "yes" ]; then
# Only append if image tag specified does not already end in -nordp
if ! echo "${IMAGE_TAG}" | grep -q -E "\-nordp$"; then
IMAGE_TAG="${IMAGE_TAG}-nordp"
fi
fi
# Grab the latest image from docker hub or use the locally built version
if [ "${USE_LOCAL_IMAGE}" == "no" ]; then
[ "${DOCKER_PULL_IMAGE}" == "yes" ] && docker pull "${DOCKER_IMAGE}:${IMAGE_TAG}"
else
DOCKER_IMAGE="${LOCAL_IMAGE}"
fi
# Add volume for user home only if not using --rm option
if [ "${NO_USER_VOLUME}" == "no" ]; then
add_run_arg --volume="${USER_VOLUME}:${USER_HOME}"
# Create the docker volume to store user's home only if using default winehome
if [ "${USER_VOLUME}" == "winehome" ] && ! docker volume ls -qf "name=winehome" | grep -q "^winehome$"; then
echo "INFO: Creating Docker volume container 'winehome'..."
docker volume create winehome
fi
fi
# NOTTY rules them all
if [ "${NOTTY}" == "yes" ] ; then
docker run "${RUN_ARGS[@]}" "${DOCKER_IMAGE}:${IMAGE_TAG}" "${CMD_ARGS[@]}"
else
docker run "${mode}" "${RUN_ARGS[@]}" "${DOCKER_IMAGE}:${IMAGE_TAG}" "${CMD_ARGS[@]}"
fi
}
# Set default values
CONTAINER_NAME="wine"
DOCKER_IMAGE="scottyhardy/docker-wine"
DOCKER_PULL_IMAGE="yes"
HOST_RDP_PORT="3389"
IMAGE_TAG="latest"
LOCAL_IMAGE="docker-wine"
NO_RDP="no"
NO_USER_VOLUME="no"
NOTTY="no"
SHM_SIZE="1g"
SOUND="default"
USE_LOCAL_IMAGE="no"
USE_RDP_SERVER="no"
USER_HOME="/home/wineuser"
USER_VOLUME="winehome"
WORKDIR="${USER_HOME}"
USE_XVFB="no"
XVFB_RESOLUTION="320x240x8"
XVFB_SCREEN="0"
XVFB_SERVER=":95"
# Array to store all of the `docker run` arguments
RUN_ARGS=()
while [ $# -gt 0 ]; do
case "$1" in
--cache)
DOCKER_PULL_IMAGE="no"
;;
--local)
USE_LOCAL_IMAGE="yes"
;;
--local=*)
USE_LOCAL_IMAGE="yes"
LOCAL_IMAGE="${1#*=}"
;;
--rm)
NO_USER_VOLUME="yes"
;;
--tag=*)
IMAGE_TAG="${1#*=}"
;;
--name=*)
CONTAINER_NAME="${1#*=}"
;;
--arm64)
add_run_arg --platform="linux/arm64"
IMAGE_TAG="arm64"
;;
--as-root)
add_run_arg --env="RUN_AS_ROOT=yes"
WORKDIR="/"
;;
--as-me)
add_run_args_for_as_me
;;
--notty)
NOTTY="yes"
;;
--nordp)
NO_RDP="yes"
;;
--rdp)
USE_RDP_SERVER="interactive"
;;
--rdp=*)
USE_RDP_SERVER="${1#*=}"
;;
--rdp-port=*)
HOST_RDP_PORT="${1#*=}"
;;
--shm-size=*)
SHM_SIZE="${1#*=}"
;;
--xvfb)
USE_XVFB="yes"
;;
--xvfb=*)
USE_XVFB="yes"
IFS=, read -r XVFB_SERVER XVFB_SCREEN XVFB_RESOLUTION <<< "${1#*=}"
;;
--sound=*)
SOUND="${1#*=}"
;;
--home-volume=*)
USER_VOLUME="${1#*=}"
# Start container as self to prevent unintentionally changing ownership of a user's local filesystem by wineuser
add_run_args_for_as_me
;;
--home=*)
USER_HOME="${1#*=}"
add_run_arg --env="USER_HOME=${USER_HOME}"
;;
--force-owner)
add_run_arg --env="FORCED_OWNERSHIP=yes"
;;
--nosudo)
add_run_arg --env="USER_SUDO=no"
;;
--password=*)
encrypt_password "${1#*=}"
;;
--password-prompt)
read -r -s -p "Password: " PASSWD
echo
encrypt_password "${PASSWD}"
;;
--secure-password=*)
add_run_arg --env="USER_PASSWD=${1#*=}"
;;
--device=*|--env=*|--mount=*|--network=*|--volume=*)
add_run_arg "$1"
;;
--workdir=*)
WORKDIR="${1#*=}"
;;
--help)
print_help
exit 0
;;
-*)
echo "ERROR: '$1' is not a valid option"
echo
print_help
exit 1
;;
*)
break
;;
esac
shift
done
# Collect remaining command line args to pass to the container to run
CMD_ARGS=("$@")
# Sanity checks
if ! docker system info >/dev/null 2>&1; then
echo "ERROR: Docker is not running or not installed, unable to proceed"
exit 1
fi
if ! echo "${USE_RDP_SERVER}" | grep -q -E "^(no|start|stop|restart|interactive)$"; then
echo "ERROR: '${USE_RDP_SERVER}' is not a valid value for --rdp option"
exit 1
fi
if [ "${USE_RDP_SERVER}" != "no" ] && [ -n "${CMD_ARGS[0]}" ]; then
echo "ERROR: Commands cannot be passed to container when using --rdp option"
exit 1
fi
if [ "${USE_RDP_SERVER}" != "no" ] && [ "${NO_RDP}" != "no" ]; then
echo "ERROR: Cannot combine conflicting options --rdp and --nordp"
exit 1
fi
# Run xvfb and send everything into the void
if [ "${USE_XVFB}" == "yes" ] ; then
add_run_arg --env="USE_XVFB=yes"
add_run_arg --env="XVFB_SERVER=${XVFB_SERVER}"
add_run_arg --env="XVFB_SCREEN=${XVFB_SCREEN}"
add_run_arg --env="XVFB_RESOLUTION=${XVFB_RESOLUTION}"
add_run_arg --env="DISPLAY=${XVFB_SERVER}"
run_container "interactive"
# Run in RDP mode
elif [ "${USE_RDP_SERVER}" != "no" ]; then
add_run_arg --env="RDP_SERVER=yes"
add_run_arg --publish="${HOST_RDP_PORT}:3389/tcp"
case "${USE_RDP_SERVER}" in
interactive)
CMD_ARGS=("/bin/bash")
run_container "interactive"
;;
start)
run_container "detached"
;;
stop)
docker kill "${CONTAINER_NAME}"
;;
restart)
docker kill "${CONTAINER_NAME}"
run_container "detached"
;;
*)
echo "ERROR: '${USE_RDP_SERVER}' is not a valid value for --rdp option"
exit 1
;;
esac
# Run in X11 forwarding mode
else
# Set CMD_ARGS to /bin/bash if no commands specified
[ -z "${CMD_ARGS[0]}" ] && CMD_ARGS=("/bin/bash")
# Run in X11 forwarding mode on macOS
if [ "$(uname)" == "Darwin" ]; then
# Advise to reboot if need to configure XQuartz
if configure_xquartz; then
echo "INFO: XQuartz configuration updated. Please reboot to enable X11 forwarding to operate."
exit 0
fi
# Ensure XQuartz is running so ~/.Xauthority file is updated with new X11 key
if ! ps -e | awk '{print $4}' | grep -q "/opt/X11/bin/Xquartz"; then
open -a xquartz
fi
# Store the X11 key for the display in ~/.docker-wine.Xkey and add to run args
add_x11_key ":0"
# Configure sound output
configure_sound "macos"
# Add macOS run args
add_run_arg --env="DISPLAY=host.docker.internal:0"
run_container "interactive"
# Run in X11 forwarding mode on Linux
elif [ "$(uname)" == "Linux" ]; then
# Store the X11 key for the display in ~/.docker-wine.Xkey and add to run args
add_x11_key "$DISPLAY"
# Configure sound output
configure_sound "linux"
# Add Linux run args
add_run_arg --env="DISPLAY"
add_run_arg --volume="/tmp/.X11-unix:/tmp/.X11-unix:ro"
run_container "interactive"
else
echo "ERROR: '$(uname)' OS is not supported"
exit 1
fi
fi