104 |
105 |
106 |
--------------------------------------------------------------------------------
/configs/shinyproxy/application.yml:
--------------------------------------------------------------------------------
1 | # BASIC AUTH
2 | proxy:
3 | ### PERSONALIZATION ###
4 | title: ShinyStudio
5 | hide-navbar: false
6 | logo-url: file:///opt/shinyproxy/templates/grid-layout/assets/img/logo.png
7 | favicon-path: /opt/shinyproxy/templates/grid-layout/assets/img/logo.png
8 | template-path: ./templates/grid-layout
9 | ### AUTHENTICATION ###
10 | admin-groups: ['admins']
11 | authentication: simple
12 | users:
13 | - name: ${USER}
14 | password: ${PASSWORD}
15 | groups: admins
16 | ### DANGER ZONE ###
17 | port: 8080 # don't change!
18 | landing-page: /
19 | container-wait-time: 30000
20 | heartbeat-rate: 15000
21 | heartbeat-timeout: 120000
22 | docker:
23 | internal-networking: true
24 | specs:
25 | - id: reports
26 | display-name: Apps & Reports
27 | logo-url: 'fas fa-chart-line'
28 | container-image: dm3ll3n/shinystudio
29 | container-cmd: ["/start.sh", "shiny-server"]
30 | container-network: shinystudio-net
31 | container-volumes:
32 | - "${CONTENT_PATH}/sites/${SITE_NAME}/_apps:/srv/shiny-server:z"
33 | - "${SITE_NAME}_r_libraries:/r-libs"
34 | - "${SITE_NAME}_py_environment:/pyenv"
35 | - "${SITE_NAME}_pwsh_modules:/home/#{proxy.userId}/.local/share/powershell/Modules"
36 | access-groups: [ 'superadmins', 'admins', 'readers' ]
37 | container-env:
38 | USER: "#{proxy.userId}"
39 | USERID: ${USERID}
40 | - id: documents
41 | display-name: Documents
42 | logo-url: 'fas fa-file-alt'
43 | container-image: dm3ll3n/shinystudio
44 | container-cmd: ["/start.sh", "shiny-server"]
45 | container-network: shinystudio-net
46 | container-volumes:
47 | - "${CONTENT_PATH}/sites/${SITE_NAME}/_docs:/srv/shiny-server:z"
48 | - "${SITE_NAME}_r_libraries:/r-libs"
49 | - "${SITE_NAME}_py_environment:/pyenv"
50 | - "${SITE_NAME}_pwsh_modules:/home/#{proxy.userId}/.local/share/powershell/Modules"
51 | access-groups: [ 'superadmins', 'admins', 'readers' ]
52 | container-env:
53 | USER: "#{proxy.userId}"
54 | USERID: ${USERID}
55 | - id: personal
56 | display-name: Personal
57 | logo-url: 'far fa-folder-open'
58 | container-image: dm3ll3n/shinystudio
59 | container-cmd: ["/start.sh", "shiny-server"]
60 | container-network: shinystudio-net
61 | container-volumes:
62 | - "${CONTENT_PATH}/users/#{proxy.userId}:/srv/shiny-server:z"
63 | - "${SITE_NAME}_r_libraries:/r-libs"
64 | - "${SITE_NAME}_py_environment:/pyenv"
65 | - "${SITE_NAME}_pwsh_modules:/home/#{proxy.userId}/.local/share/powershell/Modules"
66 | access-groups: [ 'superadmins', 'admins', 'readers' ]
67 | container-env:
68 | USER: "#{proxy.userId}"
69 | USERID: ${USERID}
70 | - id: rstudio
71 | display-name: RStudio
72 | logo-url: 'fab fa-r-project'
73 | container-image: dm3ll3n/shinystudio
74 | container-cmd: ["/start.sh", "rstudio"]
75 | container-network: shinystudio-net
76 | container-volumes:
77 | - "${CONTENT_PATH}/sites/${SITE_NAME}:/home/#{proxy.userId}/__ShinyStudio__:z"
78 | - "${CONTENT_PATH}/users/#{proxy.userId}:/home/#{proxy.userId}/__Personal__:z"
79 | - "${CONTENT_PATH}/users/#{proxy.userId}/.rstudio:/home/#{proxy.userId}/.rstudio/monitored/user-settings:z"
80 | - "${SITE_NAME}_r_libraries:/r-libs"
81 | - "${SITE_NAME}_py_environment:/pyenv"
82 | - "${SITE_NAME}_pwsh_modules:/home/#{proxy.userId}/.local/share/powershell/Modules"
83 | container-env:
84 | USER: "#{proxy.userId}"
85 | USERID: ${USERID}
86 | description: Full Screen
87 | port: 8787
88 | access-groups: [ 'admins' ]
89 | - id: vscode
90 | display-name: Visual Studio Code
91 | logo-url: 'fas fa-terminal'
92 | container-image: dm3ll3n/shinystudio
93 | container-cmd: ["/start.sh", "vscode"]
94 | container-network: shinystudio-net
95 | container-volumes:
96 | - "${CONTENT_PATH}/sites/${SITE_NAME}:/home/#{proxy.userId}/__ShinyStudio__:z"
97 | - "${CONTENT_PATH}/users/#{proxy.userId}:/home/#{proxy.userId}/__Personal__:z"
98 | - "${CONTENT_PATH}/users/#{proxy.userId}/.vscode:/home/#{proxy.userId}/.local/share/code-server:z"
99 | - "${SITE_NAME}_r_libraries:/r-libs"
100 | - "${SITE_NAME}_py_environment:/pyenv"
101 | - "${SITE_NAME}_pwsh_modules:/home/#{proxy.userId}/.local/share/powershell/Modules"
102 | container-env:
103 | USER: "#{proxy.userId}"
104 | USERID: ${USERID}
105 | description: Full Screen
106 | port: 8443
107 | access-groups: [ 'admins' ]
108 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM rocker/verse:3.6.1
2 |
3 | LABEL maintainer="dm3ll3n@gmail.com"
4 |
5 | # essential vars
6 | ENV DISABLE_AUTH true
7 | ENV R_LIBS_USER /r-libs
8 | ENV APPLICATION_LOGS_TO_STDOUT false
9 |
10 | # add shiny immediately and expose port 3838.
11 | RUN export ADD=shiny && bash /etc/cont-init.d/add
12 |
13 | RUN apt-get update && \
14 | apt-get install -y apt-transport-https && \
15 | apt-get install -y curl nano
16 |
17 | # install Java 8 and ShinyProxy
18 | RUN apt-get install -y openjdk-8-jdk-headless && \
19 | mkdir -p /opt/shinyproxy && \
20 | wget https://www.shinyproxy.io/downloads/shinyproxy-2.3.0.jar -O /opt/shinyproxy/shinyproxy.jar
21 |
22 | COPY configs/shinyproxy/grid-layout /opt/shinyproxy/templates/grid-layout
23 | COPY configs/shinyproxy/application.yml /opt/shinyproxy/application.yml
24 |
25 | # create shared /r-libs directory and ensure it's writeable by all.
26 | RUN mkdir /r-libs && \
27 | echo ".libPaths( c( '/r-libs', .libPaths() ) )" >> /usr/local/lib/R/etc/Rprofile.site
28 |
29 | # install R packages
30 | # rmarkdown 1.12 does not display floating TOC; downgrade to 1.11.
31 | RUN R -e "install.packages(c('reticulate', 'png', 'DBI', 'odbc', 'shinydashboard', 'flexdashboard', 'shinycssloaders', 'DT', 'visNetwork', 'networkD3'))" && \
32 | R -e "install.packages('https://cran.r-project.org/src/contrib/Archive/rmarkdown/rmarkdown_1.11.tar.gz', repos=NULL)"
33 |
34 | COPY samples /srv/shiny-server
35 | RUN mkdir -p /srv/shiny-server/_apps && \
36 | git clone https://github.com/dm3ll3n/Shiny-GEM /srv/shiny-server/_apps/Shiny-GEM && \
37 | Rscript '/srv/shiny-server/_apps/Shiny-GEM/install-requirements.R' && \
38 | chmod -R 777 /r-libs
39 |
40 | # setup python
41 | ENV VIRTUAL_ENV /pyenv
42 | RUN apt-get update && \
43 | apt-get install -y python3-pip python3-venv libpython-dev libpython3-dev python-dev python3-dev && \
44 | python3 -m venv "${VIRTUAL_ENV}" && \
45 | chmod -R 777 "${VIRTUAL_ENV}" && \
46 | "${VIRTUAL_ENV}/bin/activate"
47 |
48 | # install python packages
49 | ENV PATH "${VIRTUAL_ENV}/bin:${PATH}"
50 | RUN echo "export PATH=\"${VIRTUAL_ENV}/bin:\${PATH}\"" >> /etc/profile && \
51 | pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org --upgrade pip && \
52 | pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org wheel && \
53 | pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org \
54 | Cython numpy matplotlib pandas tqdm ezpq paramiko requests pylint jupyter && \
55 | apt-get install -y python3-tk && \
56 | pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org plotnine
57 |
58 | # install pwsh
59 | # https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux
60 | RUN apt-get install -y libc6 libgcc1 libgssapi-krb5-2 liblttng-ust0 libstdc++6 libcurl3 libunwind8 libuuid1 zlib1g libssl1.0.2 libicu57 && \
61 | wget https://github.com/PowerShell/PowerShell/releases/download/v6.2.3/powershell_6.2.3-1.debian.9_amd64.deb -O /tmp/pwsh.deb && \
62 | dpkg -i /tmp/pwsh.deb && \
63 | rm -f /tmp/pwsh.deb && \
64 | pwsh -c "Install-Module SqlServer -Force"
65 |
66 | # install VS code-server
67 | RUN wget https://github.com/cdr/code-server/releases/download/1.1156-vsc1.33.1/code-server1.1156-vsc1.33.1-linux-x64.tar.gz -O /tmp/vs-code-server.tar.gz && \
68 | mkdir /tmp/vs-code-server && \
69 | tar -xzf /tmp/vs-code-server.tar.gz --strip 1 --directory /tmp/vs-code-server && \
70 | mv -f /tmp/vs-code-server/code-server /usr/local/bin/code-server && \
71 | rm -rf /tmp/vs-code-server.tar.gz && \
72 | mkdir /code-server-template && \
73 | code-server --user-data-dir /code-server-template --install-extension ms-python.python && \
74 | code-server --user-data-dir /code-server-template --install-extension ms-vscode.powershell && \
75 | # code-server --user-data-dir /code-server-template --install-extension ms-mssql.mssql && \
76 | code-server --user-data-dir /code-server-template --install-extension yzhang.markdown-all-in-one && \
77 | echo '#!/usr/bin/env bash' > '/setup_vscode.sh' && \
78 | echo 'cp -Rn /code-server-template/* ~/.local/share/code-server' >> '/setup_vscode.sh' && \
79 | chmod 555 '/setup_vscode.sh' && \
80 | # unsure why this is necessary, but it solves a fatal 'file not found' error.
81 | mkdir -p /src/packages/server/build/web && \
82 | echo '' > /src/packages/server/build/web/index.html
83 |
84 | COPY configs/vscode/User/settings.json /code-server-template/User/settings.json
85 | COPY configs/vscode/User/snippets /code-server-template/User/snippets
86 |
87 | # install kerberos
88 | RUN export DEBIAN_FRONTEND=noninteractive && \
89 | apt-get install -y krb5-user
90 |
91 | # install SQL Server odbc driver
92 | RUN apt-get install -y unixodbc && \
93 | wget https://packages.microsoft.com/debian/9/prod/pool/main/m/msodbcsql17/msodbcsql17_17.3.1.1-1_amd64.deb -O /tmp/msodbcsql.deb && \
94 | ACCEPT_EULA=Y dpkg -i /tmp/msodbcsql.deb && \
95 | rm -f /tmp/msodbcsql.deb
96 |
97 | # install PostgreSQL odbc driver
98 | RUN apt-get install -y odbc-postgresql
99 |
100 | # install cloudera odbc driver
101 | RUN wget https://downloads.cloudera.com/connectors/ClouderaImpala_ODBC_2.6.2.1002/Debian/clouderaimpalaodbc_2.6.2.1002-2_amd64.deb -O /tmp/clouderaimpalaodbc_amd64.deb && \
102 | dpkg -i /tmp/clouderaimpalaodbc_amd64.deb && \
103 | rm -f /tmp/clouderaimpalaodbc_amd64.deb
104 |
105 | # custom configs
106 | COPY configs/rstudio/rserver.conf /etc/rstudio/rserver_custom.conf
107 |
108 | COPY configs/odbc/odbcinst.ini /etc/odbcinst.ini
109 | COPY configs/odbc/odbc.ini /etc/odbc.ini
110 |
111 | COPY configs/krb/krb5.conf /etc/krb5.conf
112 | ENV KRB5_CONFIG /etc/krb5.conf
113 |
114 | # copy custom run commands.
115 | COPY configs/rstudio/run /etc/services.d/rstudio/run
116 | COPY configs/vscode/run /etc/services.d/vscode/run
117 | COPY configs/shinyproxy/run /etc/services.d/shinyproxy/run
118 |
119 | # copy custom start command and make it executable.
120 | COPY configs/start.sh /start.sh
121 | RUN chmod +x /start.sh
122 |
123 | CMD [ "/start.sh", "shinyproxy" ]
124 |
--------------------------------------------------------------------------------
/samples/_docs/ShinyStudio/README.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: ShinyStudio
3 | subtitle: 'ShinyProxy + RStudio + Shiny + VS Code for teams, in a browser.'
4 | output:
5 | html_document:
6 | self_contained: true
7 | fig_caption: no
8 | theme: 'spacelab'
9 | highlight: 'haddock'
10 | toc: yes
11 | toc_depth: 3
12 | toc_float:
13 | collapsed: no
14 | smooth_scroll: yes
15 | md_document:
16 | variant: gfm
17 | toc: true
18 | toc_depth: 3
19 | ---
20 |
21 | ## Overview
22 |
23 | 
24 |
25 | The ShinyStudio project is an orchestration of various open-source solutions with the goal of providing:
26 |
27 | * a secured, collaborative development environment for R, Python, PowerShell, and more.
28 | * a secured, convenient way to share apps and documents written in Shiny, RMarkdown, plain Markdown, or HTML.
29 | * easily reproducible, cross-platform setup leveraging Docker for all components.
30 |
31 | 
32 |
33 | 
34 |
35 | There are two distributions of ShinyStudio, the *image* and the *stack*, explained below.
36 |
37 | ### ShinyStudio Image
38 |
39 | The ShinyStudio image, hosted on [DockerHub](https://hub.docker.com/r/dm3ll3n/shinystudio), builds upon the [Rocker project](https://www.rocker-project.org/) to include:
40 |
41 | - [ShinyProxy](https://www.shinyproxy.io/)
42 | - [RStudio Server](https://www.rstudio.com/)
43 | - [VS Code](https://code.visualstudio.com/), modified by [Coder.com](https://coder.com/)
44 | - [Shiny Server](https://shiny.rstudio.com/)
45 |
46 | The image is great for a personal instance, a quick demo, or the building blocks for a very customized setup.
47 |
48 | [Get Started with the Image](#image)
49 |
50 | 
51 |
52 | ### ShinyStudio Stack
53 |
54 | The ShinyStudio stack builds upon the image to incorporate:
55 |
56 | - [NGINX](https://www.nginx.com/) with HTTPS enabled.
57 | - [InfluxDB](https://www.influxdata.com/) for monitoring site usage.
58 |
59 | Each component of the stack is run in a Docker container for reproducibility, scalability, and security. Only the NGINX port is exposed on the host system; all communication between ShinyProxy and other components happens inside an isolated Docker network.
60 |
61 | [Get Started with the Stack](#stack)
62 |
63 | 
64 |
65 | ## Getting Started
66 |
67 | The setup has been verified to work on each of [Docker](https://docs.docker.com/install/linux/docker-ce/ubuntu/) (for Linux) and [Docker Desktop](https://www.docker.com/products/docker-desktop) (for Mac and Windows).
68 |
69 | > Note: when upgrading ShinyStudio, please setup from scratch and migrate existing content/settings afterward.
70 |
71 | > Note: Setup must be run as a non-root user.
72 |
73 | ### Image
74 |
75 | To download and run the ShinyStudio image from [DockerHub](https://hub.docker.com/r/dm3ll3n/shinystudio), first, create a docker network named `shinystudio-net`:
76 |
77 | ```text
78 | docker network create shinystudio-net
79 | ```
80 |
81 | Then, execute `docker run` in the terminal for your OS:
82 |
83 | * Bash (Linux/Mac)
84 |
85 | ``` text
86 | docker run -d --restart always --name shinyproxy \
87 | --network shinystudio-net \
88 | -v /var/run/docker.sock:/var/run/docker.sock \
89 | -e USERID=$USERID \
90 | -e USER=$USER \
91 | -e PASSWORD=password \
92 | -e CONTENT_PATH="${HOME}/ShinyStudio" \
93 | -e SITE_NAME=shinystudio \
94 | -p 8080:8080 \
95 | dm3ll3n/shinystudio
96 | ```
97 |
98 | * PowerShell (Windows)
99 |
100 | ```text
101 | docker run -d --restart always --name shinyproxy `
102 | --network shinystudio-net `
103 | -v /var/run/docker.sock:/var/run/docker.sock `
104 | -e USERID=1000 `
105 | -e USER=$env:USERNAME `
106 | -e PASSWORD=password `
107 | -e CONTENT_PATH="/host_mnt/c/Users/$env:USERNAME/ShinyStudio" `
108 | -e SITE_NAME=shinystudio `
109 | -p 8080:8080 `
110 | dm3ll3n/shinystudio
111 | ```
112 |
113 | > Notice the unique form of the path for the `CONTENT_PATH` variable in the Windows setup.
114 |
115 | Once complete, open a web browser and navigate to `http://:8080`. Log in with your username and the password `password`.
116 |
117 | ### Stack
118 |
119 | The *stack* distribution of ShinyStudio is delivered through the [GitHub repo](https://github.com/dm3ll3n/ShinyStudio) and introduces two additional requirements:
120 |
121 | * [docker-compose](https://docs.docker.com/compose/install/) (ships with Docker Desktop)
122 | * [Git](https://git-scm.com/downloads)
123 |
124 | HTTPS is configured by default, so SSL/TLS certs are required in order for the stack to operate. Use the provided script `certify.sh` (`certify.ps1` for Windows) to create a self-signed certificate, or to request one from LetsEncrypt (more on that).
125 |
126 | #### Minimal setup:
127 |
128 | ```text
129 | # copy the setup files.
130 | git clone https://github.com/dm3ll3n/ShinyStudio
131 |
132 | # enter the directory.
133 | cd ShinyStudio
134 |
135 | # run certify to generate self-signed cert.
136 | ./certify.[sh/ps1]
137 | ```
138 |
139 | Now, browse to `http://` (e.g., `http://localhost`) to access ShinyStudio. On first launch, you will need to accept the warning about an untrusted certificate. See the customized setup to see how to request a trusted cert from LetsEncrypt.
140 |
141 | The default logins are below. See the customized setup to see how to add/remove accounts.
142 |
143 | | **username** | **password** |
144 | |:------------:|:------------:|
145 | | user | user |
146 | | admin | admin |
147 | | superadmin | superadmin |
148 |
149 |
150 | #### Customized setup:
151 |
152 | There are three files essential to a customized configuration:
153 |
154 | 1. `.env`
155 |
156 | > The docker-compose environment file. The project name, content path, and HTTP ports can be changed here.
157 |
158 | Note that Docker volume names are renamed along with the project name, so be prepared to migrate or recreate data stored in Docker volumes when changing the project name.
159 |
160 | 2. `application.yml`
161 |
162 | > The ShinyProxy config file. Users can be added/removed here. Other configurations are available too, such as the site title and the ability to provide a non-standard landing page.
163 |
164 | Using the provided template, you can assign users to the following groups with tiered access:
165 |
166 | - **readers**: can only view content from "Apps & Reports", "Documents", and "Personal".
167 | - **admins**: can view all site content and develop content with RStudio and VS Code.
168 | - **superadmins**: can view and develop site content across multiple instances of ShinyStudio. Can also manage *all* user files.
169 |
170 | Review the [ShinyProxy configuration documentation](https://www.shinyproxy.io/configuration/) for all options.
171 |
172 | 3. `nginx.conf`
173 |
174 | > The NGINX config file. Defines the accepted site name and what ports to listen on.
175 |
176 | If you change the ports here, you must also change the ports defined in the `.env` file. Also, if you change the domain name, you must provide/generate a new certificate for it.
177 |
178 | 4. `certify.[sh/ps1]`
179 |
180 | > The script used to generate a self-signed cert, or to request a trusted cert from LetsEncrypt.
181 |
182 | With no parameters, `certify` generates a self-signed cert for `example.com` (the default domain name defined in `nginx.conf`).
183 |
184 | To generate a self-signed cert with another domain name, first edit the domain name in `nginx.conf`. Afterward, generate a new cert with:
185 |
186 | ```
187 | ./certify.sh
188 |
189 | # e.g., ./certify.sh www.shinystudio.com
190 | ```
191 |
192 | If your server is accessible from the web, you can request a trusted certificate from LetsEncrypt. First, edit `nginx.conf` with your domain name, then request a new cert from LetsEncrypt like so:
193 |
194 | ```
195 | ./certify.sh
196 |
197 | # e.g., ./certify.sh www.shinystudio.com donald@email.com
198 | ```
199 |
200 | CertBot, included in the stack, will automatically renew your LetsEncrypt certificate.
201 |
202 | To manage the services in the stack, use the native docker-compose commands, e.g.:
203 |
204 | ```
205 | # stop all services.
206 | docker-compose down
207 |
208 | # start all services.
209 | docker-compose up -d
210 | ```
211 |
212 | ## Develop
213 |
214 | Open either RStudio or VS Code and notice two important directories:
215 |
216 | - \_\_ShinyStudio\_\_
217 | - \_\_Personal\_\_
218 |
219 | > Files must be saved in either of these two directories in order to persist between sessions.
220 |
221 | 
222 |
223 | These two folders are shared between instances RStudio, VS Code, and Shiny Server. So, creating new content is as simple as saving a file to the appropriate directory.
224 |
225 | 
226 |
227 | ## Tools
228 |
229 | The ShinyStudio image comes with...
230 |
231 | - R
232 | - Python 3
233 | - PowerShell
234 |
235 | ...and ODBC drivers for:
236 |
237 | - SQL Server
238 | - PostgresSQL
239 | - Cloudera Impala.
240 |
241 | These are persistent because they are built into the image.
242 |
243 | | | Persistent |
244 | |----------------------------:|:----------:|
245 | | \_\_ShinyStudio__ directory | Yes |
246 | | \_\_Personal__ directory | Yes |
247 | | Other directories | **No** |
248 | | R Libraries | Yes |
249 | | Python Packages | Yes |
250 | | PowerShell Modules | Yes |
251 | | RStudio User Settings | Yes |
252 | | VS Code User Settings | Yes |
253 | | Installed Apps | **No** |
254 | | Installed Drivers | **No** |
255 |
256 |
257 | ## References
258 |
259 | * https://www.shinyproxy.io/
260 | * https://www.rocker-project.org/
261 | * https://telethonkids.wordpress.com/2019/02/08/deploying-an-r-shiny-app-with-docker/
262 | * https://appsilon.com/alternatives-to-scaling-shiny
263 | * https://github.com/wmnnd/nginx-certbot
264 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShinyStudio
2 |
3 | ## *A Docker orchestration of open-source solutions to facilitate secure, collaborative development.*
4 |
5 | - [Overview](#overview)
6 | - [ShinyStudio Image](#shinystudio-image)
7 | - [ShinyStudio Stack](#shinystudio-stack)
8 | - [Getting Started](#getting-started)
9 | - [Image](#image)
10 | - [Stack](#stack)
11 | - [Develop](#develop)
12 | - [Tools](#tools)
13 | - [References](#references)
14 |
15 | ## Overview
16 |
17 | 
18 |
19 | The ShinyStudio project is an orchestration of various open-source
20 | solutions with the goal of providing:
21 |
22 | - a secured, collaborative development environment for R, Python,
23 | PowerShell, and more.
24 | - a secured, convenient way to share apps and documents written in
25 | Shiny, RMarkdown, plain Markdown, or HTML.
26 | - easily reproducible, cross-platform setup leveraging Docker for all
27 | components.
28 |
29 | 
30 |
31 | 
32 |
33 | There are two distributions of ShinyStudio, the *image* and the *stack*,
34 | explained below.
35 |
36 | ### ShinyStudio Image
37 |
38 | The ShinyStudio image, hosted on
39 | [DockerHub](https://hub.docker.com/r/dm3ll3n/shinystudio), builds upon
40 | the [Rocker project](https://www.rocker-project.org/) to include:
41 |
42 | - [ShinyProxy](https://www.shinyproxy.io/)
43 | - [RStudio Server](https://www.rstudio.com/)
44 | - [VS Code](https://code.visualstudio.com/), modified by
45 | [Coder.com](https://coder.com/)
46 | - [Shiny Server](https://shiny.rstudio.com/)
47 |
48 | The image is great for a personal instance, a quick demo, or the
49 | building blocks for a very customized setup.
50 |
51 | [Get Started with the Image](#image)
52 |
53 | 
54 |
55 | ### ShinyStudio Stack
56 |
57 | The ShinyStudio stack builds upon the image to incorporate:
58 |
59 | - [NGINX](https://www.nginx.com/) with HTTPS enabled.
60 | - [InfluxDB](https://www.influxdata.com/) for monitoring site usage.
61 |
62 | Each component of the stack is run in a Docker container for
63 | reproducibility, scalability, and security. Only the NGINX port is
64 | exposed on the host system; all communication between ShinyProxy and
65 | other components happens inside an isolated Docker network.
66 |
67 | [Get Started with the Stack](#stack)
68 |
69 | 
70 |
71 | ## Getting Started
72 |
73 | The setup has been verified to work on each of
74 | [Docker](https://docs.docker.com/install/linux/docker-ce/ubuntu/) (for
75 | Linux) and [Docker
76 | Desktop](https://www.docker.com/products/docker-desktop) (for Mac and
77 | Windows).
78 |
79 | > Note: when upgrading ShinyStudio, please setup from scratch and
80 | > migrate existing content/settings afterward.
81 |
82 | > Note: Setup must be run as a non-root user.
83 |
84 | ### Image
85 |
86 | To download and run the ShinyStudio image from
87 | [DockerHub](https://hub.docker.com/r/dm3ll3n/shinystudio), first, create
88 | a docker network named `shinystudio-net`:
89 |
90 | ``` text
91 | docker network create shinystudio-net
92 | ```
93 |
94 | Then, execute `docker run` in the terminal for your OS:
95 |
96 | - Bash (Linux/Mac)
97 |
98 |
99 |
100 | ``` text
101 | docker run -d --restart always --name shinyproxy \
102 | --network shinystudio-net \
103 | -v /var/run/docker.sock:/var/run/docker.sock \
104 | -e USERID=$USERID \
105 | -e USER=$USER \
106 | -e PASSWORD=password \
107 | -e CONTENT_PATH="${HOME}/ShinyStudio" \
108 | -e SITE_NAME=shinystudio \
109 | -p 8080:8080 \
110 | dm3ll3n/shinystudio
111 | ```
112 |
113 | - PowerShell (Windows)
114 |
115 |
116 |
117 | ``` text
118 | docker run -d --restart always --name shinyproxy `
119 | --network shinystudio-net `
120 | -v /var/run/docker.sock:/var/run/docker.sock `
121 | -e USERID=1000 `
122 | -e USER=$env:USERNAME `
123 | -e PASSWORD=password `
124 | -e CONTENT_PATH="/host_mnt/c/Users/$env:USERNAME/ShinyStudio" `
125 | -e SITE_NAME=shinystudio `
126 | -p 8080:8080 `
127 | dm3ll3n/shinystudio
128 | ```
129 |
130 | > Notice the unique form of the path for the `CONTENT_PATH` variable in
131 | > the Windows setup.
132 |
133 | Once complete, open a web browser and navigate to
134 | `http://:8080`. Log in with your username and the password
135 | `password`.
136 |
137 | ### Stack
138 |
139 | The *stack* distribution of ShinyStudio is delivered through the [GitHub
140 | repo](https://github.com/dm3ll3n/ShinyStudio) and introduces two
141 | additional requirements:
142 |
143 | - [docker-compose](https://docs.docker.com/compose/install/) (ships
144 | with Docker Desktop)
145 | - [Git](https://git-scm.com/downloads)
146 |
147 | HTTPS is configured by default, so SSL/TLS certs are required in order
148 | for the stack to operate. Use the provided script `certify.sh`
149 | (`certify.ps1` for Windows) to create a self-signed certificate, or to
150 | request one from LetsEncrypt (more on that).
151 |
152 | #### Minimal setup:
153 |
154 | ``` text
155 | # copy the setup files.
156 | git clone https://github.com/dm3ll3n/ShinyStudio
157 |
158 | # enter the directory.
159 | cd ShinyStudio
160 |
161 | # run certify to generate self-signed cert.
162 | ./certify.[sh/ps1]
163 | ```
164 |
165 | Now, browse to `http://` (e.g., `http://localhost`) to access
166 | ShinyStudio. On first launch, you will need to accept the warning about
167 | an untrusted certificate. See the customized setup to see how to request
168 | a trusted cert from LetsEncrypt.
169 |
170 | The default logins are below. See the customized setup to see how to
171 | add/remove accounts.
172 |
173 | | **username** | **password** |
174 | | :----------: | :----------: |
175 | | user | user |
176 | | admin | admin |
177 | | superadmin | superadmin |
178 |
179 | #### Customized setup:
180 |
181 | There are three files essential to a customized configuration:
182 |
183 | 1. `.env`
184 |
185 | > The docker-compose environment file. The project name, content path,
186 | > and HTTP ports can be changed here.
187 |
188 | Note that Docker volume names are renamed along with the project name,
189 | so be prepared to migrate or recreate data stored in Docker volumes when
190 | changing the project name.
191 |
192 | 2. `application.yml`
193 |
194 | > The ShinyProxy config file. Users can be added/removed here. Other
195 | > configurations are available too, such as the site title and the
196 | > ability to provide a non-standard landing page.
197 |
198 | Using the provided template, you can assign users to the following
199 | groups with tiered access:
200 |
201 | - **readers**: can only view content from “Apps & Reports”,
202 | “Documents”, and “Personal”.
203 | - **admins**: can view all site content and develop content with
204 | RStudio and VS Code.
205 | - **superadmins**: can view and develop site content across multiple
206 | instances of ShinyStudio. Can also manage *all* user files.
207 |
208 | Review the [ShinyProxy configuration
209 | documentation](https://www.shinyproxy.io/configuration/) for all
210 | options.
211 |
212 | 3. `nginx.conf`
213 |
214 | > The NGINX config file. Defines the accepted site name and what ports
215 | > to listen on.
216 |
217 | If you change the ports here, you must also change the ports defined in
218 | the `.env` file. Also, if you change the domain name, you must
219 | provide/generate a new certificate for it.
220 |
221 | 4. `certify.[sh/ps1]`
222 |
223 | > The script used to generate a self-signed cert, or to request a
224 | > trusted cert from LetsEncrypt.
225 |
226 | With no parameters, `certify` generates a self-signed cert for
227 | `example.com` (the default domain name defined in `nginx.conf`).
228 |
229 | To generate a self-signed cert with another domain name, first edit the
230 | domain name in `nginx.conf`. Afterward, generate a new cert with:
231 |
232 | ./certify.sh
233 |
234 | # e.g., ./certify.sh www.shinystudio.com
235 |
236 | If your server is accessible from the web, you can request a trusted
237 | certificate from LetsEncrypt. First, edit `nginx.conf` with your domain
238 | name, then request a new cert from LetsEncrypt like so:
239 |
240 | ./certify.sh
241 |
242 | # e.g., ./certify.sh www.shinystudio.com donald@email.com
243 |
244 | CertBot, included in the stack, will automatically renew your
245 | LetsEncrypt certificate.
246 |
247 | To manage the services in the stack, use the native docker-compose
248 | commands, e.g.:
249 |
250 | # stop all services.
251 | docker-compose down
252 |
253 | # start all services.
254 | docker-compose up -d
255 |
256 | ## Develop
257 |
258 | Open either RStudio or VS Code and notice two important directories:
259 |
260 | - \_\_ShinyStudio\_\_
261 | - \_\_Personal\_\_
262 |
263 | > Files must be saved in either of these two directories in order to
264 | > persist between sessions.
265 |
266 | 
267 |
268 | These two folders are shared between instances RStudio, VS Code, and
269 | Shiny Server. So, creating new content is as simple as saving a file to
270 | the appropriate directory.
271 |
272 | 
273 |
274 | ## Tools
275 |
276 | The ShinyStudio image comes with…
277 |
278 | - R
279 | - Python 3
280 | - PowerShell
281 |
282 | …and ODBC drivers for:
283 |
284 | - SQL Server
285 | - PostgresSQL
286 | - Cloudera Impala.
287 |
288 | These are persistent because they are built into the image.
289 |
290 | | | Persistent |
291 | | ----------------------------: | :--------: |
292 | | \_\_ShinyStudio\_\_ directory | Yes |
293 | | \_\_Personal\_\_ directory | Yes |
294 | | Other directories | **No** |
295 | | R Libraries | Yes |
296 | | Python Packages | Yes |
297 | | PowerShell Modules | Yes |
298 | | RStudio User Settings | Yes |
299 | | VS Code User Settings | Yes |
300 | | Installed Apps | **No** |
301 | | Installed Drivers | **No** |
302 |
303 | ## References
304 |
305 | -
306 | -
307 | -
308 | -
309 | -
310 |
--------------------------------------------------------------------------------
/samples/_docs/Jupyter Notebook/example.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{},"source":[" Use VS code to author Python scripts and easily convert them to Jupyter notebooks.\n","\n"," Afterward, conver the Jupyter notebook (.ipynb) to HTML so it is viewable in Shiny Server.\n","\n"," `jupyter nbconvert *.ipynb --to html -y --template full`\n","\n"," See more: https://code.visualstudio.com/docs/python/jupyter-support"]},{"cell_type":"markdown","metadata":{},"source":[" Below is a quick demo of the Python library, [ezpq](https://github.com/dm3ll3n/ezpq)."]},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[],"source":["\n","import ezpq\n","import time\n","import pandas as pd\n",""]},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":"60 job results.\n"},{"data":{"text/html":"
\n\n
\n \n
\n
\n
qid
\n
id
\n
lane
\n
runtime
\n
\n \n \n
\n
0
\n
queue_1
\n
1
\n
0
\n
1.010010
\n
\n
\n
1
\n
queue_1
\n
2
\n
1
\n
1.022951
\n
\n
\n
2
\n
queue_1
\n
3
\n
2
\n
1.036379
\n
\n
\n
3
\n
queue_1
\n
4
\n
3
\n
1.030257
\n
\n
\n
4
\n
queue_1
\n
5
\n
4
\n
1.017502
\n
\n \n
\n
","text/plain":" qid id lane runtime\n0 queue_1 1 0 1.010010\n1 queue_1 2 1 1.022951\n2 queue_1 3 2 1.036379\n3 queue_1 4 3 1.030257\n4 queue_1 5 4 1.017502"},"execution_count":5,"metadata":{},"output_type":"execute_result"}],"source":["all_output = list()\n","\n","# run three different `ezpq` parallel queues, sequentially.\n","for qid in [1, 2, 3]:\n"," # each queue will process 5 jobs at a time.\n"," with ezpq.Queue(5, qid='queue_' + str(qid)) as Q:\n"," # submit 20 jobs, each taking exactly one second.\n"," for i in range(20):\n"," lane = i % 5 # lanes handle dependent jobs.\n"," Q.put(time.sleep, args=1,\n"," lane=lane, name='Job '+str(i))\n","\n"," # wait for all enqueued jobs to complete.\n"," Q.wait()\n"," \n"," # collect job results\n"," all_output.extend( Q.collect() )\n","\n","print('{} job results.'.format(len(all_output)))\n","\n","# Peek at results in a dataframe.\n","pd.DataFrame( all_output )[['qid', 'id', 'lane', 'runtime']].head()\n",""]},{"cell_type":"code","execution_count":6,"metadata":{},"outputs":[{"data":{"image/png":"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\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":""},"execution_count":6,"metadata":{},"output_type":"execute_result"}],"source":["\n","# Plot queue operations.\n","ezpq.Plot(all_output).build(facet_by='qid',\n"," color_by='lane',\n"," color_pal=['blue', 'orange', 'green',\n"," 'red', 'purple'])\n",""]},{"cell_type":"code","execution_count":7,"metadata":{},"outputs":[],"source":""}],"nbformat":4,"nbformat_minor":2,"metadata":{"language_info":{"name":"python","codemirror_mode":{"name":"ipython","version":3}},"orig_nbformat":2,"file_extension":".py","mimetype":"text/x-python","name":"python","npconvert_exporter":"python","pygments_lexer":"ipython3","version":3}}
--------------------------------------------------------------------------------