mirror of
https://github.com/linuxserver/docker-baseimage-selkies.git
synced 2026-03-16 00:02:11 +08:00
adding first LSIO based build logic
This commit is contained in:
parent
2e7c80abe7
commit
fbff039a98
201
Dockerfile
Normal file
201
Dockerfile
Normal file
@ -0,0 +1,201 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM ghcr.io/linuxserver/baseimage-debian:bookworm
|
||||
|
||||
# set version label
|
||||
ARG BUILD_DATE
|
||||
ARG VERSION
|
||||
LABEL build_version="Linuxserver.io version:- ${VERSION} Build-date:- ${BUILD_DATE}"
|
||||
LABEL maintainer="thelamer"
|
||||
|
||||
# env
|
||||
ENV DISPLAY=:1 \
|
||||
PERL5LIB=/usr/local/bin \
|
||||
HOME=/config \
|
||||
START_DOCKER=true \
|
||||
PULSE_RUNTIME_PATH=/defaults \
|
||||
SELKIES_INTERPOSER=/usr/lib/selkies_joystick_interposer.so \
|
||||
SDL_JOYSTICK_DEVICE=/dev/input/js0 \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all
|
||||
|
||||
RUN \
|
||||
echo "**** dev deps ****" && \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
cmake \
|
||||
g++ \
|
||||
gcc \
|
||||
libjpeg62-turbo-dev \
|
||||
libx11-dev \
|
||||
libxext-dev \
|
||||
libxxhash-dev \
|
||||
make \
|
||||
python3-dev \
|
||||
python3-pip && \
|
||||
echo "**** enable locales ****" && \
|
||||
sed -i \
|
||||
'/locale/d' \
|
||||
/etc/dpkg/dpkg.cfg.d/docker && \
|
||||
echo "**** install deps ****" && \
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | tee /usr/share/keyrings/docker.asc >/dev/null && \
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list && \
|
||||
curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
ca-certificates \
|
||||
console-data \
|
||||
containerd.io \
|
||||
docker-ce \
|
||||
docker-ce-cli \
|
||||
docker-buildx-plugin \
|
||||
docker-compose-plugin \
|
||||
dbus-x11 \
|
||||
dunst \
|
||||
file \
|
||||
fonts-noto-cjk \
|
||||
fonts-noto-color-emoji \
|
||||
fonts-noto-core \
|
||||
gir1.2-gst-plugins-bad-1.0 \
|
||||
gir1.2-gstreamer-1.0 \
|
||||
gstreamer1.0-nice \
|
||||
gstreamer1.0-plugins-* \
|
||||
gstreamer1.0-pulseaudio \
|
||||
intel-media-va-driver \
|
||||
kbd \
|
||||
libfontenc1 \
|
||||
libfreetype6 \
|
||||
libgbm1 \
|
||||
libgcrypt20 \
|
||||
libgirepository-1.0-1 \
|
||||
libgl1-mesa-dri \
|
||||
libglu1-mesa \
|
||||
libgnutls30 \
|
||||
libgstreamer1.0 \
|
||||
libnotify-bin \
|
||||
libp11-kit0 \
|
||||
libpam0g \
|
||||
libtasn1-6 \
|
||||
libjpeg62-turbo \
|
||||
libvulkan1 \
|
||||
libx11-6 \
|
||||
libxau6 \
|
||||
libxcb1 \
|
||||
libxcursor1 \
|
||||
libxdmcp6 \
|
||||
libxext6 \
|
||||
libxfixes3 \
|
||||
libxfont2 \
|
||||
libxinerama1 \
|
||||
libxshmfence1 \
|
||||
libxtst6 \
|
||||
locales-all \
|
||||
mesa-va-drivers \
|
||||
mesa-vulkan-drivers \
|
||||
nginx \
|
||||
openbox \
|
||||
openssh-client \
|
||||
openssl \
|
||||
pciutils \
|
||||
procps \
|
||||
pulseaudio \
|
||||
pulseaudio-utils \
|
||||
python3 \
|
||||
python3-gi \
|
||||
python3-gst-1.0 \
|
||||
software-properties-common \
|
||||
ssl-cert \
|
||||
sudo \
|
||||
tar \
|
||||
util-linux \
|
||||
vulkan-tools \
|
||||
x11-apps \
|
||||
x11-common \
|
||||
x11-utils \
|
||||
x11-xkb-utils \
|
||||
x11-xserver-utils \
|
||||
xauth \
|
||||
xdg-utils \
|
||||
xfonts-base \
|
||||
xkb-data \
|
||||
xsel \
|
||||
xserver-common \
|
||||
xserver-xorg-core \
|
||||
xserver-xorg-video-amdgpu \
|
||||
xserver-xorg-video-ati \
|
||||
xserver-xorg-video-intel \
|
||||
xserver-xorg-video-nouveau \
|
||||
xserver-xorg-video-qxl \
|
||||
xterm \
|
||||
xutils \
|
||||
xvfb \
|
||||
zlib1g && \
|
||||
echo "**** install selkies ****" && \
|
||||
pip3 install x11-screen-capture --break-system-packages && \
|
||||
SELKIES_RELEASE=$(curl -sX GET "https://api.github.com/repos/selkies-project/selkies-gstreamer/releases/latest" \
|
||||
| awk '/tag_name/{print $4;exit}' FS='[""]') && \
|
||||
mkdir -p \
|
||||
/usr/share/selkies/www/ && \
|
||||
curl -o \
|
||||
/tmp/web.tar.gz -L \
|
||||
"https://github.com/selkies-project/selkies-gstreamer/releases/download/${SELKIES_RELEASE}/selkies-gstreamer-web_${SELKIES_RELEASE}.tar.gz" && \
|
||||
curl -o \
|
||||
/tmp/selkies.tar.gz -L \
|
||||
"https://github.com/selkies-project/selkies-gstreamer/archive/refs/heads/main.tar.gz" && \
|
||||
tar xf \
|
||||
/tmp/web.tar.gz -C \
|
||||
/usr/share/selkies/www/ --strip-components=1 && \
|
||||
cd /tmp && \
|
||||
tar xf selkies.tar.gz && \
|
||||
cd selkies-gstreamer-* && \
|
||||
pip3 install . --break-system-packages && \
|
||||
echo "**** openbox tweaks ****" && \
|
||||
sed -i \
|
||||
-e 's/NLIMC/NLMC/g' \
|
||||
-e '/debian-menu/d' \
|
||||
-e 's|</applications>| <application class="*"><maximized>yes</maximized></application>\n</applications>|' \
|
||||
-e 's|</keyboard>| <keybind key="C-S-d"><action name="ToggleDecorations"/></keybind>\n</keyboard>|' \
|
||||
/etc/xdg/openbox/rc.xml && \
|
||||
echo "**** user perms ****" && \
|
||||
sed -e 's/%sudo ALL=(ALL:ALL) ALL/%sudo ALL=(ALL:ALL) NOPASSWD: ALL/g' \
|
||||
-i /etc/sudoers && \
|
||||
echo "abc:abc" | chpasswd && \
|
||||
usermod -s /bin/bash abc && \
|
||||
usermod -aG sudo abc && \
|
||||
echo "**** proot-apps ****" && \
|
||||
mkdir /proot-apps/ && \
|
||||
PAPPS_RELEASE=$(curl -sX GET "https://api.github.com/repos/linuxserver/proot-apps/releases/latest" \
|
||||
| awk '/tag_name/{print $4;exit}' FS='[""]') && \
|
||||
curl -L https://github.com/linuxserver/proot-apps/releases/download/${PAPPS_RELEASE}/proot-apps-x86_64.tar.gz \
|
||||
| tar -xzf - -C /proot-apps/ && \
|
||||
echo "${PAPPS_RELEASE}" > /proot-apps/pversion && \
|
||||
echo "**** dind support ****" && \
|
||||
useradd -U dockremap && \
|
||||
usermod -G dockremap dockremap && \
|
||||
echo 'dockremap:165536:65536' >> /etc/subuid && \
|
||||
echo 'dockremap:165536:65536' >> /etc/subgid && \
|
||||
curl -o \
|
||||
/usr/local/bin/dind -L \
|
||||
https://raw.githubusercontent.com/moby/moby/master/hack/dind && \
|
||||
chmod +x /usr/local/bin/dind && \
|
||||
echo 'hosts: files dns' > /etc/nsswitch.conf && \
|
||||
usermod -aG docker abc && \
|
||||
echo "**** locales ****" && \
|
||||
for LOCALE in $(curl -sL https://raw.githubusercontent.com/thelamer/lang-stash/master/langs); do \
|
||||
localedef -i $LOCALE -f UTF-8 $LOCALE.UTF-8; \
|
||||
done && \
|
||||
echo "**** theme ****" && \
|
||||
curl -s https://raw.githubusercontent.com/thelamer/lang-stash/master/theme.tar.gz \
|
||||
| tar xzvf - -C /usr/share/themes/Clearlooks/openbox-3/ && \
|
||||
echo "**** cleanup ****" && \
|
||||
apt-get autoclean && \
|
||||
rm -rf \
|
||||
/config/.cache \
|
||||
/var/lib/apt/lists/* \
|
||||
/var/tmp/* \
|
||||
/tmp/*
|
||||
|
||||
# add local files
|
||||
COPY /root /
|
||||
|
||||
# ports and volumes
|
||||
EXPOSE 3000 3001
|
||||
VOLUME /config
|
||||
1
root/defaults/autostart
Executable file
1
root/defaults/autostart
Executable file
@ -0,0 +1 @@
|
||||
xterm
|
||||
149
root/defaults/default.conf
Normal file
149
root/defaults/default.conf
Normal file
@ -0,0 +1,149 @@
|
||||
server {
|
||||
#auth_basic "Login";
|
||||
#auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
listen 3000 default_server;
|
||||
listen [::]:3000 default_server;
|
||||
location / {
|
||||
root /usr/share/selkies/www/;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location SUBFOLDERhealth {
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
}
|
||||
location SUBFOLDERturn {
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
}
|
||||
location SUBFOLDERws {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
}
|
||||
location SUBFOLDERwebrtc/signalling {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
}
|
||||
location SUBFOLDERmetrics {
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:9081;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = SUBFOLDER50x.html {
|
||||
root /usr/share/selkies/www/;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
#auth_basic "Login";
|
||||
#auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
listen 3001 ssl;
|
||||
listen [::]:3001 ssl;
|
||||
ssl_certificate /config/ssl/cert.pem;
|
||||
ssl_certificate_key /config/ssl/cert.key;
|
||||
location / {
|
||||
root /usr/share/selkies/www/;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location SUBFOLDERhealth {
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
}
|
||||
location SUBFOLDERturn {
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
}
|
||||
location SUBFOLDERws {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
}
|
||||
location SUBFOLDERwebrtc/signalling {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
}
|
||||
location SUBFOLDERmetrics {
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
client_max_body_size 10M;
|
||||
proxy_pass http://127.0.0.1:9081;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = SUBFOLDER50x.html {
|
||||
root /usr/share/selkies/www/;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6
root/defaults/menu.xml
Normal file
6
root/defaults/menu.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openbox_menu xmlns="http://openbox.org/3.4/menu">
|
||||
<menu id="root-menu" label="MENU">
|
||||
<item label="xterm" icon="/usr/share/pixmaps/xterm-color_48x48.xpm"><action name="Execute"><command>/usr/bin/xterm</command></action></item>
|
||||
</menu>
|
||||
</openbox_menu>
|
||||
10
root/defaults/startwm.sh
Executable file
10
root/defaults/startwm.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Enable Nvidia GPU support if detected
|
||||
if which nvidia-smi; then
|
||||
export LIBGL_KOPPER_DRI2=1
|
||||
export MESA_LOADER_DRIVER_OVERRIDE=zink
|
||||
export GALLIUM_DRIVER=zink
|
||||
fi
|
||||
|
||||
/usr/bin/openbox-session
|
||||
35
root/etc/s6-overlay/s6-rc.d/init-nginx/run
Executable file
35
root/etc/s6-overlay/s6-rc.d/init-nginx/run
Executable file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
# nginx Path
|
||||
NGINX_CONFIG=/etc/nginx/sites-available/default
|
||||
|
||||
# user passed env vars
|
||||
CPORT="${CUSTOM_PORT:-3000}"
|
||||
CHPORT="${CUSTOM_HTTPS_PORT:-3001}"
|
||||
CUSER="${CUSTOM_USER:-abc}"
|
||||
SFOLDER="${SUBFOLDER:-/}"
|
||||
|
||||
# create self signed cert
|
||||
if [ ! -f "/config/ssl/cert.pem" ]; then
|
||||
mkdir -p /config/ssl
|
||||
openssl req -new -x509 \
|
||||
-days 3650 -nodes \
|
||||
-out /config/ssl/cert.pem \
|
||||
-keyout /config/ssl/cert.key \
|
||||
-subj "/C=US/ST=CA/L=Carlsbad/O=Linuxserver.io/OU=LSIO Server/CN=*"
|
||||
chmod 600 /config/ssl/cert.key
|
||||
chown -R abc:abc /config/ssl
|
||||
fi
|
||||
|
||||
# modify nginx config
|
||||
cp /defaults/default.conf ${NGINX_CONFIG}
|
||||
sed -i "s/3000/$CPORT/g" ${NGINX_CONFIG}
|
||||
sed -i "s/3001/$CHPORT/g" ${NGINX_CONFIG}
|
||||
sed -i "s|SUBFOLDER|$SFOLDER|g" ${NGINX_CONFIG}
|
||||
if [ ! -z ${DISABLE_IPV6+x} ]; then
|
||||
sed -i '/listen \[::\]/d' ${NGINX_CONFIG}
|
||||
fi
|
||||
if [ ! -z ${PASSWORD+x} ]; then
|
||||
printf "${CUSER}:$(openssl passwd -apr1 ${PASSWORD})\n" > /etc/nginx/.htpasswd
|
||||
sed -i 's/#//g' ${NGINX_CONFIG}
|
||||
fi
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-nginx/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-nginx/type
Normal file
@ -0,0 +1 @@
|
||||
oneshot
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-nginx/up
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-nginx/up
Normal file
@ -0,0 +1 @@
|
||||
/etc/s6-overlay/s6-rc.d/init-nginx/run
|
||||
74
root/etc/s6-overlay/s6-rc.d/init-selkies-config/run
Executable file
74
root/etc/s6-overlay/s6-rc.d/init-selkies-config/run
Executable file
@ -0,0 +1,74 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
# default file copies first run
|
||||
if [[ ! -f /config/.config/openbox/autostart ]]; then
|
||||
mkdir -p /config/.config/openbox
|
||||
cp /defaults/autostart /config/.config/openbox/autostart
|
||||
chown -R abc:abc /config/.config/openbox
|
||||
fi
|
||||
if [[ ! -f /config/.config/openbox/menu.xml ]]; then
|
||||
mkdir -p /config/.config/openbox && \
|
||||
cp /defaults/menu.xml /config/.config/openbox/menu.xml && \
|
||||
chown -R abc:abc /config/.config
|
||||
fi
|
||||
|
||||
# XDG Home
|
||||
printf "${HOME}/.XDG" > /run/s6/container_environment/XDG_RUNTIME_DIR
|
||||
if [ ! -d "${HOME}/.XDG" ]; then
|
||||
mkdir -p ${HOME}/.XDG
|
||||
chown abc:abc ${HOME}/.XDG
|
||||
fi
|
||||
|
||||
# Locale Support
|
||||
if [ ! -z ${LC_ALL+x} ]; then
|
||||
printf "${LC_ALL%.UTF-8}" > /run/s6/container_environment/LANGUAGE
|
||||
printf "${LC_ALL}" > /run/s6/container_environment/LANG
|
||||
fi
|
||||
|
||||
# Remove window borders
|
||||
if [[ ! -z ${NO_DECOR+x} ]] && [[ ! -f /decorlock ]]; then
|
||||
sed -i \
|
||||
's|</applications>| <application class="*"> <decor>no</decor> </application>\n</applications>|' \
|
||||
/etc/xdg/openbox/rc.xml
|
||||
touch /decorlock
|
||||
fi
|
||||
|
||||
# Fullscreen everything in openbox unless the user explicitly disables it
|
||||
if [[ ! -z ${NO_FULL+x} ]] && [[ ! -f /fulllock ]]; then
|
||||
sed -i \
|
||||
'/<application class="\*"><maximized>yes<\/maximized><\/application>/d' \
|
||||
/etc/xdg/openbox/rc.xml
|
||||
touch /fulllock
|
||||
fi
|
||||
|
||||
# Add proot-apps
|
||||
if [ ! -f "${HOME}/.local/bin/proot-apps" ]; then
|
||||
mkdir -p ${HOME}/.local/bin/
|
||||
cp /proot-apps/* ${HOME}/.local/bin/
|
||||
echo 'export PATH="$HOME/.local/bin:$PATH"' >> $HOME/.bashrc
|
||||
chown abc:abc \
|
||||
${HOME}/.bashrc \
|
||||
${HOME}/.local/ \
|
||||
${HOME}/.local/bin \
|
||||
${HOME}/.local/bin/{ncat,proot-apps,proot,jq,pversion}
|
||||
elif ! diff -q /proot-apps/pversion ${HOME}/.local/bin/pversion > /dev/null; then
|
||||
cp /proot-apps/* ${HOME}/.local/bin/
|
||||
chown abc:abc ${HOME}/.local/bin/{ncat,proot-apps,proot,jq,pversion}
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# set env based on vars
|
||||
#printf "${SELKIES_INTERPOSER}${LD_PRELOAD:+:${LD_PRELOAD}}" > /run/s6/container_environment/LD_PRELOAD
|
||||
printf "${GST_DEBUG:-*:2}" > /run/s6/container_environment/GST_DEBUG
|
||||
printf "${SELKIES_ENCODER:-x264enc}" > /run/s6/container_environment/SELKIES_ENCODER
|
||||
printf "${SELKIES_VIDEO_BITRATE:-8000}" > /run/s6/container_environment/SELKIES_VIDEO_BITRATE
|
||||
printf "${SELKIES_FRAMERATE:-30}" > /run/s6/container_environment/SELKIES_FRAMERATE
|
||||
printf "${SELKIES_AUDIO_BITRATE:-128000}" > /run/s6/container_environment/SELKIES_AUDIO_BITRATE
|
||||
printf "${SELKIES_ENABLE_RESIZE:-true}" > /run/s6/container_environment/SELKIES_ENABLE_RESIZE
|
||||
printf "${DISPLAY_SIZEW:-1024}" > /run/s6/container_environment/DISPLAY_SIZEW
|
||||
printf "${DISPLAY_SIZEH:-768}" > /run/s6/container_environment/DISPLAY_SIZEH
|
||||
printf "${DISPLAY_REFRESH:-30}" > /run/s6/container_environment/DISPLAY_REFRESH
|
||||
printf "${DISPLAY_DPI:-30}" > /run/s6/container_environment/DISPLAY_DPI
|
||||
printf "${DISPLAY_CDEPTH:-24}" > /run/s6/container_environment/DISPLAY_CDEPTH
|
||||
printf "unix:path=${XDG_RUNTIME_DIR:-/tmp}/dbus-system-bus" > /run/s6/container_environment/DBUS_SYSTEM_BUS_ADDRESS
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-selkies-config/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-selkies-config/type
Normal file
@ -0,0 +1 @@
|
||||
oneshot
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-selkies-config/up
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-selkies-config/up
Normal file
@ -0,0 +1 @@
|
||||
/etc/s6-overlay/s6-rc.d/init-selkies-config/run
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-selkies-end/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-selkies-end/type
Normal file
@ -0,0 +1 @@
|
||||
oneshot
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-selkies-end/up
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-selkies-end/up
Normal file
@ -0,0 +1 @@
|
||||
# This file doesn't do anything, it's just the end of the selkies init process
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-selkies/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-selkies/type
Normal file
@ -0,0 +1 @@
|
||||
oneshot
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-selkies/up
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-selkies/up
Normal file
@ -0,0 +1 @@
|
||||
# This file doesn't do anything, it's just the beginning of the selkies init process
|
||||
35
root/etc/s6-overlay/s6-rc.d/init-video/run
Executable file
35
root/etc/s6-overlay/s6-rc.d/init-video/run
Executable file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
FILES=$(find /dev/dri /dev/dvb -type c -print 2>/dev/null)
|
||||
|
||||
for i in $FILES
|
||||
do
|
||||
VIDEO_GID=$(stat -c '%g' "${i}")
|
||||
VIDEO_UID=$(stat -c '%u' "${i}")
|
||||
# check if user matches device
|
||||
if id -u abc | grep -qw "${VIDEO_UID}"; then
|
||||
echo "**** permissions for ${i} are good ****"
|
||||
else
|
||||
# check if group matches and that device has group rw
|
||||
if id -G abc | grep -qw "${VIDEO_GID}" && [ $(stat -c '%A' "${i}" | cut -b 5,6) = "rw" ]; then
|
||||
echo "**** permissions for ${i} are good ****"
|
||||
# check if device needs to be added to video group
|
||||
elif ! id -G abc | grep -qw "${VIDEO_GID}"; then
|
||||
# check if video group needs to be created
|
||||
VIDEO_NAME=$(getent group "${VIDEO_GID}" | awk -F: '{print $1}')
|
||||
if [ -z "${VIDEO_NAME}" ]; then
|
||||
VIDEO_NAME="video$(head /dev/urandom | tr -dc 'a-z0-9' | head -c4)"
|
||||
groupadd "${VIDEO_NAME}"
|
||||
groupmod -g "${VIDEO_GID}" "${VIDEO_NAME}"
|
||||
echo "**** creating video group ${VIDEO_NAME} with id ${VIDEO_GID} ****"
|
||||
fi
|
||||
echo "**** adding ${i} to video group ${VIDEO_NAME} with id ${VIDEO_GID} ****"
|
||||
usermod -a -G "${VIDEO_NAME}" abc
|
||||
fi
|
||||
# check if device has group rw
|
||||
if [ $(stat -c '%A' "${i}" | cut -b 5,6) != "rw" ]; then
|
||||
echo -e "**** The device ${i} does not have group read/write permissions, attempting to fix inside the container.If it doesn't work, you can run the following on your docker host: ****\nsudo chmod g+rw ${i}\n"
|
||||
chmod g+rw "${i}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-video/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-video/type
Normal file
@ -0,0 +1 @@
|
||||
oneshot
|
||||
1
root/etc/s6-overlay/s6-rc.d/init-video/up
Normal file
1
root/etc/s6-overlay/s6-rc.d/init-video/up
Normal file
@ -0,0 +1 @@
|
||||
/etc/s6-overlay/s6-rc.d/init-video/run
|
||||
15
root/etc/s6-overlay/s6-rc.d/svc-dbus/run
Executable file
15
root/etc/s6-overlay/s6-rc.d/svc-dbus/run
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
# Folder setup
|
||||
mkdir -pm700 "${XDG_RUNTIME_DIR}"
|
||||
chown -f "${PUID}:${PGID}" "${XDG_RUNTIME_DIR}"
|
||||
chmod -f 700 "${XDG_RUNTIME_DIR}"
|
||||
|
||||
# Run dbus
|
||||
exec s6-setuidgid abc \
|
||||
dbus-daemon \
|
||||
--system \
|
||||
--nofork \
|
||||
--nosyslog \
|
||||
--nopidfile \
|
||||
--address="${DBUS_SYSTEM_BUS_ADDRESS}"
|
||||
1
root/etc/s6-overlay/s6-rc.d/svc-dbus/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/svc-dbus/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
||||
5
root/etc/s6-overlay/s6-rc.d/svc-de/run
Executable file
5
root/etc/s6-overlay/s6-rc.d/svc-de/run
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
cd $HOME
|
||||
exec s6-setuidgid abc \
|
||||
/bin/bash /defaults/startwm.sh
|
||||
1
root/etc/s6-overlay/s6-rc.d/svc-de/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/svc-de/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
||||
15
root/etc/s6-overlay/s6-rc.d/svc-docker/run
Executable file
15
root/etc/s6-overlay/s6-rc.d/svc-docker/run
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
# We need to wait for kclient to be full up as docker init breaks audio
|
||||
sleep 5
|
||||
|
||||
# Make sure this is a priv container
|
||||
if [ -e /dev/cpu_dma_latency ]; then
|
||||
if [ "${START_DOCKER}" == "true" ]; then
|
||||
exec /usr/local/bin/dockerd-entrypoint.sh -l error
|
||||
else
|
||||
sleep infinity
|
||||
fi
|
||||
fi
|
||||
# if anything goes wrong with Docker don't loop
|
||||
sleep infinity
|
||||
1
root/etc/s6-overlay/s6-rc.d/svc-docker/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/svc-docker/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
||||
16
root/etc/s6-overlay/s6-rc.d/svc-nginx/run
Executable file
16
root/etc/s6-overlay/s6-rc.d/svc-nginx/run
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
# shellcheck shell=bash
|
||||
|
||||
if pgrep -f "[n]ginx:" >/dev/null; then
|
||||
echo "Zombie nginx processes detected, sending SIGTERM"
|
||||
pkill -ef [n]ginx:
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
if pgrep -f "[n]ginx:" >/dev/null; then
|
||||
echo "Zombie nginx processes still active, sending SIGKILL"
|
||||
pkill -9 -ef [n]ginx:
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
exec /usr/sbin/nginx -g 'daemon off;'
|
||||
1
root/etc/s6-overlay/s6-rc.d/svc-nginx/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/svc-nginx/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
||||
7
root/etc/s6-overlay/s6-rc.d/svc-pulseaudio/run
Executable file
7
root/etc/s6-overlay/s6-rc.d/svc-pulseaudio/run
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
exec s6-setuidgid abc \
|
||||
/usr/bin/pulseaudio \
|
||||
--log-level=0 \
|
||||
--log-target=stderr \
|
||||
--exit-idle-time=-1 > /dev/null 2>&1
|
||||
1
root/etc/s6-overlay/s6-rc.d/svc-pulseaudio/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/svc-pulseaudio/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
||||
13
root/etc/s6-overlay/s6-rc.d/svc-selkies/run
Executable file
13
root/etc/s6-overlay/s6-rc.d/svc-selkies/run
Executable file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
# Clear the cache registry
|
||||
rm -rf "${HOME}/.cache/gstreamer-1.0"
|
||||
|
||||
# Start the Selkies-GStreamer WebRTC HTML5 remote desktop application
|
||||
exec s6-setuidgid abc \
|
||||
selkies-gstreamer \
|
||||
--addr="localhost" \
|
||||
--port="8081" \
|
||||
--enable_basic_auth="false" \
|
||||
--enable_metrics_http="true" \
|
||||
--metrics_http_port="9081"
|
||||
1
root/etc/s6-overlay/s6-rc.d/svc-selkies/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/svc-selkies/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
||||
22
root/etc/s6-overlay/s6-rc.d/svc-xorg/run
Executable file
22
root/etc/s6-overlay/s6-rc.d/svc-xorg/run
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
|
||||
# Run Xvfb server with required extensions
|
||||
exec s6-setuidgid abc \
|
||||
/usr/bin/Xvfb \
|
||||
"${DISPLAY}" \
|
||||
-screen 0 "8192x4096x${DISPLAY_CDEPTH}" \
|
||||
-dpi "${DISPLAY_DPI}" \
|
||||
+extension "COMPOSITE" \
|
||||
+extension "DAMAGE" \
|
||||
+extension "GLX" \
|
||||
+extension "RANDR" \
|
||||
+extension "RENDER" \
|
||||
+extension "MIT-SHM" \
|
||||
+extension "XFIXES" \
|
||||
+extension "XTEST" \
|
||||
+iglx \
|
||||
+render \
|
||||
-nolisten "tcp" \
|
||||
-ac \
|
||||
-noreset \
|
||||
-shmem
|
||||
1
root/etc/s6-overlay/s6-rc.d/svc-xorg/type
Normal file
1
root/etc/s6-overlay/s6-rc.d/svc-xorg/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
||||
0
root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-de
Normal file
0
root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-de
Normal file
196
root/usr/local/bin/dockerd-entrypoint.sh
Executable file
196
root/usr/local/bin/dockerd-entrypoint.sh
Executable file
@ -0,0 +1,196 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
_tls_ensure_private() {
|
||||
local f="$1"; shift
|
||||
[ -s "$f" ] || openssl genrsa -out "$f" 4096
|
||||
}
|
||||
_tls_san() {
|
||||
{
|
||||
ip -oneline address | awk '{ gsub(/\/.+$/, "", $4); print "IP:" $4 }'
|
||||
{
|
||||
cat /etc/hostname
|
||||
echo 'docker'
|
||||
echo 'localhost'
|
||||
hostname -f
|
||||
hostname -s
|
||||
} | sed 's/^/DNS:/'
|
||||
[ -z "${DOCKER_TLS_SAN:-}" ] || echo "$DOCKER_TLS_SAN"
|
||||
} | sort -u | xargs printf '%s,' | sed "s/,\$//"
|
||||
}
|
||||
_tls_generate_certs() {
|
||||
local dir="$1"; shift
|
||||
|
||||
# if ca/key.pem || !ca/cert.pem, generate CA public if necessary
|
||||
# if ca/key.pem, generate server public
|
||||
# if ca/key.pem, generate client public
|
||||
# (regenerating public certs every startup to account for SAN/IP changes and/or expiration)
|
||||
|
||||
# https://github.com/FiloSottile/mkcert/issues/174
|
||||
local certValidDays='825'
|
||||
|
||||
if [ -s "$dir/ca/key.pem" ] || [ ! -s "$dir/ca/cert.pem" ]; then
|
||||
# if we either have a CA private key or do *not* have a CA public key, then we should create/manage the CA
|
||||
mkdir -p "$dir/ca"
|
||||
_tls_ensure_private "$dir/ca/key.pem"
|
||||
openssl req -new -key "$dir/ca/key.pem" \
|
||||
-out "$dir/ca/cert.pem" \
|
||||
-subj '/CN=docker:dind CA' -x509 -days "$certValidDays"
|
||||
fi
|
||||
|
||||
if [ -s "$dir/ca/key.pem" ]; then
|
||||
# if we have a CA private key, we should create/manage a server key
|
||||
mkdir -p "$dir/server"
|
||||
_tls_ensure_private "$dir/server/key.pem"
|
||||
openssl req -new -key "$dir/server/key.pem" \
|
||||
-out "$dir/server/csr.pem" \
|
||||
-subj '/CN=docker:dind server'
|
||||
cat > "$dir/server/openssl.cnf" <<-EOF
|
||||
[ x509_exts ]
|
||||
subjectAltName = $(_tls_san)
|
||||
EOF
|
||||
openssl x509 -req \
|
||||
-in "$dir/server/csr.pem" \
|
||||
-CA "$dir/ca/cert.pem" \
|
||||
-CAkey "$dir/ca/key.pem" \
|
||||
-CAcreateserial \
|
||||
-out "$dir/server/cert.pem" \
|
||||
-days "$certValidDays" \
|
||||
-extfile "$dir/server/openssl.cnf" \
|
||||
-extensions x509_exts
|
||||
cp "$dir/ca/cert.pem" "$dir/server/ca.pem"
|
||||
openssl verify -CAfile "$dir/server/ca.pem" "$dir/server/cert.pem"
|
||||
fi
|
||||
|
||||
if [ -s "$dir/ca/key.pem" ]; then
|
||||
# if we have a CA private key, we should create/manage a client key
|
||||
mkdir -p "$dir/client"
|
||||
_tls_ensure_private "$dir/client/key.pem"
|
||||
chmod 0644 "$dir/client/key.pem" # openssl defaults to 0600 for the private key, but this one needs to be shared with arbitrary client contexts
|
||||
openssl req -new \
|
||||
-key "$dir/client/key.pem" \
|
||||
-out "$dir/client/csr.pem" \
|
||||
-subj '/CN=docker:dind client'
|
||||
cat > "$dir/client/openssl.cnf" <<-'EOF'
|
||||
[ x509_exts ]
|
||||
extendedKeyUsage = clientAuth
|
||||
EOF
|
||||
openssl x509 -req \
|
||||
-in "$dir/client/csr.pem" \
|
||||
-CA "$dir/ca/cert.pem" \
|
||||
-CAkey "$dir/ca/key.pem" \
|
||||
-CAcreateserial \
|
||||
-out "$dir/client/cert.pem" \
|
||||
-days "$certValidDays" \
|
||||
-extfile "$dir/client/openssl.cnf" \
|
||||
-extensions x509_exts
|
||||
cp "$dir/ca/cert.pem" "$dir/client/ca.pem"
|
||||
openssl verify -CAfile "$dir/client/ca.pem" "$dir/client/cert.pem"
|
||||
fi
|
||||
}
|
||||
|
||||
# no arguments passed
|
||||
# or first arg is `-f` or `--some-option`
|
||||
if [ "$#" -eq 0 ] || [ "${1#-}" != "$1" ]; then
|
||||
# set "dockerSocket" to the default "--host" *unix socket* value (for both standard or rootless)
|
||||
uid="$(id -u)"
|
||||
if [ "$uid" = '0' ]; then
|
||||
dockerSocket='unix:///var/run/docker.sock'
|
||||
else
|
||||
# if we're not root, we must be trying to run rootless
|
||||
: "${XDG_RUNTIME_DIR:=/run/user/$uid}"
|
||||
dockerSocket="unix://$XDG_RUNTIME_DIR/docker.sock"
|
||||
fi
|
||||
case "${DOCKER_HOST:-}" in
|
||||
unix://*)
|
||||
dockerSocket="$DOCKER_HOST"
|
||||
;;
|
||||
esac
|
||||
|
||||
# add our default arguments
|
||||
if [ -n "${DOCKER_TLS_CERTDIR:-}" ] \
|
||||
&& _tls_generate_certs "$DOCKER_TLS_CERTDIR" \
|
||||
&& [ -s "$DOCKER_TLS_CERTDIR/server/ca.pem" ] \
|
||||
&& [ -s "$DOCKER_TLS_CERTDIR/server/cert.pem" ] \
|
||||
&& [ -s "$DOCKER_TLS_CERTDIR/server/key.pem" ] \
|
||||
; then
|
||||
# generate certs and use TLS if requested/possible (default in 19.03+)
|
||||
set -- dockerd \
|
||||
--host="$dockerSocket" \
|
||||
--host=tcp://0.0.0.0:2376 \
|
||||
--tlsverify \
|
||||
--tlscacert "$DOCKER_TLS_CERTDIR/server/ca.pem" \
|
||||
--tlscert "$DOCKER_TLS_CERTDIR/server/cert.pem" \
|
||||
--tlskey "$DOCKER_TLS_CERTDIR/server/key.pem" \
|
||||
"$@"
|
||||
DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS="${DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS:-} -p 0.0.0.0:2376:2376/tcp"
|
||||
else
|
||||
# TLS disabled (-e DOCKER_TLS_CERTDIR='') or missing certs
|
||||
set -- dockerd \
|
||||
--host="$dockerSocket" \
|
||||
"$@"
|
||||
DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS="${DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS:-} -p 0.0.0.0:2375:2375/tcp"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$1" = 'dockerd' ]; then
|
||||
# explicitly remove Docker's default PID file to ensure that it can start properly if it was stopped uncleanly (and thus didn't clean up the PID file)
|
||||
find /run /var/run -iname 'docker*.pid' -delete || :
|
||||
|
||||
if dockerd --version | grep -qF ' 20.10.'; then
|
||||
set -- docker-init -- "$@"
|
||||
fi
|
||||
|
||||
if ! iptables -nL > /dev/null 2>&1; then
|
||||
# if iptables fails to run, chances are high the necessary kernel modules aren't loaded (perhaps the host is using nftables with the translating "iptables" wrappers, for example)
|
||||
# https://github.com/docker-library/docker/issues/350
|
||||
# https://github.com/moby/moby/issues/26824
|
||||
modprobe ip_tables || :
|
||||
fi
|
||||
|
||||
uid="$(id -u)"
|
||||
if [ "$uid" != '0' ]; then
|
||||
# if we're not root, we must be trying to run rootless
|
||||
if ! command -v rootlesskit > /dev/null; then
|
||||
echo >&2 "error: attempting to run rootless dockerd but missing 'rootlesskit' (perhaps the 'docker:dind-rootless' image variant is intended?)"
|
||||
exit 1
|
||||
fi
|
||||
user="$(id -un 2>/dev/null || :)"
|
||||
if ! grep -qE "^($uid${user:+|$user}):" /etc/subuid || ! grep -qE "^($uid${user:+|$user}):" /etc/subgid; then
|
||||
echo >&2 "error: attempting to run rootless dockerd but missing necessary entries in /etc/subuid and/or /etc/subgid for $uid"
|
||||
exit 1
|
||||
fi
|
||||
: "${XDG_RUNTIME_DIR:=/run/user/$uid}"
|
||||
export XDG_RUNTIME_DIR
|
||||
if ! mkdir -p "$XDG_RUNTIME_DIR" || [ ! -w "$XDG_RUNTIME_DIR" ] || ! mkdir -p "$HOME/.local/share/docker" || [ ! -w "$HOME/.local/share/docker" ]; then
|
||||
echo >&2 "error: attempting to run rootless dockerd but need writable HOME ($HOME) and XDG_RUNTIME_DIR ($XDG_RUNTIME_DIR) for user $uid"
|
||||
exit 1
|
||||
fi
|
||||
if [ -f /proc/sys/kernel/unprivileged_userns_clone ] && unprivClone="$(cat /proc/sys/kernel/unprivileged_userns_clone)" && [ "$unprivClone" != '1' ]; then
|
||||
echo >&2 "error: attempting to run rootless dockerd but need 'kernel.unprivileged_userns_clone' (/proc/sys/kernel/unprivileged_userns_clone) set to 1"
|
||||
exit 1
|
||||
fi
|
||||
if [ -f /proc/sys/user/max_user_namespaces ] && maxUserns="$(cat /proc/sys/user/max_user_namespaces)" && [ "$maxUserns" = '0' ]; then
|
||||
echo >&2 "error: attempting to run rootless dockerd but need 'user.max_user_namespaces' (/proc/sys/user/max_user_namespaces) set to a sufficiently large value"
|
||||
exit 1
|
||||
fi
|
||||
# TODO overlay support detection?
|
||||
exec rootlesskit \
|
||||
--net="${DOCKERD_ROOTLESS_ROOTLESSKIT_NET:-vpnkit}" \
|
||||
--mtu="${DOCKERD_ROOTLESS_ROOTLESSKIT_MTU:-1500}" \
|
||||
--disable-host-loopback \
|
||||
--port-driver=builtin \
|
||||
--copy-up=/etc \
|
||||
--copy-up=/run \
|
||||
${DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS:-} \
|
||||
"$@"
|
||||
elif [ -x '/usr/local/bin/dind' ]; then
|
||||
# if we have the (mostly defunct now) Docker-in-Docker wrapper script, use it
|
||||
set -- '/usr/local/bin/dind' "$@"
|
||||
fi
|
||||
else
|
||||
# if it isn't `dockerd` we're trying to run, pass it through `docker-entrypoint.sh` so it gets `DOCKER_HOST` set appropriately too
|
||||
set -- docker-entrypoint.sh "$@"
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
Loading…
x
Reference in New Issue
Block a user