export const LINUX_LAUNCHER_BINARY_SUFFIX = '-bin'; export const APPARMOR_USERNS_RESTRICTION_PATH = '/proc/sys/kernel/apparmor_restrict_unprivileged_userns'; export const UNPRIVILEGED_USERNS_CLONE_PATH = '/proc/sys/kernel/unprivileged_userns_clone'; export const MAX_USER_NAMESPACES_PATH = '/proc/sys/user/max_user_namespaces'; export interface LinuxLauncherNames { launcherFileName: string; binaryFileName: string; } export interface LinuxLauncherScriptOptions { binaryFileName: string; apparmorRestrictionPath?: string; unprivilegedUsernsClonePath?: string; maxUserNamespacesPath?: string; } export function resolveLinuxLauncherNames(executableName: string): LinuxLauncherNames { return { launcherFileName: executableName, binaryFileName: `${executableName}${LINUX_LAUNCHER_BINARY_SUFFIX}` }; } /** * Chromium reads `--no-sandbox` while the browser process boots, long before * the main script runs, so `app.commandLine.appendSwitch` cannot influence it. * The packaged executable is therefore this script, which decides before * handing over to the real binary. * * The sandbox stays on wherever the kernel can host it. It is dropped only * where unprivileged user namespaces are denied - Ubuntu 24.04+ confines * unconfined binaries through AppArmor, and hardened kernels disable the * namespaces outright. An AppImage cannot fall back to the SUID helper because * its payload is mounted `nosuid`, so without this the app aborts at startup. */ export function buildLinuxLauncherScript(options: LinuxLauncherScriptOptions): string { const apparmorRestrictionPath = options.apparmorRestrictionPath ?? APPARMOR_USERNS_RESTRICTION_PATH; const unprivilegedUsernsClonePath = options.unprivilegedUsernsClonePath ?? UNPRIVILEGED_USERNS_CLONE_PATH; const maxUserNamespacesPath = options.maxUserNamespacesPath ?? MAX_USER_NAMESPACES_PATH; return [ '#!/bin/sh', '# Generated during packaging. Chromium only honours --no-sandbox when it is', '# present on the real command line, so the decision happens here.', 'set -eu', '', 'launcher_path="$0"', '', 'case "$launcher_path" in', ' */*) ;;', ' *) launcher_path="$(command -v -- "$launcher_path" 2>/dev/null || printf \'%s\' "$launcher_path")" ;;', 'esac', '', 'launcher_path="$(readlink -f -- "$launcher_path" 2>/dev/null || printf \'%s\' "$launcher_path")"', `binary_path="$(dirname -- "$launcher_path")/${options.binaryFileName}"`, '', 'read_kernel_flag() {', ' if [ ! -r "$1" ]; then', ' printf \'%s\' "$2"', ' return 0', ' fi', '', ' cat -- "$1" 2>/dev/null || printf \'%s\' "$2"', '}', '', 'sandbox_is_blocked() {', ` if [ "$(read_kernel_flag ${apparmorRestrictionPath} 0)" = "1" ]; then`, ' return 0', ' fi', '', ` if [ "$(read_kernel_flag ${unprivilegedUsernsClonePath} 1)" = "0" ]; then`, ' return 0', ' fi', '', ` if [ "$(read_kernel_flag ${maxUserNamespacesPath} 1)" = "0" ]; then`, ' return 0', ' fi', '', ' return 1', '}', '', 'for launcher_arg in "$@"; do', ' case "$launcher_arg" in', ' --no-sandbox) exec "$binary_path" "$@" ;;', ' esac', 'done', '', 'if sandbox_is_blocked; then', ' exec "$binary_path" --no-sandbox "$@"', 'fi', '', 'exec "$binary_path" "$@"', '' ].join('\n'); }