From 546b7f47df3fefb951e3a146a52f4db7492458e4 Mon Sep 17 00:00:00 2001 From: Cosimo Cecchi Date: Wed, 8 Aug 2018 12:47:52 -0700 Subject: [PATCH] Use flatpak-spawn to run Google Chrome This allows us to make a self-contained extra-data flatpak that does not need to rely on anything on the host to work. --- com.google.Chrome.json | 9 +- eos-google-chrome-app | 207 ----------------------------------------- google-chrome-launcher | 6 ++ 3 files changed, 12 insertions(+), 210 deletions(-) delete mode 100644 eos-google-chrome-app create mode 100644 google-chrome-launcher diff --git a/com.google.Chrome.json b/com.google.Chrome.json index 6fd525c..f1bb0f4 100644 --- a/com.google.Chrome.json +++ b/com.google.Chrome.json @@ -3,7 +3,10 @@ "runtime": "org.freedesktop.Platform", "runtime-version": "1.6", "sdk": "org.freedesktop.Sdk", - "command": "/app/bin/eos-google-chrome-app", + "command": "google-chrome-launcher", + "finish-args": [ + "--talk-name=org.freedesktop.Flatpak" + ], "cleanup": [ "/etc", "/include", @@ -22,7 +25,7 @@ "sources": [ { "type": "file", - "path": "eos-google-chrome-app" + "path": "google-chrome-launcher" }, { "type": "file", @@ -47,7 +50,7 @@ ], "build-commands": [ "install -d /app/bin", - "install eos-google-chrome-app /app/bin/eos-google-chrome-app", + "install google-chrome-launcher /app/bin/google-chrome-launcher", "install -d /app/share/appdata", "install -m644 com.google.Chrome.appdata.xml /app/share/appdata/com.google.Chrome.appdata.xml", diff --git a/eos-google-chrome-app b/eos-google-chrome-app deleted file mode 100644 index 81c501b..0000000 --- a/eos-google-chrome-app +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/python3 -# -# eos-google-chrome-app: launcher script to launch Google Chrome -# -# Copyright (C) 2016 Endless Mobile, Inc. -# Authors: -# Mario Sanchez Prada -# Joaquim Rocha -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -import logging -import os -import subprocess -import sys - -def _running_inside_flatpak(): - runtime_dir = os.environ.get('XDG_RUNTIME_DIR') - if not runtime_dir: - return False - return os.path.exists(os.path.join(runtime_dir, 'flatpak-info')) - -# Verify if we're running inside Flatpak before we proceed with code that may fail due -# to that reason -if _running_inside_flatpak(): - logging.warning("Google Chrome cannot be run inside a Flatpak sandboxed environment") - sys.exit(0) - -import gi -gi.require_version('Flatpak', '1.0') -from gi.repository import Flatpak -from gi.repository import GLib - - -def exit_with_error(message): - logging.error(message) - sys.exit(1) - - -def find_flatpak_ref(ref_id, kind=Flatpak.RefKind.APP, branch=None, arch=None): - installations = [Flatpak.Installation.new_user()] + Flatpak.get_system_installations() - ref_found = None - - for installation in installations: - try: - # We use get_current_installed_app() for apps since that will find - # the current dir for the app regardless of what the branch, while - # get_installed_ref() would force us to either specify it manually - # or to rely on the defaults when passing 'None', which is 'master'. - if kind == Flatpak.RefKind.APP and not branch: - ref_found = installation.get_current_installed_app(ref_id, None) - else: - ref_found = installation.get_installed_ref(kind, ref_id, arch, branch, None) - break - except GLib.Error as e: - logging.warning("Could not find Flatpak ref %s in %s: %s", ref_id, - installation.get_path().get_path(), repr(e)) - - return ref_found - - -class GoogleChromeFlatpakLauncher: - - FLATPAK_CHROME_APP_ID = 'com.google.Chrome' - - def __init__(self, arch, params): - self._params = params - self._arch = arch - self._start() - - def _start(self): - app_info = self._get_chrome_app_info() - if app_info: - logging.info("Local installation of Chrome found. Launching...") - self._run_google_chrome(app_info, self._params) - else: - exit_with_error("Could not find any local installation of Chrome") - - def _run_google_chrome(self, app_info, params): - # We'll be running Chrome outside of flatpak's sandbox, meaning that flatpak - # has no clue about that and could choose to update/remove the files during an - # update of the application, which would be bad. To prevent that, we make sure - # a sandboxed process for that app is running in parallel to Chrome, keeping the - # sandbox 'alive' after the main Chrome process finishes, in which case we'll send - # a flag (end of line character) via the stdin FD to let it know it can die now. - try: - sandbox_process = subprocess.Popen(['flatpak', 'run', - '--command=bash', self.FLATPAK_CHROME_APP_ID, '-c', 'read'], - stdin=subprocess.PIPE) - logging.info("Running monitor process inside the sandbox with PID %d", sandbox_process.pid) - except OSError as e: - exit_with_error("Could not run sandbox process: {}".format(repr(e))) - - # Bundled libraries need to be taken also into account - os.environ['LD_LIBRARY_PATH'] = app_info['app_lib_path'] - - chrome_bin_path = os.path.join(app_info['chrome_path'], 'opt', 'google', 'chrome', - 'google-chrome') - - try: - chrome_process = subprocess.Popen([chrome_bin_path] + params, env=os.environ) - logging.info("Running Google Chrome with PID %d", chrome_process.pid) - except OSError as e: - exit_with_error("Could not run Google Chrome: {}".format(repr(e))) - - chrome_process.wait() - logging.info("Google Chrome's main process stopped. Finishing sandbox process...") - - # Send an end of line byte string to make the read finish in - # the sandboxing process, effectively terminating the process. - sandbox_process.communicate(input=os.linesep.encode()) - logging.info("Sandbox process stopped") - - def _get_legacy_chrome_runtime_path(self, app_path): - runtime_extension_id = '{}.external'.format(self.FLATPAK_CHROME_APP_ID) - try: - metadata_file = GLib.KeyFile() - metadata_file.load_from_file(os.path.join(app_path, 'metadata'), GLib.KeyFileFlags.NONE) - - keyfile_group = 'Extension {}'.format(runtime_extension_id) - if not metadata_file.has_group(keyfile_group): - exit_with_error("Flatpak metadata not referencing external runtime") - - runtime_version = metadata_file.get_string(keyfile_group, 'version') - if not runtime_version: - exit_with_error("Could not determine version of external runtime") - except GLib.Error: - exit_with_error("Could not read flatpak's metadata for Chrome") - - runtime_path = None - runtime = find_flatpak_ref(runtime_extension_id, Flatpak.RefKind.RUNTIME, - runtime_version, self._arch) - if runtime: - runtime_path = runtime.get_deploy_dir() - if not runtime_path or not os.path.exists(runtime_path): - exit_with_error("Could not find Chrome's external runtime directory") - - runtime_path = os.path.join(runtime_path, 'files') - if not os.path.exists(runtime_path): - exit_with_error("Could not find Chrome's binaries in external runtime directory") - else: - exit_with_error("Could not find Chrome's external runtime") - - return runtime_path - - def _get_chrome_app_info(self): - app_lib_path = None - - app = find_flatpak_ref(self.FLATPAK_CHROME_APP_ID) - if not app: - logging.info("Chrome application is not installed") - return None - - app_path = app.get_deploy_dir() - if not app_path or not os.path.exists(app_path): - exit_with_error("Could not find Chrome's application directory") - - app_lib_path = os.path.join(app_path, 'files/lib') - if not os.path.exists(app_lib_path): - exit_with_error("Could not find bundled libraries for Chrome") - - if app_path and not app_lib_path: - exit_with_error("Chrome's installation directory found, but not bundled libraries") - - chrome_path = os.path.join(app_path, 'files', 'extra') - # We check for legacy runtimes to maintain backward-compatibility for users without - # support for native external apps (Flatpak <0.6.13) - if not os.path.exists(chrome_path): - logging.warning("Could not find 'extra data' directory in Chrome's Flatpak; falling " - "back to the legacy runtime...") - chrome_path = self._get_legacy_chrome_runtime_path(app_path) - - app_info = {'app_lib_path': app_lib_path, 'chrome_path': chrome_path} - logging.info("Found information for Chrome: %s", repr(app_info)) - return app_info - - -if __name__ == '__main__': - # Google Chrome is only available for Intel 64-bit - app_arch = Flatpak.get_default_arch() - if app_arch != 'x86_64': - exit_with_error("Found installation of unsupported architecture: {}".format(app_arch)) - - # We must NOT run this script from inside flatpak's sandbox - # or Chrome's own sandbox will fail (it's a setuid executable). - if os.path.exists(os.path.join(GLib.get_user_runtime_dir(), '.flatpak-info')): - exit_with_error("This launcher can NOT be run from inside a flatpak sandbox") - - cmdline_args = sys.argv[1:] - if len(sys.argv) > 1 and sys.argv[1] == '--debug': - logging.basicConfig(level=logging.INFO) - cmdline_args = sys.argv[2:] - - GoogleChromeFlatpakLauncher(app_arch, cmdline_args) - sys.exit(0) diff --git a/google-chrome-launcher b/google-chrome-launcher new file mode 100644 index 0000000..09f3fb6 --- /dev/null +++ b/google-chrome-launcher @@ -0,0 +1,6 @@ +#!/bin/bash + +chrome_flatpak_dir=`flatpak-spawn --host flatpak info -l com.google.Chrome` +chrome_binary=${chrome_flatpak_dir}/files/extra/opt/google/chrome/google-chrome + +exec flatpak-spawn --host --env=LD_LIBRARY_PATH=${chrome_flatpak_dir}/files/lib ${chrome_binary}