From a3d61f765549b373ce94122ea6e69353937d1c56 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 8 Mar 2021 03:50:55 +0530 Subject: [PATCH 01/21] Re-organize documentation to match new structure - Added docs on authentication methods we support - Split up 'users' docs into 3 separate page, to match different kinds of content - Make docs slightly more verbose in the service of completeness --- admin/configuration/culling.md | 11 +++++++++ admin/configuration/login.md | 25 +++++++++++++++++++ admin/howto/access-server.md | 30 +++++++++++++++++++++++ admin/howto/manage-users.md | 45 ++++++++++++++++++++++++++++++++++ admin/users.md | 44 --------------------------------- index.md | 15 +++++++++++- 6 files changed, 125 insertions(+), 45 deletions(-) create mode 100644 admin/configuration/culling.md create mode 100644 admin/configuration/login.md create mode 100644 admin/howto/access-server.md create mode 100644 admin/howto/manage-users.md delete mode 100644 admin/users.md diff --git a/admin/configuration/culling.md b/admin/configuration/culling.md new file mode 100644 index 0000000..dc6df60 --- /dev/null +++ b/admin/configuration/culling.md @@ -0,0 +1,11 @@ +# User server culling + +To ensure efficient resource usage, user servers without interactive usage for a +period of time (default 1h) are automatically stopped (via +[jupyterhub-idle-culler](https://github.com/jupyterhub/jupyterhub-idle-culler)). +This means your notebook server might be stopped for inactivity even if you have +a long running process in the notebook. This timeout can be configured. + +This has the same effect as a user stopping their own server. User servers stopping doesn't lose any data in your home directories. However, any packages temporarily installed via `!pip` or `!conda` are cleared, to make sure that everyone in the hub is operating from the same clean environment as much as possible. Active notebooks have their kernel killed as well. + +There is currently no maximum time limit for a user's notebook. diff --git a/admin/configuration/login.md b/admin/configuration/login.md new file mode 100644 index 0000000..0ea682b --- /dev/null +++ b/admin/configuration/login.md @@ -0,0 +1,25 @@ +# User authentication & authorization + +(admin/configuration/authentication)= +## Authentication + +Users can prove who they are by logging in via an *authentication provider*. Currently, the following providers are supported: + +1. *Google*. This includes public `@gmail.com` accounts, as well as [Google Workspace](https://workspace.google.com/) accounts set up for your workspace or university. If you use the GMail interface to access your work / university email, it can be used here. + +2. [*GitHub*](https://github.com/). Extremely popular community of people creating, publishing and collaborating on code. Accounts are free, and many people already have them especially since the target community for most hubs are people who also write some kind of code. + +3. [*ORCID*](https://orcid.org/). Everyone who has published a paper has one of these, and anyone else can easily sign up. Almost exclusively used by researchers. + +4. ???. We could probably support other authentication providers, depending on your specific needs and the provider's complexity. Please reach out to us if none of these 3 work. + +## Authorization + +Not everyone who can authenticate is granted access to the hub - that +would mean everyone with a `@gmail.com` account can log in if you use Google as your authentication provider! Instead, we support multiple ways for hub admins to specify which users are *authorized* to be on the hub. + +Currently, there are only two supported methods: + +1. Manually add users via the admin panel in JupyterHub +2. (Google only) Allow all users who are logged in via aparticular domain - so you can allow access to anyone who is part of your organization or educational institution. + diff --git a/admin/howto/access-server.md b/admin/howto/access-server.md new file mode 100644 index 0000000..411b37f --- /dev/null +++ b/admin/howto/access-server.md @@ -0,0 +1,30 @@ +# Access a user's server + +Accessing a user's server is very useful when trying to debug or reproduce an issue they might have. This facility is available to admins via the admin panel. + +1. You can access the admin panel by clicking the 'Admin' button in the top bar + in your hub control panel. Alternatively, you can go to this URL in our + browser: `https:///hub/admin`. + +2. You can click `access server` to gain control of a user's currently running server. If it isn't running, you can click `start server` first and wait for it to start. + + ```{figure} ../../images/access-server.png + Clicking "access server" will allow you to control the user's session. + ``` + +3. This will bring you to the default interface that the user would have seen if they had just logged into the hub. From here, you can navigate to the notebook the user has reported issues with, and help them debug. + + ```{warning} + If you both work on the same notebook at the same time, you will just + overwrite each other's code! The state of the notebook will be that of + whoever saved the notebook last. There is no Google Docs' style + real-time collaboration yet, although [it is coming](https://github.com/jupyterlab/rtc) + ``` + + ```{warning} + When you control a user's server, all of your actions will be run *as + if the user ran it themselves*. This can be confusing for some users + and is generally not best-practice. We recommend telling users when + you are taking over their session, and using this feature mostly to understand what the user was trying to do, rather than to make major + changes to their code or notebook outputs. + ``` diff --git a/admin/howto/manage-users.md b/admin/howto/manage-users.md new file mode 100644 index 0000000..3bb0c36 --- /dev/null +++ b/admin/howto/manage-users.md @@ -0,0 +1,45 @@ +# Manage access to the hub + +The **Administrator Panel** can be used to maintain the list of users +who are authorized to use your hub. You can access this panel by clicking +the 'Admin' button in the top bar in your hub control panel. +Alternatively, you can go to this URL in your browser: +`https:///hub/admin` + +## To add users + +1. Click the `Add Users` button. The `Add Users` dialog box will pop up. +2. Add one or more users, and hit the `Add Users` button to authorize all the users you just added. + + +````{panels} +:container: full-width +:card: border-1 +```{figure} ../../images/add-users-button.png +The add users button in the Administrator Panel. +``` +--- +```{figure} ../../images/add-users-form.png +Fill in usernames and optionally make them administrators. You can add multiple users at once by putting a username on each line. +``` +```` + +## Finding usernames + +Access is granted or revoked based on `usernames`, and these depend on the kind +of (authentication provider)[admin/configuration/authentication] your hub is +using. In general, it matches whatever the visible 'username' in your +authentication provider is. The table below lists the available providers, and +how to determine their username. + + +| Provider | Username | +|-|-| +| Google | Email address | +| GitHub | GitHub user name | +| ORCID | ORCID id | + + +## To remove users + +???? diff --git a/admin/users.md b/admin/users.md deleted file mode 100644 index c5868ba..0000000 --- a/admin/users.md +++ /dev/null @@ -1,44 +0,0 @@ -# Managing users and their sessions - - -(add-users)= -## Add / remove users - -To add or remove users for your 2i2c Hub, go to the **Administrator Panel** and click on the `Add Users` button. This will allow you to add one-or-more users to the hub. - -The types of usernames you add will depend on the kind of authentication you've requested for your hub (e.g., email addresses vs user names). - -````{panels} -:container: full-width -:card: border-1 -```{figure} ../images/add-users-button.png -The add users button in the Administrator Panel. -``` ---- -```{figure} ../images/add-users-form.png -Fill in usernames and optionally make them administrators. You can add multiple users at once by putting a username on each line. -``` -```` - -(access-server)= -## Take control of a user's server - -If you'd like to debug a user's server, you may take control over their session by clicking the **access server** button. This will show you the latest file that they were working on. This is particularly useful for helping them debug a problem with their session. - -```{figure} ../images/access-server.png -Clicking "access server" will allow you to control the user's session. -``` - -```{warning} -When you control a user's server, all of your actions will be run *as if the user ran it themselves*. This can be confusing for some users and is generally not best-practice. We recommend telling users when you are taking over their session, and using this feature mostly to understand what the user was trying to do, rather than to make major changes to their code or notebook outputs. -``` - -## User session duration - -After 1h of *inactivity*, the user's session is culled. This stops all running -notebooks & terminals. They can start them back up again on login - their home -directories are preserved. Any packages they've temporarily installed with `!pip` -or `!conda` are also cleared, so they might have to install them again. - -There is no total time limit on how long a user's session -can last. diff --git a/index.md b/index.md index b3b9166..8af19c1 100644 --- a/index.md +++ b/index.md @@ -25,11 +25,24 @@ admin/support admin/environment admin/interfaces admin/data -admin/users admin/migrate admin/content ``` +```{toctree} +:caption: Administrator configuration guides +:maxdepth: 1 +admin/configuration/login +admin/configuration/culling +``` + +```{toctree} +:caption: Administrator how-to guides +:maxdepth: 1 +admin/howto/manage-users +admin/howto/access-server +``` + ```{toctree} :maxdepth: 1 :caption: User's Guide From 986a38d8c96ab3838a5bab2e39afcc0e2a9dab38 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 8 Mar 2021 15:48:40 +0530 Subject: [PATCH 02/21] Rework content in admin/interfaces Moved into howto style content. I left out the bits about 'Last Activity' - it is mostly self-evident, and I'm not sure I want to encourage instructors looking at when students last logged-in! --- ...ccess-server.md => control-user-server.md} | 32 +++++++++++---- admin/{ => howto}/support.md | 0 admin/interfaces.md | 41 ------------------- index.md | 8 ++-- 4 files changed, 29 insertions(+), 52 deletions(-) rename admin/howto/{access-server.md => control-user-server.md} (51%) rename admin/{ => howto}/support.md (100%) delete mode 100644 admin/interfaces.md diff --git a/admin/howto/access-server.md b/admin/howto/control-user-server.md similarity index 51% rename from admin/howto/access-server.md rename to admin/howto/control-user-server.md index 411b37f..9101865 100644 --- a/admin/howto/access-server.md +++ b/admin/howto/control-user-server.md @@ -1,18 +1,27 @@ -# Access a user's server +# Controlling a user's server -Accessing a user's server is very useful when trying to debug or reproduce an issue they might have. This facility is available to admins via the admin panel. +Hub admins can unilaterally perform actions on user's servers via the +**Administrator's Panel**. These are primarily used to debug user's session +easily. + +You can access the admin panel by clicking the 'Admin' button in the top bar +in your hub control panel. Alternatively, you can go to this URL in our +browser: `https:///hub/admin`. -1. You can access the admin panel by clicking the 'Admin' button in the top bar - in your hub control panel. Alternatively, you can go to this URL in our - browser: `https:///hub/admin`. -2. You can click `access server` to gain control of a user's currently running server. If it isn't running, you can click `start server` first and wait for it to start. +## Access a user's server + +Accessing a user's server is very useful when trying to debug or reproduce an issue they might have. This facility is available to admins via the admin panel. + +1. In the admin panel, you can click `access server` to gain control of a user's + currently running server. If it isn't running, you can click `start server` + first and wait for it to start. ```{figure} ../../images/access-server.png Clicking "access server" will allow you to control the user's session. ``` -3. This will bring you to the default interface that the user would have seen if they had just logged into the hub. From here, you can navigate to the notebook the user has reported issues with, and help them debug. +2. This will bring you to the default interface that the user would have seen if they had just logged into the hub. From here, you can navigate to the notebook the user has reported issues with, and help them debug. ```{warning} If you both work on the same notebook at the same time, you will just @@ -28,3 +37,12 @@ Accessing a user's server is very useful when trying to debug or reproduce an is you are taking over their session, and using this feature mostly to understand what the user was trying to do, rather than to make major changes to their code or notebook outputs. ``` + +## Stopping & starting a user's server + +Sometimes, you need to just turn a user's server on and off. You can +also do this from the admin interface, by hitting the `Stop server` +button, waiting for the server to stop, and the `Start server` button +again. This is particularly useful when their session might have gotten +out of whack by packages they've installed temporarily that screwed up +the default, since a restart will wipe the slate clean. diff --git a/admin/support.md b/admin/howto/support.md similarity index 100% rename from admin/support.md rename to admin/howto/support.md diff --git a/admin/interfaces.md b/admin/interfaces.md deleted file mode 100644 index a79b497..0000000 --- a/admin/interfaces.md +++ /dev/null @@ -1,41 +0,0 @@ -# Administrator interfaces - -## The Administrator Dashboard - -The JupyterHub administrator dashboard allows you to control several aspects of your JupyterHub. It is available via most of the user interfaces on your hub, as well as at the following URL: - -``` -https://.pilot.2i2c.cloud/hub/admin -``` - -:::{note} -The administrator panel will only be available to user names you have explicitly requested to be administrators! -::: - -```{figure} ../images/admin-panel.png -An example administrator's panel -``` - -From the administrator panel, you can see several columns and buttons: - -`User` -: The username of each user in your 2i2c Hub (both logged-in and logged-out) - -`Admin` -: Whether that user is an administrator - -`Last Activity` -: The last time that the user's server logged any activity (e.g. opening a file, running code, or logging in). - -`Running` -: Whether the user's server is currently running. You may also **start or stop a user's server** from this column. - -`Shutdown Hub` -: Shutdown the hub for all users (it may be restarted as well). - -`Edit User` -: Edit the username information or make a user an administrator. - -`access server` -: Take over the user's session so that you may inspect what they are doing (this is helpful for debugging). - diff --git a/index.md b/index.md index 8af19c1..233c90c 100644 --- a/index.md +++ b/index.md @@ -21,9 +21,7 @@ about/projects :caption: Administrator Guide :maxdepth: 1 -admin/support admin/environment -admin/interfaces admin/data admin/migrate admin/content @@ -39,12 +37,14 @@ admin/configuration/culling ```{toctree} :caption: Administrator how-to guides :maxdepth: 1 +admin/howto/support admin/howto/manage-users -admin/howto/access-server +admin/howto/control-user-server + ``` ```{toctree} :maxdepth: 1 :caption: User's Guide users/interface -``` \ No newline at end of file +``` From 4ac3a33ceb192c472d7e7b1a2c922fff283ba436 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 8 Mar 2021 16:04:35 +0530 Subject: [PATCH 03/21] Rework guide on sharing large datasets --- admin/data.md | 33 --------------------------------- admin/howto/share-datasets.md | 33 +++++++++++++++++++++++++++++++++ index.md | 2 +- 3 files changed, 34 insertions(+), 34 deletions(-) delete mode 100644 admin/data.md create mode 100644 admin/howto/share-datasets.md diff --git a/admin/data.md b/admin/data.md deleted file mode 100644 index 2bcee59..0000000 --- a/admin/data.md +++ /dev/null @@ -1,33 +0,0 @@ -# Data access and sharing - -## Share data and files across users - -Hubs have a folder called `shared` that is meant for distributing files, data, etc that **all users have access to**. This can be useful for utilizing a shared resource on the hub so that all students don't have to download it themselves (for example, a dataset that is commonly-used in course material). - -The `shared` folder is **read-only**, and accessible by *all* users. There is also a `shared-readwrite` folder that has **write and read** privileges, and **only accessible by hub administrators**. All files placed in `shared-readwrite` will show up in `shared` for users. - -To share files across users, follow these steps: - -- **Hub Aministrators** put files in `shared-readwrite`. - - e.g. on an admin account, put a CSV file in a location like: - - ``` - ~/shared-readwrite/myshareddata.csv - ``` -- **Users access those files** in their code. E.g., run code like: - - ```python - import pandas as pd - pd.read_csv("~/shared/myshareddata.csv") - ``` - -```{seealso} -To share *content* that is stored in a public repository, see [](include-content). -``` - -## User storage - -Each user gets their own storage that persists across user sessions. -These files are only accessible to the user and to hub administrators. -However, see the other sections on this page for ways to share content and data between users. diff --git a/admin/howto/share-datasets.md b/admin/howto/share-datasets.md new file mode 100644 index 0000000..ba7e504 --- /dev/null +++ b/admin/howto/share-datasets.md @@ -0,0 +1,33 @@ +# Share large data files with your users + +Sometimes you might need to distribute a set of large files to all +your users, so they don't have to re-download it once per person. +This is particularly useful in educational contexts, where you might +be teaching a course that reads a common dataset. + +```{warning} +If you are teaching with large datasets, you might run out of +memory! So consider teaching with just a subset of data before +distributing large datasets to your users. +``` + +All users have a directory called `shared` in their home directory. +This is meant to be used to distribute datasets and other files that +can be read by all users. This is a *readonly* directory - regular +users can not write to it. + +Admin users will also have a directory called `shared-readwrite` in +their home directory. This is the *same* as the `shared` directory, +but writeable! So any files admins put here will be immediately +visible in all users' `shared` directories. + +So to share datasets with users, admins should put the dataset in +`~/shared-readwrite`. If they are distributing notebook / content +that *reads* this dataset, it should refer to files in `~/shared/` +rather than in `~/shared-readwrite`. This will prevent accidental +erasures / writes on behalf of admins. + +```{warning} +This is an experimental feature, and the names of these directories +and their structure is subject to change. +``` diff --git a/index.md b/index.md index 233c90c..398018e 100644 --- a/index.md +++ b/index.md @@ -22,7 +22,6 @@ about/projects :maxdepth: 1 admin/environment -admin/data admin/migrate admin/content ``` @@ -40,6 +39,7 @@ admin/configuration/culling admin/howto/support admin/howto/manage-users admin/howto/control-user-server +admin/howto/share-datasets ``` From f51ba2a7037a74e6737ec208d39d7e4b237ab440 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 8 Mar 2021 18:35:28 +0530 Subject: [PATCH 04/21] Expand & rewrite content on using nbgitpuller --- admin/content.md | 27 ------------------------ admin/howto/nbgitpuller.md | 42 ++++++++++++++++++++++++++++++++++++++ index.md | 1 + 3 files changed, 43 insertions(+), 27 deletions(-) create mode 100644 admin/howto/nbgitpuller.md diff --git a/admin/content.md b/admin/content.md index ab70def..0cc778c 100644 --- a/admin/content.md +++ b/admin/content.md @@ -8,30 +8,3 @@ To connect your public content with a 2i2c Hub, we recommend using [Jupyter Book You can tell Jupyter Book to place links *directly to your 2i2c Hub* on each page that is served from a notebook. To do so, follow the [launch buttons for JupyterHubs instructions](https://jupyterbook.org/interactive/launchbuttons.html#jupyterhub-buttons-for-your-pages). Make sure that you configure your `jupyterhub_url` to point to the URL of your 2i2c Hub (e.g., `https://.pilot.2i2c.cloud`). - -(include-content)= -## Include content in your hub - -To include content in your hub (e.g., scripts, notebooks, etc) we recommend using [`nbgitpuller`](https://jupyterhub.github.io/nbgitpuller). - -You can use `nbgitpuller` to generate a link to a public repository, or a file in that repository. When a user clicks that link, a copy of the link's target will be automatically placed in the user's home directory, and they will be directed to that content in the JupyterHub (if they are logged in). - -- **Generate an nbgitpuller link** by going to [`nbgitpuller.link`](http://nbgitpuller.link/). You'll be asked to provide some information about the content you wish to share, and can copy the link when you are done. - - Use `https://.pilot.2i2c.cloud` as your JupyterHub address - - Fill in the GitHub repository where your content exists (along with an optional file path or branch name) - - The link will be in the field just above your form. - -- **Share this link with your users**. Anybody can click an `nbgitpuller` link. If they have an account on the hub to which it points, then they'll get a copy of the content that you've linked to. - -:::{admonition} Double-check your hub URL -:class: important -Make sure that the hub URL you insert into the nbgitpuller form is correct! See [](note-on-urls) for more information. -::: - -```{link-button} http://nbgitpuller.link -:text: Go to nbgitpuller.link -:classes: btn-outline-primary btn-block -``` -```{figure} ../images/nbgitpuller-ui.png -The [`nbgitpuller.link`](http://nbgitpuller.link) user interface, along with some important fields highlighted. -``` \ No newline at end of file diff --git a/admin/howto/nbgitpuller.md b/admin/howto/nbgitpuller.md new file mode 100644 index 0000000..c217b03 --- /dev/null +++ b/admin/howto/nbgitpuller.md @@ -0,0 +1,42 @@ +# Distribute content with nbgitpuller + +You'll often want to distribute *content* (such as notebooks, scripts, sample data, etc) to your users so they can do exercises, follow along with a lecture, or use as a starting point for their own work. This content is often constantly updated as time goes on, and needs to not overwrite your student's work if you make an adjustment to content that has already been touched by the student. + + +[nbgitpuller](https://jupyterhub.github.io/nbgitpuller) is the tool +we recommend for this. The workflow goes something like this: + +1. Create a repository on [GitHub](https://github.com) and start putting + your content there. This is the *source* of the content that will + be distributed to your users. You can update it as often as you + wish. While instructors will need to know how github works, *your users will never have to interact with git directly*. + +2. Generate an [nbgitpuller link](http://nbgitpuller.link). This generates a *clickable link* that contains within it the following + pieces of information: + + 1. The URL to your hub. Upon clicking the link, users will be redirected to this hub, and content will be pulled into their home directory there. + 2. The URL of the git repository where the content lives. + 3. The branch in the git repository where the content lives. The default specified there is `master`, although newer GitHub repositories use `main` as the default. You can find yours on the Github page of your content repository + 4. The default interface to open when users click this link. The default is the classic notebook, but many other apps are available. + 5. A file to open when the link is clicked. When left empty, a directory listing with the content of the repository will be shown. + + ```{figure} ../../images/nbgitpuller-ui.png + The [`nbgitpuller.link`](http://nbgitpuller.link) user interface, along with some important fields highlighted. + ``` + + ```{tip} + Unfortunately, RStudio does not support opening a specific file, and will always show the home directory. Users will have to manually navigate to the appropriate file. + ``` + + Once you've filled these out, you can copy the link from the textbox above the form. + +3. Distribute the link you have generated to your users. Upon clicking the link, they will be: + + 1. Redirected to your hub, and asked to log in if they have not already + 2. The first time the link is clicked, your content repository will be pulled into their home directory! + 3. If they had already clicked the link before, any new changes in your content repository will be pulled in. Any changes the user has made will be [automatically merged](https://jupyterhub.github.io/nbgitpuller/topic/automatic-merging.html) with changes in the content repository, in such a way that the user's changes are never overwritten. All merge conflicts will also be automatically resolved, so users don't have to interact with git. + 4. If you have picked a specific file to be displayed, the user will be redirected to that file, open in the application you picked. If not, the directory listing of local copy of the content repository will be shown in the application you selected. + +4. You **do not** have to create a new link each time you update your content repository! The same link will continue to work, so you can simply ask your users to click the link again to fetch the latest changes. + + However, if you want to create links to individual files that should be opened at specific points - like one link per class or assignment - you can regenerate the links with different values for the file to open or interface. As long as the hub url, content repository url and the branch name are the same, users will be not be duplicating content. diff --git a/index.md b/index.md index 398018e..5ff0144 100644 --- a/index.md +++ b/index.md @@ -37,6 +37,7 @@ admin/configuration/culling :caption: Administrator how-to guides :maxdepth: 1 admin/howto/support +admin/howto/nbgitpuller admin/howto/manage-users admin/howto/control-user-server admin/howto/share-datasets From 8f0cfc89952943d82d445e70549ff299e3bf71e0 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 8 Mar 2021 20:57:00 +0530 Subject: [PATCH 05/21] Wrap lines --- admin/howto/nbgitpuller.md | 73 +++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/admin/howto/nbgitpuller.md b/admin/howto/nbgitpuller.md index c217b03..ce530bf 100644 --- a/admin/howto/nbgitpuller.md +++ b/admin/howto/nbgitpuller.md @@ -1,42 +1,73 @@ # Distribute content with nbgitpuller -You'll often want to distribute *content* (such as notebooks, scripts, sample data, etc) to your users so they can do exercises, follow along with a lecture, or use as a starting point for their own work. This content is often constantly updated as time goes on, and needs to not overwrite your student's work if you make an adjustment to content that has already been touched by the student. +You'll often want to distribute *content* (such as notebooks, scripts, sample +data, etc) to your users so they can do exercises, follow along with a lecture, +or use as a starting point for their own work. This content is often constantly +updated as time goes on, and needs to not overwrite your student's work if you +make an adjustment to content that has already been touched by the student. [nbgitpuller](https://jupyterhub.github.io/nbgitpuller) is the tool we recommend for this. The workflow goes something like this: -1. Create a repository on [GitHub](https://github.com) and start putting - your content there. This is the *source* of the content that will - be distributed to your users. You can update it as often as you - wish. While instructors will need to know how github works, *your users will never have to interact with git directly*. +1. Create a repository on [GitHub](https://github.com) and start putting your + content there. This is the *source* of the content that will be distributed + to your users. You can update it as often as you wish. While instructors will + need to know how github works, *your users will never have to interact with + git directly*. -2. Generate an [nbgitpuller link](http://nbgitpuller.link). This generates a *clickable link* that contains within it the following - pieces of information: +2. Generate an [nbgitpuller link](http://nbgitpuller.link). This generates a + *clickable link* that contains within it the following pieces of information: - 1. The URL to your hub. Upon clicking the link, users will be redirected to this hub, and content will be pulled into their home directory there. + 1. The URL to your hub. Upon clicking the link, users will be redirected to + this hub, and content will be pulled into their home directory there. 2. The URL of the git repository where the content lives. - 3. The branch in the git repository where the content lives. The default specified there is `master`, although newer GitHub repositories use `main` as the default. You can find yours on the Github page of your content repository - 4. The default interface to open when users click this link. The default is the classic notebook, but many other apps are available. - 5. A file to open when the link is clicked. When left empty, a directory listing with the content of the repository will be shown. + 3. The branch in the git repository where the content lives. The default + specified there is `master`, although newer GitHub repositories use `main` + as the default. You can find yours on the Github page of your content + repository + 4. The default interface to open when users click this link. The default is + the classic notebook, but many other apps are available. + 5. A file to open when the link is clicked. When left empty, a directory + listing with the content of the repository will be shown. ```{figure} ../../images/nbgitpuller-ui.png - The [`nbgitpuller.link`](http://nbgitpuller.link) user interface, along with some important fields highlighted. + The [`nbgitpuller.link`](http://nbgitpuller.link) user interface, along with + some important fields highlighted. ``` - ```{tip} - Unfortunately, RStudio does not support opening a specific file, and will always show the home directory. Users will have to manually navigate to the appropriate file. - ``` + ```{tip} + Unfortunately, RStudio does not support opening a specific file, and will + always show the home directory. Users will have to manually navigate to + the appropriate file. + ``` Once you've filled these out, you can copy the link from the textbox above the form. -3. Distribute the link you have generated to your users. Upon clicking the link, they will be: +3. Distribute the link you have generated to your users. Upon clicking the link, + they will be: 1. Redirected to your hub, and asked to log in if they have not already - 2. The first time the link is clicked, your content repository will be pulled into their home directory! - 3. If they had already clicked the link before, any new changes in your content repository will be pulled in. Any changes the user has made will be [automatically merged](https://jupyterhub.github.io/nbgitpuller/topic/automatic-merging.html) with changes in the content repository, in such a way that the user's changes are never overwritten. All merge conflicts will also be automatically resolved, so users don't have to interact with git. - 4. If you have picked a specific file to be displayed, the user will be redirected to that file, open in the application you picked. If not, the directory listing of local copy of the content repository will be shown in the application you selected. + 2. The first time the link is clicked, your content repository will be pulled + into their home directory! + 3. If they had already clicked the link before, any new changes in your + content repository will be pulled in. Any changes the user has made will + be [automatically + merged](https://jupyterhub.github.io/nbgitpuller/topic/automatic-merging.html) + with changes in the content repository, in such a way that the user's + changes are never overwritten. All merge conflicts will also be + automatically resolved, so users don't have to interact with git. + 4. If you have picked a specific file to be displayed, the user will be + redirected to that file, open in the application you picked. If not, the + directory listing of local copy of the content repository will be shown in + the application you selected. -4. You **do not** have to create a new link each time you update your content repository! The same link will continue to work, so you can simply ask your users to click the link again to fetch the latest changes. +4. You **do not** have to create a new link each time you update your content + repository! The same link will continue to work, so you can simply ask your + users to click the link again to fetch the latest changes. - However, if you want to create links to individual files that should be opened at specific points - like one link per class or assignment - you can regenerate the links with different values for the file to open or interface. As long as the hub url, content repository url and the branch name are the same, users will be not be duplicating content. + However, if you want to create links to individual files that should be + opened at specific points - like one link per class or assignment - you can + regenerate the links with different values for the file to open or interface. + As long as the hub url, content repository url and the branch name are the + same, users will be not be duplicating content. From 365a0ec7288f69e4714f2966befb0b22e58f9eac Mon Sep 17 00:00:00 2001 From: Yuvi Panda Date: Fri, 12 Mar 2021 17:54:26 +0530 Subject: [PATCH 06/21] Apply @choldgraf's inline suggestions Co-authored-by: Chris Holdgraf --- admin/howto/control-user-server.md | 6 +++--- admin/howto/share-datasets.md | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/admin/howto/control-user-server.md b/admin/howto/control-user-server.md index 9101865..2357865 100644 --- a/admin/howto/control-user-server.md +++ b/admin/howto/control-user-server.md @@ -1,17 +1,17 @@ # Controlling a user's server Hub admins can unilaterally perform actions on user's servers via the -**Administrator's Panel**. These are primarily used to debug user's session +**Administrator's Panel**. These are primarily used to debug a user's session easily. You can access the admin panel by clicking the 'Admin' button in the top bar -in your hub control panel. Alternatively, you can go to this URL in our +in your hub control panel. Alternatively, you can go to this URL in your browser: `https:///hub/admin`. ## Access a user's server -Accessing a user's server is very useful when trying to debug or reproduce an issue they might have. This facility is available to admins via the admin panel. +Accessing a user's server is useful when trying to debug or reproduce an issue they might have. This facility is available to admins via the admin panel. 1. In the admin panel, you can click `access server` to gain control of a user's currently running server. If it isn't running, you can click `start server` diff --git a/admin/howto/share-datasets.md b/admin/howto/share-datasets.md index ba7e504..e87eb91 100644 --- a/admin/howto/share-datasets.md +++ b/admin/howto/share-datasets.md @@ -11,6 +11,8 @@ memory! So consider teaching with just a subset of data before distributing large datasets to your users. ``` +## The `shared` directory + All users have a directory called `shared` in their home directory. This is meant to be used to distribute datasets and other files that can be read by all users. This is a *readonly* directory - regular @@ -21,7 +23,9 @@ their home directory. This is the *same* as the `shared` directory, but writeable! So any files admins put here will be immediately visible in all users' `shared` directories. -So to share datasets with users, admins should put the dataset in +## A workflow for sharing datasets + +To share datasets with users, admins should put the dataset in `~/shared-readwrite`. If they are distributing notebook / content that *reads* this dataset, it should refer to files in `~/shared/` rather than in `~/shared-readwrite`. This will prevent accidental @@ -29,5 +33,5 @@ erasures / writes on behalf of admins. ```{warning} This is an experimental feature, and the names of these directories -and their structure is subject to change. +and their structure are subject to change. ``` From 81f92c951a28a19e3cd8ac27e2452f5d72df7963 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Sat, 13 Mar 2021 13:40:42 +0530 Subject: [PATCH 07/21] Mention when login provider choice will be made --- admin/configuration/login.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/admin/configuration/login.md b/admin/configuration/login.md index 0ea682b..d27026d 100644 --- a/admin/configuration/login.md +++ b/admin/configuration/login.md @@ -13,6 +13,8 @@ Users can prove who they are by logging in via an *authentication provider*. Cur 4. ???. We could probably support other authentication providers, depending on your specific needs and the provider's complexity. Please reach out to us if none of these 3 work. +We will ask you what provider you want when we set up the hub. We can change the provider after the fact, but only if absolutely strictly necessary. + ## Authorization Not everyone who can authenticate is granted access to the hub - that From ac8402edd507b3588238e27673558f814f2d8817 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Sat, 13 Mar 2021 13:47:09 +0530 Subject: [PATCH 08/21] Document how admin users are currently authorized --- admin/configuration/login.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/admin/configuration/login.md b/admin/configuration/login.md index d27026d..2c79aad 100644 --- a/admin/configuration/login.md +++ b/admin/configuration/login.md @@ -17,11 +17,19 @@ We will ask you what provider you want when we set up the hub. We can change the ## Authorization -Not everyone who can authenticate is granted access to the hub - that -would mean everyone with a `@gmail.com` account can log in if you use Google as your authentication provider! Instead, we support multiple ways for hub admins to specify which users are *authorized* to be on the hub. +Not everyone who can authenticate is granted access to the hub - that would mean +everyone with a `@gmail.com` account can log in if you use Google as your +authentication provider! Instead, we support multiple ways for hub admins to +specify which users are *authorized* to be on the hub. -Currently, there are only two supported methods: +Currently, there are only two supported methods for authorizing regular users: 1. Manually add users via the admin panel in JupyterHub -2. (Google only) Allow all users who are logged in via aparticular domain - so you can allow access to anyone who is part of your organization or educational institution. +2. (Google only) Allow all users who are logged in via aparticular domain - so + you can allow access to anyone who is part of your organization or + educational institution. +Admin users are instead authorized [in YAML config](https://github.com/2i2c-org/pilot-hubs/blob/master/hubs.yaml), +with support from 2i2c staff. + +% TODO: Link to SRE docs on how to do this once we have it From 1010e1d4b7b39c118288fa084327b1df87df9cbd Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Sat, 13 Mar 2021 13:47:34 +0530 Subject: [PATCH 09/21] Add TODO on configuring culling --- admin/configuration/culling.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/admin/configuration/culling.md b/admin/configuration/culling.md index dc6df60..72ad133 100644 --- a/admin/configuration/culling.md +++ b/admin/configuration/culling.md @@ -4,8 +4,14 @@ To ensure efficient resource usage, user servers without interactive usage for a period of time (default 1h) are automatically stopped (via [jupyterhub-idle-culler](https://github.com/jupyterhub/jupyterhub-idle-culler)). This means your notebook server might be stopped for inactivity even if you have -a long running process in the notebook. This timeout can be configured. +a long running process in the notebook. This timeout can be configured. -This has the same effect as a user stopping their own server. User servers stopping doesn't lose any data in your home directories. However, any packages temporarily installed via `!pip` or `!conda` are cleared, to make sure that everyone in the hub is operating from the same clean environment as much as possible. Active notebooks have their kernel killed as well. +% TODO: Add link to SRE guide on how to configure this, once it exists + +This has the same effect as a user stopping their own server. User servers +stopping doesn't lose any data in your home directories. However, any packages +temporarily installed via `!pip` or `!conda` are cleared, to make sure that +everyone in the hub is operating from the same clean environment as much as +possible. Active notebooks have their kernel killed as well. There is currently no maximum time limit for a user's notebook. From 7aa0279ae7f79d608b692cbde553457405fd43ec Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 15 Mar 2021 13:13:54 +0530 Subject: [PATCH 10/21] Clarify hub config & how-to section purpose --- index.md | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/index.md b/index.md index 5ff0144..9591691 100644 --- a/index.md +++ b/index.md @@ -9,32 +9,38 @@ This guide is for **administrators and community champions** of 2i2c Hubs, or fo See the sections below (or to the left) for more information. +## About 2i2c hubs + ```{toctree} -:caption: About the 2i2c Hubs :maxdepth: 1 about/overview about/infrastructure about/projects ``` -```{toctree} -:caption: Administrator Guide -:maxdepth: 1 -admin/environment -admin/migrate -admin/content -``` +## Hub configuration options + +These pages list the different ways hub admins can configure how +their hub behaves. Most of them require working with a 2i2c engineer +to realize the configuration option. ```{toctree} -:caption: Administrator configuration guides :maxdepth: 1 admin/configuration/login admin/configuration/culling +admin/environment +admin/migrate +admin/content ``` +## Hub administrator how-to guides + +These guides have information on how hub admins can perform specific +tasks on their hubs, mostly without requiring any interaction with +2i2c engineers. + ```{toctree} -:caption: Administrator how-to guides :maxdepth: 1 admin/howto/support admin/howto/nbgitpuller @@ -44,8 +50,10 @@ admin/howto/share-datasets ``` +## Hub user guides + ```{toctree} :maxdepth: 1 -:caption: User's Guide users/interface ``` + From 04705c0147c1f46ae5da41501d45a742f3d44b5e Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 15 Mar 2021 13:16:21 +0530 Subject: [PATCH 11/21] Remove empty section on removing users --- admin/howto/manage-users.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/admin/howto/manage-users.md b/admin/howto/manage-users.md index 3bb0c36..f0c5b8b 100644 --- a/admin/howto/manage-users.md +++ b/admin/howto/manage-users.md @@ -40,6 +40,4 @@ how to determine their username. | ORCID | ORCID id | -## To remove users - -???? +% TODO: Document how to remove users From c3eec8f365e70007e64b415c327ebdefa3f11fd6 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 15 Mar 2021 13:21:44 +0530 Subject: [PATCH 12/21] Link login config reference to manage users howto --- admin/configuration/login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/configuration/login.md b/admin/configuration/login.md index 2c79aad..5aa7a2e 100644 --- a/admin/configuration/login.md +++ b/admin/configuration/login.md @@ -24,7 +24,7 @@ specify which users are *authorized* to be on the hub. Currently, there are only two supported methods for authorizing regular users: -1. Manually add users via the admin panel in JupyterHub +1. [Manually add users](admin/howto/manage-users) via the admin panel in JupyterHub 2. (Google only) Allow all users who are logged in via aparticular domain - so you can allow access to anyone who is part of your organization or educational institution. From f0279a05ff57dacfeab615c49977f92054f83996 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 15 Mar 2021 15:59:28 +0530 Subject: [PATCH 13/21] Add section on hub user environment --- admin/environment.md | 36 ---------------- admin/howto/environment.md | 86 ++++++++++++++++++++++++++++++++++++++ index.md | 2 +- 3 files changed, 87 insertions(+), 37 deletions(-) delete mode 100644 admin/environment.md create mode 100644 admin/howto/environment.md diff --git a/admin/environment.md b/admin/environment.md deleted file mode 100644 index d9d9a6e..0000000 --- a/admin/environment.md +++ /dev/null @@ -1,36 +0,0 @@ - - -# Your hub's environment - -## Where is your hub configured? - -All of the infrastructure that 2i2c deploys uses standard open source tooling and configuration. This means that the configuration for your JupyterHub can be modified just as if you were deploying it yourself. The [`pilot-hubs` repository](https://github.com/2i2c-org/pilot-hubs) has information about the configuration for each hub that 2i2c supports. For example, [this configuration file](https://github.com/2i2c-org/pilot-hubs/blob/master/hubs.yaml) has configuration for a collection of hubs. Each entry (corresponding to one hub) has [a Zero to JupyterHub configuration](https://zero-to-jupyterhub.readthedocs.io/en/latest/resources/reference.html). This can be modified to customize your hub's setup. - -## The default user environment - -The default environment for all pilot hubs is defined [in this folder](https://github.com/2i2c-org/low-touch-hubs/tree/master/images/user). It is a bit technical, but gives an idea of the kinds of libraries that are installed by default. - -In particular: - -- For Python: [see this `environment.yml` file](https://github.com/2i2c-org/low-touch-hubs/blob/master/images/user/environment.yml) for common Python packages -- For R: [see this `install.R` file](https://github.com/2i2c-org/low-touch-hubs/blob/master/images/user/install.R) - -In addition, some [types of hubs](hub-types) have modifications (usually additions) to this software environment. - -(environment/custom)= -## Customize your hub's environment - -There are two ways that you can customize your hub's environment: - -- **Temporarily `pip install` the packages you wish**. These will be installed **for your current session only**, and will be removed if your server stops and re-starts. As such, we recommend putting a collection of `!pip install` commands in the first cell of your notebooks that need extra libraries. -- **Request an update to the hub environments**. To request a new or updated package, [open an issue in the `2i2c-org/pilot` repository](https://github.com/2i2c-org/pilot/issues/new?labels=enhancement&template=tech-request.md) and ask for the new package to be installed. -- **Bring your own Docker image**. You may also use your own Docker image. Simply push your image to a public registry, and [open an issue in the `2i2c-org/pilot-hubs` repository](https://github.com/2i2c-org/pilot-hubs/issues/new) to request that your image be used. In the issue, include the public registry, image name, and tag that you wish to use. - - See the [pilot hubs custom image documentation](ph:custom-image) for information about the requirements for these Docker images. - -We are working on ways to let individual hubs customize their environment on their own, and will update these docs when that happens! - -:::{admonition} Try to avoid installing packages in the user directory -:class: note -Each of your users has their own filesystem that persists across sessions, so it is technically possible to use `pip install --user` to install packages to the home directory so they persist across sessions. However, this is discouraged because it often leads to mismatches between user and instructor environments, and may break functionality on the hub if packages are updated in the base environment that clash with a user-installed package. -::: diff --git a/admin/howto/environment.md b/admin/howto/environment.md new file mode 100644 index 0000000..0435e69 --- /dev/null +++ b/admin/howto/environment.md @@ -0,0 +1,86 @@ +# Modify your hub's user environment + +When your users log in to their hub, they are presented with a +configured environment with base libraries, user interfaces and +languages installed. This allows them to start working immediately, +without having to install packages themselves. + +## Default user environment + +The default environment for all pilot hubs is defined [in this +folder](https://github.com/2i2c-org/pilot-hubs/tree/master/images/user). +It is configured with the following: + +- Python packages defined in[this `environment.yml` + file](https://github.com/2i2c-org/pilot-hubs/blob/master/images/user/environment.yml). Many common scientific python packages are installed here. +- R packages installed from [this `install.R` + file](https://github.com/2i2c-org/pilot-hubs/blob/master/images/user/install.R). +- Many popular data science user interfaces installed: + - [Classic Jupyter Notebook](https://github.com/jupyter/notebook/) + - [JupyterLab](https://github.com/jupyterlab/jupyterlab/) + - [RStudio](https://rstudio.com/) +- An Ubuntu 20.04 base image, with common utility packages installed. + +## Customizing your hub environment + +Sometimes, what is in the base user environment is not enough for +your use case. You might need new packages installed, a different +language version, etc. Here are a few ways to customize yours. + +### Ask for changes to the base image + +If you only need one / two extra packages, the easiest way is to +[open an issue in the `2i2c-org/pilot` repository](https://github.com/2i2c-org/pilot/issues/new?labels=enhancement&template=tech-request.md) +and ask for the new package to be installed. This is often the simplest +way forward. + +### Bring your own docker image + +Our hubs use [docker images](https://www.docker.com/) to provide the +user environment. You can build and bring your own docker image, +which gives you *full control* over your environment. + +We recommend the following setup for building & maintaining your +docker image: + +1. Create a GitHub repository that will contain just your *environment* files, + not content. You could use just files that help [create your environment on + mybinder.org](https://repo2docker.readthedocs.io/en/latest/config_files.html) - like `environment.yml` or `requirements.txt` for python, + `install.R` for R, etc. You can also instead have a `Dockerfile` + for full control. Another popular option is to use a `Dockerfile` but + inherit from a [pangeo base image](https://github.com/pangeo-data/pangeo-docker-images), + making just the modifications you need. + +2. Use the [repo2docker GitHub Action](https://github.com/jupyterhub/repo2docker-action) + to automatically build, name and + [push your image to dockerhub](https://github.com/jupyterhub/repo2docker-action#push-repo2docker-image-to-dockerhub). + +3. [Open an issue in the `2i2c-org/pilot` repository](https://github.com/2i2c-org/pilot/issues/new?labels=enhancement&template=tech-request.md) + with a link to your docker image. 2i2c hub engineers can then + configure your hub to use the new image. + + ```{note} + Currently, you need to open an issue *every time your base environment changes*. This will hopefully be a bit more streamlined + in the future. + ``` + +### Temporarily install packages for a session + + +You can temporarily install packages in your environment that will +just last the duration of your user session. They will get wiped out +when your user server is stopped, to ensure that you always start from +a 'clean slate' environment. + +The recommended way is to put `%pip install ` or +`%conda install ` in the first cell of any notebook +you distribute, so when run it'll install necessary packages. For R, +you can use `install.packages("package-name")` as you normally would. + +```{warning} + +While tempting, do not use `!pip install --user ` to install +packages. This makes the base environment different for different users, +causing hard to debug issues. This could also render your user server +unable to start, due to conflicting packages. +``` diff --git a/index.md b/index.md index 9591691..f3b23ef 100644 --- a/index.md +++ b/index.md @@ -29,7 +29,6 @@ to realize the configuration option. :maxdepth: 1 admin/configuration/login admin/configuration/culling -admin/environment admin/migrate admin/content ``` @@ -43,6 +42,7 @@ tasks on their hubs, mostly without requiring any interaction with ```{toctree} :maxdepth: 1 admin/howto/support +admin/howto/environment admin/howto/nbgitpuller admin/howto/manage-users admin/howto/control-user-server From 5debc2381d00d52040bf3c7918c4c69608f4c17a Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 15 Mar 2021 16:52:15 +0530 Subject: [PATCH 14/21] Move home-directory download to its own page --- admin/migrate.md | 13 ------------- index.md | 3 ++- user/download-data.md | 27 +++++++++++++++++++++++++++ {users => user}/interface.md | 0 4 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 user/download-data.md rename {users => user}/interface.md (100%) diff --git a/admin/migrate.md b/admin/migrate.md index 968bf88..33b40aa 100644 --- a/admin/migrate.md +++ b/admin/migrate.md @@ -51,16 +51,3 @@ JupyterLab ```` -(download-user-files)= -### Download your data from a hub - -If you'd like to stop using your 2i2c Hub, or would simply like to move your data onto your own machine (or elsewhere in the cloud), take the following steps to download your data locally: - -1. Navigate to the Jupyter "tree" view by changing your URL path to `/tree`. e.g., `.pilot.2i2c.cloud/user//tree` -2. Click on **`Download Directory`**. - - ```{figure} ../images/download-directory.png - :alt: The download directory button - ``` - -This will zip up the contents of your user file system and download them to your machine. diff --git a/index.md b/index.md index f3b23ef..2c63527 100644 --- a/index.md +++ b/index.md @@ -54,6 +54,7 @@ admin/howto/share-datasets ```{toctree} :maxdepth: 1 -users/interface +user/download-data +user/interface ``` diff --git a/user/download-data.md b/user/download-data.md new file mode 100644 index 0000000..7455899 --- /dev/null +++ b/user/download-data.md @@ -0,0 +1,27 @@ +# Download your home directory + +You might want to download your entire home directory for many +reasons - to get data off a hub that is closing, to migrate to +a different service, for archival, etc. Your home directory +will contain all your data *and* your notebooks. +Hubs managed by 2i2c make this easy. + + +1. You need to use the classic jupyter notebook interface for this. If you are + using another interface, navigate to the classic interface by changing your + URL path to `/tree`. e.g., + `.pilot.2i2c.cloud/user//tree` + +2. Click on **`Download Directory`**. + + ```{figure} ../images/download-directory.png + :alt: The download directory button + ``` + +This will zip up the contents of your user file system and download them to your machine. + +```{note} +If your hub is using a [custom user environment](admin/howto/environment), it needs the +[jupyter-tree-download](https://github.com/ryanlovett/jupyter-tree-download) package +installed to make this feature available. +``` diff --git a/users/interface.md b/user/interface.md similarity index 100% rename from users/interface.md rename to user/interface.md From 0f975e9e5053c5822c233be3c5ac1b3d6f08a442 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 15 Mar 2021 17:38:19 +0530 Subject: [PATCH 15/21] Remove section on PDF download I don't think this is specific enough to us to keep. --- admin/migrate.md | 22 ---------------------- images/download-latexpdf-classic.png | Bin 20165 -> 0 bytes images/download-latexpdf-lab.png | Bin 40108 -> 0 bytes 3 files changed, 22 deletions(-) delete mode 100644 images/download-latexpdf-classic.png delete mode 100644 images/download-latexpdf-lab.png diff --git a/admin/migrate.md b/admin/migrate.md index 33b40aa..b615c2a 100644 --- a/admin/migrate.md +++ b/admin/migrate.md @@ -29,25 +29,3 @@ For more information about creating custom environments for a JupyterHub, we rec Finally, if you wish to use a different managed cloud service, there are many for you to choose from. These tend to have proprietary components interwoven with open-source ones, which can be a "pro" or a "con" depending on your use-case. 2i2c Hubs are designed to be 100% open source and interoperable with a variety of cloud vendors, however your workflows may be transportable to a proprietary cloud service just the same. -## Download your content and data - -(download-as-pdf)= -### Download your notebooks as PDFs - -2i2c Hub come with the ability to convert a Jupyter Notebook as a PDF that users may download locally. To do so, use the Jupyter interface of your choice as shown below: - -````{panels} -:container: full-width -:column: + text-center -Classic Notebooks -```{figure} ../images/download-latexpdf-classic.png -:height: 300px -``` ---- -JupyterLab -```{figure} ../images/download-latexpdf-lab.png -:height: 300px -``` -```` - - diff --git a/images/download-latexpdf-classic.png b/images/download-latexpdf-classic.png deleted file mode 100644 index 60bc7cd57b2fb593cf80ebb3aaad369987e1c6d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20165 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 fwt+Rz^Y~x?e*S08^BwMcN=e^j;+3 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 From 61e99560b1f51605a961656469160dd89d548b9a Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 15 Mar 2021 18:15:52 +0530 Subject: [PATCH 16/21] Move rest of 'creating content' to howto/ --- admin/content.md | 10 ---------- admin/howto/create-content.md | 25 +++++++++++++++++++++++++ index.md | 2 +- 3 files changed, 26 insertions(+), 11 deletions(-) delete mode 100644 admin/content.md create mode 100644 admin/howto/create-content.md diff --git a/admin/content.md b/admin/content.md deleted file mode 100644 index 0cc778c..0000000 --- a/admin/content.md +++ /dev/null @@ -1,10 +0,0 @@ -# Create content for your Hub - -## Write public books that connect to a 2i2c Hub - -You can create public content that is designed to have connections with your 2i2c Hub. For example, you can create lectures from Jupyter Notebooks, and allow students to grab their own copy of the notebook to interact with on the 2i2c Hub. - -To connect your public content with a 2i2c Hub, we recommend using [Jupyter Book](https://jupyterbook.org). This is an open-source project that allows you to share collections of notebooks and markdown files as an online website and book. Check out the [Jupyter Book getting started guide](https://jupyterbook.org/start/overview.html) for more information about Jupyter Book. - -You can tell Jupyter Book to place links *directly to your 2i2c Hub* on each page that is served from a notebook. To do so, follow the [launch buttons for JupyterHubs instructions](https://jupyterbook.org/interactive/launchbuttons.html#jupyterhub-buttons-for-your-pages). Make sure that you configure your `jupyterhub_url` to point to the URL of your 2i2c Hub (e.g., `https://.pilot.2i2c.cloud`). - diff --git a/admin/howto/create-content.md b/admin/howto/create-content.md new file mode 100644 index 0000000..7ca33eb --- /dev/null +++ b/admin/howto/create-content.md @@ -0,0 +1,25 @@ +# Create content for your Hub + +## Write public books that connect to a 2i2c Hub + +You can create public content that is designed to have connections with your +2i2c Hub. For example, you can create lectures from Jupyter Notebooks, and allow +students to grab their own copy of the notebook to interact with on the 2i2c +Hub. + +To connect your public content with a 2i2c Hub, we recommend using [Jupyter +Book](https://jupyterbook.org). This is an open-source project that allows you +to share collections of notebooks and markdown files as an online website and +book. Check out the [Jupyter Book getting started +guide](https://jupyterbook.org/start/overview.html) for more information about +Jupyter Book. + +You can tell Jupyter Book to place links *directly to your 2i2c Hub* on each +page that is served from a notebook. To do so, follow the [launch buttons for +JupyterHubs +instructions](https://jupyterbook.org/interactive/launchbuttons.html#jupyterhub-buttons-for-your-pages). +Make sure that you configure your `jupyterhub_url` to point to the URL of your +2i2c Hub (e.g., `https://.pilot.2i2c.cloud`). +This will use automatically [create nbgitpuller links](admin/howto/nbgitpuller) +for you. + diff --git a/index.md b/index.md index 2c63527..1bf567d 100644 --- a/index.md +++ b/index.md @@ -30,7 +30,6 @@ to realize the configuration option. admin/configuration/login admin/configuration/culling admin/migrate -admin/content ``` ## Hub administrator how-to guides @@ -44,6 +43,7 @@ tasks on their hubs, mostly without requiring any interaction with admin/howto/support admin/howto/environment admin/howto/nbgitpuller +admin/howto/create-content admin/howto/manage-users admin/howto/control-user-server admin/howto/share-datasets From 3aa41757734c14ca75852edb44c6d5ec07b7adc4 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 15 Mar 2021 18:59:53 +0530 Subject: [PATCH 17/21] Remove migrate guide https://github.com/2i2c-org/pilot/pull/54 has all that information, and is a better fit for a 'howto' section. --- admin/migrate.md | 31 ------------------------------- index.md | 1 - 2 files changed, 32 deletions(-) delete mode 100644 admin/migrate.md diff --git a/admin/migrate.md b/admin/migrate.md deleted file mode 100644 index b615c2a..0000000 --- a/admin/migrate.md +++ /dev/null @@ -1,31 +0,0 @@ -(migration-guide)= -# Migrating off of a 2i2c Hub - -2i2c Hubs are designed to use entirely open source tools that are vendor- and workflow-agnostic. Our goal is to provide you with interactive computing environments that seamlessly integrate with pre-existing workflows across the research and education community. That means we want it to be **extremely easy to move off of a 2i2c Hub**. This section has a few tips for how you can move off of a 2i2c Hub and into a different environment that uses the Jupyter stack. - -## The 2i2c Hubs for All deployment repository - -If you'd like to see the configuration and deployment scripts for the Hubs for All pilot, you can find it at [the `2i2c-hubs` configuration repository](https://github.com/2i2c-org/pilot-hubs). This is more complex than setting up a single JupyterHub, since it manages the entire federation of 2i2c Hubs in this pilot. You can also find [our documentation for running and configuring hubs with this repository](https://2i2c.org/pilot-hubs/). - -## Different ways to re-create your environment without 2i2c - -### Deploy your own JupyterHub - -2i2c Hubs are an opinionated collection of open source tools, customized for research and education. Using an entirely open stack means that you can deploy these tools yourself if you wish. The first step is to deploy your own JupyterHub, which can run on a variety of cloud vendors (or even your own hardware). - -[The Zero to JupyterHub for Kubernetes guide](https://z2jh.jupyter.org) is an opinionated guide to deploying JupyterHub on Kubernetes. The 2i2c team has written much of the content in this guide, and encourages you to follow it in deploying your own hub infrastructure! The 2i2c Hubs use much of the steps in this guide for their deployment. They also use the [JupyterHub Helm Chart](https://github.com/jupyterhub/helm-chart) in order to deploy JupyterHub in a scalable and flexible way. - -:::{tip} -If you'd like a more lightweight distribution of JupyterHub, you can try out [The Littlest JupyterHub](https://tljh.jupyter.org). This distribution of JupyterHub is easier to set up for smaller teams. -::: - -### Re-create the user environment locally - -The `pilot-hubs` repository has [the configuration for default user environments](https://github.com/2i2c-org/pilot-hubs/tree/master/../images/user). These scripts and files are used to create the Docker image that is used in a 2i2c Hub. We upload that Docker image to a registry and then [configure each JupyterHub to use it here](https://github.com/2i2c-org/pilot-hubs/blob/master/hub/values.yaml#L85). - -For more information about creating custom environments for a JupyterHub, we recommend checking out the [repo2docker project](https://repo2docker.readthedocs.io/), which builds Docker images from Binder repositories. - -### Use a different managed cloud service - -Finally, if you wish to use a different managed cloud service, there are many for you to choose from. These tend to have proprietary components interwoven with open-source ones, which can be a "pro" or a "con" depending on your use-case. 2i2c Hubs are designed to be 100% open source and interoperable with a variety of cloud vendors, however your workflows may be transportable to a proprietary cloud service just the same. - diff --git a/index.md b/index.md index 1bf567d..d339a0f 100644 --- a/index.md +++ b/index.md @@ -29,7 +29,6 @@ to realize the configuration option. :maxdepth: 1 admin/configuration/login admin/configuration/culling -admin/migrate ``` ## Hub administrator how-to guides From 67338f772de9168a21a1dd191feb5cd688a16147 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 15 Mar 2021 23:19:16 +0530 Subject: [PATCH 18/21] Suggest using quay.io for docker registry --- admin/howto/environment.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/admin/howto/environment.md b/admin/howto/environment.md index 0435e69..21a658b 100644 --- a/admin/howto/environment.md +++ b/admin/howto/environment.md @@ -50,10 +50,14 @@ docker image: for full control. Another popular option is to use a `Dockerfile` but inherit from a [pangeo base image](https://github.com/pangeo-data/pangeo-docker-images), making just the modifications you need. - + 2. Use the [repo2docker GitHub Action](https://github.com/jupyterhub/repo2docker-action) - to automatically build, name and - [push your image to dockerhub](https://github.com/jupyterhub/repo2docker-action#push-repo2docker-image-to-dockerhub). + to automatically build, name and push your image to a docker registry. + We recommend [pushing to quay.io](https://github.com/jupyterhub/repo2docker-action#push-image-to-quayio), + a registry with more generous rate limits than DockerHub's. You can + [use DockerHub](https://github.com/jupyterhub/repo2docker-action#push-repo2docker-image-to-dockerhub), + or any other public registry. + 3. [Open an issue in the `2i2c-org/pilot` repository](https://github.com/2i2c-org/pilot/issues/new?labels=enhancement&template=tech-request.md) with a link to your docker image. 2i2c hub engineers can then From 36ce6fc86c262217ac57b93f798e479644931488 Mon Sep 17 00:00:00 2001 From: Chris Holdgraf Date: Mon, 15 Mar 2021 11:26:49 -0700 Subject: [PATCH 19/21] Update admin/configuration/login.md --- admin/configuration/login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/configuration/login.md b/admin/configuration/login.md index 5aa7a2e..98b0420 100644 --- a/admin/configuration/login.md +++ b/admin/configuration/login.md @@ -25,7 +25,7 @@ specify which users are *authorized* to be on the hub. Currently, there are only two supported methods for authorizing regular users: 1. [Manually add users](admin/howto/manage-users) via the admin panel in JupyterHub -2. (Google only) Allow all users who are logged in via aparticular domain - so +2. (Google only) Allow all users who are logged in via a particular domain - so you can allow access to anyone who is part of your organization or educational institution. From 4db087f72fff446d18c0c658b3756631488f4a49 Mon Sep 17 00:00:00 2001 From: Chris Holdgraf Date: Mon, 15 Mar 2021 11:41:55 -0700 Subject: [PATCH 20/21] Fixing broken Sphinx links --- about/infrastructure.md | 7 ++++--- about/overview.md | 7 +++++-- admin/configuration/login.md | 2 +- admin/howto/create-content.md | 3 +-- admin/howto/environment.md | 1 + user/download-data.md | 2 +- user/interface.md | 2 +- 7 files changed, 14 insertions(+), 10 deletions(-) diff --git a/about/infrastructure.md b/about/infrastructure.md index 26c92e4..24780c4 100644 --- a/about/infrastructure.md +++ b/about/infrastructure.md @@ -84,6 +84,7 @@ Each 2i2c Hub has **hub name** (denoted by ``) and a **community name* It is also possible to provide your own URL that points to a 2i2c Hub. -## How could I deploy my own 2i2c Hub? - -Check out [](../admin/migrate.md) for information about migrating off of a 2i2c Hub, including deploying your own hub. +% TODO: add back in once #54 is merged +% ## How could I deploy my own 2i2c Hub? +% +% Check out [](../admin/migrate.md) for information about migrating off of a 2i2c Hub, including deploying your own hub. diff --git a/about/overview.md b/about/overview.md index 2af2ce5..a2768e4 100644 --- a/about/overview.md +++ b/about/overview.md @@ -37,7 +37,7 @@ It does this in collaboration with leaders from the community for a particular h :::{seealso} - **Roles needed to run a hub**: see {doc}`tc:sre` -- **Support options**: see [](../admin/support.md). +- **Support options**: see [](../admin/howto/support.md). ::: ## Funding open source @@ -49,7 +49,10 @@ We see this as an opportunity to solve two problems with one stream of funding: ## Moving off of a 2i2c Hub? -2i2c Hubs are designed to use entirely open-source tools that work in other contexts. You can take your workflows elsewhere if you wish, and you can even deploy your own JupyterHub that recreates the same cloud-based experience. For more information, see [](migration-guide). +2i2c Hubs are designed to use entirely open-source tools that work in other contexts. You can take your workflows elsewhere if you wish, and you can even deploy your own JupyterHub that recreates the same cloud-based experience. + +% TODO: add back in once #54 is merged. +% For more information, see [](migration-guide). ### Wait, you really want it to be easy for people to _leave_ 2i2c Hubs? diff --git a/admin/configuration/login.md b/admin/configuration/login.md index 98b0420..c34b69b 100644 --- a/admin/configuration/login.md +++ b/admin/configuration/login.md @@ -24,7 +24,7 @@ specify which users are *authorized* to be on the hub. Currently, there are only two supported methods for authorizing regular users: -1. [Manually add users](admin/howto/manage-users) via the admin panel in JupyterHub +1. [Manually add users](../howto/manage-users.md) via the admin panel in JupyterHub 2. (Google only) Allow all users who are logged in via a particular domain - so you can allow access to anyone who is part of your organization or educational institution. diff --git a/admin/howto/create-content.md b/admin/howto/create-content.md index 7ca33eb..34b7e3e 100644 --- a/admin/howto/create-content.md +++ b/admin/howto/create-content.md @@ -20,6 +20,5 @@ JupyterHubs instructions](https://jupyterbook.org/interactive/launchbuttons.html#jupyterhub-buttons-for-your-pages). Make sure that you configure your `jupyterhub_url` to point to the URL of your 2i2c Hub (e.g., `https://.pilot.2i2c.cloud`). -This will use automatically [create nbgitpuller links](admin/howto/nbgitpuller) +This will use automatically [create nbgitpuller links](nbgitpuller.md) for you. - diff --git a/admin/howto/environment.md b/admin/howto/environment.md index 21a658b..756c3d2 100644 --- a/admin/howto/environment.md +++ b/admin/howto/environment.md @@ -21,6 +21,7 @@ It is configured with the following: - [RStudio](https://rstudio.com/) - An Ubuntu 20.04 base image, with common utility packages installed. +(environment/custom)= ## Customizing your hub environment Sometimes, what is in the base user environment is not enough for diff --git a/user/download-data.md b/user/download-data.md index 7455899..d179f28 100644 --- a/user/download-data.md +++ b/user/download-data.md @@ -21,7 +21,7 @@ Hubs managed by 2i2c make this easy. This will zip up the contents of your user file system and download them to your machine. ```{note} -If your hub is using a [custom user environment](admin/howto/environment), it needs the +If your hub is using a [custom user environment](environment/custom), it needs the [jupyter-tree-download](https://github.com/ryanlovett/jupyter-tree-download) package installed to make this feature available. ``` diff --git a/user/interface.md b/user/interface.md index acdd84e..1e94f4e 100644 --- a/user/interface.md +++ b/user/interface.md @@ -12,4 +12,4 @@ You can replace the contents of `` to be one of the following: - **Jupyter Notebook**: `/tree` - **RStudio**: `/rstudio` -Note that your 2i2c Hub administrator can also configure the **default** interface that users see. In addition, you can configure the interface that **nbgitpuller links** point to, see [](include-content) for information about nbgitpuller links. +Note that your 2i2c Hub administrator can also configure the **default** interface that users see. In addition, you can configure the interface that **nbgitpuller links** point to, see [](../../admin/howto/nbgitpuller.md) for information about nbgitpuller links. From 890fd5362f9b221b3175cc2b9093b3036a41b21a Mon Sep 17 00:00:00 2001 From: Chris Holdgraf Date: Mon, 15 Mar 2021 12:36:07 -0700 Subject: [PATCH 21/21] Edits to improve content --- admin/configuration/culling.md | 10 +-- admin/configuration/login.md | 22 +++--- admin/howto/control-user-server.md | 36 +++++---- admin/howto/create-content.md | 4 +- admin/howto/manage-users.md | 6 +- admin/howto/nbgitpuller.md | 119 ++++++++++++++++------------- admin/howto/share-datasets.md | 24 +++--- conf.py | 3 +- index.md | 10 ++- user/download-data.md | 11 ++- 10 files changed, 135 insertions(+), 110 deletions(-) diff --git a/admin/configuration/culling.md b/admin/configuration/culling.md index 72ad133..9511980 100644 --- a/admin/configuration/culling.md +++ b/admin/configuration/culling.md @@ -1,17 +1,13 @@ # User server culling To ensure efficient resource usage, user servers without interactive usage for a -period of time (default 1h) are automatically stopped (via +period of time (default `1h`) are automatically stopped (via [jupyterhub-idle-culler](https://github.com/jupyterhub/jupyterhub-idle-culler)). This means your notebook server might be stopped for inactivity even if you have -a long running process in the notebook. This timeout can be configured. +a long running process in the notebook. This timeout can be configured. % TODO: Add link to SRE guide on how to configure this, once it exists -This has the same effect as a user stopping their own server. User servers -stopping doesn't lose any data in your home directories. However, any packages -temporarily installed via `!pip` or `!conda` are cleared, to make sure that -everyone in the hub is operating from the same clean environment as much as -possible. Active notebooks have their kernel killed as well. +Culling has the same effect as [stopping a user's server](user-server/stopping). There is currently no maximum time limit for a user's notebook. diff --git a/admin/configuration/login.md b/admin/configuration/login.md index c34b69b..259f23c 100644 --- a/admin/configuration/login.md +++ b/admin/configuration/login.md @@ -1,5 +1,8 @@ # User authentication & authorization +**Authentication** allows your users to prove who their are. +**Authorization** gives users certain permissions depending on their identity (such as "access to your hub", or "administrative privileges"). + (admin/configuration/authentication)= ## Authentication @@ -11,9 +14,9 @@ Users can prove who they are by logging in via an *authentication provider*. Cur 3. [*ORCID*](https://orcid.org/). Everyone who has published a paper has one of these, and anyone else can easily sign up. Almost exclusively used by researchers. -4. ???. We could probably support other authentication providers, depending on your specific needs and the provider's complexity. Please reach out to us if none of these 3 work. +4. ``. We may be able to support other authentication providers, depending on your specific needs and the provider's complexity. Please reach out to us if none of these 3 work. -We will ask you what provider you want when we set up the hub. We can change the provider after the fact, but only if absolutely strictly necessary. +We will ask you what provider you want when we set up the hub. We can change the provider after the fact, but only if absolutely necessary. ## Authorization @@ -22,14 +25,15 @@ everyone with a `@gmail.com` account can log in if you use Google as your authentication provider! Instead, we support multiple ways for hub admins to specify which users are *authorized* to be on the hub. -Currently, there are only two supported methods for authorizing regular users: +Authorizing regular users +: Currently, there are only two supported methods for authorizing regular users: -1. [Manually add users](../howto/manage-users.md) via the admin panel in JupyterHub -2. (Google only) Allow all users who are logged in via a particular domain - so - you can allow access to anyone who is part of your organization or - educational institution. + 1. [Manually add users](../howto/manage-users.md) via the admin panel in JupyterHub + 2. (Google only) Allow all users who are logged in via a particular domain - so + you can allow access to anyone who is part of your organization or + educational institution. -Admin users are instead authorized [in YAML config](https://github.com/2i2c-org/pilot-hubs/blob/master/hubs.yaml), -with support from 2i2c staff. +Authorizing admin users +: Admin users are authorized [in a hub's YAML config](https://github.com/2i2c-org/pilot-hubs/blob/master/hubs.yaml), with support from 2i2c staff. % TODO: Link to SRE docs on how to do this once we have it diff --git a/admin/howto/control-user-server.md b/admin/howto/control-user-server.md index 2357865..ea4bb2b 100644 --- a/admin/howto/control-user-server.md +++ b/admin/howto/control-user-server.md @@ -1,20 +1,20 @@ # Controlling a user's server -Hub admins can unilaterally perform actions on user's servers via the +Hub admins can unilaterally perform actions on a user's server via the **Administrator's Panel**. These are primarily used to debug a user's session easily. -You can access the admin panel by clicking the 'Admin' button in the top bar +You can access the admin panel by clicking the {guilabel}`Admin` button in the top bar in your hub control panel. Alternatively, you can go to this URL in your browser: `https:///hub/admin`. ## Access a user's server -Accessing a user's server is useful when trying to debug or reproduce an issue they might have. This facility is available to admins via the admin panel. +Accessing a user's server is useful when trying to debug or reproduce an issue they might have. This facility is available to admins via the admin panel. -1. In the admin panel, you can click `access server` to gain control of a user's - currently running server. If it isn't running, you can click `start server` +1. In the admin panel, you can click {guilabel}`access server` to gain control of a user's + currently running server. If it isn't running, you can click {guilabel}`start server` first and wait for it to start. ```{figure} ../../images/access-server.png @@ -23,26 +23,36 @@ Accessing a user's server is useful when trying to debug or reproduce an issue t 2. This will bring you to the default interface that the user would have seen if they had just logged into the hub. From here, you can navigate to the notebook the user has reported issues with, and help them debug. - ```{warning} + :::{warning} If you both work on the same notebook at the same time, you will just overwrite each other's code! The state of the notebook will be that of whoever saved the notebook last. There is no Google Docs' style real-time collaboration yet, although [it is coming](https://github.com/jupyterlab/rtc) - ``` + ::: - ```{warning} + :::{warning} When you control a user's server, all of your actions will be run *as if the user ran it themselves*. This can be confusing for some users and is generally not best-practice. We recommend telling users when you are taking over their session, and using this feature mostly to understand what the user was trying to do, rather than to make major changes to their code or notebook outputs. - ``` + ::: -## Stopping & starting a user's server +(user-server/stopping)= +## Stop or start a user's server Sometimes, you need to just turn a user's server on and off. You can -also do this from the admin interface, by hitting the `Stop server` -button, waiting for the server to stop, and the `Start server` button -again. This is particularly useful when their session might have gotten +also do this from the admin interface, by hitting the {guilabel}`Stop server` +button, waiting for the server to stop, and the {guilabel}`Start server` button +again. + +This is particularly useful when their session might have gotten out of whack by packages they've installed temporarily that screwed up the default, since a restart will wipe the slate clean. + +:::{important} +When a user's server is stopped (by an admin, or by the user themselves), no data is lost in the user's home directory. +However, any packages temporarily installed via `!pip` or `!conda` are cleared, to make sure that everyone in the hub is operating from the same clean environment as much as +possible. +Active notebooks have their kernel killed as well. +::: diff --git a/admin/howto/create-content.md b/admin/howto/create-content.md index 34b7e3e..545cd9a 100644 --- a/admin/howto/create-content.md +++ b/admin/howto/create-content.md @@ -2,7 +2,7 @@ ## Write public books that connect to a 2i2c Hub -You can create public content that is designed to have connections with your +You can create public content that is designed to connect with your 2i2c Hub. For example, you can create lectures from Jupyter Notebooks, and allow students to grab their own copy of the notebook to interact with on the 2i2c Hub. @@ -11,7 +11,7 @@ To connect your public content with a 2i2c Hub, we recommend using [Jupyter Book](https://jupyterbook.org). This is an open-source project that allows you to share collections of notebooks and markdown files as an online website and book. Check out the [Jupyter Book getting started -guide](https://jupyterbook.org/start/overview.html) for more information about +guide](jb:start/overview) for more information about Jupyter Book. You can tell Jupyter Book to place links *directly to your 2i2c Hub* on each diff --git a/admin/howto/manage-users.md b/admin/howto/manage-users.md index f0c5b8b..5227b13 100644 --- a/admin/howto/manage-users.md +++ b/admin/howto/manage-users.md @@ -8,15 +8,15 @@ Alternatively, you can go to this URL in your browser: ## To add users -1. Click the `Add Users` button. The `Add Users` dialog box will pop up. -2. Add one or more users, and hit the `Add Users` button to authorize all the users you just added. +1. Click the {guilabel}`Add Users` button. The {guilabel}`Add Users` dialog box will pop up. +2. Add one or more users, and hit the {guilabel}`Add Users` button to authorize all the users you just added. ````{panels} :container: full-width :card: border-1 ```{figure} ../../images/add-users-button.png -The add users button in the Administrator Panel. +The {guilabel}`Add Users` button in the Administrator Panel. ``` --- ```{figure} ../../images/add-users-form.png diff --git a/admin/howto/nbgitpuller.md b/admin/howto/nbgitpuller.md index ce530bf..eba7def 100644 --- a/admin/howto/nbgitpuller.md +++ b/admin/howto/nbgitpuller.md @@ -10,64 +10,73 @@ make an adjustment to content that has already been touched by the student. [nbgitpuller](https://jupyterhub.github.io/nbgitpuller) is the tool we recommend for this. The workflow goes something like this: -1. Create a repository on [GitHub](https://github.com) and start putting your - content there. This is the *source* of the content that will be distributed - to your users. You can update it as often as you wish. While instructors will - need to know how github works, *your users will never have to interact with - git directly*. - -2. Generate an [nbgitpuller link](http://nbgitpuller.link). This generates a - *clickable link* that contains within it the following pieces of information: +## Put your content in a public GitHub repository + +Create a repository on [GitHub](https://github.com) and start putting your +content there. This is the *source* of the content that will be distributed +to your users. You can update it as often as you wish. While instructors will +need to know how github works, *your users will never have to interact with +git directly*. - 1. The URL to your hub. Upon clicking the link, users will be redirected to - this hub, and content will be pulled into their home directory there. - 2. The URL of the git repository where the content lives. - 3. The branch in the git repository where the content lives. The default - specified there is `master`, although newer GitHub repositories use `main` - as the default. You can find yours on the Github page of your content - repository - 4. The default interface to open when users click this link. The default is - the classic notebook, but many other apps are available. - 5. A file to open when the link is clicked. When left empty, a directory - listing with the content of the repository will be shown. - - ```{figure} ../../images/nbgitpuller-ui.png - The [`nbgitpuller.link`](http://nbgitpuller.link) user interface, along with - some important fields highlighted. - ``` - - ```{tip} - Unfortunately, RStudio does not support opening a specific file, and will - always show the home directory. Users will have to manually navigate to - the appropriate file. - ``` +## Generate an nbgitpuller link + +Generate an [nbgitpuller link](http://nbgitpuller.link). This generates a +*clickable link* that contains within it the following pieces of information: + +1. The URL to your hub. Upon clicking the link, users will be redirected to + this hub, and content will be pulled into their home directory there. +2. The URL of the git repository where the content lives. +3. The branch in the git repository where the content lives. The default + specified there is `master`, although newer GitHub repositories use `main` + as the default. You can find yours on the Github page of your content + repository +4. The default interface to open when users click this link. The default is + the classic notebook, but many other apps are available. +5. A file to open when the link is clicked. When left empty, a directory + listing with the content of the repository will be shown. - Once you've filled these out, you can copy the link from the textbox above the form. +```{figure} ../../images/nbgitpuller-ui.png +The [`nbgitpuller.link`](http://nbgitpuller.link) user interface, along with +some important fields highlighted. +``` -3. Distribute the link you have generated to your users. Upon clicking the link, - they will be: +```{tip} +Unfortunately, RStudio does not support opening a specific file, and will +always show the home directory. Users will have to manually navigate to +the appropriate file. +``` - 1. Redirected to your hub, and asked to log in if they have not already - 2. The first time the link is clicked, your content repository will be pulled - into their home directory! - 3. If they had already clicked the link before, any new changes in your - content repository will be pulled in. Any changes the user has made will - be [automatically - merged](https://jupyterhub.github.io/nbgitpuller/topic/automatic-merging.html) - with changes in the content repository, in such a way that the user's - changes are never overwritten. All merge conflicts will also be - automatically resolved, so users don't have to interact with git. - 4. If you have picked a specific file to be displayed, the user will be - redirected to that file, open in the application you picked. If not, the - directory listing of local copy of the content repository will be shown in - the application you selected. +Once you've filled these out, you can copy the link from the textbox above the form. + +## Distribute your nbgitpuller link + +Distribute the link you have generated to your users. Upon clicking the link, +they will be: + +1. Redirected to your hub, and asked to log in if they have not already +2. The first time the link is clicked, your content repository will be pulled + into their home directory! +3. If they had already clicked the link before, any new changes in your + content repository will be pulled in. Any changes the user has made will + be [automatically + merged](https://jupyterhub.github.io/nbgitpuller/topic/automatic-merging.html) + with changes in the content repository, in such a way that the user's + changes are never overwritten. All merge conflicts will also be + automatically resolved, so users don't have to interact with git. +4. If you have picked a specific file to be displayed, the user will be + redirected to that file, open in the application you picked. If not, the + directory listing of local copy of the content repository will be shown in + the application you selected. -4. You **do not** have to create a new link each time you update your content - repository! The same link will continue to work, so you can simply ask your - users to click the link again to fetch the latest changes. +:::{important} + +You **do not** have to create a new link each time you update your content +repository! The same link will continue to work, so you can simply ask your +users to click the link again to fetch the latest changes. - However, if you want to create links to individual files that should be - opened at specific points - like one link per class or assignment - you can - regenerate the links with different values for the file to open or interface. - As long as the hub url, content repository url and the branch name are the - same, users will be not be duplicating content. +However, if you want to create links to individual files that should be +opened at specific points - like one link per class or assignment - you can +regenerate the links with different values for the file to open or interface. +As long as the hub url, content repository url and the branch name are the +same, users will be not be duplicating content. +::: diff --git a/admin/howto/share-datasets.md b/admin/howto/share-datasets.md index e87eb91..7517624 100644 --- a/admin/howto/share-datasets.md +++ b/admin/howto/share-datasets.md @@ -1,6 +1,6 @@ -# Share large data files with your users +# Share data files with your users -Sometimes you might need to distribute a set of large files to all +Sometimes you might need to distribute a set of files to all your users, so they don't have to re-download it once per person. This is particularly useful in educational contexts, where you might be teaching a course that reads a common dataset. @@ -13,15 +13,19 @@ distributing large datasets to your users. ## The `shared` directory -All users have a directory called `shared` in their home directory. -This is meant to be used to distribute datasets and other files that -can be read by all users. This is a *readonly* directory - regular -users can not write to it. +There are two folders that are used together to allow Administrators to +share data files with all users. -Admin users will also have a directory called `shared-readwrite` in -their home directory. This is the *same* as the `shared` directory, -but writeable! So any files admins put here will be immediately -visible in all users' `shared` directories. +`shared` +: All users have a directory called `shared` in their home directory. + This is a *readonly* directory - users and administrators can not write to it. + However, anybody can *access* and *read from* the `shared` directory. + This is how a user accesses a data file distributed by a hub administrator. + +`shared-readwrite` +: **(administrators only)** Admin users also have a directory called `shared-readwrite` in their home directory. + This is the *same folder* as the `shared` directory, but writeable! + Any files admins put here will be immediately visible in all users' `shared` directories. ## A workflow for sharing datasets diff --git a/conf.py b/conf.py index cee4dde..fe85a60 100644 --- a/conf.py +++ b/conf.py @@ -57,7 +57,8 @@ html_logo = "images/logo.png" intersphinx_mapping = { "tc": ('https://2i2c.org/team-compass', None), - "ph": ('https://2i2c.org/pilot-hubs', None) + "ph": ('https://2i2c.org/pilot-hubs', None), + "jb": ('https://jupyterbook.org', None) } rediraffe_redirects = { diff --git a/index.md b/index.md index d339a0f..f8ed6ca 100644 --- a/index.md +++ b/index.md @@ -8,17 +8,16 @@ This guide is for **administrators and community champions** of 2i2c Hubs, or fo See the sections below (or to the left) for more information. - -## About 2i2c hubs +## About the 2i2c hubs ```{toctree} :maxdepth: 1 +:caption: About the 2i2c hubs about/overview about/infrastructure about/projects ``` - ## Hub configuration options These pages list the different ways hub admins can configure how @@ -27,6 +26,8 @@ to realize the configuration option. ```{toctree} :maxdepth: 1 +:caption: Hub configuration options + admin/configuration/login admin/configuration/culling ``` @@ -39,6 +40,7 @@ tasks on their hubs, mostly without requiring any interaction with ```{toctree} :maxdepth: 1 +:caption: Administrator how-to guides admin/howto/support admin/howto/environment admin/howto/nbgitpuller @@ -53,7 +55,7 @@ admin/howto/share-datasets ```{toctree} :maxdepth: 1 +:caption: User guides user/download-data user/interface ``` - diff --git a/user/download-data.md b/user/download-data.md index d179f28..8dd2ea5 100644 --- a/user/download-data.md +++ b/user/download-data.md @@ -2,13 +2,12 @@ You might want to download your entire home directory for many reasons - to get data off a hub that is closing, to migrate to -a different service, for archival, etc. Your home directory +a different service, for archival purposes, etc. Your home directory will contain all your data *and* your notebooks. Hubs managed by 2i2c make this easy. - -1. You need to use the classic jupyter notebook interface for this. If you are - using another interface, navigate to the classic interface by changing your +1. **Open the classic Jupyter Notebook file browser.** If you are + using another interface, navigate to the classic interface by changing your URL path to `/tree`. e.g., `.pilot.2i2c.cloud/user//tree` @@ -20,8 +19,8 @@ Hubs managed by 2i2c make this easy. This will zip up the contents of your user file system and download them to your machine. -```{note} +:::{note} If your hub is using a [custom user environment](environment/custom), it needs the [jupyter-tree-download](https://github.com/ryanlovett/jupyter-tree-download) package installed to make this feature available. -``` +:::