Skip to content

Add Selkies desktop streaming module #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions registry/coder/modules/selkies/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
display_name: Selkies Desktop
description: Stream and access remote Linux desktops using Selkies-GStreamer
icon: ../../../../.icons/vnc.svg
maintainer_github: coder
verified: true
tags: [selkies, desktop, streaming]
---

# Selkies Desktop

Automatically install [Selkies-GStreamer](https://selkies.io/) in a workspace, and create an app to access it via the dashboard.

```tf
module "selkies" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/selkies/coder"
# version = "1.0.0"
agent_id = coder_agent.example.id
# Selkies does not require desktop_environment option
subdomain = true
}
```

> **Note:** Requires X11 desktop and PulseAudio; see [Selkies quick start docs](https://selkies-project.github.io/selkies/start/#quick-start) for setup.
41 changes: 41 additions & 0 deletions registry/coder/modules/selkies/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, expect, it } from "bun:test";
import {
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "~test";

const allowedDesktopEnvs = ["xfce", "kde", "gnome", "lxde", "lxqt"] as const;
type AllowedDesktopEnv = (typeof allowedDesktopEnvs)[number];

type TestVariables = Readonly<{
agent_id: string;
# desktop_environment not required for Selkies
port?: string;
selkies_version?: string;
}>;

describe("Selkies Desktop", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables<TestVariables>(import.meta.dir, {
agent_id: "foo",
desktop_environment: "gnome",
});

it("Successfully installs for all expected Kasm desktop versions", async () => {
const testVars = {
agent_id: "foo",
selkies_version: "latest"
};
runTerraformApply<TestVariables>(import.meta.dir, testVars);
const applyWithEnv = () => {
runTerraformApply<TestVariables>(import.meta.dir, {
agent_id: "foo",
desktop_environment: v,
});
};

expect(applyWithEnv).not.toThrow();
}
});
});
68 changes: 68 additions & 0 deletions registry/coder/modules/selkies/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.5"
}
}
}

variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}

variable "port" {
type = number
description = "The port to run Selkies Desktop on."
default = 6800
}

variable "selkies_version" {
type = string
description = "Version of Selkies-GStreamer to install (latest recommended)."
default = "latest"
}

variable "order" {
type = number
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
default = null
}

variable "group" {
type = string
description = "The name of a group that this app belongs to."
default = null
}



resource "coder_script" "selkies_desktop" {
agent_id = var.agent_id
display_name = "Selkies Desktop"
icon = "/icon/vnc.svg"
run_on_start = true
script = templatefile("${path.module}/run.sh", {
PORT = var.port
SELKIES_VERSION = var.selkies_version
})
}

resource "coder_app" "selkies_desktop" {
agent_id = var.agent_id
slug = "selkies-desktop"
display_name = "Selkies Desktop"
url = "http://localhost:${var.port}"
icon = "/icon/vnc.svg"
share = "owner"
order = var.order
group = var.group

healthcheck {
url = "http://localhost:${var.port}/"
interval = 5
threshold = 5
}
}
81 changes: 81 additions & 0 deletions registry/coder/modules/selkies/path_vnc.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<title>Path-Sharing Bounce Page</title>
<style type="text/css">
:root {
color-scheme: light dark;
--dark: #121212;
--header-bg: rgba(127,127,127,0.2);
--light: white;
--rule-color: light-dark(rgba(0,0,0,0.8), rgba(255,255,255,0.8));
background-color: light-dark(var(--light), var(--dark));
color: light-dark(var(--dark), var(--light));
}
body, h1, p {
box-sizing: border-box;
margin:0; padding:0;
}
body{
font-family:Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
h1{
width: 100%;
padding: 1rem;
letter-spacing: -1.5pt;
padding-bottom:10px;
border-bottom: 1px solid var(--rule-color);
background-color: var(--header-bg);
}
p {
padding: 1rem; letter-spacing: -0.5pt;}
a.indent { display:inline-block; padding-top:0.5rem; padding-left: 2rem; font-size:0.8rem }
</style>
<meta charset="UTF-8" />
</head>
<body>
<h1>Path-Sharing Bounce Page</h1>
<p>
This application is being served via path sharing.
If you are not redirected, <span id="help">check the
Javascript console in your browser's developer tools
for more information.</span>
</p>
</body>
<script language="javascript">
// This page exists to satisfy the querystring driven client API
// specified here - https://raw.githubusercontent.com/kasmtech/noVNC/bce2d6a7048025c6e6c05df9d98b206c23f6dbab/docs/EMBEDDING.md
// tl;dr:
// * `host` - The WebSocket host to connect to.
// This is just the hostname component of the original URL
// * `port` - The WebSocket port to connect to.
// It doesn't look like we need to set this unless it's different
// than the incoming http request.
// * `encrypt` - If TLS should be used for the WebSocket connection.
// we base this on whether or not the protocol is `https`, seems
// reasonable for now.
// * `path` - The WebSocket path to use.
// This apparently doesn't tolerate a leading `/` so we use a
// function to tidy that up.
function trimFirstCharIf(str, char) {
return str.charAt(0) === char ? str.slice(1) : str;
}
function trimLastCharIf(str, char) {
return str.endsWith("/") ? str.slice(0,str.length-1) : str;
}
const newloc = new URL(window.location);
const h = document.getElementById("help")

// Building the websockify path must happen before we append the filename to newloc.pathname
newloc.searchParams.append("path",
trimLastCharIf(trimFirstCharIf(newloc.pathname,"/"),"/")+"/websockify");
newloc.searchParams.append("encrypted", newloc.protocol==="https:"? true : false);

newloc.pathname += "vnc.html"
console.log(newloc);

h.innerHTML = `click <a id="link" href="${newloc.toString()}">here</a> to go to the application.
<br/><br/>The rewritten URL is:<br/><a id="link" class="indent" href="${newloc.toString()}">${newloc.toString()}</a>`
window.location = newloc.href;
</script>
</html>
43 changes: 43 additions & 0 deletions registry/coder/modules/selkies/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail

error() { printf "💀 ERROR: %s\n" "$@"; exit 1; }


SELKIES_DIR="/opt/selkies-gstreamer-$SELKIES_VERSION"
if [[ ! -d "$SELKIES_DIR" ]]; then
apt-get update -yq && apt-get install -yq --no-install-recommends \
ca-certificates curl jq tar gzip libpulse0 libegl1-mesa xvfb pulseaudio
mkdir -p "$SELKIES_DIR"
SELKIES_VERSION_RESOLVED=$SELKIES_VERSION
if [[ "$SELKIES_VERSION" == "latest" ]]; then
SELKIES_VERSION_RESOLVED=$(curl -fsSL "https://api.github.com/repos/selkies-project/selkies-gstreamer/releases/latest" | jq -r .tag_name | sed 's/^v//')
fi
curl -fsSL "https://github.com/selkies-project/selkies-gstreamer/releases/download/v${SELKIES_VERSION_RESOLVED}/selkies-gstreamer-portable-v${SELKIES_VERSION_RESOLVED}_amd64.tar.gz" \
| tar -xzf - -C "$SELKIES_DIR"
fi

export DISPLAY=":0"
export PULSE_SERVER="unix:${XDG_RUNTIME_DIR:-/tmp}/pulse/native"

BASIC_USER="${USER:-coder}"
BASIC_PASS="mypasswd"

set +e
"$SELKIES_DIR/selkies-gstreamer-run" \
--addr=0.0.0.0 \
--port="$PORT" \
--enable_https=false \
--basic_auth_user="$BASIC_USER" \
--basic_auth_password="$BASIC_PASS" \
--encoder=x264enc \
> /tmp/selkies_desktop.log 2>&1 &
sleep 3
RETVAL=$(pgrep -f selkies-gstreamer-run > /dev/null && echo 0 || echo 1)
set -e
if [[ $RETVAL -ne 0 ]]; then
echo "ERROR: Failed to start Selkies server"
cat /tmp/selkies_desktop.log || true
exit 1
fi
printf "🚀 Selkies Desktop streaming started successfully!\n"