initial commit

This commit is contained in:
Josh.5
2022-01-11 01:03:30 +13:00
parent d1daefe07d
commit 51e965de8e
23 changed files with 1489 additions and 2 deletions

1
.dockerignore Executable file
View File

@@ -0,0 +1 @@
config

1
.gitignore vendored Executable file
View File

@@ -0,0 +1 @@
config

363
Dockerfile Normal file
View File

@@ -0,0 +1,363 @@
FROM debian:bullseye-slim
# Update package repos
ARG DEBIAN_FRONTEND=noninteractive
RUN \
echo "**** Update apt database ****" \
&& sed -i '/^.*main/ s/$/ contrib non-free/' /etc/apt/sources.list \
&& \
echo
# Update locale
RUN \
echo "**** Update apt database ****" \
&& apt-get update \
&& \
echo "**** Install and configure locals ****" \
&& apt-get -y install --no-install-recommends \
locales \
procps \
&& echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen \
&& locale-gen \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
ENV \
LANG=en_US.UTF-8 \
LANGUAGE=en_US:en \
LC_ALL=en_US.UTF-8
# Re-install certificates
RUN \
echo "**** Update apt database ****" \
&& apt-get update \
&& \
echo "**** Install certificates ****" \
&& apt-get -y install --reinstall \
ca-certificates \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
# Install core packages
RUN \
echo "**** Update apt database ****" \
&& apt-get update \
&& \
echo "**** Install tools ****" \
&& apt-get -y install \
bash \
bash-completion \
gcc \
git \
kmod \
less \
make \
nano \
sudo \
vim \
wget \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
# Install desktop requirements
RUN \
echo "**** Update apt database ****" \
&& apt-get update \
&& \
echo "**** Install desktop requirements ****" \
&& apt-get -y install --no-install-recommends \
dbus-x11 \
libxcomposite-dev \
libxcursor1 \
man-db \
mlocate \
net-tools \
pciutils \
pkg-config \
python3 \
python3-numpy \
python3-setuptools \
rsync \
x11vnc \
xauth \
xorg \
xserver-xorg-legacy \
xvfb \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
# Install supervisor
RUN \
echo "**** Update apt database ****" \
&& apt-get update \
&& \
echo "**** Install supervisor ****" \
&& apt-get -y install \
supervisor \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
# Install openssh server
RUN \
echo "**** Update apt database ****" \
&& apt-get update \
&& \
echo "**** Install openssh server ****" \
&& apt-get -y install \
openssh-server \
&& echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
# Install noVNC
ARG NOVNC_VERSION=1.2.0
RUN \
echo "**** Fetch noVNC ****" \
&& cd /tmp \
&& wget -O /tmp/novnc.tar.gz https://github.com/novnc/noVNC/archive/v${NOVNC_VERSION}.tar.gz \
&& \
echo "**** Extract noVNC ****" \
&& cd /tmp \
&& tar -xvf /tmp/novnc.tar.gz \
&& \
echo "**** Configure noVNC ****" \
&& cd /tmp/noVNC-${NOVNC_VERSION} \
&& sed -i 's/credentials: { password: password } });/credentials: { password: password },\n wsProtocols: ["'"binary"'"] });/g' app/ui.js \
&& mkdir -p /opt \
&& rm -rf /opt/noVNC \
&& cd /opt \
&& mv -f /tmp/noVNC-${NOVNC_VERSION} /opt/noVNC \
&& cd /opt/noVNC \
&& ln -s vnc.html index.html \
&& chmod -R 755 /opt/noVNC \
&& \
echo "**** Modify noVNC title ****" \
&& sed -i '/ document.title =/c\ document.title = "Steam Headless - noVNC";' \
/opt/noVNC/app/ui.js \
&& \
echo "**** Section cleanup ****" \
&& rm -rf \
/tmp/noVNC* \
/tmp/novnc.tar.gz
# Install Websockify
ARG WEBSOCKETIFY_VERSION=0.10.0
RUN \
echo "**** Fetch Websockify ****" \
&& cd /tmp \
&& wget -O /tmp/websockify.tar.gz https://github.com/novnc/websockify/archive/v${WEBSOCKETIFY_VERSION}.tar.gz \
&& \
echo "**** Extract Websockify ****" \
&& cd /tmp \
&& tar -xvf /tmp/websockify.tar.gz \
&& \
echo "**** Install Websockify to main ****" \
&& cd /tmp/websockify-${WEBSOCKETIFY_VERSION} \
&& python3 ./setup.py install \
&& \
echo "**** Install Websockify to noVNC path ****" \
&& cd /tmp \
&& mv -v /tmp/websockify-${WEBSOCKETIFY_VERSION} /opt/noVNC/utils/websockify \
&& \
echo "**** Section cleanup ****" \
&& rm -rf \
/tmp/websockify-* \
/tmp/websockify.tar.gz
# Install desktop environment
RUN \
echo "**** Update apt database ****" \
&& apt-get update \
&& \
echo "**** Install desktop environment ****" \
&& apt-get -y install \
xfce4 \
xfce4-terminal \
msttcorefonts \
fonts-vlgothic \
gedit \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
# Install firefox
RUN \
echo "**** Update apt database ****" \
&& apt-get update \
&& \
echo "**** Install firefox ****" \
&& apt-get -y install \
firefox-esr \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
# Install Steam
RUN \
echo "**** Install steam ****" \
&& dpkg --add-architecture i386 \
&& apt-get update \
&& echo steam steam/question select "I AGREE" | debconf-set-selections \
&& echo steam steam/license note '' | debconf-set-selections \
&& apt-get -y install \
steam \
steam-devices \
vulkan-tools \
mesa-utils \
mesa-vulkan-drivers \
libglx-mesa0:i386 \
mesa-vulkan-drivers:i386 \
libgl1-mesa-dri:i386 \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
# Setup browser audio streaming deps
RUN \
echo "**** Update apt database ****" \
&& apt-get update \
&& \
echo "**** Install audio streaming deps ****" \
&& apt-get -y install --no-install-recommends \
bzip2 \
gstreamer1.0-alsa \
gstreamer1.0-gl \
gstreamer1.0-gtk3 \
gstreamer1.0-libav \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-pulseaudio \
gstreamer1.0-qt5 \
gstreamer1.0-tools \
gstreamer1.0-x \
libglu1-mesa \
libgstreamer1.0-0 \
libgtk2.0-0 \
libncursesw5 \
libopenal1 \
libsdl-image1.2 \
libsdl-ttf2.0-0 \
libsdl1.2debian \
libsndfile1 \
novnc \
pulseaudio \
ucspi-tcp \
&& \
echo "**** Section cleanup ****" \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt/lists/* \
/var/tmp/* \
/tmp/* \
&& \
echo
# Configure default user and set env
ENV \
USER="default" \
USER_PASSWORD="password" \
USER_HOME="/home/default" \
TZ="Pacific/Auckland" \
USER_LOCALES="en_US.UTF-8 UTF-8"
RUN \
echo "**** Configure default user '${USER}' ****" \
&& mkdir -p \
${USER_HOME} \
/games \
&& useradd -d ${USER_HOME} -s /bin/bash ${USER} \
&& chown -R ${USER} \
${USER_HOME} \
/games \
&& echo "${USER} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
# Add FS overlay
COPY overlay /
# Set display environment variables
ENV \
DISPLAY_CDEPTH="24" \
DISPLAY_DPI="96" \
DISPLAY_REFRESH="60" \
DISPLAY_SIZEH="900" \
DISPLAY_SIZEW="1600" \
DISPLAY_VIDEO_PORT="DFP" \
DISPLAY=":0" \
NVIDIA_DRIVER_CAPABILITIES="all" \
NVIDIA_VISIBLE_DEVICES="all"
# Be sure that the noVNC port is exposed
EXPOSE 8083
EXPOSE 32123
# Set entrypoint
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,2 +1,41 @@
# docker-steam-headless
Headless Steam supporting NVIDIA GPU and accessible via NoVNC
# Headless Steam Service
Play your games in the browser with audio. Connect another device and use it with Steam Remote Play.
## Features:
- Full video/audio noVNC web access to a Xfce4 Desktop
- NVIDIA GPU support
- Root access
- SSH server for remote terminal access
---
## Notes:
### ADDITIONAL SOFTWARE:
If you wish to install additional applications, you can generate a
script inside the `~/init.d` directory ending with ".sh". This will be executed on the container startup.
### STORAGE PATHS:
Everything that you wish to save in this container should be stored in the home directory or a docker container mount that you have specified. All files that are store outside your home directory are not persistent and will be wiped if there is an update of the container or you change something in the template.
### GAMES LIBRARY:
It is recommended that you mount your games library to `/games` and configure Steam to add that path.
### AUTO START APPLICATIONS:
In this container, Steam is configured to automatically start. If you wish to add additional services to automatically start, add them under **Applications > Settings > Session and Startup** in the WebUI.
### NETWORK MODE:
If you want to use the container as a Steam Remote Play (previously "In Home Streaming") host device you should create a custom network and assign this container it's own IP, if you don't do this the traffic will be routed through the internet since Steam thinks you are on a different network.
---
## Running:
For a development environment, I have created a script in the devops directory.
---
## TODO:
- Configure xorg.conf with no NVIDIA GPU
- Lock SSH access to user only (remove root access)
- Require user to enter password for sudo
- Document how to run this container

66
devops/run_server.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
###
# File: run.sh
# Project: docker-steamos
# File Created: Saturday, 8th January 2022 2:34:23 pm
# Author: Josh.5 (jsunnex@gmail.com)
# -----
# Last Modified: Monday, 10th January 2022 11:04:51 pm
# Modified By: Josh.5 (jsunnex@gmail.com)
###
script_path=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd );
project_base_path=$(realpath ${script_path}/..);
if [[ ${1} == "stop" ]]; then
docker stop steam-headless
docker rm steam-headless
exit $?
elif [[ ${1} == "tail" ]]; then
docker logs -f steam-headless
exit $?
elif [[ ${1} == "user" ]]; then
docker exec -ti --user default steam-headless bash
exit $?
elif [[ ${1} == "root" ]]; then
docker exec -ti --user 0 steam-headless bash
exit $?
fi
docker stop steam-headless
docker rm steam-headless
sleep 1
docker run -d --name='steam-headless' \
--privileged=true \
--net='br0' --ip='192.168.1.208' \
--cpuset-cpus='3,9,4,10,5,11' \
-e PUID="99" \
-e PGID="100" \
-e UMASK='000' \
-e USER_PASSWORD="password" \
-e USER="default" \
-e HOME="/home/test" \
-e USER_HOME="/home/default" \
-e TZ="Pacific/Auckland" \
-e USER_LOCALES="en_US.UTF-8 UTF-8" \
-e DISPLAY_CDEPTH="24" \
-e DISPLAY_DPI="96" \
-e DISPLAY_REFRESH="60" \
-e DISPLAY_SIZEH="720" \
-e DISPLAY_SIZEW="1280" \
-e DISPLAY_VIDEO_PORT="DFP" \
-e DISPLAY=":0" \
-e NVIDIA_DRIVER_CAPABILITIES="all" \
-e NVIDIA_VISIBLE_DEVICES="all" \
-v "${project_base_path}/config/home/default":'/home/default':'rw' \
--hostname='steam-headless' \
--shm-size=2G \
--runtime=nvidia \
josh5/steam-headless:latest
docker logs -f steam-headless

BIN
images/steam-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

47
overlay/entrypoint.sh Normal file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env bash
###
# File: entrypoint.sh
# Project: docker-steamos
# File Created: Saturday, 8th January 2022 7:08:46 pm
# Author: Josh.5 (jsunnex@gmail.com)
# -----
# Last Modified: Tuesday, 11th January 2022 1:02:16 am
# Modified By: Josh.5 (jsunnex@gmail.com)
###
set -e
# If a command was passed, run that instead of the usual init process
if [ ! -z "$@" ]; then
exec $@
exit $?
fi
# Execute all init scripts
for init_script in /scripts/*.sh ; do
echo
echo "[ ${init_script}: executing... ]"
sed -i 's/\r$//' "${init_script}"
source "${init_script}"
done
# Execute any user generated init scripts
mkdir -p ${USER_HOME}/init.d
chown -R ${USER} ${USER_HOME}/init.d
for user_init_script in ${USER_HOME}/init.d/*.sh ; do
# Check that a file was found
# (If no files exist in this directory, then user_init_script will be empty)
if [[ -e "${user_init_script}" ]]; then
echo
echo "[ USER:${user_init_script}: executing... ]"
sed -i 's/\r$//' "${user_init_script}"
source "${user_init_script}"
fi
done
echo "**** Starting supervisord ****";
exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf --nodaemon

View File

@@ -0,0 +1,9 @@
#
# ~/.bashrc
#
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
alias ls='ls --color=auto'
PS1='[\u@\h \W]\$ '

View File

@@ -0,0 +1,14 @@
[Desktop Entry]
Encoding=UTF-8
Version=0.9.4
Type=Application
Name=Steam
Comment=launch steam on login
Exec=/usr/games/steam %U
Icon=steam
OnlyShowIn=XFCE;
RunHook=0
StartupNotify=false
Terminal=false
Hidden=false

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<channel name="xfce4-desktop" version="1.0">
<property name="backdrop" type="empty">
<property name="screen0" type="empty">
<property name="monitor0" type="empty">
<property name="brightness" type="empty"/>
<property name="color1" type="empty"/>
<property name="color2" type="empty"/>
<property name="color-style" type="empty"/>
<property name="image-path" type="empty"/>
<property name="image-show" type="empty"/>
<property name="last-image" type="empty"/>
<property name="last-single-image" type="empty"/>
</property>
<property name="monitor1" type="empty">
<property name="brightness" type="empty"/>
<property name="color1" type="empty"/>
<property name="color2" type="empty"/>
<property name="color-style" type="empty"/>
<property name="image-path" type="empty"/>
<property name="image-show" type="empty"/>
<property name="last-image" type="empty"/>
<property name="last-single-image" type="empty"/>
</property>
<property name="monitorDVI-D-0" type="empty">
<property name="workspace0" type="empty">
<property name="color-style" type="int" value="1"/>
<property name="image-style" type="int" value="5"/>
<property name="last-image" type="string" value="/usr/share/images/desktop-base/default"/>
</property>
<property name="workspace1" type="empty">
<property name="color-style" type="int" value="1"/>
<property name="image-style" type="int" value="5"/>
<property name="last-image" type="string" value="/usr/share/images/desktop-base/default"/>
</property>
<property name="workspace2" type="empty">
<property name="color-style" type="int" value="1"/>
<property name="image-style" type="int" value="5"/>
<property name="last-image" type="string" value="/usr/share/images/desktop-base/default"/>
</property>
<property name="workspace3" type="empty">
<property name="color-style" type="int" value="1"/>
<property name="image-style" type="int" value="5"/>
<property name="last-image" type="string" value="/usr/share/images/desktop-base/default"/>
</property>
</property>
</property>
</property>
</channel>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<channel name="xfce4-panel" version="1.0">
<property name="configver" type="int" value="2"/>
<property name="panels" type="array">
<value type="int" value="1"/>
<property name="dark-mode" type="bool" value="true"/>
<property name="panel-1" type="empty">
<property name="position" type="string" value="p=6;x=0;y=0"/>
<property name="length" type="uint" value="100"/>
<property name="position-locked" type="bool" value="true"/>
<property name="icon-size" type="uint" value="16"/>
<property name="size" type="uint" value="26"/>
<property name="plugin-ids" type="array">
<value type="int" value="1"/>
<value type="int" value="2"/>
<value type="int" value="3"/>
<value type="int" value="4"/>
<value type="int" value="5"/>
<value type="int" value="6"/>
<value type="int" value="8"/>
<value type="int" value="10"/>
<value type="int" value="11"/>
<value type="int" value="12"/>
<value type="int" value="13"/>
<value type="int" value="14"/>
</property>
<property name="background-style" type="uint" value="0"/>
</property>
</property>
<property name="plugins" type="empty">
<property name="plugin-1" type="string" value="applicationsmenu"/>
<property name="plugin-2" type="string" value="tasklist">
<property name="grouping" type="uint" value="1"/>
</property>
<property name="plugin-3" type="string" value="separator">
<property name="expand" type="bool" value="true"/>
<property name="style" type="uint" value="0"/>
</property>
<property name="plugin-4" type="string" value="pager"/>
<property name="plugin-5" type="string" value="separator">
<property name="style" type="uint" value="0"/>
</property>
<property name="plugin-6" type="string" value="systray">
<property name="square-icons" type="bool" value="true"/>
</property>
<property name="plugin-8" type="string" value="pulseaudio">
<property name="enable-keyboard-shortcuts" type="bool" value="true"/>
<property name="show-notifications" type="bool" value="true"/>
</property>
<property name="plugin-9" type="string" value="power-manager-plugin"/>
<property name="plugin-10" type="string" value="notification-plugin"/>
<property name="plugin-11" type="string" value="separator">
<property name="style" type="uint" value="0"/>
</property>
<property name="plugin-12" type="string" value="clock"/>
<property name="plugin-13" type="string" value="separator">
<property name="style" type="uint" value="0"/>
</property>
<property name="plugin-14" type="string" value="actions"/>
</property>
</channel>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<channel name="xsettings" version="1.0">
<property name="Net" type="empty">
<property name="ThemeName" type="string" value="Adwaita-dark"/>
<property name="IconThemeName" type="string" value="Adwaita"/>
<property name="DoubleClickTime" type="empty"/>
<property name="DoubleClickDistance" type="empty"/>
<property name="DndDragThreshold" type="empty"/>
<property name="CursorBlink" type="empty"/>
<property name="CursorBlinkTime" type="empty"/>
<property name="SoundThemeName" type="empty"/>
<property name="EnableEventSounds" type="empty"/>
<property name="EnableInputFeedbackSounds" type="empty"/>
</property>
<property name="Xft" type="empty">
<property name="DPI" type="empty"/>
<property name="Antialias" type="empty"/>
<property name="Hinting" type="empty"/>
<property name="HintStyle" type="empty"/>
<property name="RGBA" type="empty"/>
</property>
<property name="Gtk" type="empty">
<property name="CanChangeAccels" type="empty"/>
<property name="ColorPalette" type="empty"/>
<property name="FontName" type="empty"/>
<property name="MonospaceFontName" type="empty"/>
<property name="IconSizes" type="empty"/>
<property name="KeyThemeName" type="empty"/>
<property name="ToolbarStyle" type="empty"/>
<property name="ToolbarIconSize" type="empty"/>
<property name="MenuImages" type="empty"/>
<property name="ButtonImages" type="empty"/>
<property name="MenuBarAccel" type="empty"/>
<property name="CursorThemeName" type="empty"/>
<property name="CursorThemeSize" type="empty"/>
<property name="DecorationLayout" type="empty"/>
<property name="DialogsUseHeader" type="empty"/>
<property name="TitlebarMiddleClick" type="empty"/>
</property>
<property name="Gdk" type="empty">
<property name="WindowScalingFactor" type="empty"/>
</property>
</channel>

View File

@@ -0,0 +1,12 @@
# Hide application from menu
[Desktop Entry]
Name=X11VNC Server
Comment=Share this desktop by VNC
Exec=x11vnc -gui tray=setpass -rfbport PROMPT -bg -o %%HOME/.x11vnc.log.%%VNCDISPLAY
Icon=computer
Terminal=false
Type=Application
StartupNotify=false
#StartupWMClass=x11vnc_port_prompt
Categories=Network;RemoteAccess;
Hidden=true

View File

@@ -0,0 +1,130 @@
[supervisord]
user=root
nodaemon=true
[program:ssh]
autostart=true
priority=10
directory=/
command=/usr/sbin/sshd -D
user=root
autorestart=true
stopsignal=QUIT
[program:dbus]
autostart=true
priority=10
directory=/
command=dbus-daemon --config-file=/usr/share/dbus-1/system.conf --nofork --nopidfile
user=%(ENV_USER)s
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s"
autorestart=true
stopsignal=QUIT
[program:pulseaudio]
autostart=true
priority=10
directory=/
command=/usr/bin/pulseaudio -vvvv --disallow-module-loading --disallow-exit --exit-idle-time=-1
user=%(ENV_USER)s
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s"
autorestart=true
stopsignal=QUIT
stdout_logfile=/home/%(ENV_USER)s/.cache/log/pulseaudio.log
stderr_logfile=/home/%(ENV_USER)s/.cache/log/pulseaudio.err
[program:audiostream]
autostart=true
priority=10
command=tcpserver localhost 5901 gst-launch-1.0 -q pulsesrc server=/tmp/pulseaudio.socket ! audio/x-raw, channels=2, rate=24000 ! cutter ! opusenc ! webmmux ! fdsink fd=1
autorestart=true
stopsignal=QUIT
stdout_logfile=/home/%(ENV_USER)s/.cache/log/audiostream.log
stderr_logfile=/home/%(ENV_USER)s/.cache/log/audiostream.err
[program:audiowebsock]
autostart=true
priority=10
command=/usr/local/bin/websockify 32123 localhost:5901
autorestart=true
stopsignal=QUIT
stdout_logfile=/home/%(ENV_USER)s/.cache/log/audiowebsock.log
stderr_logfile=/home/%(ENV_USER)s/.cache/log/audiowebsock.err
[program:xorg]
autostart=true
priority=20
directory=/
command=/usr/bin/Xorg vt7 -novtswitch -sharevts -dpi "%(ENV_DISPLAY_DPI)s" +extension "MIT-SHM" %(ENV_DISPLAY)s
#command=/usr/bin/Xorg -novtswitch -sharevts -dpi "%(ENV_DISPLAY_DPI)s" +extension "MIT-SHM" "%(ENV_DISPLAY)s"
user=%(ENV_USER)s
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s",XDG_RUNTIME_DIR="/tmp/xdg",RUNLEVEL="3"
autorestart=true
stopsignal=QUIT
stdout_logfile=/home/%(ENV_USER)s/.cache/log/xorg.log
stderr_logfile=/home/%(ENV_USER)s/.cache/log/xorg.err
# Dont use xvfb as it does not work with nvidia X11 configuration
# [program:xvfb]
# autostart=false
# priority=20
# directory=/
# command=/usr/bin/Xvfb %(ENV_DISPLAY)s -screen 0 1920x1080x24
# user=%(ENV_USER)s
# environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s"
# autorestart=true
# stopsignal=QUIT
# stdout_logfile=/home/%(ENV_USER)s/.cache/log/xvfb.log
# stderr_logfile=/home/%(ENV_USER)s/.cache/log/xvfb.err
[program:x11vnc]
autostart=true
priority=30
directory=/
#command=/usr/bin/x11vnc -display %(ENV_DISPLAY)s -xkb
command=/usr/bin/x11vnc -display %(ENV_DISPLAY)s -rfbport 5900 -shared -forever
user=%(ENV_USER)s
autorestart=true
stopsignal=QUIT
stdout_logfile=/home/%(ENV_USER)s/.cache/log/x11vnc.log
stderr_logfile=/home/%(ENV_USER)s/.cache/log/x11vnc.err
[program:de]
autostart=true
priority=50
directory=/home/%(ENV_USER)s
command=/usr/bin/startxfce4
#command=/usr/bin/mate-session
#command=/usr/bin/startlxqt
#command=/usr/bin/dbus-launch startxfce4
#command=/usr/bin/dbus-launch xfce4-session
#command=/usr/bin/dbus-launch startplasma-x11
user=%(ENV_USER)s
autorestart=true
stopsignal=QUIT
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
stdout_logfile=/home/%(ENV_USER)s/.cache/log/de.log
stderr_logfile=/home/%(ENV_USER)s/.cache/log/de.err
## Experimental launch directly to steam big picture... (this is cool, but not ideal for headless)
# [program:steam]
# autostart=true
# priority=50
# directory=/home/%(ENV_USER)s
# command=/usr/bin/steam -bigpicture
# user=%(ENV_USER)s
# autorestart=true
# stopsignal=QUIT
# environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
# stdout_logfile=/home/%(ENV_USER)s/.cache/log/steam.log
# stderr_logfile=/home/%(ENV_USER)s/.cache/log/steam.err
[program:novnc]
autostart=true
priority=100
command=/opt/noVNC/utils/launch.sh --vnc localhost:5900 --listen 8083
#command=/usr/share/novnc/utils/launch.sh --vnc localhost:5900 --listen 8083
#command=/usr/local/bin/websockify -D --web=/usr/share/novnc/ --cert=/etc/ssl/novnc.pem 8083 localhost:5900
user=%(ENV_USER)s
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s"
autorestart=true

View File

@@ -0,0 +1,416 @@
diff --git a/app/styles/base.css b/app/styles/base.css
index adad415..dfefa6a 100644
--- a/app/styles/base.css
+++ b/app/styles/base.css
@@ -683,7 +683,7 @@ select:active {
#noVNC_setting_port {
width: 80px;
}
-#noVNC_setting_path {
+#noVNC_setting_path #noVNC_setting_apath {
width: 100px;
}
diff --git a/app/ui.js b/app/ui.js
index cb6a9fd..4d599e1 100644
--- a/app/ui.js
+++ b/app/ui.js
@@ -15,6 +15,7 @@ import KeyTable from "../core/input/keysym.js";
import keysyms from "../core/input/keysymdef.js";
import Keyboard from "../core/input/keyboard.js";
import RFB from "../core/rfb.js";
+import WebAudio from "../core/webaudio.js";
import * as WebUtil from "./webutil.js";
const PAGE_TITLE = "noVNC";
@@ -40,6 +41,7 @@ const UI = {
inhibitReconnect: true,
reconnectCallback: null,
reconnectPassword: null,
+ webaudio: null,
prime() {
return WebUtil.initSettings().then(() => {
@@ -171,6 +173,7 @@ const UI = {
UI.initSetting('compression', 2);
UI.initSetting('shared', true);
UI.initSetting('view_only', false);
+ UI.initSetting('audio', true);
UI.initSetting('show_dot', false);
UI.initSetting('path', 'websockify');
UI.initSetting('repeaterID', '');
@@ -200,6 +203,20 @@ const UI = {
}
},
+ toggleAudio() {
+ console.log('here');
+ const audio = UI.getSetting('audio');
+ if (audio) {
+ UI.webaudio.start();
+ } else {
+ if(UI.webaudio !== null) {
+ if (UI.webaudio.connected) {
+ UI.webaudio.stop();
+ }
+ }
+ }
+ },
+
/* ------^-------
* /INIT
* ==============
@@ -356,6 +373,8 @@ const UI = {
UI.addSettingChangeHandler('shared');
UI.addSettingChangeHandler('view_only');
UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
+ UI.addSettingChangeHandler('audio');
+ UI.addSettingChangeHandler('audio', UI.updateEnableAudio);
UI.addSettingChangeHandler('show_dot');
UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor);
UI.addSettingChangeHandler('host');
@@ -841,6 +860,7 @@ const UI = {
UI.updateSetting('compression');
UI.updateSetting('shared');
UI.updateSetting('view_only');
+ UI.updateSetting('audio');
UI.updateSetting('path');
UI.updateSetting('repeaterID');
UI.updateSetting('logging');
@@ -1041,6 +1061,7 @@ const UI = {
UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
+ UI.rfb.enableAudio = UI.getSetting('audio');
UI.rfb.showDotCursor = UI.getSetting('show_dot');
UI.updateViewOnly(); // requires UI.rfb
@@ -1056,6 +1077,10 @@ const UI = {
UI.updateVisualState('disconnecting');
+ if(UI.webaudio !== null && UI.webaudio.socket !== null) {
+ UI.webaudio.socket.close();
+ }
+
// Don't display the connection settings until we're actually disconnected
},
@@ -1097,6 +1122,19 @@ const UI = {
// Do this last because it can only be used on rendered elements
UI.rfb.focus();
+
+ let audio_url;
+ let host = window.location.hostname;
+ let port = 32123;
+ if (window.location.protocol === "https:") {
+ audio_url = 'wss';
+ } else {
+ audio_url = 'ws';
+ }
+ audio_url += '://' + host + ':' + port;
+
+ UI.webaudio = new WebAudio(audio_url);
+ UI.toggleAudio();
},
disconnectFinished(e) {
@@ -1647,6 +1685,12 @@ const UI = {
}
},
+ updateEnableAudio() {
+ if (!UI.rfb) return;
+ UI.rfb.enableAudio = UI.getSetting('audio');
+ UI.toggleAudio();
+ },
+
updateShowDotCursor() {
if (!UI.rfb) return;
UI.rfb.showDotCursor = UI.getSetting('show_dot');
diff --git a/core/webaudio.js b/core/webaudio.js
new file mode 100644
index 0000000..28c71ac
--- /dev/null
+++ b/core/webaudio.js
@@ -0,0 +1,142 @@
+export default class WebAudio {
+ constructor(url) {
+ this.url = url
+
+ this.connected = false;
+
+ //constants for audio behavoir
+ this.maximumAudioLag = 1.5; //amount of seconds we can potentially be behind the server audio stream
+ this.syncLagInterval = 5000; //check every x milliseconds if we are behind the server audio stream
+ this.updateBufferEvery = 20; //add recieved data to the player buffer every x milliseconds
+ this.reduceBufferInterval = 500; //trim the output audio stream buffer every x milliseconds so we don't overflow
+ this.maximumSecondsOfBuffering = 1; //maximum amount of data to store in the play buffer
+ this.connectionCheckInterval = 500; //check the connection every x milliseconds
+
+ //register all our background timers. these need to be created only once - and will run independent of the object's streams/properties
+ this.updateCheck = null;
+ this.syncCheck = null;
+ this.reduceCheck = null;
+ this.ConnCheck = null;
+
+ }
+
+ //registers all the event handlers for when this stream is closed - or when data arrives.
+ registerHandlers() {
+ this.mediaSource.addEventListener('sourceended', e => this.socketDisconnected(e))
+ this.mediaSource.addEventListener('sourceclose', e => this.socketDisconnected(e))
+ this.mediaSource.addEventListener('error', e => this.socketDisconnected(e))
+ this.buffer.addEventListener('error', e => this.socketDisconnected(e))
+ this.buffer.addEventListener('abort', e => this.socketDisconnected(e))
+ }
+
+ //starts the web audio stream. only call this method on button click.
+ start() {
+ if (!!this.connected) return;
+ if (!!this.audio) this.audio.remove();
+ this.queue = null;
+
+ if (this.updateCheck === null) this.updateCheck = setInterval(() => this.updateQueue(), this.updateBufferEvery);
+ if (this.syncCheck === null) this.syncCheck = setInterval(() => this.syncInterval(), this.syncLagInterval);
+ if (this.reduceCheck === null) this.reduceCheck = setInterval(() => this.reduceBuffer(), this.reduceBufferInterval);
+ if (this.ConnCheck === null) this.ConnCheck = setInterval(() => this.tryLastPacket(), this.connectionCheckInterval);
+
+ this.mediaSource = new MediaSource()
+ this.mediaSource.addEventListener('sourceopen', e => this.onSourceOpen())
+ //first we need a media source - and an audio object that contains it.
+ this.audio = document.createElement('audio');
+ this.audio.src = window.URL.createObjectURL(this.mediaSource);
+
+ //start our stream - we can only do this on user input
+ this.audio.play();
+ }
+
+ stop() {
+ // Clear all interval timers
+ clearInterval(this.updateCheck);
+ clearInterval(this.syncCheck);
+ clearInterval(this.reduceCheck);
+ clearInterval(this.ConnCheck);
+ // Close the socket
+ this.socket.close();
+ this.connected = false;
+ // Reset timers to null
+ this.updateCheck = null;
+ this.syncCheck = null;
+ this.reduceCheck = null;
+ this.ConnCheck = null;
+ }
+
+ wsConnect() {
+ if (!!this.socket) this.socket.close();
+
+ this.socket = new WebSocket(this.url, ['binary', 'base64'])
+ this.socket.binaryType = 'arraybuffer'
+ this.socket.addEventListener('message', e => this.websocketDataArrived(e), false);
+ }
+
+ //this is called when the media source contains data
+ onSourceOpen(e) {
+ this.buffer = this.mediaSource.addSourceBuffer('audio/webm; codecs="opus"')
+ this.registerHandlers();
+ this.wsConnect();
+ }
+
+ //whenever data arrives in our websocket this is called.
+ websocketDataArrived(e) {
+ this.lastPacket = Date.now();
+ this.connected = true;
+ this.queue = this.queue == null ? e.data : this.concat(this.queue, e.data);
+ }
+
+ //whenever a disconnect happens this is called.
+ socketDisconnected(e) {
+ console.log(e);
+ this.connected = false;
+ }
+
+ tryLastPacket() {
+ if (this.lastPacket == null) return;
+ if ((Date.now() - this.lastPacket) > 1000) {
+ this.socketDisconnected('timeout');
+ }
+ }
+
+ //this updates the buffer with the data from our queue
+ updateQueue() {
+ if (!(!!this.queue && !!this.buffer && !this.buffer.updating)) {
+ return;
+ }
+
+ this.buffer.appendBuffer(this.queue);
+ this.queue = null;
+ }
+
+ //reduces the stream buffer to the minimal size that we need for streaming
+ reduceBuffer() {
+ if (!(this.buffer && !this.buffer.updating && !!this.audio && !!this.audio.currentTime && this.audio.currentTime > 1)) {
+ return;
+ }
+
+ this.buffer.remove(0, this.audio.currentTime - 1);
+ }
+
+ //synchronizes the current time of the stream with the server
+ syncInterval() {
+ if (!(this.audio && this.audio.currentTime && this.audio.currentTime > 1 && this.buffer && this.buffer.buffered && this.buffer.buffered.length > 1)) {
+ return;
+ }
+
+ var currentTime = this.audio.currentTime;
+ var targetTime = this.buffer.buffered.end(this.buffer.buffered.length - 1);
+
+ if (targetTime > (currentTime + this.maximumAudioLag)) this.audio.fastSeek(targetTime);
+ }
+
+ //joins two data arrays - helper function
+ concat(buffer1, buffer2) {
+ var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
+ tmp.set(new Uint8Array(buffer1), 0);
+ tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
+ return tmp.buffer;
+ };
+}
diff --git a/vnc.html b/vnc.html
index c678c2a..d1652a0 100644
--- a/vnc.html
+++ b/vnc.html
@@ -170,6 +170,10 @@
<label><input id="noVNC_setting_view_only" type="checkbox"> View Only</label>
</li>
<li><hr></li>
+ <li>
+ <label><input id="noVNC_setting_audio" type="checkbox"> Enable Audio</label>
+ </li>
+ <li><hr></li>
<li>
<label><input id="noVNC_setting_view_clip" type="checkbox"> Clip to Window</label>
</li>
diff --git a/vnc_lite.html b/vnc_lite.html
index 8e2f5cb..d3cf6ab 100644
--- a/vnc_lite.html
+++ b/vnc_lite.html
@@ -41,6 +41,15 @@
#status {
text-align: center;
}
+ #toggleAudioButton {
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ border: 1px outset;
+ padding: 5px 5px 4px 5px;
+ cursor: pointer;
+ display: none;
+ }
#sendCtrlAltDelButton {
position: fixed;
top: 0px;
@@ -60,18 +69,38 @@
<script type="module" crossorigin="anonymous">
// RFB holds the API to connect and communicate with a VNC server
import RFB from './core/rfb.js';
+ import WebAudio from './core/webaudio.js';
let rfb;
let desktopName;
+ let wa;
// When this function is called we have
// successfully connected to a server
function connectedToServer(e) {
+ let audio_url;
+ let host = window.location.hostname;
+ let port = 32123;
+ if (window.location.protocol === "https:") {
+ audio_url = 'wss';
+ } else {
+ audio_url = 'ws';
+ }
+ audio_url += '://' + host + ':' + port;
+
+ wa = new WebAudio(audio_url);
+ document.getElementById('toggleAudioButton').style.display = "block";
+
status("Connected to " + desktopName);
}
// This function is called when we are disconnected
function disconnectedFromServer(e) {
+ if(wa !== null) {
+ wa.stop();
+ wa = null;
+ document.getElementById('toggleAudioButton').style.display = "none";
+ }
if (e.detail.clean) {
status("Disconnected");
} else {
@@ -100,6 +129,19 @@
return false;
}
+ // Enable the audio websocket
+ function toggleAudio() {
+ if (wa.connected) {
+ console.log('Stopping audio...')
+ wa.stop();
+ document.getElementById('toggleAudioButton').innerText = "Enable Audio (requires access to port 32123)";
+ } else {
+ console.log('Starting audio websocket on: ' + audio_url)
+ wa.start();
+ document.getElementById('toggleAudioButton').innerText = "Disable Audio";
+ }
+ }
+
// Show a status text in the top bar
function status(text) {
document.getElementById('status').textContent = text;
@@ -135,12 +177,16 @@
document.getElementById('sendCtrlAltDelButton')
.onclick = sendCtrlAltDel;
+ document.getElementById('toggleAudioButton')
+ .onclick = toggleAudio;
+
// Read parameters specified in the URL query string
// By default, use the host and port of server that served this file
const host = readQueryVariable('host', window.location.hostname);
let port = readQueryVariable('port', window.location.port);
const password = readQueryVariable('password');
const path = readQueryVariable('path', 'websockify');
+ const audio_path = readQueryVariable('audio_path', '');
// | | | | | |
// | | | Connect | | |
@@ -150,6 +196,7 @@
// Build the websocket URL used to connect
let url;
+ let audio_url;
if (window.location.protocol === "https:") {
url = 'wss';
} else {
@@ -159,6 +206,9 @@
if(port) {
url += ':' + port;
}
+ if(audio_path !== '') {
+ audio_url = url + '/' + audio_path;
+ }
url += '/' + path;
// Creating a new RFB object will start a new connection
@@ -179,6 +229,7 @@
<body>
<div id="top_bar">
+ <div id="toggleAudioButton">Enable Audio (requires access to port 32123)</div>
<div id="status">Loading</div>
<div id="sendCtrlAltDelButton">Send CtrlAltDel</div>
</div>

View File

@@ -0,0 +1,31 @@
PUID=${PUID:-99}
PGID=${PGID:-100}
UMASK=${UMASK:-000}
USER_PASSWORD=${USER_PASSWORD:-password}
echo "**** Configure default user ****"
echo "Setting run user uid=${PGID}(${USER}) gid=${PUID}(${USER})"
groupmod -o -g "${PGID}" ${USER}
usermod -o -u "${PUID}" ${USER}
echo "Setting umask to ${UMASK}";
umask ${UMASK}
# Setup home directory and permissions
echo "Adding default home directory template"
mkdir -p ${USER_HOME}
chown -R ${PUID}:${PGID} /etc/home_directory_template
rsync -aq /etc/home_directory_template/ ${USER_HOME}/
# Set the root and user password
echo "Setting root password"
echo "root:${USER_PASSWORD}" | chpasswd
echo "Setting user password"
echo "${USER}:${USER_PASSWORD}" | chpasswd
echo "DONE"

View File

@@ -0,0 +1,10 @@
# Generate new SSH host keys if they dont exist
if [[ ! -f /etc/ssh/ssh_host_rsa_key ]]; then
echo "**** Generating SSH keys ****";
/usr/bin/ssh-keygen -A
fi
mkdir -p /run/sshd
chmod 744 /run/sshd
echo "DONE"

View File

@@ -0,0 +1,32 @@
# Configure system paths
echo "**** Configure system paths ****";
sed -i "/ <user>/c\ <user>${USER}</user>" /usr/share/dbus-1/system.conf
if [ ! -d /tmp/xdg ]; then
mkdir -p /tmp/xdg
fi
echo "Configure dbus";
# Remove old dbus session
rm -rf ${USER_HOME}/.dbus/session-bus/* 2> /dev/null
# Remove old dbus pids
mkdir -p /var/run/dbus
chown -R ${PUID}:${PGID} /var/run/dbus/
chmod -R 770 /var/run/dbus/
# Generate a dbus machine ID
dbus-uuidgen > /var/lib/dbus/machine-id
echo "Configure X Windows context"
chown -R ${PUID}:${PGID} /tmp/xdg
chmod -R 0700 /tmp/xdg
echo "Configure X Windows session"
rm -rfv /tmp/.ICE-unix*
mkdir -p /tmp/.ICE-unix
chown root:root /tmp/.ICE-unix/
chmod 1777 /tmp/.ICE-unix/
echo "Remove old lockfiles"
find /var/run/dbus -name "pid" -exec rm -f {} \;
find /tmp -name ".X99*" -exec rm -f {} \;
echo "DONE"

View File

@@ -0,0 +1,19 @@
current_local=$(head -n 1 /etc/locale.gen)
user_local=$(echo ${USER_LOCALES} | cut -d ' ' -f 1)
if [ "${current_local}" != "${USER_LOCALES}" ]; then
echo "**** Configuring Locales to ${USER_LOCALES} ****";
rm /etc/locale.gen
echo -e "${USER_LOCALES}\nen_US.UTF-8 UTF-8" > "/etc/locale.gen"
export LANGUAGE="${user_local}"
export LANG="${user_local}"
export LC_ALL="${user_local}" 2> /dev/null
sleep 2
locale-gen
update-locale LC_ALL="${user_local}"
else
echo "**** Locales already set correctly to ${USER_LOCALES} ****";
fi
echo "DONE"

View File

@@ -0,0 +1,20 @@
echo "**** Configure pulseaudio socket ****"
sed -i 's|^; default-server.*$|default-server = unix:/tmp/pulseaudio.socket|' /etc/pulse/client.conf
sed -i 's|^load-module module-native-protocol-unix.*$|load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1|' \
/etc/pulse/default.pa
chown -R ${USER} /etc/pulse
# Credits for this audio patch:
# - https://github.com/novnc/noVNC/issues/302
# - https://github.com/vexingcodes/dwarf-fortress-docker
# - https://github.com/calebj/noVNC
if [ -f /opt/noVNC/audio.patch ]; then
echo "**** Patching noVNC with audio websocket ****"
pushd /opt/noVNC/ &> /dev/null
patch -p1 --input=/opt/noVNC/audio.patch --batch --quiet
popd &> /dev/null
rm /opt/noVNC/audio.patch
fi
echo "DONE"

View File

@@ -0,0 +1,64 @@
# Fech NVIDIA GPU device (if one exists)
if [ "$NVIDIA_VISIBLE_DEVICES" == "all" ]; then
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
elif [ -z "$NVIDIA_VISIBLE_DEVICES" ]; then
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
else
export gpu_select=$(nvidia-smi --format=csv --id=$(echo "$NVIDIA_VISIBLE_DEVICES" | cut -d ',' -f1) --query-gpu=uuid | sed -n 2p)
if [ -z "$gpu_select" ]; then
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
fi
fi
export nvidia_pci_address="$(nvidia-smi --format=csv --query-gpu=pci.bus_id --id="${gpu_select}" 2> /dev/null | sed -n 2p | cut -d ':' -f2,3)"
export nvidia_gpu_name=$(nvidia-smi --format=csv --query-gpu=name --id="${gpu_select}" | sed -n 2p 2> /dev/null)
export nvidia_host_driver_version="$(nvidia-smi 2> /dev/null | grep NVIDIA-SMI | cut -d ' ' -f3)"
function download_driver {
mkdir -p ${USER_HOME}/.cache/nvidia
if [[ ! -f "${USER_HOME}/.cache/nvidia/NVIDIA_${nvidia_host_driver_version}.run" ]]; then
echo "Downloading driver"
wget -q --show-progress --progress=bar:force:noscroll \
-O /tmp/NVIDIA.run \
http://download.nvidia.com/XFree86/Linux-x86_64/${nvidia_host_driver_version}/NVIDIA-Linux-x86_64-${nvidia_host_driver_version}.run
[[ $? -gt 0 ]] && echo "Error downloading driver. Exit!" && return 1
mv /tmp/NVIDIA.run ${USER_HOME}/.cache/nvidia/NVIDIA_${nvidia_host_driver_version}.run
fi
}
function install_driver {
# Check here if the currently installed version matches using nvidia-settings
nvidia_settings_version=$(nvidia-settings --version 2> /dev/null | grep version | cut -d ' ' -f 4)
[[ "${nvidia_settings_version}x" == "${nvidia_host_driver_version}x" ]] && return 0
# Download the driver (if it does not yet exist locally)
download_driver
echo "Installing driver"
chmod +x ${USER_HOME}/.cache/nvidia/NVIDIA_${nvidia_host_driver_version}.run
${USER_HOME}/.cache/nvidia/NVIDIA_${nvidia_host_driver_version}.run \
--silent \
--accept-license \
--no-kernel-module \
--install-compat32-libs \
--no-nouveau-check \
--no-nvidia-modprobe \
--no-rpms \
--no-backup \
--no-check-for-alternate-installs \
--no-libglx-indirect \
--no-install-libglvnd \
> ${USER_HOME}/.cache/nvidia/last_install.log 2>&1
}
if [[ -z ${nvidia_pci_address} ]]; then
echo "**** No NVIDIA device found ****";
else
echo "**** Found NVIDIA device '${nvidia_gpu_name}' ****";
install_driver
fi
echo "DONE"

View File

@@ -0,0 +1,57 @@
# Fech NVIDIA GPU device (if one exists)
if [ "$NVIDIA_VISIBLE_DEVICES" == "all" ]; then
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
elif [ -z "$NVIDIA_VISIBLE_DEVICES" ]; then
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
else
export gpu_select=$(nvidia-smi --format=csv --id=$(echo "$NVIDIA_VISIBLE_DEVICES" | cut -d ',' -f1) --query-gpu=uuid | sed -n 2p)
if [ -z "$gpu_select" ]; then
export gpu_select=$(nvidia-smi --format=csv --query-gpu=uuid 2> /dev/null | sed -n 2p)
fi
fi
export nvidia_gpu_hex_id=$(nvidia-smi --format=csv --query-gpu=pci.bus_id --id="${gpu_select}" 2> /dev/null | sed -n 2p)
# Fech current configuration (if modified in UI)
if [ -f "${USER_HOME}/.config/xfce4/xfconf/xfce-perchannel-xml/displays.xml" ]; then
new_display_sizew=$(cat ${USER_HOME}/.config/xfce4/xfconf/xfce-perchannel-xml/displays.xml | grep Resolution | head -n1 | grep -oP '(?<=value=").*?(?=")' | cut -d'x' -f1)
new_display_sizeh=$(cat ${USER_HOME}/.config/xfce4/xfconf/xfce-perchannel-xml/displays.xml | grep Resolution | head -n1 | grep -oP '(?<=value=").*?(?=")' | cut -d'x' -f2)
if [ "${new_display_sizew}x" != "x" ] && [ "${new_display_sizeh}x" != "x" ]; then
export DISPLAY_SIZEW="${new_display_sizew}"
export DISPLAY_SIZEH="${new_display_sizeh}"
fi
fi
# Configure a NVIDIA X11 config
function configure_nvidia_x_server {
# Configure x to be run by anyone
if grep -Fxq "allowed_users=console" /etc/X11/Xwrapper.config; then
echo "Configure Xwrapper.config"
sed -i "s/allowed_users=console/allowed_users=anybody/" /etc/X11/Xwrapper.config
fi
# Configure xorg for NVIDIA
echo "Configuring X11 with GPU ID: '${gpu_select}'"
nvidia_gpu_hex_id=$(nvidia-smi --format=csv --query-gpu=pci.bus_id --id="${gpu_select}" 2> /dev/null | sed -n 2p)
IFS=":." ARR_ID=(${nvidia_gpu_hex_id})
unset IFS
bus_id=PCI:$((16#${ARR_ID[1]})):$((16#${ARR_ID[2]})):$((16#${ARR_ID[3]}))
echo "Configuring X11 with PCI bus ID: '${bus_id}'"
export MODELINE=$(cvt -r "${DISPLAY_SIZEW}" "${DISPLAY_SIZEH}" "${DISPLAY_REFRESH}" | sed -n 2p)
echo "Writing X11 config with ${MODELINE}"
nvidia-xconfig --virtual="${DISPLAY_SIZEW}x${DISPLAY_SIZEH}" --depth="${DISPLAY_CDEPTH}" --mode=$(echo "${MODELINE}" | awk '{print $2}' | tr -d '"') --allow-empty-initial-configuration --no-probe-all-gpus --busid="${bus_id}" --only-one-x-screen --connected-monitor="${DISPLAY_VIDEO_PORT}"
sed -i '/Driver\s\+"nvidia"/a\ Option "ModeValidation" "NoMaxPClkCheck, NoEdidMaxPClkCheck, NoMaxSizeCheck, NoHorizSyncCheck, NoVertRefreshCheck, NoVirtualSizeCheck, NoExtendedGpuCapabilitiesCheck, NoTotalSizeCheck, NoDualLinkDVICheck, NoDisplayPortBandwidthCheck, AllowNon3DVisionModes, AllowNonHDMI3DModes, AllowNonEdidModes, NoEdidHDMI2Check, AllowDpInterlaced"' /etc/X11/xorg.conf
sed -i '/Section\s\+"Monitor"/a\ '"${MODELINE}" /etc/X11/xorg.conf
}
if [[ -z ${nvidia_gpu_hex_id} ]]; then
echo "**** Generate default xorg.conf ****";
# TODO: Configure xorg.conf with no NVIDIA GPU
else
echo "**** Generate NVIDIA xorg.conf ****";
configure_nvidia_x_server
fi
echo "DONE"