[Linuxulator] How to install Brave (Linux app) on FreeBSD 13.0+

Important notes:
1) This guide is for FreeBSD 13.0 and newer only!!
For FreeBSD 12, please see: https://forums.freebsd.org/threads/...-google-chrome-linux-binary-on-freebsd.77559/
2) This method should be valid for installing any other Chromium-based web browser (Google Chrome, Opera, Vivaldi, Edge, etc), as long as they (or their dependencies) are not Snaps/Flatpaks.
3) Most enhancements found in this tutorial were taken from/inspired by: https://github.com/mrclksr/linux-browser-installer
4) Given the many improvements made in Linuxulator and DRM drivers, we don't rely on games/linux-steam-utils workarounds anymore. Likewise, disabling DRI3 is no longer necessary.
5) Here we use an Ubuntu-based userspace, instead of default CentOS one.
6) Sound, webcam, microphone, hardware acceleration all properly work. Widevine support too (Netflix, Spotify, Amazon Prime Video, HBO Max, etc).
7) The /home and /tmp directories are shared by FreeBSD and Linux apps.
8) We now use /compat/ubuntu as Linux compat location, to avoid conflicting with CentOS-based ports. Still, be aware you can't run both Ubuntu and CentOS environments at the same time. When you want to switch back to running your CentOS-based applications, you'll need to disable Ubuntu rc.d script and enable the Linux one:
sysrc ubuntu_enable=NO && sysrc linux_enable=YES
Then reboot to make your changes take effect, or manually stop Ubuntu service and start the Linux one:
service ubuntu onestop && sysctl compat.linux.emul_path=/compat/linux && service linux start
9) I recommend (although is not a requirement as far as I know) to use tmpfs for /tmp. This is not the case when using ZFS default installation, but can be easily fixed. Assuming the ZFS pool is named "zroot":
# First reboot into single-user mode.
# Remount root filesystem as read/write:
mount -u /
# Destroy /tmp from zroot:
zfs destroy -f zroot/tmp
# Add this fstab entry:
printf "tmpfs\t/tmp\ttmpfs\trw,mode=1777\t0\t0\n" >> /etc/fstab
# Reboot back to normal multi-user mode.


Let's begin:
su
# Create Ubuntu rc.d script and make it executable:
touch /usr/local/etc/rc.d/ubuntu && chmod +x /usr/local/etc/rc.d/ubuntu

# Make it have this content:
Code:
#!/bin/sh
#
# PROVIDE: ubuntu
# REQUIRE: archdep mountlate
# KEYWORD: nojail
#
# This is a modified version of /etc/rc.d/linux
# Based on the script by mrclksr:
# https://github.com/mrclksr/linux-browser-installer/blob/main/rc.d/ubuntu.in
#
. /etc/rc.subr

name="ubuntu"
desc="Enable Ubuntu chroot, and Linux ABI"
rcvar="ubuntu_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/.."` ]
}

ubuntu_start()
{
    local _emul_path _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/ubuntu/sbin/ldconfigDisabled ]; then
        _tmpdir=`mktemp -d -t linux-ldconfig`
        /compat/ubuntu/sbin/ldconfig -C ${_tmpdir}/ld.so.cache
        if ! cmp -s ${_tmpdir}/ld.so.cache /compat/ubuntu/etc/ld.so.cache; then
            cat ${_tmpdir}/ld.so.cache > /compat/ubuntu/etc/ld.so.cache
        fi
        rm -rf ${_tmpdir}
    fi

    # Linux uses the pre-pts(4) tty naming scheme.
    load_kld pty

    # Handle unbranded ELF executables by defaulting to ELFOSABI_LINUX.
    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/ubuntu

    _emul_path="/compat/ubuntu"
    unmounted "${_emul_path}/dev" && (mount -o nocover -t devfs devfs "${_emul_path}/dev" || exit 1)
    unmounted "${_emul_path}/dev/fd" && (mount -o nocover,linrdlnk -t fdescfs fdescfs "${_emul_path}/dev/fd" || exit 1)
    unmounted "${_emul_path}/dev/shm" && (mount -o nocover,mode=1777 -t tmpfs tmpfs "${_emul_path}/dev/shm" || exit 1)
    unmounted "${_emul_path}/home" && (mount -t nullfs /home "${_emul_path}/home" || exit 1)
    unmounted "${_emul_path}/proc" && (mount -o nocover -t linprocfs linprocfs "${_emul_path}/proc" || exit 1)
    unmounted "${_emul_path}/sys" && (mount -o nocover -t linsysfs linsysfs "${_emul_path}/sys" || exit 1)
    unmounted "${_emul_path}/tmp" && (mount -t nullfs /tmp "${_emul_path}/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"

# Enable Linux compatibility layer (Ubuntu):
sysrc ubuntu_enable=YES

# Create necessary mount points for a working Linuxulator:
mkdir -p {/compat/ubuntu/dev/fd,/compat/ubuntu/dev/shm,/compat/ubuntu/home,/compat/ubuntu/tmp,/compat/ubuntu/proc,/compat/ubuntu/sys}

# Start Ubuntu service:
service ubuntu start

# Install needed packages:
pkg install debootstrap pulseaudio

# Install Ubuntu 20.04 into /compat/ubuntu:
debootstrap --arch=amd64 --no-check-gpg focal /compat/ubuntu

# Restart Ubuntu service to make sure everything is properly mounted:
service ubuntu restart

# Fix broken symlink:
cd /compat/ubuntu/lib64/ && rm ./ld-linux-x86-64.so.2 ; ln -s ../lib/x86_64-linux-gnu/ld-2.31.so ld-linux-x86-64.so.2

# Chroot into your Linux environment:
chroot /compat/ubuntu /bin/bash

# Set correct timezone inside your chroot:
printf "%b\n" "0.0 0 0.0\n0\nUTC" > /etc/adjtime
sudo dpkg-reconfigure tzdata # For some reason sudo is necessary here, otherwise it fails.

# Fix APT package manager:
printf "APT::Cache-Start 251658240;" > /etc/apt/apt.conf.d/00aptitude

# Enable more repositories:
printf "deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse" > /etc/apt/sources.list

# Install required programs:
apt update ; apt install -y apt-transport-https curl fonts-symbola gnupg pulseaudio

# Add Brave signing key:
curl -s https://brave-browser-apt-release.s3.brave.com/brave-core.asc | apt-key --keyring /etc/apt/trusted.gpg.d/brave-browser-release.gpg add -

# Add Brave repository:
echo "deb [arch=amd64] https://brave-browser-apt-release.s3.brave.com/ stable main" | tee /etc/apt/sources.list.d/brave-browser-release.list

# Install Brave web browser:
apt update ; apt install -y brave-browser

# Exit chroot:
exit

# Create custom Brave wrapper script (based on the one provided by the program itself):
touch /compat/ubuntu/opt/brave.com/brave/brave-wrapper && chmod +x /compat/ubuntu/opt/brave.com/brave/brave-wrapper

# Make it contain this:
Code:
#!/compat/ubuntu/bin/bash
#
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

HERE="/opt/brave.com/brave"

# We include some xdg utilities next to the binary, and we want to prefer them
# over the system versions when we know the system versions are very old. We
# detect whether the system xdg utilities are sufficiently new to be likely to
# work for us by looking for xdg-settings. If we find it, we leave $PATH alone,
# so that the system xdg utilities (including any distro patches) will be used.
if ! which xdg-settings &> /dev/null; then
  # Old xdg utilities. Prepend $HERE to $PATH to use ours instead.
  export PATH="$HERE:$PATH"
else
  # Use system xdg utilities. But first create mimeapps.list if it doesn't
  # exist; some systems have bugs in xdg-mime that make it fail without it.
  xdg_app_dir="${XDG_DATA_HOME:-$HOME/.local/share/applications}"
  mkdir -p "$xdg_app_dir"
  [ -f "$xdg_app_dir/mimeapps.list" ] || touch "$xdg_app_dir/mimeapps.list"
fi

# Always use our versions of ffmpeg libs.
# This also makes RPMs find the compatibly-named library symlinks.
if [[ -n "$LD_LIBRARY_PATH" ]]; then
  LD_LIBRARY_PATH="$HERE:$HERE/lib:$LD_LIBRARY_PATH"
else
  LD_LIBRARY_PATH="$HERE:$HERE/lib"
fi
export LD_LIBRARY_PATH

export CHROME_VERSION_EXTRA="stable"

# We don't want bug-buddy intercepting our crashes. http://crbug.com/24120
export GNOME_DISABLE_CRASH_DIALOG=SET_BY_GOOGLE_CHROME

# Sanitize std{in,out,err} because they'll be shared with untrusted child
# processes (http://crbug.com/376567).
exec < /dev/null
exec > >(exec cat)
exec 2> >(exec cat >&2)

"$HERE/brave" --no-sandbox --test-type --v=0 "$@" || true

# Now create linux-brave launcher script and make it executable:
touch /usr/local/bin/linux-brave && chmod +x /usr/local/bin/linux-brave

# Edit it so it has this content:
Code:
#!/bin/sh

# Ensure having working sound, independently of the DE/WM in use:
get_pa_sock_path()
{
        PA_SOCK_PATH=$(sockstat | awk -v me=$(whoami) -F'[ \t]+' '
                $1 == me && $2 == "pulseaudio" && $6 ~ /native/ {
                        print $6;
                        exit 0
                }'
        )
}

get_pa_sock_path
if [ ! -S "$PA_SOCK_PATH" ]; then
        while killall pulseaudio; do
                sleep 0.5
        done
        pulseaudio --start
        get_pa_sock_path
fi
[ -S "$PA_SOCK_PATH" ] && export PULSE_SERVER=unix:$PA_SOCK_PATH

# Let the wrapped binary know that it has been run through the wrapper:
export CHROME_WRAPPER="`readlink -f "$0"`"

# Launch Brave through our custom brave-wrapper script:
/compat/ubuntu/opt/brave.com/brave/brave-wrapper "$@"

# Exit su:
exit

# Finally, make a .desktop file to launch Brave as any other app:
touch ~/.local/share/applications/brave-browser.desktop && chmod +x ~/.local/share/applications/brave-browser.desktop

# Make it look like this:
Code:
[Desktop Entry]
Type=Application
Version=1.0
Encoding=UTF-8
Name=Brave Web Browser
Comment=Access the Internet
Icon=brave-browser
Exec=/usr/local/bin/linux-brave %U
Categories=Application;Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;
StartupNotify=true

*Notice: Accessing your google account won't work. It will display something like this:
"Couldn't sign you in This browser or app may not be secure. Try using a different browser. If you’re already using a supported browser, you can refresh your screen and try again to sign in."

To fix this, change your user agent to a non-chromium web browser, like Firefox for instance, and login to Gmail or any other google service (making sure to keep logged in). After this you can change your user agent back to default.
The good thing is you won't need to repeat this step later, not even when logging-in to a different account 👍.
For reference, this extension worked for me: User-Agent Switcher and Manager


That's all!

Sources:
https://wiki.freebsd.org/LinuxJails
https://wiki.freebsd.org/LinuxApps
https://wiki.freebsd.org/Linuxulator
https://help.ubuntu.com/lts/installation-guide/armhf/apds04.html
https://github.com/AppImage/AppImageKit/issues/98
https://pastebin.com/1W9Fh3e9
https://github.com/mrclksr/linux-browser-installer

EDIT 1:
-Changed compat location to /compat/ubuntu and added Ubuntu rc.d script to avoid conflicts with CentOS-based ports.
-Modified Brave wrapper script to resemble better the one provided by Brave itself. Also dropped --no-zygote flag.

EDIT 2:
-Fixed sound for PWAs too.

EDIT 3:
-Added "Restart Ubuntu service" immediately after running debootstrap, to make sure everything needed is properly mounted.
 
Last edited:
I think you should be able to drop --no-zygote. And definitely get rid of --test-type, this flag does nothing useful.

8) I've opted to use /compat/linux location instead of /compat/ubuntu, as CentOS and Ubuntu based environments can't both run at the same time anyway. Still, this is discouraged by some people, for it conflicts with CentOS-based ports. This means you need to remove any linux-c7* port you may have and avoid installing them afterwards too.
Well, it conflicts with my port and I don't like it.
 
I didn't check this line by line for the differences between it and your 12.2 guide. However, I used your 12.2 guide with 13.0-BETA1 and it worked without problems. (Also wanted to thank you for an older tutorial, the one about multibooting with ZFS only on the FreeBSD partition. I haven't yet tried that one, but I think, when 13 becomes full RELEASE, I'm going to do so.)

I am guessing one difference is the script one creates, and that this may eliminate the need for the one provided in the last tutorial by mrclksr
to ensure sound works.
Again, I think this is important stuff, because it enables people with just one computer to run Netflix, Amazon prime video, and the like.

Easy for me to say, without doing anything, but it would be nice if there were a way to do it without conflicting with other ports.
 
I think you should be able to drop --no-zygote. And definitely get rid of --test-type, this flag does nothing useful.


Well, it conflicts with my port and I don't like it.
Thanks for the feedback. I dropped ---no-zygote flag as suggested, but kept --test-type for it prevents the annoying warning message about running without sandbox from showing up each time you open the browser.
I also did some modifications to avoid conflicts with CentOS ports! :)
 
I didn't check this line by line for the differences between it and your 12.2 guide. However, I used your 12.2 guide with 13.0-BETA1 and it worked without problems. (Also wanted to thank you for an older tutorial, the one about multibooting with ZFS only on the FreeBSD partition. I haven't yet tried that one, but I think, when 13 becomes full RELEASE, I'm going to do so.)

I am guessing one difference is the script one creates, and that this may eliminate the need for the one provided in the last tutorial by mrclksr
to ensure sound works.
Again, I think this is important stuff, because it enables people with just one computer to run Netflix, Amazon prime video, and the like.

Easy for me to say, without doing anything, but it would be nice if there were a way to do it without conflicting with other ports.
You're welcome!
If you want, check this guide again as I changed a fair bit of it!
It doesn't conflict with CentOS ports anymore, and there are other changes too 👍
 
Wow, that's great. I'll give it a try. I did, when last trying it, have to use mrclksr's script to fix sound, and I'm wondering if your new one eliminates the need for that. I will give it a try and update this thread. (I don't know if it will be today).
 
Ok, I tried it on a fresh 13.0-BETA2 install. Had a few hiccups, which I assume were my mistakes, (first time debootstrap didn't fully install it, stuff like that), but I cleared out compat again. (First remembering to stop the rc.d ubuntu service, as otherwise, doing rm -rf cleans out my home directory) :)

Anyway, after a few tries, it works perfectly. I don't make the .desktop file because I start it with a keyboard shortcut from openbox, and with dmenu from dwm.
I don't want to get repetitive, but thanks again for your work with this, I do think that the more support desktop BSD gets, the more both hardware and software vendors will consider supporting it.
 
Ok, I tried it on a fresh 13.0-BETA2 install. Had a few hiccups, which I assume were my mistakes, (first time debootstrap didn't fully install it, stuff like that), but I cleared out compat again. (First remembering to stop the rc.d ubuntu service, as otherwise, doing rm -rf cleans out my home directory) :)

Anyway, after a few tries, it works perfectly. I don't make the .desktop file because I start it with a keyboard shortcut from openbox, and with dmenu from dwm.
I don't want to get repetitive, but thanks again for your work with this, I do think that the more support desktop BSD gets, the more both hardware and software vendors will consider supporting it.
Thanks for the feedback! Added another small correction 👍
 
Awesome guide! Thank you!

Just as a side note;
If you're running some Wayland compositor (like x11-wm/sway or x11-wm/hikari) instead of an X.org session, your $XDG_RUNTIME_DIR should be set to /tmp. In that case, you wouldn't be able to start PulseAudio, receiving the following error:
E: [(null)] core-util.c: XDG_RUNTIME_DIR (/tmp) is not owned by us (uid 1001), but by uid 0! (This could e.g. happen if you try to connect to a non-root PulseAudio as a root user, over the native protocol. Don't do that.)
As a solution, you can set the $XDG_RUNTIME_DIR local variable to an owned directory like:
XDG_RUNTIME_DIR=$HOME/.tmp linux-brave
 
Important notes:
1) This guide is for FreeBSD 13.0 and newer only!!
For FreeBSD 12, please see: https://forums.freebsd.org/threads/...-google-chrome-linux-binary-on-freebsd.77559/
2) This method should be valid for installing any other Chromium-based web browser (Google Chrome, Opera, Vivaldi, Edge, etc), as long as they (or their dependencies) are not Snaps/Flatpaks.
3) Most enhancements found in this tutorial were taken from/inspired by: https://github.com/mrclksr/linux-browser-installer
4) Given the many improvements made in Linuxulator and DRM drivers, we don't rely on games/linux-steam-utils workarounds anymore. Likewise, disabling DRI3 is no longer necessary.
5) Here we use an Ubuntu-based userspace, instead of default CentOS one.
6) Sound, webcam, microphone, hardware acceleration all properly work. Widevine support too (Netflix, Spotify, Amazon Prime Video, HBO Max, etc).
7) The /home and /tmp directories are shared by FreeBSD and Linux apps.
8) We now use /compat/ubuntu as Linux compat location, to avoid conflicting with CentOS-based ports. Still, be aware you can't run both Ubuntu and CentOS environments at the same time. When you want to switch back to running your CentOS-based applications, you'll need to disable Ubuntu rc.d script and enable the Linux one:
sysrc ubuntu_enable=NO && sysrc linux_enable=YES
Then reboot to make your changes take effect, or manually stop Ubuntu service and start the Linux one:
service ubuntu onestop && sysctl compat.linux.emul_path=/compat/linux && service linux start
9) I recommend (although is not a requirement as far as I know) to use tmpfs for /tmp. This is not the case when using ZFS default installation, but can be easily fixed. Assuming the ZFS pool is named "zroot":
# First reboot into single-user mode.
# Remount root filesystem as read/write:
mount -u /
# Destroy /tmp from zroot:
zfs destroy -f zroot/tmp
# Add this fstab entry:
printf "tmpfs\t/tmp\ttmpfs\trw,mode=1777\t0\t0\n" >> /etc/fstab
# Reboot back to normal multi-user mode.


Let's begin:
su
# Create Ubuntu rc.d script and make it executable:
touch /usr/local/etc/rc.d/ubuntu && chmod +x /usr/local/etc/rc.d/ubuntu

# Make it have this content:
Code:
#!/bin/sh
#
# PROVIDE: ubuntu
# REQUIRE: archdep mountlate
# KEYWORD: nojail
#
# This is a modified version of /etc/rc.d/linux
# Based on the script by mrclksr:
# https://github.com/mrclksr/linux-browser-installer/blob/main/rc.d/ubuntu.in
#
. /etc/rc.subr

name="ubuntu"
desc="Enable Ubuntu chroot, and Linux ABI"
rcvar="ubuntu_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/.."` ]
}

ubuntu_start()
{
    local _emul_path _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/ubuntu/sbin/ldconfigDisabled ]; then
        _tmpdir=`mktemp -d -t linux-ldconfig`
        /compat/ubuntu/sbin/ldconfig -C ${_tmpdir}/ld.so.cache
        if ! cmp -s ${_tmpdir}/ld.so.cache /compat/ubuntu/etc/ld.so.cache; then
            cat ${_tmpdir}/ld.so.cache > /compat/ubuntu/etc/ld.so.cache
        fi
        rm -rf ${_tmpdir}
    fi

    # Linux uses the pre-pts(4) tty naming scheme.
    load_kld pty

    # Handle unbranded ELF executables by defaulting to ELFOSABI_LINUX.
    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/ubuntu

    _emul_path="/compat/ubuntu"
    unmounted "${_emul_path}/dev" && (mount -o nocover -t devfs devfs "${_emul_path}/dev" || exit 1)
    unmounted "${_emul_path}/dev/fd" && (mount -o nocover,linrdlnk -t fdescfs fdescfs "${_emul_path}/dev/fd" || exit 1)
    unmounted "${_emul_path}/dev/shm" && (mount -o nocover,mode=1777 -t tmpfs tmpfs "${_emul_path}/dev/shm" || exit 1)
    unmounted "${_emul_path}/home" && (mount -t nullfs /home "${_emul_path}/home" || exit 1)
    unmounted "${_emul_path}/proc" && (mount -o nocover -t linprocfs linprocfs "${_emul_path}/proc" || exit 1)
    unmounted "${_emul_path}/sys" && (mount -o nocover -t linsysfs linsysfs "${_emul_path}/sys" || exit 1)
    unmounted "${_emul_path}/tmp" && (mount -t nullfs /tmp "${_emul_path}/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"

# Enable Linux compatibility layer (Ubuntu):
sysrc ubuntu_enable=YES

# Create necessary mount points for a working Linuxulator:
mkdir -p {/compat/ubuntu/dev/fd,/compat/ubuntu/dev/shm,/compat/ubuntu/home,/compat/ubuntu/tmp,/compat/ubuntu/proc,/compat/ubuntu/sys}

# Start Ubuntu service:
service ubuntu start

# Install needed packages:
pkg install debootstrap pulseaudio

# Install Ubuntu 20.04 into /compat/ubuntu:
debootstrap --arch=amd64 --no-check-gpg focal /compat/ubuntu

# Restart Ubuntu service to make sure everything is properly mounted:
service ubuntu restart

# Fix broken symlink:
cd /compat/ubuntu/lib64/ && rm ./ld-linux-x86-64.so.2 ; ln -s ../lib/x86_64-linux-gnu/ld-2.31.so ld-linux-x86-64.so.2

# Chroot into your Linux environment:
chroot /compat/ubuntu /bin/bash

# Set correct timezone inside your chroot:
printf "%b\n" "0.0 0 0.0\n0\nUTC" > /etc/adjtime
sudo dpkg-reconfigure tzdata # For some reason sudo is necessary here, otherwise it fails.

# Fix APT package manager:
printf "APT::Cache-Start 251658240;" > /etc/apt/apt.conf.d/00aptitude

# Enable more repositories:
printf "deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse" > /etc/apt/sources.list

# Install required programs:
apt update ; apt install -y apt-transport-https curl fonts-symbola gnupg pulseaudio

# Add Brave signing key:
curl -s https://brave-browser-apt-release.s3.brave.com/brave-core.asc | apt-key --keyring /etc/apt/trusted.gpg.d/brave-browser-release.gpg add -

# Add Brave repository:
echo "deb [arch=amd64] https://brave-browser-apt-release.s3.brave.com/ stable main" | tee /etc/apt/sources.list.d/brave-browser-release.list

# Install Brave web browser:
apt update ; apt install -y brave-browser

# Exit chroot:
exit

# Create custom Brave wrapper script (based on the one provided by the program itself):
touch /compat/ubuntu/opt/brave.com/brave/brave-wrapper && chmod +x /compat/ubuntu/opt/brave.com/brave/brave-wrapper

# Make it contain this:
Code:
#!/compat/ubuntu/bin/bash
#
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

HERE="/opt/brave.com/brave"

# We include some xdg utilities next to the binary, and we want to prefer them
# over the system versions when we know the system versions are very old. We
# detect whether the system xdg utilities are sufficiently new to be likely to
# work for us by looking for xdg-settings. If we find it, we leave $PATH alone,
# so that the system xdg utilities (including any distro patches) will be used.
if ! which xdg-settings &> /dev/null; then
  # Old xdg utilities. Prepend $HERE to $PATH to use ours instead.
  export PATH="$HERE:$PATH"
else
  # Use system xdg utilities. But first create mimeapps.list if it doesn't
  # exist; some systems have bugs in xdg-mime that make it fail without it.
  xdg_app_dir="${XDG_DATA_HOME:-$HOME/.local/share/applications}"
  mkdir -p "$xdg_app_dir"
  [ -f "$xdg_app_dir/mimeapps.list" ] || touch "$xdg_app_dir/mimeapps.list"
fi

# Always use our versions of ffmpeg libs.
# This also makes RPMs find the compatibly-named library symlinks.
if [[ -n "$LD_LIBRARY_PATH" ]]; then
  LD_LIBRARY_PATH="$HERE:$HERE/lib:$LD_LIBRARY_PATH"
else
  LD_LIBRARY_PATH="$HERE:$HERE/lib"
fi
export LD_LIBRARY_PATH

export CHROME_VERSION_EXTRA="stable"

# We don't want bug-buddy intercepting our crashes. http://crbug.com/24120
export GNOME_DISABLE_CRASH_DIALOG=SET_BY_GOOGLE_CHROME

# Sanitize std{in,out,err} because they'll be shared with untrusted child
# processes (http://crbug.com/376567).
exec < /dev/null
exec > >(exec cat)
exec 2> >(exec cat >&2)

"$HERE/brave" --no-sandbox --test-type --v=0 "$@" || true

# Now create linux-brave launcher script and make it executable:
touch /usr/local/bin/linux-brave && chmod +x /usr/local/bin/linux-brave

# Edit it so it has this content:
Code:
#!/bin/sh

# Ensure having working sound, independently of the DE/WM in use:
get_pa_sock_path()
{
        PA_SOCK_PATH=$(sockstat | awk -v me=$(whoami) -F'[ \t]+' '
                $1 == me && $2 == "pulseaudio" && $6 ~ /native/ {
                        print $6;
                        exit 0
                }'
        )
}

get_pa_sock_path
if [ ! -S "$PA_SOCK_PATH" ]; then
        while killall pulseaudio; do
                sleep 0.5
        done
        pulseaudio --start
        get_pa_sock_path
fi
[ -S "$PA_SOCK_PATH" ] && export PULSE_SERVER=unix:$PA_SOCK_PATH

# Let the wrapped binary know that it has been run through the wrapper:
export CHROME_WRAPPER="`readlink -f "$0"`"

# Launch Brave through our custom brave-wrapper script:
/compat/ubuntu/opt/brave.com/brave/brave-wrapper "$@"

# Exit su:
exit

# Finally, make a .desktop file to launch Brave as any other app:
touch ~/.local/share/applications/brave-browser.desktop && chmod +x ~/.local/share/applications/brave-browser.desktop

# Make it look like this:
Code:
[Desktop Entry]
Type=Application
Version=1.0
Encoding=UTF-8
Name=Brave Web Browser
Comment=Access the Internet
Icon=brave-browser
Exec=/usr/local/bin/linux-brave %U
Categories=Application;Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;
StartupNotify=true

*Notice: Accessing your google account won't work. It will display something like this:
"Couldn't sign you in This browser or app may not be secure. Try using a different browser. If you’re already using a supported browser, you can refresh your screen and try again to sign in."

To fix this, change your user agent to a non-chromium web browser, like Firefox for instance, and login to Gmail or any other google service (making sure to keep logged in). After this you can change your user agent back to default.
The good thing is you won't need to repeat this step later, not even when logging-in to a different account 👍.
For reference, this extension worked for me: User-Agent Switcher and Manager


That's all!

Sources:
https://wiki.freebsd.org/LinuxJails
https://wiki.freebsd.org/LinuxApps
https://wiki.freebsd.org/Linuxulator
https://help.ubuntu.com/lts/installation-guide/armhf/apds04.html
https://github.com/AppImage/AppImageKit/issues/98
https://pastebin.com/1W9Fh3e9
https://github.com/mrclksr/linux-browser-installer

EDIT 1:
-Changed compat location to /compat/ubuntu and added Ubuntu rc.d script to avoid conflicts with CentOS-based ports.
-Modified Brave wrapper script to resemble better the one provided by Brave itself. Also dropped --no-zygote flag.

EDIT 2:
-Fixed sound for PWAs too.

EDIT 3:
-Added "Restart Ubuntu service" immediately after running debootstrap, to make sure everything needed is properly mounted.
Hi all! Thanks, good job, but it doesn't work for me with focal. When debootstrap is working has a systemd error while unpacking base files. I've used bionic and Brave runs great! Nice job!
Best regards.
 
This guide is amazing. I used this to setup Chrome, because Brave is not allowing to use non-shop extensions (netflix 1080p force).
Works perfect.
 
Thanks for your efforts on this. Everything works fine, netflix etc fantastic. However I have one issue. I have a titan key setup with my google account so when I try and login to google it tries to probe udev, which causes a segfault:

Code:
ERROR:bus.cc(393)] Failed to connect to the bus: Did not receive a reply.
...
ERROR:udev_watcher.cc(51)] Failed to initialize a udev monitor.

If I chroot into /compat/ubuntu I can't start udev or dbus, should I be able to? Or am I completely misunderstanding something?
 
Code:
[65780:100636:0423/161030.566908:ERROR:file_path_watcher_linux.cc(316)] inotify_init() failed: Function not implemented (38)
[65780:65780:0423/161030.569228:ERROR:browser_main_loop.cc(536)] Failed to open an X11 connection.
[65780:65780:0423/161030.569330:ERROR:browser_main_loop.cc(1386)] Unable to open X display.
Tried as normal user and root.
 
Thanks for the guide,

when i try to update the widevine component i get the following error:

[3535:102516:0617/151107.300660:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 3653: No such file or directory (2)
[3535:102533:0617/151107.350891:ERROR:component_installer.cc(141)] Move failed.: Socket operation on non-socket (88)

the following shows up as well:

linux: jid 0 pid 6700 (ThreadPoolForeg): unsupported getsockopt level 6 optname 11
linux: jid 0 pid 6700 (ThreadPoolForeg): unsupported getsockopt level 6 optname 11
linux: jid 0 pid 6700 (ThreadPoolForeg): unsupported getsockopt level 6 optname 11
linux: jid 0 pid 6700 (ThreadPoolForeg): unsupported getsockopt level 6 optname 11
linux: jid 0 pid 6700 (ThreadPoolForeg): unsupported getsockopt level 6 optname 11
linux: jid 0 pid 6700 (ThreadPoolForeg): unsupported getsockopt level 6 optname 11
linux: jid 0 pid 6700 (ThreadPoolForeg): unsupported getsockopt level 6 optname 11
linux: jid 0 pid 6700 (ThreadPoolForeg): unsupported getsockopt level 6 optname 11
linux: jid 0 pid 6700 (ThreadPoolForeg): unsupported getsockopt level 6 optname 11

any ideas ?
 
Last edited:
Thank you for this guide!

I used it successfully to run Google Chrome (Widewine did not work in Brave, but does in Chrome) but there was no sound, neither in Brave nor in Chrome.

Reason was that Chrome (resp. Brave) took too long to start-up, which made Pulseaudio quit because of being idle (this is normal behavior). Solution is to change the start-up of Pulseaudio in the linux-brave script, line 19, as follows
Code:
pulseaudio --start --exit-idle-time=3600

This gives the application 3600 seconds to start-up, which should be enough. If you want to disable this "feature" (quitting because of being idle) of Pulseaudio, use "-1". See man pulseaudio
 
I try
Code:
debootstrap --arch=amd64 --no-check-gpg focal /compat/ubuntu
but error:
Code:
I: Retrieving Packages
I: Validating Packages
ELF interpreter /lib64/ld-linux-x86-64.so.2 not found, error 2
W: Retrying failed download of http://archive.ubuntu.com/ubuntu/dists/focal/main/binary-amd64/Packages.xz
 
Tried this, everything went well until I launch Brave, and all I get is a blank window. I've done this twice, first in a VM that uses Fluxbox, and secondly on my laptop that uses XFCE. Any ideas?

Edit: Here's the output of the terminal when I launch from there, if this gives any ideas. Lots of error messages, but I'm not sure where to start:


Code:
[64164:101078:0922/105525.006864:ERROR:file_path_watcher_linux.cc(326)] inotify_init() failed: Function not implemented (38)
[64164:101084:0922/105525.087607:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101077:0922/105525.089461:ERROR:address_tracker_linux.cc(195)] Could not create NETLINK socket: Address family not supported by protocol (97)
[64164:101083:0922/105525.089598:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101083:0922/105525.089686:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101078:0922/105525.090453:ERROR:udev_watcher.cc(52)] Failed to initialize a udev monitor.
[64164:101089:0922/105525.092670:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 66030: No such file or directory (2)
[64164:101083:0922/105525.100312:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101083:0922/105525.100575:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101089:0922/105525.101629:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 66233: No such file or directory (2)
[66098:101099:0922/105525.121168:ERROR:address_tracker_linux.cc(195)] Could not create NETLINK socket: Address family not supported by protocol (97)
[66098:101101:0922/105525.121167:ERROR:file_path_watcher_linux.cc(326)] inotify_init() failed: Function not implemented (38)
[64164:101089:0922/105525.141714:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 66275: No such file or directory (2)
[64164:101089:0922/105525.144637:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 66716: No such file or directory (2)
[64164:101083:0922/105525.195923:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101083:0922/105525.196108:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101089:0922/105525.214467:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 67141: No such file or directory (2)
[64164:101140:0922/105525.291547:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101140:0922/105525.291666:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101140:0922/105525.291816:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101140:0922/105525.291926:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101140:0922/105525.292040:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:64164:0922/105525.437513:ERROR:CONSOLE(1)] "[Shields]: Can't request shields panel data. Error: No tab url specified", source: chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/out/brave_extension_background.bundle.js (1)
^Cjacob@maccool:~/.local/share/applications $
 
Tried this, everything went well until I launch Brave, and all I get is a blank window. I've done this twice, first in a VM that uses Fluxbox, and secondly on my laptop that uses XFCE. Any ideas?

Edit: Here's the output of the terminal when I launch from there, if this gives any ideas. Lots of error messages, but I'm not sure where to start:


Code:
[64164:101078:0922/105525.006864:ERROR:file_path_watcher_linux.cc(326)] inotify_init() failed: Function not implemented (38)
[64164:101084:0922/105525.087607:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101077:0922/105525.089461:ERROR:address_tracker_linux.cc(195)] Could not create NETLINK socket: Address family not supported by protocol (97)
[64164:101083:0922/105525.089598:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101083:0922/105525.089686:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101078:0922/105525.090453:ERROR:udev_watcher.cc(52)] Failed to initialize a udev monitor.
[64164:101089:0922/105525.092670:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 66030: No such file or directory (2)
[64164:101083:0922/105525.100312:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101083:0922/105525.100575:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101089:0922/105525.101629:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 66233: No such file or directory (2)
[66098:101099:0922/105525.121168:ERROR:address_tracker_linux.cc(195)] Could not create NETLINK socket: Address family not supported by protocol (97)
[66098:101101:0922/105525.121167:ERROR:file_path_watcher_linux.cc(326)] inotify_init() failed: Function not implemented (38)
[64164:101089:0922/105525.141714:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 66275: No such file or directory (2)
[64164:101089:0922/105525.144637:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 66716: No such file or directory (2)
[64164:101083:0922/105525.195923:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101083:0922/105525.196108:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101089:0922/105525.214467:ERROR:zygote_host_impl_linux.cc(264)] Failed to adjust OOM score of renderer with pid 67141: No such file or directory (2)
[64164:101140:0922/105525.291547:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101140:0922/105525.291666:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101140:0922/105525.291816:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101140:0922/105525.291926:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:101140:0922/105525.292040:ERROR:bus.cc(392)] Failed to connect to the bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[64164:64164:0922/105525.437513:ERROR:CONSOLE(1)] "[Shields]: Can't request shields panel data. Error: No tab url specified", source: chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/out/brave_extension_background.bundle.js (1)
^Cjacob@maccool:~/.local/share/applications $
It started happening the same to me since Chromium v92.
I ended up solving it by adding "--in-process-gpu" flag to the Brave wrapper script, so the last line looks like this:
Code:
"$HERE/brave" --no-sandbox --in-process-gpu --test-type --v=0 "$@" || true
It may also be necessary to re-add "--no-zygote" as well to silence those loads of zygote-related output messages...
 
It started happening the same to me since Chromium v92.
I ended up solving it by adding "--in-process-gpu" flag to the Brave wrapper script, so the last line looks like this:
Code:
"$HERE/brave" --no-sandbox --in-process-gpu --test-type --v=0 "$@" || true
It may also be necessary to re-add "--no-zygote" as well to silence those loads of zygote-related output messages...
That worked great. I'm typing this reply from the Brave browser on my FreeBSD VM. Thanks for your hard work!
 
I'm unable to connect to my Google account from chrome, thus no add-ons.
How could I bypass the user agent to be able to connect to my Google account?
 
I'm unable to connect to my Google account from chrome, thus no add-ons.
How could I bypass the user agent to be able to connect to my Google account?
You can launch Brave one-off like this:
/compat/ubuntu/opt/brave.com/brave/brave-wrapper --user-agent="Mozilla/5.0 (X11; FreeBSD amd64; Linux x86_64)"
to be able to login to your Google account. If you don't log out you won't need to keep doing this.
 
so i got signed into google etc. but i cant use/download any extensions or themes when i try it says

An error has occurred​

Could not install package: 'FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY'

any ideas for a work around i tried chmod 777 my tmpfs dir etc no dice. any help is appreciated as is you time thanks guys.
 
so i got signed into google etc. but i cant use/download any extensions or themes when i try it says

An error has occurred​

Could not install package: 'FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY'

any ideas for a work around i tried chmod 777 my tmpfs dir etc no dice. any help is appreciated as is you time thanks guys.

i get this error as well:
[45357:397597:1021/092223.665754:ERROR:component_installer.cc(143)] Move failed.: Socket operation on non-socket (88)
[45357:397600:1021/092225.049482:ERROR:component_installer.cc(143)] Move failed.: Socket operation on non-socket (88)
[45357:397600:1021/092226.580514:ERROR:component_installer.cc(143)] Move failed.: Socket operation on non-socket (88)
[45357:397600:1021/092228.592985:ERROR:component_installer.cc(143)] Move failed.: Socket operation on non-socket (88)

are you seeing the same error ?
 
Back
Top