Skip to content
Merged
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
33 changes: 33 additions & 0 deletions docs/playbooks/rstudio_server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Playbook RStudio Server
[back to index](../index.md#Roles)

## Summary

This role installs [RStudio Server](https://posit.co/products/open-source/rstudio/), an open-source IDE for R programming. It integrates RStudio Server within a JupyterHub environment, enabling users to access the IDE via their browsers. Login via SRAM.

The playbook supports two modes of operation:
1. **Standalone mode**: RStudio Server runs independently of JupyterLab. Users are immediately directed to RStudio.
2. **Non-standalone mode**: RStudio Server is configured as a JupyterLab extension and can be launched by users within the JupyterLab interface, which they see when they login to the wroskpace. This mode integrates RStudio seamlessly into a multi-application JupyterHub setup.

### Security note

In both modes, RStudio Server instances are spawned by JupyterHub and run under the user’s credentials. Each user has an isolated instance, ensuring data separation. RStudio is run using a Unix Domain Socket, ensuring that users with shell access cannot directly connect to another user's server.

## Variables

- `rstudio_fqdn`: String. Fully Qualified Domain Name (FQDN) where RStudio Server/JupyterHub will be served. Defaults to `localhost`.
- `rstudio_base_path`: String. URI at which RStudio Server is served. Default: `/`.
- `rstudio_jupyter_venv_path`: String. Location of the virtual environment for JupyterHub and related components. Default: `/usr/local/jupyterhub`.
- `rstudio_standalone`: Boolean. Specifies whether RStudio Server should be installed in standalone mode. Default: `false`.
- `rstudio_pip_cmd`: String. Command used to install pip packages (default: `uv`, can be set to `pip`).

## See also

- Role [rstudio](../roles/rstudio.md)
- Role [jupyterhub_standalone_proxy](../roles/jupyterhub_standalone_proxy.md)
- Role [jupyterhub_app](../roles/jupyterhub_app.md)

## History
2026 Written by Dawa Ometto (Utrecht University).

[back to index](../index.md#Roles)
4 changes: 2 additions & 2 deletions docs/roles/jupyterhub_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ The resulting config for a JupyterLab server extension will look as follows:
settings = { "Example":
{'command': ['/usr/bin/python3', '-m' 'http.server', '-p', '{port}'], 'launcher_entry': {'icon_path': '/path/to/my/icon.svg', 'category': 'Other'}
}
c.ServerProxy.servers.update({
}) # use update instead of assignment so multiple apps can be added to the config.
c.ServerProxy.servers.update(settings
) # use update instead of assignment so multiple apps can be added to the config.
```

The resulting config for a standalone app will look as follows:
Expand Down
10 changes: 8 additions & 2 deletions docs/roles/rstudio.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@
[back to index](../index.md#Roles)

## Summary
Installs R-studio on Ubuntu 20.04 and 22.04
Installs RStudio Desktop or Server.

## Requires
Ubuntu 20.04 or 22.04 and an installation of R (e.g. by executing the r-workbench playbook first)
- Ubuntu >= 20.04
- Untested supported for RHEL-like Linux

## Description
Installation is done using apt and the debian package available [here](https://posit.co/download/rstudio-desktop/).

## Variables

- `rstudio_kind`: String. Whether to install the desktop or server version of RStudio. Default: `desktop` (set to `server` alternatively).

## History
2022 Written by Sytse Groenwold (Universiteit Utrecht)
2024 Updated by Jelle Treep (Utrecht University)
2025 Updated by Dawa Ometto (Utrecht University)

[back to index](../index.md#Roles)
1 change: 1 addition & 0 deletions molecule/playbook-openrefine-standalone/molecule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ provisioner:
openrefine_max_memory: '60M'
jupyterhub_app_pip_executable: uv_pip
jupyterhub_auth: noauth
jupyterhub_debug: true
jupyterhub_activate_remote_user_auth: true # mock SRAM auth by enabling remote user auth in JupyterHub config, despite setting auth to noauth.
jupyterhub_proxy_config:
proxy_set_header:
Expand Down
1 change: 1 addition & 0 deletions molecule/playbook-openrefine/molecule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ provisioner:
- name: jupyterhub
path: jupyterhub.yml
parameters:
jupyterhub_debug: true
jupyter_auth: noauth
jupyter_activate_remote_user_auth: true # mock SRAM auth by enabling remote user auth in JupyterHub config, despite setting auth to noauth.
jupyter_proxy_config: "{ proxy_set_header: { REMOTE_USER: '$$http_user'} }"
Expand Down
30 changes: 30 additions & 0 deletions molecule/playbook-openvscodeserver-standalone/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
platforms:
- name: workspace-src-ubuntu_jammy-nginx
image: ghcr.io/utrechtuniversity/src-test-workspace:ubuntu_jammy-nginx
command: /sbin/init
pre_build_image: true
published_ports:
- 8080:80
registry:
url: $DOCKER_REGISTRY
credentials:
username: $DOCKER_USER
password: $DOCKER_PW
provisioner:
name: ansible
env:
components:
- name: openvscodeserver
path: openvscodeserver.yml
parameters:
openvscode_base_path: "/ide"
openvscode_jupyter_force_install: 'true'
jhsp_external_addr: localhost:8080
jupyterhub_app_pip_executable: uv_pip
jupyterhub_debug: true
jupyterhub_auth: noauth
jupyterhub_activate_remote_user_auth: true # mock SRAM auth by enabling remote user auth in JupyterHub config, despite setting auth to noauth.
jupyterhub_proxy_config:
proxy_set_header:
REMOTE_USER: "$$http_user" # allows us to set the 'user' header in tests to mock login. We need double dollar signs because of the way ansible's `to_yaml` filters escapes!
22 changes: 22 additions & 0 deletions molecule/playbook-openvscodeserver-standalone/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
- name: Verify
hosts: all
gather_facts: false
tasks:
- name: Test OpenVSCode Server
ansible.builtin.uri:
url: http://localhost/ide
method: GET
headers:
user: testuser
return_content: true
follow_redirects: true
register: openvscode_login
failed_when: openvscode_login.failed or openvscode_login.url != 'http://localhost/ide/user/testuser/'
retries: 5
delay: 3

- name: Assert content correct
ansible.builtin.assert:
that:
- '"vscode-workbench" in openvscode_login.content'
16 changes: 8 additions & 8 deletions molecule/playbook-openvscodeserver/molecule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ provisioner:
name: ansible
env:
components:
- name: jupyterhub
path: jupyterhub.yml
parameters:
jupyterhub_debug: true
jupyter_auth: noauth
jupyter_activate_remote_user_auth: true # mock SRAM auth by enabling remote user auth in JupyterHub config, despite setting auth to noauth.
jupyter_proxy_config: "{ proxy_set_header: { REMOTE_USER: '$$http_user'} }"
- name: openvscodeserver
path: openvscodeserver.yml
parameters:
openvscode_base_path: "/ide"
openvscode_jupyter_force_install: 'true'
jhsp_external_addr: localhost:8080
jupyterhub_app_pip_executable: uv_pip
jupyterhub_auth: noauth
jupyterhub_activate_remote_user_auth: true # mock SRAM auth by enabling remote user auth in JupyterHub config, despite setting auth to noauth.
jupyterhub_proxy_config:
proxy_set_header:
REMOTE_USER: "$$http_user" # allows us to set the 'user' header in tests to mock login. We need double dollar signs because of the way ansible's `to_yaml` filters escapes!
openvscode_jupyter_force_install: 'false'
8 changes: 4 additions & 4 deletions molecule/playbook-openvscodeserver/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
hosts: all
gather_facts: false
tasks:
- name: Test OpenVSCode Server
- name: Test Open VSCode
ansible.builtin.uri:
url: http://localhost/ide
url: http://localhost/hub/spawn/testuser?next=%2Fhub%2Fuser%2Ftestuser%2Fide%2F
method: GET
headers:
user: testuser
user: testuser # mock SRAM login
return_content: true
follow_redirects: true
register: openvscode_login
failed_when: openvscode_login.failed or openvscode_login.url != 'http://localhost/ide/user/testuser/'
failed_when: openvscode_login.failed or not openvscode_login.url is match('http://localhost/user/testuser/ide')
retries: 5
delay: 3

Expand Down
29 changes: 29 additions & 0 deletions molecule/playbook-rstudio_server-standalone/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
platforms:
- name: workspace-src-ubuntu_jammy-nginx
image: ghcr.io/utrechtuniversity/src-test-workspace:ubuntu_jammy-nginx
command: /sbin/init
pre_build_image: true
published_ports:
- 8080:80
registry:
url: $DOCKER_REGISTRY
credentials:
username: $DOCKER_USER
password: $DOCKER_PW
provisioner:
name: ansible
env:
components:
- name: rstudio
path: rstudio_server.yml
parameters:
rstudio_base_path: "/rstudio"
rstudio_standalone: 'true'
jupyterhub_debug: true
jupyterhub_app_pip_executable: uv_pip
jupyterhub_auth: noauth
jupyterhub_activate_remote_user_auth: true # mock SRAM auth by enabling remote user auth in JupyterHub config, despite setting auth to noauth.
jupyterhub_proxy_config:
proxy_set_header:
REMOTE_USER: "$$http_user" # allows us to set the 'user' header in tests to mock login. We need double dollar signs because of the way ansible's `to_yaml` filters escapes!
22 changes: 22 additions & 0 deletions molecule/playbook-rstudio_server-standalone/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
- name: Verify
hosts: all
gather_facts: false
tasks:
- name: Test rstudio
ansible.builtin.uri:
url: http://localhost/rstudio/
method: GET
headers:
user: testuser # mock SRAM login
return_content: true
follow_redirects: true
register: rstudio_login
failed_when: rstudio_login.failed or rstudio_login.url != 'http://localhost/rstudio/user/testuser/'
retries: 5
delay: 3

- name: Assert content correct
ansible.builtin.assert:
that:
- '"Posit" in rstudio_login.content'
29 changes: 29 additions & 0 deletions molecule/playbook-rstudio_server/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
platforms:
- name: workspace-src-ubuntu_jammy-nginx
image: ghcr.io/utrechtuniversity/src-test-workspace:ubuntu_jammy-nginx
command: /sbin/init
pre_build_image: true
published_ports:
- 8080:80
registry:
url: $DOCKER_REGISTRY
credentials:
username: $DOCKER_USER
password: $DOCKER_PW
provisioner:
name: ansible
env:
components:
- name: jupyterhub
path: jupyterhub.yml
parameters:
jupyter_auth: noauth
jupyter_activate_remote_user_auth: true # mock SRAM auth by enabling remote user auth in JupyterHub config, despite setting auth to noauth.
jupyter_proxy_config: "{ proxy_set_header: { REMOTE_USER: '$$http_user'} }"
- name: rstudio
path: rstudio_server.yml
parameters:
rstudio_base_path: "/rstudio"
rstudio_standalone: 'false'
jupyterhub_app_pip_executable: uv_pip
22 changes: 22 additions & 0 deletions molecule/playbook-rstudio_server/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
- name: Verify
hosts: all
gather_facts: false
tasks:
- name: Test rstudio
ansible.builtin.uri:
url: http://localhost/hub/spawn/testuser?next=%2Fhub%2Fuser%2Ftestuser%2Frstudio%2F
method: GET
headers:
user: testuser # mock SRAM login
return_content: true
follow_redirects: true
register: rstudio_login
failed_when: rstudio_login.failed or not rstudio_login.url is match ('http://localhost/user/testuser/rstudio')
retries: 5
delay: 3

- name: Assert content correct
ansible.builtin.assert:
that:
- '"Posit" in rstudio_login.content'
14 changes: 14 additions & 0 deletions playbooks/roles/jupyterhub/molecule/base_path/converge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
- name: Converge
hosts: all
gather_facts: true
vars:
jupyterhub_uri: /base_path
jupyterhub_auth: noauth
jupyterhub_activate_remote_user_auth: true # mock SRAM auth by enabling remote user auth in JupyterHub config, despite setting auth to noauth.
jupyterhub_proxy_config:
proxy_set_header:
REMOTE_USER: '$http_user' # allows us to set the 'user' header in tests to mock login
jupyterhub_debug: true
roles:
- role: jupyterhub
26 changes: 26 additions & 0 deletions playbooks/roles/jupyterhub/molecule/base_path/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
driver:
name: ${DRIVER-podman}
image_settings: &image_settings
pre_build_image: true
registry:
url: $DOCKER_REGISTRY
credentials:
username: $DOCKER_USER
password: $DOCKER_PW
provisioner:
name: ansible
playbooks:
prepare: ./prepare.yml
converge: ./converge.yml
env:
ANSIBLE_ROLES_PATH: ../../../
role_name_check: 1
platforms:
- name: workspace-src-ubuntu_jammy-nginx
image: ghcr.io/utrechtuniversity/src-test-workspace:ubuntu_jammy-nginx
command: /sbin/init
pre_build_image: true
published_ports:
- 8080:80
<<: *image_settings
9 changes: 9 additions & 0 deletions playbooks/roles/jupyterhub/molecule/base_path/prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
- name: Prepare
hosts: all
gather_facts: true
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
when: ansible_pkg_mgr == 'apt'
50 changes: 50 additions & 0 deletions playbooks/roles/jupyterhub/molecule/base_path/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
- name: Verify
hosts: all
gather_facts: false
tasks:
- name: Test login
ansible.builtin.uri:
url: http://localhost:80/base_path
method: GET
headers:
user: testuser
return_content: true
failed_when: false
register: jupyterhub_login

- name: Test login with not allowed user
ansible.builtin.uri:
url: http://localhost:80/base_path
method: GET
headers:
user: root
failed_when: false
register: jupyterhub_login_not_allowed

- name: Test login with not existing user
ansible.builtin.uri:
url: http://localhost:80/base_path
method: GET
headers:
user: foo
failed_when: false
register: jupyterhub_login_not_exist

- name: Debug 1
ansible.builtin.debug:
var: jupyterhub_login

- name: Debug 2
ansible.builtin.debug:
var: jupyterhub_login_not_allowed

- name: Debug 3
ansible.builtin.debug:
var: jupyterhub_login_not_exist

- name: Assert failed logins
ansible.builtin.assert:
that:
- jupyterhub_login.status == 200
- jupyterhub_login_not_allowed.status == 500
- jupyterhub_login_not_exist.status == 500
Loading
Loading