#!/bin/bash # # toolbox-vscode: code.sh # Copyright the toolbox-vscode authors, 2021 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. verbose=false ### Helper functions if [ -t 2 ] ; then verbose() { if $verbose ; then echo -e "\033[1m\033[34mtoolbox-vscode\033[0m: $*" 1>&2 fi } info() { echo -e "\033[1m\033[34mtoolbox-vscode\033[0m: \033[1m$*\033[0m" 1>&2 } else verbose() { if $verbose ; then echo -e "toolbox-vscode: $*" 1>&2 fi } info() { echo -e "toolbox-vscode: $*" 1>&2 } fi path_remove() { # remove the argument from $PATH local -a path newpath saveIFS=$IFS IFS=: read -r -a path <<<"$PATH" newpath=() for p in "${path[@]}" ; do if [[ "$p" != "$1" ]] ; then newpath+=("$p") fi done PATH="${newpath[*]}" IFS=$saveIFS } read_cmdline() { # read the command line of $2 into the array variable $1 local PROC=${TOOLBOX_VSCODE_FAKE_PROC:-/proc} local -n var=$1 [ -e "$PROC/$2/cmdline" ] || return 1 while read -r -d '' arg ; do var+=("$arg") done < "$PROC/$2/cmdline" return 0 } if [ ! -L "$0" ] ; then echo "$(basename "$0"): Only symlinked installation currently supported" 1>&2 exit 1 fi code_sh="$(readlink "$0")" podman_host_sh="$(dirname "$code_sh")/podman-host.sh" if [ ! -f "$podman_host_sh" ] ; then echo "$(basename "$0"): $0 should be a symlink to code.sh in the toolbox-vscode checkout" 1>&2 exit 1 fi ### Argument parsing args=("$@") arg_index=0 new_args=() next_arg() { arg_index=$((arg_index + 1)) } copy_arg() { new_args+=("${args[$arg_index]}") next_arg } copy_arg_with_parameter() { copy_arg if [ "$arg_index" -lt "${#args[@]}" ] ; then copy_arg fi } toolbox_reset_configuration=false # Because 'code' without any arguments opens the last workspace in the # history, ignoring history, we add --new-window if there are no # uris, no paths, and no --new-window/--reuse-window. add_new_window=true while [ "$arg_index" -lt "${#args[@]}" ] ; do arg="${args[$arg_index]}" case "$arg" in # Custom argument added by us --toolbox-reset-configuration) toolbox_reset_configuration=true next_arg ;; --toolbox-verbose) verbose=true next_arg ;; # Explicit paths passed by user --file-uri | \ --folder-uri) add_new_window=false copy_arg_with_parameter ;; --file-uri=* | \ --folder-uri=*) add_new_window=false copy_arg ;; # Other string arguments that expect another parameter - as of 2021-03-11 --builtin-extensions-dir | \ --category | \ --crash-reporter-id | \ --debugBrkPluginHost | \ --debugBrkSearch | \ --debugId | \ --debugPluginHost | \ --debugSearch | \ --disable-extension | \ --disableExtensions | \ --driver | \ --enable-proposed-api | \ --export-default-configuration | \ --extensionDevelopmentPath | \ --extensionHomePath | \ --extensionTestsPath | \ --extensions-download-dir | \ --force-device-scale-factor | \ --inspect | \ --inspect-brk | \ --inspect-brk-extensions | \ --inspect-brk-search | \ --inspect-extensions | \ --inspect-search | \ --install-builtin-extension | \ --install-extension | \ --install-source | \ --js-flags | \ --locale | \ --locate-extension | \ --log | \ --log-net-log | \ --logsPath | \ --max-memory | \ --prof-append-timers | \ --prof-startup-prefix | \ --proxy-bypass-list | \ --proxy-pac-url | \ --proxy-server | \ --remote | \ --sync | \ --trace-category-filter | \ --trace-options | \ --uninstall-extension | \ --user-data-dir | \ --waitMarkerFilePath) copy_arg_with_parameter ;; --help | \ --new-window | \ --reuse_window) add_new_window=false copy_arg ;; # Other arguments -*) copy_arg ;; # absolute_paths /*) add_new_window=false copy_arg ;; # Special case relative path . .) add_new_window=false new_args+=("$PWD") next_arg ;; # Other relative paths *) add_new_window=false new_args+=("$PWD/$arg") next_arg ;; esac done if $add_new_window ; then new_args+=("--new-window") fi flatpak="flatpak-spawn --host flatpak" # shellcheck disable=SC1091,SC2154 container_name="$(. /run/.containerenv && echo "$name")" container_name_encoded=$(echo -n "$container_name" | od -t x1 -A none -v | tr -d ' \n') ### Make sure that we have the Visual Studio Code Flatpak installed verbose "Checking if Visual Studio Code Flatpak is installed" if $flatpak list --app --columns=application | grep -q com.visualstudio.code ; then verbose "Visual Studio Code Flatpak is installed" else read -r -p "Visual Studio Code Flatpak is not installed. Install? (y/N) " install case "$install" in y|Y) if ! $flatpak remotes --columns=name | grep -q '^flathub$' ; then echo "No flathub remote to install from, see https://flathub.org/" 1>&2 exit 1 fi if ! $flatpak install flathub com.visualstudio.code; then echo "Installation failed" 1>&2 exit 1 fi ;; *) exit 1 ;; esac fi ### Make sure that we have a podman wrapper configured podman_wrapper="$HOME/.local/bin/podman-host" if [ "$(readlink "$podman_wrapper")" != "$podman_host_sh" ] ; then info "Making $podman_wrapper a link to podman-host.sh" ln -sf "$podman_host_sh" "$podman_wrapper" fi settings_json="$HOME/.var/app/com.visualstudio.code/config/Code/User/settings.json" # Here's where we edit a JSON file with grep and sed... # Quote regular expression characters - a " in the path would still mess us up wrapper_quoted="$(echo "$podman_wrapper" | sed -r 's@([$.*[\\^])@\\\1@g')" if [ ! -f "$settings_json" ] ; then info "Creating $settings_json" mkdir -p "$(dirname "$settings_json")" cat > "$settings_json" <'. For toolboxes, three # values have been seen, depending on podman version. # # /root # / # User's homedir # # The first two are OK - we just need to make them world-writable, # but for the third, we need ot create a symlink to avoid having # different toolboxes step on each other. homevar="$(flatpak-spawn --host podman inspect "$container_name" \ --format='{{ range .Config.Env }}{{ println . }}{{ end }}' \ | grep ^HOME=)" homevar="${homevar#HOME=}" case $homevar in /) vscode_server="/.vscode-server" ;; /root) vscode_server="/root/.vscode-server" ;; "$HOME") vscode_server="/.vscode-server" if [ -e "$HOME/.vscode-server" ] && [ ! -L "$HOME/.vscode-server" ] ; then echo "$HOME/.vscode-server is not a symlink - this is probably a left-over." 1>&2 echo "Please delete this directory and re-run." 1>&2 exit 1 fi if [ "$(readlink "$HOME/.vscode-server")" != $vscode_server ] ; then info "Creating symlink from $HOME/.vscode-server to /.vscode-server" ln -T -sf $vscode_server "$HOME/.vscode-server" fi ;; *) echo "\$HOME in container config is: '$homevar' - don't know how to handle this." 1>&2 exit 1 ;; esac ### Make sure that we have a writeable-by-user .vscode-server directory if [ ! -w $vscode_server ] ; then info "Creating $vscode_server" if [ $vscode_server = /root/.vscode-server ] ; then sudo chmod a+x /root fi sudo mkdir -p $vscode_server sudo chown $UID:"$(id -g)" $vscode_server fi ### Make sure we have a visual-studio code configuration for this container global_storage="$HOME/.var/app/com.visualstudio.code/config/Code/User/globalStorage" name_config="$global_storage/ms-vscode-remote.remote-containers/nameConfigs/$container_name.json" if $toolbox_reset_configuration || [ ! -f "$name_config" ] ; then # The reason for including $PATH in remoteEnv is so that any path modifications # set up in ~/.bashrc / ~/.bash_profile are present in the environment where # vscode runs commands, not just in the interactive terminal. As a special case # we remove any Python virtualenv path, in case this script is being invoked # within a virtual env. if [ -n "$VIRTUAL_ENV" ] ; then path_remove "$VIRTUAL_ENV/bin" fi info "Creating configuration for $container_name" mkdir -p "$(dirname "$name_config")" cat > "$name_config" < $settings <