Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.

Commit 96f6bde

Browse files
committed
Handle subdomain
1 parent 50e76c7 commit 96f6bde

File tree

4 files changed

+245
-63
lines changed

4 files changed

+245
-63
lines changed

kasmvnc/main.tf

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,30 @@ terraform {
99
}
1010
}
1111

12-
variable "agent_id" {
13-
type = string
14-
description = "The ID of a Coder agent."
15-
}
16-
17-
variable "port" {
18-
type = number
19-
description = "The port to run KasmVNC on."
20-
default = 6800
21-
}
22-
23-
variable "kasm_version" {
24-
type = string
25-
description = "Version of KasmVNC to install."
26-
default = "1.3.2"
27-
}
28-
29-
variable "desktop_environment" {
30-
type = string
31-
description = "Specifies the desktop environment of the workspace. This should be pre-installed on the workspace."
32-
validation {
33-
condition = contains(["xfce", "kde", "gnome", "lxde", "lxqt"], var.desktop_environment)
34-
error_message = "Invalid desktop environment. Please specify a valid desktop environment."
35-
}
12+
locals {
13+
template_data = object({
14+
PORT = var.port,
15+
DESKTOP_ENVIRONMENT = var.desktop_environment,
16+
KASM_VERSION = var.kasm_version
17+
SUBDOMAIN = tostring(var.subdomain)
18+
})
3619
}
3720

3821
resource "coder_script" "kasm_vnc" {
3922
agent_id = var.agent_id
4023
display_name = "KasmVNC"
4124
icon = "/icon/kasmvnc.svg"
42-
script = templatefile("${path.module}/run.sh", {
43-
PORT : var.port,
44-
DESKTOP_ENVIRONMENT : var.desktop_environment,
45-
KASM_VERSION : var.kasm_version
46-
})
4725
run_on_start = true
26+
script = templatefile("${path.module}/run.sh", locals.template_data)
4827
}
4928

5029
resource "coder_app" "kasm_vnc" {
5130
agent_id = var.agent_id
5231
slug = "kasm-vnc"
53-
display_name = "kasmVNC"
32+
display_name = "KasmVNC"
5433
url = "http://localhost:${var.port}"
5534
icon = "/icon/kasmvnc.svg"
56-
subdomain = true
35+
subdomain = var.subdomain
5736
share = "owner"
5837
healthcheck {
5938
url = "http://localhost:${var.port}/app"

kasmvnc/path_vnc.html

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Path-Sharing Bounce Page</title>
5+
<style type="text/css">
6+
:root {
7+
color-scheme: light dark;
8+
--dark: #121212;
9+
--header-bg: rgba(127,127,127,0.2);
10+
--light: white;
11+
--rule-color: light-dark(rgba(0,0,0,0.8), rgba(255,255,255,0.8));
12+
background-color: light-dark(var(--light), var(--dark));
13+
color: light-dark(var(--dark), var(--light));
14+
}
15+
body, h1, p {
16+
box-sizing: border-box;
17+
margin:0; padding:0;
18+
}
19+
body{
20+
font-family:Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
21+
}
22+
h1{
23+
width: 100%;
24+
padding: 1rem;
25+
letter-spacing: -1.5pt;
26+
padding-bottom:10px;
27+
border-bottom: 1px solid var(--rule-color);
28+
background-color: var(--header-bg);
29+
}
30+
p {
31+
padding: 1rem; letter-spacing: -0.5pt;}
32+
a.indent { display:inline-block; padding-top:0.5rem; padding-left: 2rem; font-size:0.8rem }
33+
</style>
34+
<meta charset="UTF-8" />
35+
</head>
36+
<body>
37+
<h1>Path-Sharing Bounce Page</h1>
38+
<p>
39+
This application is being served via path sharing.
40+
If you are not redirected, <span id="help">check the
41+
Javascript console in your browser's developer tools
42+
for more information.</span>
43+
</p>
44+
</body>
45+
<script language="javascript">
46+
// This page exists to satisfy the querystring driven client API
47+
// specified here - https://raw.githubusercontent.com/kasmtech/noVNC/bce2d6a7048025c6e6c05df9d98b206c23f6dbab/docs/EMBEDDING.md
48+
// tl;dr:
49+
// * `host` - The WebSocket host to connect to.
50+
// This is just the hostname component of the original URL
51+
// * `port` - The WebSocket port to connect to.
52+
// It doesn't look like we need to set this unless it's different
53+
// than the incoming http request.
54+
// * `encrypt` - If TLS should be used for the WebSocket connection.
55+
// we base this on whether or not the protocol is `https`, seems
56+
// reasonable for now.
57+
// * `path` - The WebSocket path to use.
58+
// This apparently doesn't tolerate a leading `/` so we use a
59+
// function to tidy that up.
60+
function trimFirstCharIf(str, char) {
61+
return str.charAt(0) === char ? str.slice(1) : str;
62+
}
63+
const newloc = new URL(window.location);
64+
const h = document.getElementById("help")
65+
newloc.pathname+="vnc.html"
66+
newloc.searchParams.append("path", trimFirstCharIf(newloc.pathname,"/")+"/websockify");
67+
newloc.searchParams.append("encrypted", newloc.protocol==="https:"? true : false);
68+
console.log(newloc);
69+
h.innerHTML = `click <a id="link" href="${newloc.toString()}">here</a> to go to the application.
70+
<br/><br/>The rewritten URL is:<br/><a id="link" class="indent" href="${newloc.toString()}">${newloc.toString()}</a>`
71+
window.location = newloc.href;
72+
</script>
73+
</html>

kasmvnc/run.sh

Lines changed: 131 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
# Exit on error, undefined variables, and pipe failures
44
set -euo pipefail
55

6+
info() { printf "💁 INFO: %s\n" "$@"; }
7+
warn() { printf "😱 WARNING: %s\n" "$@" ;}
8+
error() { printf "💀 ERROR: %s\n" "$@"; exit 1; }
9+
debug() {
10+
if [ "${DEBUG}" != "Y" ]; then return; fi
11+
printf "🦺 DEBUG: %s\n" "$@"
12+
}
13+
614
# Function to check if vncserver is already installed
715
check_installed() {
816
if command -v vncserver &> /dev/null; then
@@ -29,14 +37,12 @@ download_file() {
2937
# shellcheck disable=SC2034
3038
download_tool=(busybox wget -O-)
3139
else
32-
echo "ERROR: No download tool available (curl, wget, or busybox required)"
33-
exit 1
40+
error "No download tool available (curl, wget, or busybox required)"
3441
fi
3542

3643
# shellcheck disable=SC2288
3744
"$${download_tool[@]}" "$url" > "$output" || {
38-
echo "ERROR: Failed to download $url"
39-
exit 1
45+
error "Failed to download $url"
4046
}
4147
}
4248

@@ -79,16 +85,14 @@ install_rpm() {
7985
# shellcheck disable=SC2034
8086
package_manager=(rpm -i)
8187
else
82-
echo "ERROR: No supported package manager available (dnf, zypper, yum, or rpm required)"
83-
exit 1
88+
error "No supported package manager available (dnf, zypper, yum, or rpm required)"
8489
fi
8590

8691
download_file "$url" "$kasmrpm"
8792

8893
# shellcheck disable=SC2288
8994
sudo "$${package_manager[@]}" "$kasmrpm" || {
90-
echo "ERROR: Failed to install $kasmrpm"
91-
exit 1
95+
error "Failed to install $kasmrpm"
9296
}
9397

9498
rm "$kasmrpm"
@@ -107,8 +111,8 @@ install_alpine() {
107111

108112
# Detect system information
109113
if [[ ! -f /etc/os-release ]]; then
110-
echo "ERROR: Cannot detect OS: /etc/os-release not found"
111-
exit 1
114+
error "Cannot detect OS: /etc/os-release not found"
115+
112116
fi
113117

114118
# shellcheck disable=SC1091
@@ -124,10 +128,11 @@ elif [[ "$ID" == "fedora" ]]; then
124128
distro_version="$(grep -oP '\(\K[\w ]+' /etc/fedora-release | tr '[:upper:]' '[:lower:]' | tr -d ' ')"
125129
fi
126130

127-
echo "Detected Distribution: $distro"
128-
echo "Detected Version: $distro_version"
129-
echo "Detected Codename: $codename"
130-
echo "Detected Architecture: $arch"
131+
echo "🕵 Detected Operating System Information"
132+
echo " 🔎 Distribution: $distro"
133+
echo " 🔎 Version: $distro_version"
134+
echo " 🔎 Codename: $codename"
135+
echo " 🔎 Architecture: $arch"
131136

132137
# Map arch to package arch
133138
case "$arch" in
@@ -145,17 +150,15 @@ case "$arch" in
145150
: # This is effectively a noop
146151
;;
147152
*)
148-
echo "ERROR: Unsupported architecture: $arch"
149-
exit 1
153+
error "Unsupported architecture: $arch"
150154
;;
151155
esac
152156

153157
# Check if vncserver is installed, and install if not
154158
if ! check_installed; then
155159
# Check for NOPASSWD sudo (required)
156160
if ! command -v sudo &> /dev/null || ! sudo -n true 2> /dev/null; then
157-
echo "ERROR: sudo NOPASSWD access required!"
158-
exit 1
161+
error "sudo NOPASSWD access required!"
159162
fi
160163

161164
base_url="https://github.com/kasmtech/KasmVNC/releases/download/v${KASM_VERSION}"
@@ -190,14 +193,14 @@ else
190193
kasm_config_file="$HOME/.vnc/kasmvnc.yaml"
191194
SUDO=
192195

193-
echo "WARNING: Sudo access not available, using user config dir!"
196+
warn "Sudo access not available, using user config dir!"
194197

195198
if [[ -f "$kasm_config_file" ]]; then
196-
echo "WARNING: Custom user KasmVNC config exists, not overwriting!"
197-
echo "WARNING: Ensure that you manually configure the appropriate settings."
199+
warn "Custom user KasmVNC config exists, not overwriting!"
200+
warm "Ensure that you manually configure the appropriate settings."
198201
kasm_config_file="/dev/stderr"
199202
else
200-
echo "WARNING: This may prevent custom user KasmVNC settings from applying!"
203+
warn "This may prevent custom user KasmVNC settings from applying!"
201204
mkdir -p "$HOME/.vnc"
202205
fi
203206
fi
@@ -213,23 +216,119 @@ network:
213216
pem_key:
214217
udp:
215218
public_ip: 127.0.0.1
219+
logging:
220+
log_writer_name: all
221+
log_dest: logfile
222+
level: 30
216223
EOF
217224

225+
get_http_dir() {
226+
# determine the served file path
227+
# Start with the default
228+
httpd_directory="/usr/share/kasmvnc/www"
229+
230+
# Check the system configuration path
231+
if [[ -e /etc/kasmvnc/kasmvnc.yaml ]]; then
232+
d=($(grep -E "^\s*httpd_directory:.*$" /etc/kasmvnc/kasmvnc.yaml))
233+
# If this grep is successful, it will return:
234+
# httpd_directory: /usr/share/kasmvnc/www
235+
if [[ $${#d[@]} -eq 2 && -d "$${d[1]}" ]]; then
236+
httpd_directory="$${d[1]}"
237+
fi
238+
fi
239+
240+
# Check the home directory for overriding values
241+
if [[ -e "$HOME/.vnc/kasmvnc.yaml" ]]; then
242+
d=($(grep -E "^\s*httpd_directory:.*$" /etc/kasmvnc/kasmvnc.yaml))
243+
if [[ $${#d[@]} -eq 2 && -d "$${d[1]}" ]]; then
244+
httpd_directory="$${d[1]}"
245+
fi
246+
fi
247+
echo $httpd_directory
248+
}
249+
250+
fix_server_index_file(){
251+
local fname=$${FUNCNAME[0]} # gets current function name
252+
if [[ $# -ne 1 ]]; then
253+
error "$fname requires exactly 1 parameter:\n\tpath to KasmVNC httpd_directory"
254+
fi
255+
local httpdir="$1"
256+
if [[ ! -d "$httpdir" ]]; then
257+
error "$fname: $httpdir is not a directory"
258+
fi
259+
pushd "$httpdir" > /dev/null
260+
261+
cat <<EOH > /tmp/path_vnc.html
262+
${file(path.module)}
263+
EOH
264+
$SUDO mv /tmp/path_vnc.html .
265+
# check for the switcheroo
266+
if [[ -f "index.html" && -L "vnc.html" ]]; then
267+
$SUDO mv $httpdir/index.html $httpdir/vnc.html
268+
fi
269+
$SUDO ln -s -f path_vnc.html index.html
270+
popd > /dev/null
271+
}
272+
273+
patch_kasm_http_files(){
274+
homedir=$(get_http_dir)
275+
fix_server_index_file "$homedir"
276+
}
277+
278+
if [[ "${SUBDOMAIN}" == "false" ]]; then
279+
info "🩹 Patching up webserver files to support path-sharing..."
280+
patch_kasm_http_files
281+
fi
282+
283+
218284
# This password is not used since we start the server without auth.
219285
# The server is protected via the Coder session token / tunnel
220286
# and does not listen publicly
221287
echo -e "password\npassword\n" | vncpasswd -wo -u "$USER"
222288

223289
# Start the server
224290
printf "🚀 Starting KasmVNC server...\n"
225-
vncserver -select-de "${DESKTOP_ENVIRONMENT}" -disableBasicAuth > /tmp/kasmvncserver.log 2>&1 &
226-
pid=$!
227-
228-
# Wait for server to start
229-
sleep 5
230-
grep -v '^[[:space:]]*$' /tmp/kasmvncserver.log | tail -n 10
231-
if ps -p $pid | grep -q "^$pid"; then
232-
echo "ERROR: Failed to start KasmVNC server. Check full logs at /tmp/kasmvncserver.log"
233-
exit 1
291+
vncserver -select-de "${DESKTOP_ENVIRONMENT}" -disableBasicAuth > /tmp/kasmvncserver.log &
292+
293+
# Kasm writes the pid and the log into ~/.vnc. We can check them for liveness
294+
is_started() {
295+
debug "ls for pidfile: $(ls -alh ~/.vnc/$(hostname):1.pid 2>&1)"
296+
297+
pidfile="$${HOME}/.vnc/$(hostname):1.pid"
298+
if [[ ! -f $pidfile ]]; then
299+
debug "is_started(): no pidfile found"
300+
return 1
301+
fi
302+
pid=$(cat $${pidfile})
303+
debug "$(ps $pid)"
304+
if kill -0 $pid; then
305+
debug "is_started(): found a live PID, setting active"
306+
declare -gx active="Y"
307+
return 0
308+
else
309+
debug "is_started(): PID is not active"
310+
return 1
311+
fi
312+
warning "is_started(): REACHED THE END WITHOUT HITTING A CASE"
313+
return 1
314+
}
315+
316+
# Use a sleep based polling timer to see when Kasm comes up.
317+
waited=0
318+
is_started && debug "is Started: true" || debug "is_started: false"
319+
[[ waited -le 30 ]] && debug "waited -le 30: true" || debug "waited -le 30: false"
320+
while [[ waited -le 30 ]] && ! is_started; do
321+
sleep 1
322+
waited=$((waited+1))
323+
if [[ waited -ne 0 && $((waited % 5)) -eq 0 ]]; then
324+
echo "⏳ Waiting for KasmVNC to start ($waited seconds...)"
325+
fi
326+
is_started && debug "is Started: true" || debug "is_started: false"
327+
[[ waited -le 30 ]] && debug "waited -le 30: true" || debug "waited -le 30: false"
328+
done
329+
330+
if [[ "$active" == "" ]]; then
331+
error "timed out waiting for KasmVNC to start."
234332
fi
235-
printf "🚀 KasmVNC server started successfully!\n"
333+
334+
printf "🚀 KasmVNC server started successfully in $waited seconds!\n"

0 commit comments

Comments
 (0)