@@ -9,15 +9,20 @@ set -euo pipefail
99# is attempted — the system already has every shared library Phoenix Code
1010# needs at startup, regardless of which package name provides it.
1111#
12- # Two things are checked independently of the --version probe:
13- # - gnome-keyring daemon presence (libsecret needs the daemon for keytar
14- # credential storage; --version exits before keytar is required).
15- # - AppArmor unprivileged-userns profile on Ubuntu 24.04+ / Debian 13+
16- # (required for Chromium's sandbox; --version exits before the zygote).
12+ # Beyond the --version probe, the installer also checks for the gnome-keyring
13+ # daemon binary — libsecret needs the daemon for keytar credential storage,
14+ # and --version exits before keytar is require()d. Sudo is only requested if
15+ # libs or the keyring daemon are actually missing.
1716#
18- # Sudo is only requested if at least one of the three checks fails, and only
19- # the missing piece gets installed. Also wires up a desktop entry + `phcode`
20- # wrapper script that handles AppImage launch fallbacks.
17+ # On Ubuntu 24.04+ / Debian 13+, Chromium's unprivileged userns sandbox can
18+ # be blocked by the kernel sysctl. The app still launches because the wrapper
19+ # script falls back to --no-sandbox; we no longer install an AppArmor profile
20+ # to keep the kernel sandbox enabled (the renderer-RCE-to-syscall threat the
21+ # kernel sandbox guards against is narrow, and Phoenix Code's actual attack
22+ # surface flows through the IPC bridge which the kernel sandbox doesn't gate).
23+ #
24+ # Also wires up a desktop entry + `phcode` wrapper script that handles
25+ # AppImage launch fallbacks.
2126
2227DESKTOP_DIR=$HOME /.local/share/applications
2328UPDATE_JSON_URL=" https://updates.phcode.io/tauri/update-latest-experimental-build.json"
@@ -30,7 +35,6 @@ DESKTOP_ENTRY="$DESKTOP_DIR/$DESKTOP_ENTRY_NAME"
3035SCRIPT_NAME=" phcode"
3136BINARY_NAME=" phoenix-code"
3237APPIMAGE_NAME=" phoenix-code.AppImage"
33- APPARMOR_PROFILE_PATH=" /etc/apparmor.d/phoenix-code"
3438
3539declare -a MIME_TYPES=(
3640 " text/html"
@@ -106,11 +110,10 @@ check_architecture() {
106110# Runtime-library probe: ask the AppImage to print its version and exit. This
107111# is handled at src-electron/main.js:23-26, but `require('electron')` runs
108112# before that handler — which means Chromium initializes its sandbox bits even
109- # for --version. On Ubuntu 24.04+ / Debian 13+ without our AppArmor profile,
110- # that init can SIGTRAP. Pass --no-sandbox so the probe ALSO works on systems
111- # where the AppArmor profile isn't installed yet — this isolates the probe to
112- # "are the system shared libraries present and loadable?" and decouples it
113- # from the AppArmor concern (which is handled separately).
113+ # for --version. On Ubuntu 24.04+ / Debian 13+ where unprivileged user
114+ # namespaces are restricted, that init can SIGTRAP. Pass --no-sandbox so the
115+ # probe isolates to "are the system shared libraries present and loadable?"
116+ # regardless of the kernel's userns policy.
114117#
115118# The `{ … } 2>/dev/null` wrapper suppresses bash's own "Trace/breakpoint trap
116119# (core dumped)" report when the subprocess dies on signal — those messages
@@ -126,15 +129,6 @@ gnome_keyring_present() {
126129 command -v gnome-keyring-daemon > /dev/null 2>&1
127130}
128131
129- # Returns 0 if this system's kernel restricts unprivileged user namespaces
130- # (Ubuntu 24.04+ / Debian 13+ / Kali rolling default), which blocks Chromium's
131- # sandbox inside Electron AppImages unless an AppArmor profile grants userns.
132- apparmor_userns_restricted () {
133- [ -d /etc/apparmor.d ] || return 1
134- command -v apparmor_parser > /dev/null 2>&1 || return 1
135- [ " $( sysctl -n kernel.apparmor_restrict_unprivileged_userns 2> /dev/null || echo 0) " = " 1" ]
136- }
137-
138132downloadLatestReleaseInfo () {
139133 local release_info_file=" $TMP_DIR /latest_release.json"
140134 if [ ! -f " $release_info_file " ]; then
@@ -179,11 +173,11 @@ create_invocation_script() {
179173APPIMAGE="$install_dir /$APPIMAGE_NAME "
180174ERR=\$ (mktemp)
181175if ! "\$ APPIMAGE" "\$ @" 2>"\$ ERR"; then
182- if grep -qiE 'fuse|libfuse|dlopen ' "\$ ERR"; then
176+ if grep -qiE 'fuse|libfuse|fusermount|/dev/fuse ' "\$ ERR"; then
183177 rm -f "\$ ERR"
184178 exec "\$ APPIMAGE" --appimage-extract-and-run "\$ @"
185179 fi
186- if grep -qiE 'userns|apparmor|sandbox|setuid|namespace' "\$ ERR"; then
180+ if grep -qiE 'userns|apparmor|sandbox|setuid|namespace|Check failed ' "\$ ERR"; then
187181 rm -f "\$ ERR"
188182 exec "\$ APPIMAGE" --no-sandbox "\$ @"
189183 fi
@@ -212,32 +206,6 @@ gnome_keyring_pkg_if_needed() {
212206 fi
213207}
214208
215- # Installs an AppArmor profile that allows the AppImage to use unprivileged
216- # user namespaces. Required on Ubuntu 24.04+ / Debian 13+ where the kernel knob
217- # kernel.apparmor_restrict_unprivileged_userns=1 blocks Chromium's sandbox in
218- # every Electron AppImage. No-op elsewhere, and a no-op if the profile is
219- # already present (idempotent across re-runs and --upgrade).
220- install_apparmor_profile_if_needed () {
221- apparmor_userns_restricted || return 0
222- [ -f " $APPARMOR_PROFILE_PATH " ] && return 0
223-
224- sudo tee " $APPARMOR_PROFILE_PATH " > /dev/null << EOF
225- # Auto-generated by the Phoenix Code installer.
226- # Required on Ubuntu 24.04+ / Debian 13+ where unprivileged user namespaces are
227- # restricted by default, which would otherwise block Chromium's sandbox in the
228- # Phoenix Code AppImage.
229- abi <abi/4.0>,
230- include <tunables/global>
231- profile phoenix-code "$INSTALL_DIR /$APPIMAGE_NAME " flags=(unconfined) {
232- userns,
233- include if exists <local/phoenix-code>
234- }
235- EOF
236- if ! sudo apparmor_parser -r " $APPARMOR_PROFILE_PATH " 2> /dev/null; then
237- echo -e " ${YELLOW} AppArmor profile reload failed; the wrapper will fall back to --no-sandbox if needed.${RESET} "
238- fi
239- }
240-
241209# Picks the libfuse2 package name for whatever Ubuntu/Debian release we're on.
242210# Ubuntu 24.04 Noble+ / Debian 13 Trixie+ / Kali rolling / Mint 22+ / Neon 24+
243211# ship libfuse2t64 (the post-y2038 rename); older releases (Ubuntu 20.04 Focal
@@ -304,11 +272,8 @@ install_packages_for_distro() {
304272# mounts the AppImage and AFTER the Electron binary loads its NEEDED libs.
305273# 2. gnome-keyring daemon — binary-presence check; `--version` doesn't
306274# exercise keytar so it can't verify libsecret/Secret Service end-to-end.
307- # 3. AppArmor unprivileged-userns profile — Chromium sandbox prereq on
308- # Ubuntu 24.04+ / Debian 13+. Independent of the other two.
309275#
310- # Sudo is requested ONLY if at least one of the three probes fails, and only
311- # the failing pieces are installed.
276+ # Sudo is requested ONLY if at least one of the probes fails.
312277ensure_runtime_dependencies () {
313278 local appimage=" $1 "
314279 if [ ! -f /etc/os-release ]; then
@@ -326,49 +291,30 @@ ensure_runtime_dependencies() {
326291 esac
327292 local keyring; keyring=$( gnome_keyring_pkg_if_needed)
328293
329- local libs_ok=1 keyring_ok=1 apparmor_ok=1
294+ local libs_ok=1 keyring_ok=1
330295 verify_appimage_launches " $appimage " || libs_ok=0
331296 if [ -n " $keyring " ] && ! gnome_keyring_present; then keyring_ok=0; fi
332- case " $distro " in
333- ubuntu|debian|linuxmint|kali|neon)
334- if apparmor_userns_restricted && [ ! -f " $APPARMOR_PROFILE_PATH " ]; then
335- apparmor_ok=0
336- fi
337- ;;
338- esac
339297
340- if [ " $libs_ok " = 1 ] && [ " $keyring_ok " = 1 ] && [ " $apparmor_ok " = 1 ] ; then
298+ if [ " $libs_ok " = 1 ] && [ " $keyring_ok " = 1 ]; then
341299 echo -e " ${GREEN} All runtime dependencies already present.${RESET} "
342300 return 0
343301 fi
344302
345- local need_pkgs=0
346- [ " $libs_ok " = 0 ] || [ " $keyring_ok " = 0 ] && need_pkgs=1
347- if [ " $need_pkgs " = 1 ] && [ " $apparmor_ok " = 0 ]; then
348- echo " Installing runtime packages and AppArmor sandbox profile..."
349- elif [ " $libs_ok " = 0 ]; then
303+ if [ " $libs_ok " = 0 ]; then
350304 echo " Installing missing runtime libraries..."
351- elif [ " $keyring_ok " = 0 ]; then
352- echo " Installing system keychain daemon..."
353305 else
354- echo " Installing AppArmor sandbox profile (keeps Chromium's sandbox enabled on Ubuntu 24.04+) ..."
306+ echo " Installing system keychain daemon ..."
355307 fi
356308 echo " This step requires administrative access."
357309 if ! sudo -n true 2> /dev/null; then
358310 echo " Please enter your password to proceed."
359311 fi
360312
361- if [ " $need_pkgs " = 1 ]; then
362- install_packages_for_distro " $distro " " $keyring "
363- # Re-probe libraries; if still failing, surface a clear warning but don't
364- # abort — the AppImage may have a runtime-specific issue we can't fix here.
365- if [ " $libs_ok " = 0 ] && ! verify_appimage_launches " $appimage " ; then
366- echo -e " ${YELLOW} WARN: AppImage still fails --version after dep install.${RESET} "
367- fi
368- fi
369-
370- if [ " $apparmor_ok " = 0 ]; then
371- install_apparmor_profile_if_needed
313+ install_packages_for_distro " $distro " " $keyring "
314+ # Re-probe libraries; if still failing, surface a clear warning but don't
315+ # abort — the AppImage may have a runtime-specific issue we can't fix here.
316+ if [ " $libs_ok " = 0 ] && ! verify_appimage_launches " $appimage " ; then
317+ echo -e " ${YELLOW} WARN: AppImage still fails --version after dep install.${RESET} "
372318 fi
373319}
374320
@@ -552,15 +498,15 @@ install() {
552498 echo -e " ${YELLOW} Phoenix Code appears to be already installed.${RESET} "
553499 if [ ! -t 0 ]; then
554500 echo -e " ${GREEN} Reinstalling Phoenix Code...${RESET} "
555- uninstall keep_apparmor
501+ uninstall
556502 downloadAndInstall
557503 copyFilesToDestination
558504 else
559505 read -r -p " Would you like to reinstall it? (y/N): " response
560506 case " $response " in
561507 [Yy]* )
562508 echo -e " ${GREEN} Reinstalling Phoenix Code...${RESET} "
563- uninstall keep_apparmor
509+ uninstall
564510 downloadAndInstall
565511 copyFilesToDestination
566512 ;;
@@ -595,7 +541,7 @@ upgrade() {
595541 if [ -n " $latest_version " ] && [ " $( printf ' %s\n' " $latest_version " " $current_version " | sort -V | tail -n1) " = " $latest_version " ] && [ " $latest_version " != " $current_version " ]; then
596542 echo -e " ${YELLOW} A newer version of Phoenix Code is available. Proceeding with the upgrade...${RESET} "
597543 downloadAndInstall
598- uninstall keep_apparmor
544+ uninstall
599545 copyFilesToDestination
600546 echo -e " ${GREEN} Upgrade completed successfully. Phoenix Code has been updated to the latest version.${RESET} "
601547 else
@@ -604,15 +550,6 @@ upgrade() {
604550}
605551
606552uninstall () {
607- # mode:
608- # "full" (default) — remove every artifact, including the
609- # system-level AppArmor profile under /etc/apparmor.d/.
610- # Used by the --uninstall CLI entry-point.
611- # "keep_apparmor" — remove user-scope artifacts only; leave the AppArmor
612- # profile in place. Used by the internal reinstall and
613- # --upgrade flows so a routine version bump doesn't need
614- # sudo just to recreate the same profile content.
615- local mode=" ${1:- full} "
616553 echo -e " ${YELLOW} Starting uninstallation of Phoenix Code...${RESET} "
617554 uninstallBetaAppImage
618555
@@ -650,14 +587,6 @@ uninstall() {
650587 echo -e " ${RED} Installation directory not found. Skipping...${RESET} "
651588 fi
652589
653- if [ " $mode " != " keep_apparmor" ] && [ -f " $APPARMOR_PROFILE_PATH " ]; then
654- echo -e " ${YELLOW} Removing AppArmor profile...${RESET} "
655- sudo rm -f " $APPARMOR_PROFILE_PATH " || true
656- if command -v apparmor_parser > /dev/null 2>&1 ; then
657- sudo apparmor_parser -R " $APPARMOR_PROFILE_PATH " 2> /dev/null || true
658- fi
659- fi
660-
661590 echo -e " ${GREEN} Uninstallation of Phoenix Code completed.${RESET} "
662591}
663592
0 commit comments