Skip to content

Commit

Permalink
add desktop.sh
Browse files Browse the repository at this point in the history
  • Loading branch information
eeholmes committed Oct 30, 2024
1 parent 086f24c commit 25bb711
Show file tree
Hide file tree
Showing 6 changed files with 418 additions and 280 deletions.
27 changes: 20 additions & 7 deletions book/desktop.qmd
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# Desktop

py-rocket-base includes the [xfce4 Desktop UI](https://www.xfce.org/) where you can run applications. A common application is to run QGIS or java-based applications. py-rocket-base
looks for files with the ending `.desktop`, `.xml` (optional), and `.png` (optional) associated with an application in a directory called Desktop in your repo.
py-rocket-base includes the [xfce4 Desktop UI](https://www.xfce.org/) where you can run applications. It is built off [freedesktop](https://www.freedesktop.org/wiki/Software/) (which is helpful to know if you are debugging and configuring). A common application is to run QGIS or java-based applications. py-rocket-base
looks for files with the ending `.desktop`, `.xml` (optional), and `.png` or `.svg` (optional) associated with an application in a directory called Desktop in your repo.

py-rocket-base puts these `.desktop` files in `/usr/share/Desktop`. Typically these desktop files are in `~/Desktop`. But in a JupyterHub, the home directory is often persistent and py-rocket-base tries not to alter the user home directory. Also there might be orphaned desktop files in `~/Desktop` and so the user desktop UI would be inconsistent between users. Using `/usr/share/Desktop` does mean that users cannot add their own persistent applications to xfce4 Desktop but this would be difficult anyhow without root access in many cases.


## Adding an application in your child docker image

### Create the Desktop directory

Create the directory and add the .desktop and optional .png and .xml files.
Create the directory and add the .desktop and optional .png and .xml files. py-rocket-base will move them to the correct places (`/usr/share/applications` and `/usr/share/Desktop`, `/usr/share/mime/packages` and icon locations).

```
your-repo/
Expand Down Expand Up @@ -44,9 +46,19 @@ Keywords=map;globe;

You can specify the mime types via xml.

#### .png
#### icons

You can include a png or svg for the icon. py-rocket-base will place this in `/usr/share/icons/packages`. If you put your icon file in the Desktop directory in your repo, then in your desktop file, use the file name without the extension.

```
Icon=cdat
```

You can include a png for the icon. py-rocket-base will place this in `/srv/repo/Desktop/` so use a location like `/srv/repo/Desktop/my.png` for your png that you include in your Desktop folder.
You can also use an absolute file path.

```
Icon=/srv/conda/envs/notebook/share/qgis/images/icons/qgis-icon-512x512.png
```


### Install the application
Expand Down Expand Up @@ -115,7 +127,8 @@ Add `cdat.png` icon to Desktop directory.
To add new desktop applications, one needs to do the following. py-rocket-base does these steps automatically (via the start script) so that the user only has to put files into a Desktop directory in the docker build repo.

* Install the application. See examples.
* Add a `.desktop` file to `${HOME}/.local/share/applications/`.
* To have an icon on the Desktop, you create a folder Desktop in the `${HOME}` and create a soft link to the `.desktop` files in `${HOME}/.local/share/applications/`.
* Add a `.desktop` file to an application directory. py-rocket-base puts these in `/usr/share/applications` but you will also see `${HOME}/.local/share/applications/`.
* To have an icon on the Desktop, you create a folder Desktop and tell XDG what directory to use. The directory is specified in `~/.config/user-dirs.dirs` which XDG sets. By default (XDG), this folder is `~/Desktop` but you can set it to something else. py-rocket-base sets a default value in `/etc/xdg/user-dirs.defaults`. This updates `~/.config/user-dirs.dirs` when the `/etc/xdg/xfce4/xinitrc` start script is run (when the `/desktop` button is clicked).
* XDG looks for .desktop files in the Desktop directory. py-rocket-base creates a soft link to the `.desktop` files in `/usr/share/applications/` in `/usr/share/Desktop`.

See this [Medium article](https://medium.com/@zoldin/how-to-add-new-application-to-xfce-menu-list-d90955e101d5) for a description.
147 changes: 87 additions & 60 deletions book/developers.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -96,28 +96,30 @@ most of the work in py-rocket-base is done here. appendix calls rocker.sh and in

Each file is described below.

### apt2.txt
## apt2.txt

This is not named `apt.txt` because these packages need to be installed after R is installed because the R scripts uninstall packages as part of cleanup. There are some packages that are required for Desktop (`/desktop`) to operate correctly. Packages needed for R and RStudio building (`/rstudio`) are installed via the rocker install scripts.

### environment.yml
## environment.yml

These are added to the notebook conda environment and in py-rocket-base the basic packages needed for Jupyter Lab, RStudio and Desktop are added. Scientific packages are not added here. They will be added via child images that use py-rocket-base as the base image (in the FROM line).


### appendix
## appendix

This a long file with many pieces. The pieces are explained below. Click on the number next to code to read about what that code block does.


```r
USER root # <1>

# repo2docker does not set this. This is the default env in repo2docker type images
# Set env variables # <2>
# This is the default env in repo2docker type images # <2>
ENV CONDA_ENV=notebook # <2>
# Tell applications where to open desktop apps # <2>
DISPLAY=":1.0" # <2>

# Install R, RStudio via Rocker scripts
ENV R_VERSION="4.4.1" # <3>
ENV R_VERSION="4.4.1" # <3>
ENV R_DOCKERFILE="verse_${R_VERSION}" # <3>
# This is in the rocker script but will not run since ${NB_USER} already exists # <3>
# Needed because rocker scripts set permissions based on the staff group # <3>
Expand Down Expand Up @@ -165,19 +167,22 @@ ONBUILD USER ${NB_USER} # <10>
# copy all the files in the repo (i.e. ".") into ${REPO_DIR}
ONBUILD COPY --chown=${NB_USER}:${NB_USER} . ${REPO_DIR}/childimage # <11>

# Desktop and apt.txt installs need to be done by root
ONBUILD USER root

# Copy Desktop files into ${REPO_DIR}/Desktop if they exist. start will copy to Application dir and Desktop
# Will not fail if Desktop dir exists but is empty
ONBUILD RUN echo "Checking for 'Desktop directory'..." \ # <13>
; cd "${REPO_DIR}/childimage/" \ # <13>
; if test -d Desktop ; then \ # <13>
mkdir -p "${REPO_DIR}/Desktop" && \ # <13>
[ "$(ls -A Desktop 2>/dev/null)" ] && cp -r Desktop/* "${REPO_DIR}/Desktop/"; \ # <13>
fi # <13>
# Copy Desktop files into ${REPO_DIR}/Desktop if they exist. start will copy to Application dir and Desktop # <13>
# Will not fail if Desktop dir exists but is empty # <13>
ONBUILD RUN echo "Checking for 'Desktop directory'..." \ # <13>
; cd "${REPO_DIR}/childimage/" \ # <13>
; if test -d Desktop ; then \ # <13>
mkdir -p "${REPO_DIR}/Desktop" && \ # <13>
cp -r Desktop/* "${REPO_DIR}/Desktop/" 2>/dev/null && \ # <13>
chmod +x "${REPO_DIR}/desktop.sh" ; \ # <13>
fi \ # <13>
; "${REPO_DIR}/desktop.sh" # <13>

# Install apt packages specified in a apt.txt file if it exists.
# blank lines and comments are supported in apt.txt
ONBUILD USER root # <14>
ONBUILD RUN echo "Checking for 'apt.txt'..." \ # <14>
; cd "${REPO_DIR}/childimage/" \ # <14>
; if test -f "apt.txt" ; then \ # <14>
Expand All @@ -188,6 +193,7 @@ ONBUILD RUN echo "Checking for 'apt.txt'..." \ # <14>
&& apt-get clean \ # <14>
&& rm -rf /var/lib/apt/lists/* \ # <14>
; fi

ONBUILD USER ${NB_USER} # <15>

# Add the conda environment
Expand Down Expand Up @@ -256,9 +262,9 @@ USER ${NB_USER} # <21>
WORKDIR ${HOME} # <21>
```
1. Some commands need to be run as root, such as installing linux packages with `apt-get`
2. repo2docker does not set this environment variable and it is useful for child builds
2. Set variables. CONDA_ENV is useful for child builds
3. This section runs the script `rocker.sh` which installs R and RStudio using rocker scripts.
4. The rocker scripts build R from source and as part of clean up in the script, linux packages are removed that are not needed. repo2docker installs the packages in `apt.txt` automatically before the code in `appendix` thus the needed linux packages (which include packages for desktop) are put in `apt2.txt` (which repo2docker will not detect) and are installed separately here. The `grep -v ` etc code is processing `apt2.txt` and removing comments and blank lines.
4. The rocker scripts build R from source and as part of clean up in the script, linux packages are removed that are not needed. repo2docker installs the packages in `apt.txt` automatically before the code in `appendix` thus the needed linux packages (which include packages for the Xfce Desktop Environment in `\desktop`) are put in `apt2.txt`. repo2docker will not detect this file and we can install the packages here after R is built. The `grep -v ` etc code is processing `apt2.txt` and removing comments and blank lines.
5. Ubuntu does not have man pages installed by default. These lines activate `man` so users have the common help files.
6. This is some custom jupyter config to allow hidden files to be listed in the folder browser.
7. `book` and `docs` are the documentation files and are not needed in the image.
Expand All @@ -267,7 +273,7 @@ WORKDIR ${HOME} # <21>
10. Set the user to NB_USER. Not strictly necessary but helps ensure that we don't accidentally create files that jovyan (NB_USER) cannot access.
11. Copy the child build context (files with the Docker file) into `${REPO_DIR}`. Make sure that jovyan owns the directory. Note, jovyan owns `${REPO_DIR}` (this is set by repo2docker).
12. empty
13. The Desktop files are put in a directory called Desktop. Copy them into `${REPO_DIR}/Desktop`. The start script will copy these into the correct location for the Desktop server.
13. The Desktop files are put in a directory called Desktop. Copy them into `${REPO_DIR}/Desktop`. The `desktop.sh` script will copy these into the correct location for the Desktop server.
14. If `apt.txt` is present, then install the packages. The code processes any comments or blank lines in `apt.txt`. This must be run as root so we switch to root to install.
15. Switch back to jovyan so we don't accidentally make files as belonging to root.
16. If `environment.yml` is present, install these into the conda environment and do some clean-up. Sometimes package solving will get rid of pip installed packages. We need to make sure that jupyter-remote-desktop-proxy does not disappear.
Expand Down Expand Up @@ -342,9 +348,7 @@ fi # <13>

## start

Within a JupyterHub, the user home directory `$HOME` is typically re-mapped to the user persistent home directory. That means that the image build process cannot put things into `$HOME`, they would just be lost when `$HOME` is re-mapped. If a process needs to have something in the home directory, e.g. in some local user configuration, this must be done in the `start` script. The repo2docker docker image specifies that the start script is `${REPO_DIR}/start`.

For py-rocket-base, the start script is used to move the Desktop files (`*.desktop`) to where the Desktop server expects them, which is `${HOME}/.local/share/applications/` and `${HOME}/Desktop`. Any `*.desktop` files found there will appear as clickable icons in `\desktop`. This must be done in start, since the files need to be put in `${HOME}` which not available until after the server starts.
Within a JupyterHub, the user home directory `$HOME` is typically re-mapped to the user persistent home directory. That means that the image build process cannot put things into `$HOME`, they would just be lost when `$HOME` is re-mapped. If a process needs to have something in the home directory, e.g. in some local user configuration, this must be done in the `start` script. The repo2docker docker image specifies that the start script is `${REPO_DIR}/start`. In py-rocket-base, the start scripts in a child docker file is souces in a subshell from the py-rocket-base start script.

```
#!/bin/bash
Expand All @@ -353,49 +357,72 @@ set -euo pipefail
# Start - Set any environment variables here # <1>
# These are inherited by all processes, *except* RStudio # <1>
# USE export <parname>=value # <1>
# Tell applications where to open desktop apps - this allows notebooks to pop open GUIs # <1>
export DISPLAY=":1.0" # <1>
# source this file to get the variables defined in the rocker Dockerfile # <1>
source ${REPO_DIR}/env.txt # <1>
# End - Set any environment variables here # <1>
# The for loops will fail if they return null (no files). Set shell option nullglob
shopt -s nullglob # <2>
# Add any .desktop files to the application database and desktop. This is done
# at startup-time because it's expected that a remote filesystem will be
# mounted at $HOME, which would overwrite the data if it was created at
# build-time.
APPLICATIONS_DIR="${HOME}/.local/share/applications" # <3>
DESKTOP_DIR="${HOME}/Desktop" # <3>
# Remove DESKTOP_DIR if it exists to avoid leftover files
if [ -d "${DESKTOP_DIR}" ]; then # <4>
rm -rf "${DESKTOP_DIR}" # <4>
fi # <4>
mkdir -p "${APPLICATIONS_DIR}" # <5>
mkdir -p "${DESKTOP_DIR}" # <5>
for desktop_file_path in ${REPO_DIR}/Desktop/*.desktop; do # <6>
cp "${desktop_file_path}" "${APPLICATIONS_DIR}/." # <6>
# Symlink application to desktop and set execute permission so xfce (desktop) doesn't complain
desktop_file_name="$(basename ${desktop_file_path})" # <7>
# Set execute permissions on the copied .desktop file # <7>
chmod +x "${APPLICATIONS_DIR}/${desktop_file_name}" # <7>
ln -sf "${APPLICATIONS_DIR}/${desktop_file_name}" "${DESKTOP_DIR}/${desktop_file_name}" # <8>
done
update-desktop-database "${APPLICATIONS_DIR}" # <9>
# Add MIME Type data from XML files to the MIME database.
MIME_DIR="${HOME}/.local/share/mime" # <10>
MIME_PACKAGES_DIR="${MIME_DIR}/packages" # <10>
mkdir -p "${MIME_PACKAGES_DIR}" # <10>
for mime_file_path in ${REPO_DIR}/Desktop/*.xml; do # <10>
cp "${mime_file_path}" "${MIME_PACKAGES_DIR}/." # <10>
done # <10>
update-mime-database "${MIME_DIR}" # <10>
# Run child start in a subshell to contain its environment
[ -f ${REPO_DIR}/childimage/start ] && ( source ${REPO_DIR}/childimage/start ) # <11>
[ -f ${REPO_DIR}/childimage/start ] && ( source ${REPO_DIR}/childimage/start ) # <2>
exec "$@"
```
```
1. In a Docker file so no way to dynamically set environmental variables, so the `env.txt` file with the `export <var>=<value>` are source at start up.
2. Run any child start script in a subshell. Run in a subshell to contain any `set` statements or similar.

## desktop.sh

The default for XDG and xfce4 is for Desktop files to be in ~/Desktop but this leads to a variety of problems. First we are altering the user directiory which seems rude, second orphan desktop files might be in ~/Desktop so who knows what the user Desktop experience with be, here the Desktop dir is set to /usr/share/Desktop so is part of the image. Users that really want to customize Desktop can change `~/.config/user-dirs.dirs`. Though py-rocket-base might not respect that. Not sure why you'd do that instead of just using a different image that doesn't have the py-rocket-base behavior.

```
#!/bin/bash
set -e
# Copy in the Desktop files
APPLICATIONS_DIR=/usr/share/applications # <1>
DESKTOP_DIR=/usr/share/Desktop # <2>
mkdir -p "${DESKTOP_DIR}" # <2>
chown :staff /usr/share/Desktop # <2>
chmod 775 /usr/share/Desktop # <2>
# set the Desktop dir default for XDG
echo 'XDG_DESKTOP_DIR="${DESKTOP_DIR}"' > /etc/xdg/user-dirs.defaults # <3>
# The for loops will fail if they return null (no files). Set shell option nullglob
shopt -s nullglob
for desktop_file_path in ${REPO_DIR}/Desktop/*.desktop; do # <4>
cp "${desktop_file_path}" "${APPLICATIONS_DIR}/." # <4>
# Symlink application to desktop and set execute permission so xfce (desktop) doesn't complain # <4>
desktop_file_name="$(basename ${desktop_file_path})" # <4>
# Set execute permissions on the copied .desktop file # <4>
chmod +x "${APPLICATIONS_DIR}/${desktop_file_name}" # <4>
ln -sf "${APPLICATIONS_DIR}/${desktop_file_name}" "${DESKTOP_DIR}/${desktop_file_name}" # <4>
done # <4>
update-desktop-database "${APPLICATIONS_DIR}" # <4>
# Add MIME Type data from XML files to the MIME database. # <5>
MIME_DIR="/usr/share/mime" # <5>
MIME_PACKAGES_DIR="${MIME_DIR}/packages" # <5>
mkdir -p "${MIME_PACKAGES_DIR}" # <5>
for mime_file_path in ${REPO_DIR}/Desktop/*.xml; do # <5>
cp "${mime_file_path}" "${MIME_PACKAGES_DIR}/." # <5>
done # <5>
update-mime-database "${MIME_DIR}" # <5>
# Add icons # <6>
ICON_DIR="/usr/share/icons" # <6>
ICON_PACKAGES_DIR="${ICON_DIR}/packages" # <6>
mkdir -p "${ICON_PACKAGES_DIR}" # <6>
for icon_file_path in "${REPO_DIR}"/Desktop/*.png; do # <6>
cp "${icon_file_path}" "${ICON_PACKAGES_DIR}/" || echo "Failed to copy ${icon_file_path}" # <6>
done # <6>
for icon_file_path in "${REPO_DIR}"/Desktop/*.svg; do # <6>
cp "${icon_file_path}" "${ICON_PACKAGES_DIR}/" || echo "Failed to copy ${icon_file_path}" # <6>
done # <6>
gtk-update-icon-cache "${ICON_DIR}" # <6>
```
1. This is the default local for system applications.
2. Create the Desktop directory and make sure jovyan can put files there. This is mainly for debugging.
3. Set up the default XDG_DESKTOP_DIR value. This will be copied to the `~.config` (by xinitrc).
4. Copy the .desktop file in the Desktop directory into the applications directory and make a symlink to the Desktop directory. The former means that the applications will appear in the menu in xfce4 desktop and the latter means there will be a desktop icon.
5. Add any mime xml files to the mime folder and update the mime database.
6. Add any png or svg icon files to the icon folder and update the icon database.
Loading

0 comments on commit 25bb711

Please sign in to comment.