|
| 1 | +#!/bin/bash |
| 2 | +### every exit != 0 fails the script |
| 3 | +set -e |
| 4 | + |
| 5 | +no_proxy="localhost,127.0.0.1" |
| 6 | + |
| 7 | +# dict to store processes |
| 8 | +declare -A KASM_PROCS |
| 9 | + |
| 10 | +# switch passwords to local variables |
| 11 | +tmpval=$VNC_VIEW_ONLY_PW |
| 12 | +unset VNC_VIEW_ONLY_PW |
| 13 | +VNC_VIEW_ONLY_PW=$tmpval |
| 14 | +tmpval=$VNC_PW |
| 15 | +unset VNC_PW |
| 16 | +VNC_PW=$tmpval |
| 17 | +BUILD_ARCH=$(uname -p) |
| 18 | + |
| 19 | +STARTUP_COMPLETE=0 |
| 20 | + |
| 21 | +######## FUNCTION DECLARATIONS ########## |
| 22 | + |
| 23 | +## print out help |
| 24 | +function help (){ |
| 25 | + echo " |
| 26 | + USAGE: |
| 27 | +
|
| 28 | + OPTIONS: |
| 29 | + -w, --wait (default) keeps the UI and the vncserver up until SIGINT or SIGTERM will received |
| 30 | + -s, --skip skip the vnc startup and just execute the assigned command. |
| 31 | + example: docker run kasmweb/core --skip bash |
| 32 | + -d, --debug enables more detailed startup output |
| 33 | + e.g. 'docker run kasmweb/core --debug bash' |
| 34 | + -h, --help print out this help |
| 35 | +
|
| 36 | + Fore more information see: https://github.com/ConSol/docker-headless-vnc-container |
| 37 | + " |
| 38 | +} |
| 39 | + |
| 40 | +## correct forwarding of shutdown signal |
| 41 | +function cleanup () { |
| 42 | + kill -s SIGTERM $! |
| 43 | + exit 0 |
| 44 | +} |
| 45 | + |
| 46 | +function start_kasmvnc (){ |
| 47 | + if [[ $DEBUG == true ]]; then |
| 48 | + echo -e "\n------------------ Start KasmVNC Server ------------------------" |
| 49 | + fi |
| 50 | + |
| 51 | + DISPLAY_NUM=$(echo $DISPLAY | grep -Po ':\d+') |
| 52 | + |
| 53 | + if [[ $STARTUP_COMPLETE == 0 ]]; then |
| 54 | + vncserver -kill $DISPLAY &> $STARTUPDIR/vnc_startup.log \ |
| 55 | + || rm -rfv /tmp/.X*-lock /tmp/.X11-unix &> $STARTUPDIR/vnc_startup.log \ |
| 56 | + || echo "no locks present" |
| 57 | + fi |
| 58 | + |
| 59 | + rm -rf $HOME/.vnc/*.pid |
| 60 | + |
| 61 | + VNCOPTIONS="$VNCOPTIONS -select-de manual" |
| 62 | + if [[ "${BUILD_ARCH}" =~ ^aarch64$ ]] && [[ -f /lib/aarch64-linux-gnu/libgcc_s.so.1 ]] ; then |
| 63 | + LD_PRELOAD=/lib/aarch64-linux-gnu/libgcc_s.so.1 vncserver $DISPLAY -disableBasicAuth -depth $VNC_COL_DEPTH -geometry $VNC_RESOLUTION -websocketPort $NO_VNC_PORT -httpd ${KASM_VNC_PATH}/www -FrameRate=$MAX_FRAME_RATE -interface 0.0.0.0 -BlacklistThreshold=0 -FreeKeyMappings $VNCOPTIONS $KASM_SVC_SEND_CUT_TEXT $KASM_SVC_ACCEPT_CUT_TEXT |
| 64 | + else |
| 65 | + vncserver $DISPLAY -disableBasicAuth -depth $VNC_COL_DEPTH -geometry $VNC_RESOLUTION -websocketPort $NO_VNC_PORT -httpd ${KASM_VNC_PATH}/www -FrameRate=$MAX_FRAME_RATE -interface 0.0.0.0 -BlacklistThreshold=0 -FreeKeyMappings $VNCOPTIONS $KASM_SVC_SEND_CUT_TEXT $KASM_SVC_ACCEPT_CUT_TEXT |
| 66 | + fi |
| 67 | + |
| 68 | + KASM_PROCS['kasmvnc']=$(cat $HOME/.vnc/*${DISPLAY_NUM}.pid) |
| 69 | + |
| 70 | + if [[ $DEBUG == true ]]; then |
| 71 | + echo -e "\n------------------ Started Websockify ----------------------------" |
| 72 | + echo "Websockify PID: ${KASM_PROCS['kasmvnc']}"; |
| 73 | + fi |
| 74 | +} |
| 75 | + |
| 76 | +function start_window_manager (){ |
| 77 | + echo -e "\n------------------ Xfce4 window manager startup------------------" |
| 78 | + |
| 79 | + if [ "${START_XFCE4}" == "1" ] ; then |
| 80 | + if [ -f /opt/VirtualGL/bin/vglrun ] && [ ! -z "${KASM_EGL_CARD}" ] && [ ! -z "${KASM_RENDERD}" ] && [ -O "${KASM_RENDERD}" ] && [ -O "${KASM_EGL_CARD}" ] ; then |
| 81 | + echo "Starting XFCE with VirtualGL using EGL device ${KASM_EGL_CARD}" |
| 82 | + DISPLAY=:1 /opt/VirtualGL/bin/vglrun -d "${KASM_EGL_CARD}" /usr/bin/startxfce4 --replace & |
| 83 | + else |
| 84 | + echo "Starting XFCE" |
| 85 | + if [ -f '/usr/bin/zypper' ]; then |
| 86 | + DISPLAY=:1 /usr/bin/dbus-launch /usr/bin/startxfce4 --replace & |
| 87 | + else |
| 88 | + /usr/bin/startxfce4 --replace & |
| 89 | + fi |
| 90 | + fi |
| 91 | + KASM_PROCS['window_manager']=$! |
| 92 | + else |
| 93 | + echo "Skipping XFCE Startup" |
| 94 | + fi |
| 95 | +} |
| 96 | + |
| 97 | +function start_audio_out_websocket (){ |
| 98 | + if [[ ${KASM_SVC_AUDIO:-1} == 1 ]]; then |
| 99 | + echo 'Starting audio websocket server' |
| 100 | + $STARTUPDIR/jsmpeg/kasm_audio_out-linux kasmaudio 8081 4901 ${HOME}/.vnc/self.pem ${HOME}/.vnc/self.pem "kasm_user:$VNC_PW" & |
| 101 | + |
| 102 | + KASM_PROCS['kasm_audio_out_websocket']=$! |
| 103 | + |
| 104 | + if [[ $DEBUG == true ]]; then |
| 105 | + echo -e "\n------------------ Started Audio Out Websocket ----------------------------" |
| 106 | + echo "Kasm Audio Out Websocket PID: ${KASM_PROCS['kasm_audio_out_websocket']}"; |
| 107 | + fi |
| 108 | + fi |
| 109 | +} |
| 110 | + |
| 111 | +function start_audio_out (){ |
| 112 | + if [[ ${KASM_SVC_AUDIO:-1} == 1 ]]; then |
| 113 | + echo 'Starting audio server' |
| 114 | + |
| 115 | + if [ "${START_PULSEAUDIO:-0}" == "1" ] ; |
| 116 | + then |
| 117 | + echo "Starting Pulse" |
| 118 | + HOME=/var/run/pulse pulseaudio --start |
| 119 | + fi |
| 120 | + |
| 121 | + if [[ $DEBUG == true ]]; then |
| 122 | + echo 'Starting audio service in debug mode' |
| 123 | + HOME=/var/run/pulse no_proxy=127.0.0.1 ffmpeg -f pulse -fragment_size ${PULSEAUDIO_FRAGMENT_SIZE:-2000} -ar 44100 -i default -f mpegts -correct_ts_overflow 0 -codec:a mp2 -b:a 128k -ac 1 -muxdelay 0.001 http://127.0.0.1:8081/kasmaudio & |
| 124 | + KASM_PROCS['kasm_audio_out']=$! |
| 125 | + else |
| 126 | + echo 'Starting audio service' |
| 127 | + HOME=/var/run/pulse no_proxy=127.0.0.1 ffmpeg -v verbose -f pulse -fragment_size ${PULSEAUDIO_FRAGMENT_SIZE:-2000} -ar 44100 -i default -f mpegts -correct_ts_overflow 0 -codec:a mp2 -b:a 128k -ac 1 -muxdelay 0.001 http://127.0.0.1:8081/kasmaudio > /dev/null 2>&1 & |
| 128 | + KASM_PROCS['kasm_audio_out']=$! |
| 129 | + echo -e "\n------------------ Started Audio Out ----------------------------" |
| 130 | + echo "Kasm Audio Out PID: ${KASM_PROCS['kasm_audio_out']}"; |
| 131 | + fi |
| 132 | + fi |
| 133 | +} |
| 134 | + |
| 135 | +function start_audio_in (){ |
| 136 | + if [[ ${KASM_SVC_AUDIO_INPUT:-1} == 1 ]]; then |
| 137 | + echo 'Starting audio input server' |
| 138 | + $STARTUPDIR/audio_input/kasm_audio_input_server --ssl --auth-token "kasm_user:$VNC_PW" --cert ${HOME}/.vnc/self.pem --certkey ${HOME}/.vnc/self.pem & |
| 139 | + |
| 140 | + KASM_PROCS['kasm_audio_in']=$! |
| 141 | + |
| 142 | + if [[ $DEBUG == true ]]; then |
| 143 | + echo -e "\n------------------ Started Audio Out Websocket ----------------------------" |
| 144 | + echo "Kasm Audio In PID: ${KASM_PROCS['kasm_audio_in']}"; |
| 145 | + fi |
| 146 | + fi |
| 147 | +} |
| 148 | + |
| 149 | +function start_upload (){ |
| 150 | + if [[ ${KASM_SVC_UPLOADS:-1} == 1 ]]; then |
| 151 | + echo 'Starting upload server' |
| 152 | + $STARTUPDIR/upload_server/kasm_upload_server --ssl --auth-token "kasm_user:$VNC_PW" & |
| 153 | + |
| 154 | + KASM_PROCS['upload_server']=$! |
| 155 | + |
| 156 | + if [[ $DEBUG == true ]]; then |
| 157 | + echo -e "\n------------------ Started Audio Out Websocket ----------------------------" |
| 158 | + echo "Kasm Audio In PID: ${KASM_PROCS['upload_server']}"; |
| 159 | + fi |
| 160 | + fi |
| 161 | +} |
| 162 | + |
| 163 | +function start_gamepad (){ |
| 164 | + if [[ ${KASM_SVC_GAMEPAD:-1} == 1 ]]; then |
| 165 | + echo 'Starting gamepad server' |
| 166 | + $STARTUPDIR/gamepad/kasm_gamepad_server --ssl --auth-token "kasm_user:$VNC_PW" --cert ${HOME}/.vnc/self.pem --certkey ${HOME}/.vnc/self.pem & |
| 167 | + |
| 168 | + KASM_PROCS['kasm_gamepad']=$! |
| 169 | + |
| 170 | + if [[ $DEBUG == true ]]; then |
| 171 | + echo -e "\n------------------ Started Gamepad Websocket ----------------------------" |
| 172 | + echo "Kasm Gamepad PID: ${KASM_PROCS['kasm_gamepad']}"; |
| 173 | + fi |
| 174 | + fi |
| 175 | +} |
| 176 | + |
| 177 | +function custom_startup (){ |
| 178 | + custom_startup_script=/dockerstartup/custom_startup.sh |
| 179 | + if [ -f "$custom_startup_script" ]; then |
| 180 | + if [ ! -x "$custom_startup_script" ]; then |
| 181 | + echo "${custom_startup_script}: not executable, exiting" |
| 182 | + exit 1 |
| 183 | + fi |
| 184 | + |
| 185 | + "$custom_startup_script" & |
| 186 | + KASM_PROCS['custom_startup']=$! |
| 187 | + fi |
| 188 | +} |
| 189 | + |
| 190 | +############ END FUNCTION DECLARATIONS ########### |
| 191 | + |
| 192 | +if [[ $1 =~ -h|--help ]]; then |
| 193 | + help |
| 194 | + exit 0 |
| 195 | +fi |
| 196 | + |
| 197 | +# should also source $STARTUPDIR/generate_container_user |
| 198 | +if [ -f $HOME/.bashrc ]; then |
| 199 | + source $HOME/.bashrc |
| 200 | +fi |
| 201 | + |
| 202 | +if [[ ${KASM_DEBUG:-0} == 1 ]]; then |
| 203 | + echo -e "\n\n------------------ DEBUG KASM STARTUP -----------------" |
| 204 | + export DEBUG=true |
| 205 | + set -x |
| 206 | +fi |
| 207 | + |
| 208 | +trap cleanup SIGINT SIGTERM |
| 209 | + |
| 210 | +## resolve_vnc_connection |
| 211 | +VNC_IP=$(hostname -i) |
| 212 | +if [[ $DEBUG == true ]]; then |
| 213 | + echo "IP Address used for external bind: $VNC_IP" |
| 214 | +fi |
| 215 | + |
| 216 | +# Create cert for KasmVNC |
| 217 | +mkdir -p ${HOME}/.vnc |
| 218 | +openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout ${HOME}/.vnc/self.pem -out ${HOME}/.vnc/self.pem -subj "/C=US/ST=VA/L=None/O=None/OU=DoFu/CN=kasm/[email protected]" |
| 219 | + |
| 220 | +# first entry is control, second is view (if only one is valid for both) |
| 221 | +mkdir -p "$HOME/.vnc" |
| 222 | +PASSWD_PATH="$HOME/.kasmpasswd" |
| 223 | +if [[ -f $PASSWD_PATH ]]; then |
| 224 | + echo -e "\n--------- purging existing VNC password settings ---------" |
| 225 | + rm -f $PASSWD_PATH |
| 226 | +fi |
| 227 | +VNC_PW_HASH=$(python3 -c "import crypt; print(crypt.crypt('${VNC_PW}', '\$5\$kasm\$'));") |
| 228 | +VNC_VIEW_PW_HASH=$(python3 -c "import crypt; print(crypt.crypt('${VNC_VIEW_ONLY_PW}', '\$5\$kasm\$'));") |
| 229 | +echo "kasm_user:${VNC_PW_HASH}:ow" > $PASSWD_PATH |
| 230 | +echo "kasm_viewer:${VNC_VIEW_PW_HASH}:" >> $PASSWD_PATH |
| 231 | +chmod 600 $PASSWD_PATH |
| 232 | + |
| 233 | + |
| 234 | +# start processes |
| 235 | +start_kasmvnc |
| 236 | +start_window_manager |
| 237 | +start_audio_out_websocket |
| 238 | +start_audio_out |
| 239 | +start_audio_in |
| 240 | +start_upload |
| 241 | +start_gamepad |
| 242 | + |
| 243 | +STARTUP_COMPLETE=1 |
| 244 | + |
| 245 | + |
| 246 | +## log connect options |
| 247 | +echo -e "\n\n------------------ KasmVNC environment started ------------------" |
| 248 | + |
| 249 | +# tail vncserver logs |
| 250 | +tail -f $HOME/.vnc/*$DISPLAY.log & |
| 251 | + |
| 252 | +KASMIP=$(hostname -i) |
| 253 | +echo "Kasm User ${KASM_USER}(${KASM_USER_ID}) started container id ${HOSTNAME} with local IP address ${KASMIP}" |
| 254 | + |
| 255 | +# start custom startup script |
| 256 | +custom_startup |
| 257 | + |
| 258 | +# Monitor Kasm Services |
| 259 | +sleep 3 |
| 260 | +while : |
| 261 | +do |
| 262 | + for process in "${!KASM_PROCS[@]}"; do |
| 263 | + if ! kill -0 "${KASM_PROCS[$process]}" ; then |
| 264 | + |
| 265 | + # If DLP Policy is set to fail secure, default is to be resilient |
| 266 | + if [[ ${DLP_PROCESS_FAIL_SECURE:-0} == 1 ]]; then |
| 267 | + exit 1 |
| 268 | + fi |
| 269 | + |
| 270 | + case $process in |
| 271 | + kasmvnc) |
| 272 | + if [ "$KASMVNC_AUTO_RECOVER" = true ] ; then |
| 273 | + echo "KasmVNC crashed, restarting" |
| 274 | + start_kasmvnc |
| 275 | + else |
| 276 | + echo "KasmVNC crashed, exiting container" |
| 277 | + exit 1 |
| 278 | + fi |
| 279 | + ;; |
| 280 | + window_manager) |
| 281 | + echo "Window manager crashed, restarting" |
| 282 | + start_window_manager |
| 283 | + ;; |
| 284 | + kasm_audio_out_websocket) |
| 285 | + echo "Restarting Audio Out Websocket Service" |
| 286 | + start_audio_out_websocket |
| 287 | + ;; |
| 288 | + kasm_audio_out) |
| 289 | + echo "Restarting Audio Out Service" |
| 290 | + start_audio_out |
| 291 | + ;; |
| 292 | + kasm_audio_in) |
| 293 | + echo "Audio In Service Failed" |
| 294 | + # TODO: Needs work in python project to support auto restart |
| 295 | + # start_audio_in |
| 296 | + ;; |
| 297 | + upload_server) |
| 298 | + echo "Restarting Upload Service" |
| 299 | + # TODO: This will only work if both processes are killed, requires more work |
| 300 | + start_upload |
| 301 | + ;; |
| 302 | + kasm_gamepad) |
| 303 | + echo "Gamepad Service Failed" |
| 304 | + # TODO: Needs work in python project to support auto restart |
| 305 | + # start_gamepad |
| 306 | + ;; |
| 307 | + custom_script) |
| 308 | + echo "The custom startup script exited." |
| 309 | + # custom startup scripts track the target process on their own, they should not exit |
| 310 | + custom_startup |
| 311 | + ;; |
| 312 | + *) |
| 313 | + echo "Unknown Service: $process" |
| 314 | + ;; |
| 315 | + esac |
| 316 | + fi |
| 317 | + done |
| 318 | + sleep 3 |
| 319 | +done |
| 320 | + |
| 321 | + |
| 322 | +echo "Exiting Kasm container" |
0 commit comments