Skip to content

Instantly share code, notes, and snippets.

@mateuszkwiatkowski
Last active May 11, 2024 10:58
Show Gist options
  • Save mateuszkwiatkowski/ce486d692b4cb18afc2c8c68dcfe8602 to your computer and use it in GitHub Desktop.
Save mateuszkwiatkowski/ce486d692b4cb18afc2c8c68dcfe8602 to your computer and use it in GitHub Desktop.
Configure FreeBSD to work with VScode's remote ssh extension

This procedure was tested on FreeBSD-CURRENT build from d8819d88af52.

  1. Enable linuxulator and install linux userland:

    # sysrc linux_enable="YES"
    # service linux start
    # pkg install linux_base-c7
    

    Test it:

    $ /compat/linux/usr/bin/uname -a
    Linux monster-1 4.4.0 FreeBSD 14.0-CURRENT #1 main-n254392-d8819d88af52: Wed Apr  6 22 x86_64 x86_64 x86_64 GNU/Linux
    

    For more details on linuxulator setup please refer to handbook.

  2. remote-ssh seem to ignore bash config files so we need to change $PATH in /etc/login.conf or send custom env with SetEnv directive in ssh_config. Choose which fits you better.

    Confiugure /etc/login.conf

    By default it's class default, we need to give priority to linux binaries: :path=/compat/linux/usr/sbin /compat/linux/usr/bin /sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin ~/bin:\ rebuild CAP database: cap_mkdb /etc/login.conf

    Or add this to your ssh config (~/.ssh/config)

    Host your-freebsd.box
      SetEnv PATH="/compat/linux/usr/sbin:/compat/linux/usr/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
    
  3. Set you normal PATH for your every day use in your shell's config file (.bashrc od .zshrc, etc)

  4. Connect to your FreeBSD box with VScode's remote SSH extension!

@oliver-giersch
Copy link

oliver-giersch commented Feb 5, 2024

Ok so it seems that since the latest VS Code update (1.86.0) the glibc version in the CentOS-7 based in linux_base-c7 is finally too old to run the vscode-server. However, I found out how to achieve the same thing using Linux emulation + debootstrap. Note, that I am using FreeBSD 14.0 and Debian bookworm emulation. From my understanding Linux emulation gained some additional capabilities in FreeBSD 14 and it may well be that you have to use an older Debian/Ubuntu version on FreeBSD 13.

Steps:

Install debootstrap package:

# pkg install debootstrap

Enable linux emulation and change the emulation path:

# service linux enable && service linux start
# sysctl compat.linux.emul_path=/compat/debian

The last settings can also be persisted, if desired:

# echo "compat.linux.emul_path=/compat/debian" >> /etc/sysctl.conf

Install Debian bookworm (or any other distro, adjust paths as necessary):

# debootstrap bookworm /compat/debian

At this point it should be possible to chroot into the emulated Linux distribution (just for testing):

# chroot /compat/debian /bin/bash
# uname -a
Linux testbsd 5.15.0 FreeBSD 14.0-RELEASE #0 releng/14.0-n265380-f9716eee8ab4: Fri No x86_64 GNU/Linux
# exit

But trying to directly execute bash (which we need for running vscode-server while also being able to access the remote host system) might * fail with an error like this:

$ /compat/debian/bin/bash
ELF interpreter /lib64/ld-linux-x86-64.so.2 not found, error 2
Abort trap

* I think I have tried with some Ubuntu version where this worked out of the box.

Fix this by repairing the incorrect symlink [1]:

# rm /compat/debian/lib64/ld-linux-x86-64.so.2
# ln -s ../lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /compat/debian/lib64/ld-linux-x86-64.so.2

Finally, vscode-server wants to use awk during installation but can't resolve the symlink in the emulated Debian for some reason, so I added another symlink:

# ln -s /compat/debian/usr/bin/mawk ../usr/local/bin/awk

At this point, the remaining steps are the same as before. I basically use the following .ssh/config entry to connect via VS Code:

Host remote_vscode
	Hostname	<host>
	User		<user>
	RequestTTY	force
	RemoteCommand	/compat/debian/bin/bash

@morganwdavis
Copy link

morganwdavis commented Feb 7, 2024

@oliver-giersch, thanks for this work to get VSCode 1.86 working in FreeBSD.

From my understanding Linux emulation gained some additional capabilities in FreeBSD 14 and it may well be that you have to use an older Debian/Ubuntu version on FreeBSD 13.

I can confirm that you do NOT need to upgrade to FreeBSD 14 to get your solution to work under FreeBSD 13. However I am using a different ~/ssh/config entry for it:

Host remote-vscode
	Hostname	<host>
	SetEnv BASH_ENV=".bash_debian"

I could not get the RemoteCommand method you're using to work on either FreeBSD 13.2 or 14 under Windows. To get the BASH_ENV method to work, add this to /etc/rc.conf:

sshd_flags="-o AcceptEnv=BASH_ENV"

... and to create a .bash_debian file in your $HOME that looks like this:

PATH="/compat/debian/usr/local/sbin:/compat/debian/usr/local/bin:/compat/debian/usr/sbin:/compat/debian/usr/bin:/compat/debian/sbin:/compat/debian/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

A convenient way to invoke this from Windows is to create a shortcut icon on the desktop and set the target property to something like this:

"C:\Program Files\Microsoft VS Code\Code.exe" --folder-uri vscode-remote://ssh-remote+remote-vscode/home/username/path/to/project

... where remote-vscode matches the Host identifier in the ssh config entry and everything else after that matches your home directory and path to your project.

Update: I've added these steps to a gist I've been maintaining here:
https://gist.github.com/morganwdavis/6c1880d49102c69c8d9b3ca63f41528b

@mightyjoe781
Copy link

@oliver-giersch On an M1 Mac, your steps work fine, and I am able to get VSCode working. However, there are two differences:

ln -s ../lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /compat/debian/lib64/ld-linux-x86-64.so.2

This doesn't work because I am running an arm64 image.

Secondly, in the bash terminal that opens in VSCode, I am encountering the following issue: I have no name!@freebsd. I have tried a few solutions, such as running chmod 644 /etc/passwd, but that doesn't seem to work. All commands are functioning properly; it's just that aesthetically, it doesn't look good.
Any suggestion of Ideas for fixing that ?

@oliver-giersch
Copy link

@oliver-giersch, thanks for this work to get VSCode 1.86 working in FreeBSD.

From my understanding Linux emulation gained some additional capabilities in FreeBSD 14 and it may well be that you have to use an older Debian/Ubuntu version on FreeBSD 13.

I can confirm that you do NOT need to upgrade to FreeBSD 14 to get your solution to work under FreeBSD 13. However I am using a different ~/ssh/config entry for it:

Host remote-vscode
	Hostname	<host>
	SetEnv BASH_ENV=".bash_debian"

I could not get the RemoteCommand method you're using to work on either FreeBSD 13.2 or 14 under Windows. To get the BASH_ENV method to work, add this to /etc/rc.conf:

sshd_flags="-o AcceptEnv=BASH_ENV"

... and to create a .bash_debian file in your $HOME that looks like this:

PATH="/compat/debian/usr/local/sbin:/compat/debian/usr/local/bin:/compat/debian/usr/sbin:/compat/debian/usr/bin:/compat/debian/sbin:/compat/debian/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

A convenient way to invoke this from Windows is to create a shortcut icon on the desktop and set the target property to something like this:

"C:\Program Files\Microsoft VS Code\Code.exe" --folder-uri vscode-remote://ssh-remote+remote-vscode/home/username/path/to/project

... where remote-vscode matches the Host identifier in the ssh config entry and everything else after that matches your home directory and path to your project.

Update: I've added these steps to a gist I've been maintaining here: https://gist.github.com/morganwdavis/6c1880d49102c69c8d9b3ca63f41528b

Thanks for the addition! I should have mentioned I've only tried these steps on Linux and (x86-64) macos. I also have this setting in my vscode config:

"remote.SSH.enableRemoteCommand": true,

Maybe you are missing that? Either that or the SSH client on Windows just works differently.

@oliver-giersch On an M1 Mac, your steps work fine, and I am able to get VSCode working. However, there are two differences:

ln -s ../lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /compat/debian/lib64/ld-linux-x86-64.so.2

This doesn't work because I am running an arm64 image.

Secondly, in the bash terminal that opens in VSCode, I am encountering the following issue: I have no name!@freebsd. I have tried a few solutions, such as running chmod 644 /etc/passwd, but that doesn't seem to work. All commands are functioning properly; it's just that aesthetically, it doesn't look good. Any suggestion of Ideas for fixing that ?

I assume you are running an arm64 FreeBSD image in an VM? That the problematic symlink would be different makes sense. I would look at the error message (if any) that is printed after attempting to run /compat/debian/bin/bash and try to figure out the appropriate fix from there. Although it does read as if you are not getting any error at all and in the arm64 version the symlinks are just not broken. Other than that I have no clue, I've never tried to run arm64 FreeBSD.

@morganwdavis
Copy link

morganwdavis commented Feb 8, 2024

Sorry to report that even though I had some success with what I described earlier, I tried to start with a fresh home directory that did not have an existing .vscode-server directory, and everything fails spectacularly. It seems to work fine if you had a previous .vscode-server hierarchy that was built with the older linux_base-c7.

Now after a couple of days of trying to get this to work with VS Code 1.86, if it cannot create and populate the .vscode-server directory if needed, or when Microsoft decides it's time to update the .vscode-server directory under 1.86+, that makes this a non-solution for me. I've reverted back to VS Code 1.85.2, disabled automatic updates, and am back on linux_base-c7. Bummer.

@oliver-giersch
Copy link

oliver-giersch commented Feb 8, 2024

Sorry to report that even though I had some success with what I described earlier, I tried to start with a fresh home directory that did not have an existing .vscode-server directory, and everything fails spectacularly. It seems to work fine if you had a previous .vscode-server hierarchy that was built with the older linux_base-c7.

Now after a couple of days of trying to get this to work with VS Code 1.86, if it cannot create and populate the .vscode-server directory if needed, or when Microsoft decides it's time to update the .vscode-server directory under 1.86+, that makes this a non-solution for me. I've reverted back to VS Code 1.85.2, disabled automatic updates, and am back on linux_base-c7. Bummer.

Maybe some additional context on the setup I'm using helps: I do mostly FreeBSD kernel development/research and use very few extensions in the remote VS Code (server) instance. For this I use a Linux host and a FreeBSD guest in a QEMU VM and I connect to it through the VS Code Remote SSH extension on the host. Once connected, I exclusively use the FreeBSD system and toolchains, not the emulated Debian toolchains! I presume this is especially important when using the built-in terminal, which should default to the Debian bash executable in this setup, which will probably reveal further broken symlinks etc sooner or later. My FreeBSD VM does also not have any users, I just do everything as root. I get that this is not practicable in most other setups. For writing this guide I used a fresh VM, with no previous code-server installations, so this should not be a factor.

Just now I tried following my own guide but using a non-root user account (in yet another freshly installed VM). For me this failed due to some permission errors when trying to accessing the user's home dir. I was able to fix this following the FS mount instructions in this chapter of the FreeBSD manual. Specifically, I appended this:

# Device        Mountpoint              FStype          Options                      Dump    Pass#
devfs           /compat/debian/dev      devfs           rw,late                      0       0
tmpfs           /compat/debian/dev/shm  tmpfs           rw,late,size=1g,mode=1777    0       0
fdescfs         /compat/debian/dev/fd   fdescfs         rw,late,linrdlnk             0       0
linprocfs       /compat/debian/proc     linprocfs       rw,late                      0       0
linsysfs        /compat/debian/sys      linsysfs        rw,late                      0       0
/tmp            /compat/debian/tmp      nullfs          rw,late                      0       0
/home           /compat/debian/home     nullfs          rw,late                      0       0

... to /etc/fstab (as root of course) in FreeBSD and mounted everything with:

# mount -al

With this, I am able to connect to VM again and everything seems to be working. I guess I should have kept this step in the guide, but while writing it I figured out that it was not necessary for my particular setup. It just didn't occur to me that this might cause issues when using non-root user accounts.

@morganwdavis
Copy link

@oliver-giersch I had some time to look over all this again and I found that these two fstab entries are the ones necessary:

# Linux Compat Device   Mountpoint              FStype          Options         Dump    Pass#
devfs                   /compat/debian/dev      devfs           rw,late         0       0
/home                   /compat/debian/home     nullfs          rw,late         0       0

I've updated my gist VS Code Remote SSH on FreeBSD to include all the above as a successful solution. I tested this on VS Code 1.86.2 with no errors or warnings, and that includes recreating the .vscode-server directory from scratch.

@willtanium
Copy link

@oliver-giersch thank you for this fix.
If I could add one more thing that I am doing on my computer, Instead of adding the entries into /etc/fstab another option would be to create a script that would make the /compat/debian get started up by debian_enable="YES"

#!/bin/sh
#
# PROVIDE: debian
# REQUIRE: archdep mountlate
# KEYWORD: nojail
#

. /etc/rc.subr

name="debian"
desc="Debian for FreeBSD Linux Binary Compatibility"
rcvar="debian_enable"
start_cmd="${name}_start"
stop_cmd=":"

unmounted()
{
  [ `stat -f "%d" "$1"` == `stat -f "%d" "$1/.."` -a `stat -f "%i" "$1"` != `stat -f "%i" "$1/.."` ]
}

debian_start()
{
  local _tmpdir
  load_kld -e 'linux(aout|elf)' linux
  case `sysctl -n hw.machine_arch` in
    amd64)
      load_kld -e 'linux64elf' linux64
      ;;
  esac
  if [ -x "/compat/debian/sbin/ldconfigDisabled" ]; then
    _tmpdir=`mktemp -d -t linux-ldconfig`
    /compat/debian/sbin/ldconfig -C ${_tmpdir}/ld.so.cache
    if ! cmp -s "${_tmpdir}/ld.so.cache" "/compat/debian/etc/ld.so.cache"; then
      cat "${_tmpdir}/ld.so.cache" > "/compat/debian/etc/ld.so.cache"
    fi
    rm -rf ${_tmpdir}
  fi
  load_kld pty
  if [ `sysctl -ni kern.elf64.fallback_brand` -eq "-1" ]; then
    sysctl kern.elf64.fallback_brand=3 > /dev/null
  fi
  if [ `sysctl -ni kern.elf32.fallback_brand` -eq "-1" ]; then
    sysctl kern.elf32.fallback_brand=3 > /dev/null
  fi
  sysctl compat.linux.emul_path="/compat/debian"
  unmounted "/compat/debian/dev" && (mount -o nocover -t devfs devfs "/compat/debian/dev" || exit 1)
  unmounted "/compat/debian/dev/fd" && (mount -o nocover,linrdlnk -t fdescfs fdescfs "/compat/debian/dev/fd" || exit 1)
  unmounted "/compat/debian/dev/shm" && (mount -o nocover,mode=1777 -t tmpfs tmpfs "/compat/debian/dev/shm" || exit 1)
  unmounted "/compat/debian/home" && (mount -t nullfs /home "/compat/debian/home" || exit 1)
  unmounted "/compat/debian/proc" && (mount -o nocover -t linprocfs linprocfs "/compat/debian/proc" || exit 1)
  unmounted "/compat/debian/sys" && (mount -o nocover -t linsysfs linsysfs "/compat/debian/sys" || exit 1)
  unmounted "/compat/debian/tmp" && (mount -t nullfs /tmp "/compat/debian/tmp" || exit 1)
  unmounted /dev/fd && (mount -o nocover -t fdescfs fdescfs /dev/fd || exit 1)
  unmounted /proc && (mount -o nocover -t procfs procfs /proc || exit 1)
  true
}

load_rc_config $name
run_rc_command "$1"

This can be written inside /usr/local/etc/rc.d/debian and made executable with chmod +x /usr/local/etc/rc.d/debian

@morganwdavis
Copy link

Instead of adding the entries into /etc/fstab another option would be to create a script ...

@willtanium This is great. Thank you for the inspiration. This also solves several other messy changes in earlier solutions (some that could have prevented it from booting if conditions caused startup failures).

I was inspired to work on this again today after the recent VS Code update for March (v. 1.88) which broke my remote SSH connections yet again. After incorporating your rc.d script, updating to Debian 12.5, and doing some additional testing to pare down the number of changes to the least I could find that still allowed remote SSH to work, I ended up making a repo to document it.

VS Code Server and Remote SSH on FreeBSD

This rolls up all the steps into a simpler set of tasks, with the fewest intrusive changes to key configuration files in FreeBSD. I hope others find this helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment