Modifying the Official Google Chrome RPM to Run on Amazon Linux and CentOS 6

By Evan Sangaline | September 6, 2017
Follow @sangaline

Installing Google Chrome On CentOS, Amazon Linux, or Red Hat Enterprise Linux

CentOS, Amazon Linux AMI, and Red Hat Enterprise Linux are three closely related GNU/Linux distributions which are all popular choices for server installations. They offer excellent performance and stability, but package availability can often be lacking. The Extra Packages for Enterprise Linux (EPEL), a community maintained repository of additional packages, significantly improves the situation, but doesn’t include Google Chrome/Chromium or a lot of other software that you would expect on more desktop-oriented distributions. This can be a bit frustrating when you’re interested in running headless Chrome instances in the cloud for testing or web scraping.

Google does maintain a repository with a Chrome RPM package, but it only works with RHEL/CentOS 7.X versions. Amazon Linux is currently only available as a 6.X version, and the 6.X versions of RHEL/CentOS remain fairly common (in part due to the transition from upstart to systemd in 7.X). If you’re using one of these versions then you’re kind of on your own… and the installation process is quite complicated due to the lack of GTK 3 on RHEL 6.X.

We use headless Chrome instances for web scraping on Amazon Linux here at Intoli and so we maintain our own package which bundles the otherwise missing dependencies. In this article, we’ll show you both how to get up and running as quickly as possible with our modified Chrome RPM and explain the process of modifying RPMs to include additional libraries. If you’ve been searching around for how to run Chrome on Amazon Linux then hopefully you’ll find this useful!

The Easy Way

First, you’ll need to determine whether you’re using a 6.X or 7.X version. If you’re using Amazon Linux AMI then you’re on a 6.X version, on RHEL or CentOS you can run

cat /etc/redhat-release

and check the output for the version number. For example, if you’re using RHEL 7.0 then you would expect to see something like:

Red Hat Enterprise Linux Server release 7.0 (Maipo)

Once you’ve determined the version number, you can proceed to one of the next two sections for instructions on how to install Google Chrome.

RHEL/CentOS 7.X

You can use the official Google repository if you’re on 7.X. To add it to your system, simply create a file called /etc/yum.repos.d/google-chrome.repo with the following contents.

[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub

Then you just need to run

sudo yum install google-chrome-stable

to install the Chrome package. This will add the google-chrome-stable script in /usr/bin/.

Amazon Linux/RHEL/CentOS 6.X

You can install our modified Google Chrome RPM by simply running the following.

sudo yum --nogpgcheck localinstall https://intoli.com/blog/installing-google-chrome-on-centos/google-chrome-stable-60.0.3112.113-1.x86_64.rpm

This package will provide the missing dependencies in addition to the google-chrome-stable script.

The Hard Way

If you just want to install Google Chrome on a RHEL variant then I highly recommend using one of the easier methods in the previous section. That said, I’ll walk through the process of creating the modified Chrome RPM because some of our more technical readers might find it interesting. The basic idea is to use the 7.X Google Chrome RPM as a starting point and to kind of hack in the dependencies and extras that we need in order for it to work on 6.X versions.

Getting the RPM

First, we’ll grab the official RPM file for the 7.X variants. It won’t work on 6.X versions, but it gives us a good starting point for making changes. We can download the file by creating the same /etc/yum.repos.d/google-chrome.repo file that we would use with the 7.X versions

[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub

and then running:

# install the yum utilities
sudo yum install yum-utils

# download the google-chrome-stable package
yumdownloader google-chrome-stable

The second command should download the RPM file to the current working directory. The current version has a file name of google-chrome-stable-60.0.3112.113-1.x86_64.rpm, but this will change as new versions get released.

Stripping Out the Missing Dependencies

Let’s start by installing as many of the dependencies of google-chrome-stable as we can. This can be done by using repoquery, another tool provided by yum-utils, to resolve the dependency packages and then passing these as arguments to yum using xargs.

repoquery --requires --resolve google-chrome-stable | xargs sudo yum -y install

After these finish installing, we can try to install the downloaded RPM.

sudo rpm -i google-chrome-stable-60.0.3112.113-1.x86_64.rpm

This will output a list of dependencies that aren’t available on the system.

error: Failed dependencies:
	xdg-utils is needed by google-chrome-stable-60.0.3112.113-1.x86_64
	libatk-1.0.so.0()(64bit) is needed by google-chrome-stable-60.0.3112.113-1.x86_64
	libgconf-2.so.4()(64bit) is needed by google-chrome-stable-60.0.3112.113-1.x86_64
	libgdk-3.so.0()(64bit) is needed by google-chrome-stable-60.0.3112.113-1.x86_64
	libgdk_pixbuf-2.0.so.0()(64bit) is needed by google-chrome-stable-60.0.3112.113-1.x86_64
	libgtk-3.so.0()(64bit) is needed by google-chrome-stable-60.0.3112.113-1.x86_64
	libXss.so.1()(64bit) is needed by google-chrome-stable-60.0.3112.113-1.x86_64

Our first modification of the RPM is going to be to remove these dependencies. This will allow the package to install without any complaints, but there will of course be unhandled dependencies. We’ll revisit these later once we have the broken package installed.

We’ll use a tool called rpmrebuild to modify the RPM file. It can be installed by running

# install rpmrebuild
sudo yum install rpmrebuild

and then launched with

sudo rpmrebuild -e -d output -p google-chrome-stable-60.0.3112.113-1.x86_64.rpm

which will open the RPM spec file in vi. Now we’ll go through and delete each of the seven requirements that can’t be satisfied. To remove the libgconf requirement for example, we need to remove

Requires:      libgconf-2.so.4()(64bit)

from the spec file.

We’ll also want to remove any code that uses xdg-icon-resource. This is normally provided by the xdg-utils requirement that we removed, but it’s only used in the (un)installation scripts. If we don’t remove this code now then the package will fail to uninstall cleanly. You can also force the uninstallation with

sudo yum --setopt=tsflags=noscripts remove google-chrome-stable

if you have to, but it’s easier to just remove the code from the start.

The installation block to remove looks like

# Add icons to the system icons
XDG_ICON_RESOURCE="`which xdg-icon-resource 2> /dev/null || true`"
if [ ! -x "$XDG_ICON_RESOURCE" ]; then
  echo "Error: Could not find xdg-icon-resource" >&2
  exit 1
fi
for icon in "/opt/google/chrome/product_logo_"*.png; do
  size="${icon##*/product_logo_}"
  "$XDG_ICON_RESOURCE" install --size "${size%%.png}" "$icon" "google-chrome"
done

and then the uninstallation block looks like:

# Remove icons from the system icons
XDG_ICON_RESOURCE="`which xdg-icon-resource 2> /dev/null || true`"
if [ ! -x "$XDG_ICON_RESOURCE" ]; then
  echo "Error: Could not find xdg-icon-resource" >&2
  exit 1
fi
for icon in "/opt/google/chrome/product_logo_"*.png; do
  size="${icon##*/product_logo_}"
  "$XDG_ICON_RESOURCE" uninstall --size "${size%%.png}" "google-chrome"
done

Once all of the requirements and xdg-icon-resource lines have been removed, we’ll save the file and close the editor to trigger the creation of a new RPM file in the output/ directory. This can now be installed with

sudo rpm -i output/x86_64/google-chrome-stable-60.0.3112.113-1.x86_64.rpm

but, unsurprisingly, running google-chrome-stable will result in an error:

google-chrome-stable: error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directory

We’ll need to provide the dependencies that we removed in order for Chrome to actually work.

Providing the Missing Dependencies

We can use ldd to find the missing shared libraries that google-chrome-stable links to.

ldd /opt/google/chrome/chrome | grep "not found"

This command will output the list of missing libraries, all of which are ones that we removed as requirements.

	libgconf-2.so.4 => not found
	libXss.so.1 => not found
	libatk-1.0.so.0 => not found
	libgtk-3.so.0 => not found
	libgdk-3.so.0 => not found
	libgdk_pixbuf-2.0.so.0 => not found

We could try to build these libraries ourselves, or even to create new packages for them, but this would end up being an immense amount of work because each of these will have their own sets of dependencies. A much easier solution is to grab these files from another GNU/Linux system where the same version of Google Chrome is installed. This is a bit of a hack, but it’s the easiest way to get things working quickly.

There are 25 libraries amounting to 14MB that ultimately need to be copied over when you take into account the secondary dependencies of the six direct dependencies. To speed things up a little bit, I wrote a simple script that will iteratively copy over local files to /tmp/lib/ on a remote machine until all of the dependencies are satisfied.

#! /bin/bash
set -e

SERVER=sangaline@intoli.com

# move to the directory where the libraries will be
cd /usr/lib/

# create the remote directory
ssh $SERVER "mkdir -p /tmp/lib/"

while true
do
    FINISHED=true
    # loop through each of the missing libraries
    while read -r LINE
    do
        if [[ $LINE == *"/"* ]]; then
            # extract the filename when a path is present (e.g. /lib64/)
            FILE=`echo $LINE | sed 's>.*/\([^/:]*\):.*>\1>'`
        else
            # extract the filename for missing libraries without a path
            FILE=`echo $LINE | awk '{print $1;}'`
        fi
        # copy the local version to the server
        scp $FILE $SERVER:/tmp/lib/
        FINISHED=false
    done < <(ssh $SERVER ' \
        export LD_LIBRARY_PATH=/tmp/lib/:$LD_LIBRARY_PATH && \
        ldd /opt/google/chrome/chrome 2>&1 | \
        grep -e "no version information" -e "not found"  \
    ')

    # break once no new files have been copied in a loop
    if [ "$FINISHED" = true ]; then
        break
    fi
done

An important line to notice here is the fact that $LD_LIBRARY_PATH is being set to include the /tmp/lib/ directory before we’re checking the linking with ldd. We’ll need to move these libraries to a more permanent home and to make sure that they’re somewhere where the linker can find them. One obvious choice would be /usr/lib64/, but we actually have some library files here that are in conflict with existing system libraries. If we look inside of the /usr/bin/google-chrome-stable script, we can see that it actually prepends a few directories of its own to LD_LIBRARY_PATH before it launches Chrome.

# 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

With the standard Chrome installation location, the extra library paths are /opt/google/chrome/ and /opt/google/chrome/lib/. The /opt/google/chrome/lib/ directory is actually non-existent in the package, but it’s a convenient location to place all of our library files.

sudo mkdir /opt/google/chrome/lib/
sudo cp /tmp/lib/* /opt/google/chrome/lib/

At this point, running Google Chrome from the command-line should work!

google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 https://intoli.com

As long as you don’t get any errors after running that command then it means that all of the dependencies are being found successfully. You can stop here if you just want a working installation, but we’ll continue on to build a new RPM file which includes the dependencies.

We can open our currently installed RPM in rpmrebuild again by running

sudo rpmrebuild -e -d final-output -p output/x86_64/google-chrome-stable-60.0.3112.113-1.x86_64.rpm

which will open the spec file in vi. Now we can find the files section which begins with %files and add the /opt/google/chrome/lib directory.

%dir %attr(0755, root, root) "/opt/google/chrome/lib"

I placed this directly under the line that adds the /opt/google/chrome directory, but it doesn’t really matter exactly where it goes.

We’ll also need to add all of the individual library files. Running

find /opt/google/chrome/lib/ -type f | sed 's/\(.*\)/%attr(0644, root, root) "\1"/g'

will list them in a format that can be directly copy and pasted into the spec file. The output is

%attr(0644, root, root) "/opt/google/chrome/lib/libwayland-egl.so.1"
%attr(0644, root, root) "/opt/google/chrome/lib/libwayland-client.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libatk-bridge-2.0.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libwayland-cursor.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libdbus-1.so.3"
%attr(0644, root, root) "/opt/google/chrome/lib/libXss.so.1"
%attr(0644, root, root) "/opt/google/chrome/lib/libcairo-gobject.so.2"
%attr(0644, root, root) "/opt/google/chrome/lib/libxkbcommon.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libgdk-3.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libEGL.so.1"
%attr(0644, root, root) "/opt/google/chrome/lib/libXinerama.so.1"
%attr(0644, root, root) "/opt/google/chrome/lib/libgtk-3.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libgconf-2.so.4"
%attr(0644, root, root) "/opt/google/chrome/lib/libdbus-glib-1.so.2"
%attr(0644, root, root) "/opt/google/chrome/lib/libgcrypt.so.20"
%attr(0644, root, root) "/opt/google/chrome/lib/libatk-1.0.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libpng16.so.16"
%attr(0644, root, root) "/opt/google/chrome/lib/libepoxy.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libz.so.1"
%attr(0644, root, root) "/opt/google/chrome/lib/libgdk_pixbuf-2.0.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libgpg-error.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libGLdispatch.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/liblz4.so.1"
%attr(0644, root, root) "/opt/google/chrome/lib/libsystemd.so.0"
%attr(0644, root, root) "/opt/google/chrome/lib/libatspi.so.0"

for me, but these will likely change in future Chrome versions. These can be added directly below the line which added the /opt/google/chrome/lib directory.

Finally, we need to actually add the library files themselves. If you look at the top of the spec file, there should be a BuildRoot specified.

# rpmrebuild autogenerated specfile

BuildRoot: /root/.tmp/rpmrebuild.7254/work/root

This is where the RPM has been temporarily extracted and we can make modifications here before repackaging it. To copy over all of our library files for example, we can run the following.

sudo mkdir -p /root/.tmp/rpmrebuild.7254/work/root/opt/google/chrome/lib/
sudo cp /opt/google/chrome/lib/* /root/.tmp/rpmrebuild.7254/work/root/opt/google/chrome/lib/

Now all we need to do is save the file and close the editor so that rpmrebuild proceeds with the build process. It will create a new RPM file in final-output/x86_64/ that we can install with:

# uninstall the previous version
sudo yum remove google-chrome-stable

# install the new version
sudo rpm -i final-output/x86_64/google-chrome-stable-60.0.3112.113-1.x86_64.rpm

This package can be installed on other RHEL 6.X machines and will include all of our bundled dependencies. These dependencies will be isolated in the /opt/google/chrome/ directory and will be removed if you uninstall the package at some point in the future.

Conclusion

We’ve walked through the process of modifying a standard Google Chrome RPM package to work on CentOS, RHEL, and Amazon Linux 6.X versions. If you were already using one of these operating systems and were trying to get Google Chrome installed then hopefully you’ve found this useful. Even if not, perhaps it was interesting just to see the process of modifying a package to bundle additional dependencies. I wish you the best of luck with your web scraping and test automation endeavours either way!

As always, please feel free to get in touch with us here at Intoli. Our team has broad expertise in web scraping and data intelligence, and we would really love to hear about what you’re working on!