diff --git a/CHANGELOG.md b/CHANGELOG.md index b127ec13..e65005b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ### Fixed +- `codex-update-manager` now prunes unreferenced updater workspaces under `~/.cache/codex-update-manager/workspaces`, removing heavy build artifacts (`builder/`, `codex-app/`, `dist/`) while preserving lightweight diagnostics such as `logs/` and rebuild reports. - The Chrome native-messaging host now evicts stale browser clients when a newer Codex browser client connects, preventing old Node REPL sessions from repeatedly reattaching CDP and driving extension service-worker CPU. - The bundled Chrome plugin is now auto-installed during app startup, matching Browser Use, so the plugin page no longer falls back to an install button after restart when the Linux native host is already staged. - Nix builds, installer apps, and dev shells now use modern `7zz`, and the installer dependency check accepts `7zz` without requiring a separate legacy `7z` binary. diff --git a/Cargo.lock b/Cargo.lock index a8f00b9a..9300fcf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,7 +486,7 @@ dependencies = [ [[package]] name = "codex-update-manager" -version = "0.8.0" +version = "0.8.1" dependencies = [ "anyhow", "chrono", diff --git a/README.md b/README.md index 9bebc44a..e13392d2 100644 --- a/README.md +++ b/README.md @@ -511,7 +511,7 @@ pacman -Qlp dist/codex-desktop-*.pkg.tar.zst | sed -n '1,40p' ## Versioning -`codex-update-manager` current crate version: `0.8.0` +`codex-update-manager` current crate version: `0.8.1` SemVer policy: diff --git a/contrib/user-local-install/files/.local/share/applications/codex-desktop.desktop b/contrib/user-local-install/files/.local/share/applications/codex-desktop.desktop index 19d5c3c3..f845eb7b 100644 --- a/contrib/user-local-install/files/.local/share/applications/codex-desktop.desktop +++ b/contrib/user-local-install/files/.local/share/applications/codex-desktop.desktop @@ -3,7 +3,7 @@ Type=Application Version=1.0 Name=Codex Desktop Comment=OpenAI Codex desktop wrapper for Linux -Exec=@HOME@/.local/bin/codex-desktop %U +Exec=env BAMF_DESKTOP_FILE_HINT=@HOME@/.local/share/applications/codex-desktop.desktop CHROME_DESKTOP=codex-desktop.desktop @HOME@/.local/bin/codex-desktop %U TryExec=@HOME@/.local/bin/codex-desktop Terminal=false Categories=Development;IDE; @@ -11,4 +11,10 @@ MimeType=x-scheme-handler/codex;x-scheme-handler/codex-browser-sidebar; Keywords=codex;openai;ai;coding; StartupNotify=true StartupWMClass=codex-desktop +X-GNOME-WMClass=codex-desktop Icon=codex-desktop +Actions=new-window; + +[Desktop Action new-window] +Name=New Window +Exec=env BAMF_DESKTOP_FILE_HINT=@HOME@/.local/share/applications/codex-desktop.desktop CHROME_DESKTOP=codex-desktop.desktop CODEX_MULTI_LAUNCH=1 @HOME@/.local/bin/codex-desktop --new-instance diff --git a/contrib/user-local-install/install-user-local.sh b/contrib/user-local-install/install-user-local.sh index fd87924c..bae4208c 100755 --- a/contrib/user-local-install/install-user-local.sh +++ b/contrib/user-local-install/install-user-local.sh @@ -5,6 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" FILES_DIR="${SCRIPT_DIR}/files" SCRIPT_REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" SOURCE_REPO_ROOT="${CODEX_USER_LOCAL_SOURCE_REPO_DIR:-$SCRIPT_REPO_ROOT}" +DESKTOP_ENTRY_DOCTOR="${SOURCE_REPO_ROOT}/packaging/linux/codex-desktop-entry-doctor.sh" OPT_ROOT="${HOME}/.local/opt/codex-desktop-linux" OPT_BIN_DIR="${OPT_ROOT}/bin" OPT_LIB_DIR="${OPT_ROOT}/lib/codex-desktop-linux" @@ -19,6 +20,9 @@ FROM_UPDATE=0 ENABLE_TIMER=0 USER_LOCAL_OZONE_PLATFORM_SETTING="" +# shellcheck disable=SC1090 +. "$DESKTOP_ENTRY_DOCTOR" + while [ $# -gt 0 ]; do case "$1" in --from-update) @@ -109,7 +113,10 @@ set -euo pipefail exec "${HOME}/.local/opt/codex-desktop-linux/bin/codex-desktop-version" "$@" EOF - sed "s|@HOME@|${HOME}|g" "${FILES_DIR}/.local/share/applications/codex-desktop.desktop" > "${HOME}/.local/share/applications/codex-desktop.desktop" + codex_desktop_write_user_local_entry \ + "${FILES_DIR}/.local/share/applications/codex-desktop.desktop" \ + "${HOME}/.local/share/applications/codex-desktop.desktop" \ + "${HOME}" copy_file "${FILES_DIR}/.config/systemd/user/codex-desktop-update.service" "${systemd_user_dir}/codex-desktop-update.service" copy_file "${FILES_DIR}/.config/systemd/user/codex-desktop-update.timer" "${systemd_user_dir}/codex-desktop-update.timer" @@ -145,10 +152,6 @@ if command -v systemctl >/dev/null 2>&1; then fi fi -if command -v update-desktop-database >/dev/null 2>&1; then - update-desktop-database "${HOME}/.local/share/applications" >/dev/null 2>&1 || true -fi - if [ "$FROM_UPDATE" -eq 0 ] && [ -x "${HOME}/.local/bin/codex-desktop-update" ]; then "${HOME}/.local/bin/codex-desktop-update" --record-only >/dev/null 2>&1 || true fi diff --git a/packaging/appimage/codex-desktop.desktop b/packaging/appimage/codex-desktop.desktop index 394ef319..e321f875 100644 --- a/packaging/appimage/codex-desktop.desktop +++ b/packaging/appimage/codex-desktop.desktop @@ -8,6 +8,11 @@ Type=Application Categories=Development; MimeType=x-scheme-handler/codex;x-scheme-handler/codex-browser-sidebar; StartupNotify=true -StartupWMClass=Codex -X-GNOME-WMClass=Codex +StartupWMClass=codex-desktop +X-GNOME-WMClass=codex-desktop X-AppImage-Version=__VERSION__ +Actions=new-window; + +[Desktop Action new-window] +Name=New Window +Exec=env CHROME_DESKTOP=codex-desktop.desktop CODEX_MULTI_LAUNCH=1 AppRun --new-instance diff --git a/packaging/linux/codex-desktop-entry-doctor.sh b/packaging/linux/codex-desktop-entry-doctor.sh new file mode 100644 index 00000000..2277fcb1 --- /dev/null +++ b/packaging/linux/codex-desktop-entry-doctor.sh @@ -0,0 +1,123 @@ +#!/bin/sh + +codex_desktop_refresh_desktop_database() { + codex_desktop_db_dir="${1:-}" + [ -n "$codex_desktop_db_dir" ] || return 0 + + if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database "$codex_desktop_db_dir" >/dev/null 2>&1 || true + fi +} + +codex_desktop_write_user_local_entry() { + codex_desktop_template_path="${1:?missing desktop template path}" + codex_desktop_target_path="${2:?missing desktop target path}" + codex_desktop_home_dir="${3:?missing home directory}" + + mkdir -p "$(dirname "$codex_desktop_target_path")" + sed "s|@HOME@|${codex_desktop_home_dir}|g" \ + "$codex_desktop_template_path" > "$codex_desktop_target_path" + chmod 0644 "$codex_desktop_target_path" + codex_desktop_refresh_desktop_database "$(dirname "$codex_desktop_target_path")" +} + +codex_desktop_entry_has_sidebar_mime() { + grep -Eq '^MimeType=.*x-scheme-handler/codex-browser-sidebar([;]|$)' "$1" +} + +codex_desktop_entry_has_new_window_action() { + grep -Eq '^Actions=.*new-window([;]|$)' "$1" && + grep -Eq '^\[Desktop Action new-window\]$' "$1" +} + +codex_desktop_entry_is_legacy_generated() { + codex_desktop_file="${1:?missing desktop entry path}" + [ -f "$codex_desktop_file" ] || return 1 + + grep -q '^Name=Codex Desktop$' "$codex_desktop_file" || return 1 + grep -Eq '(^Exec=.*codex-desktop|^TryExec=.*codex-desktop|^Icon=codex-desktop$)' \ + "$codex_desktop_file" || return 1 + + if grep -Eq 'codex-desktop-open-next|^Actions=NewWindow([;]|$)|^\[Desktop Action NewWindow\]$|^Actions=NewInstance([;]|$)|^\[Desktop Action NewInstance\]$' \ + "$codex_desktop_file"; then + return 0 + fi + + if ! codex_desktop_entry_has_sidebar_mime "$codex_desktop_file"; then + return 0 + fi + + if ! codex_desktop_entry_has_new_window_action "$codex_desktop_file"; then + return 0 + fi + + return 1 +} + +codex_desktop_next_backup_path() { + codex_desktop_backup_target="${1:?missing desktop entry path}.bak" + codex_desktop_backup_index=0 + + while [ -e "$codex_desktop_backup_target" ]; do + codex_desktop_backup_index=$((codex_desktop_backup_index + 1)) + codex_desktop_backup_target="${1}.bak.${codex_desktop_backup_index}" + done + + printf '%s\n' "$codex_desktop_backup_target" +} + +codex_desktop_repair_shadow_entry() { + codex_desktop_target_path="${1:?missing desktop entry path}" + codex_desktop_backup_target="" + + if ! codex_desktop_entry_is_legacy_generated "$codex_desktop_target_path"; then + return 1 + fi + + codex_desktop_backup_target="$(codex_desktop_next_backup_path "$codex_desktop_target_path")" + mv "$codex_desktop_target_path" "$codex_desktop_backup_target" + codex_desktop_refresh_desktop_database "$(dirname "$codex_desktop_target_path")" +} + +codex_desktop_repair_system_package_shadow_entries() { + codex_desktop_package_name="${1:-codex-desktop}" + codex_desktop_target_file="${codex_desktop_package_name}.desktop" + + if ! command -v runuser >/dev/null 2>&1 || ! command -v getent >/dev/null 2>&1; then + return 0 + fi + + for codex_desktop_runtime_dir in /run/user/*; do + [ -d "$codex_desktop_runtime_dir" ] || continue + + codex_desktop_uid="$(basename "$codex_desktop_runtime_dir")" + case "$codex_desktop_uid" in + ''|*[!0-9]*|0) + continue + ;; + esac + + codex_desktop_passwd_entry="$(getent passwd "$codex_desktop_uid" || true)" + [ -n "$codex_desktop_passwd_entry" ] || continue + + codex_desktop_user_name="$(printf '%s\n' "$codex_desktop_passwd_entry" | cut -d: -f1)" + codex_desktop_home_dir="$(printf '%s\n' "$codex_desktop_passwd_entry" | cut -d: -f6)" + [ -n "$codex_desktop_user_name" ] || continue + [ -n "$codex_desktop_home_dir" ] || continue + [ "$codex_desktop_home_dir" != "/" ] || continue + + codex_desktop_user_entry="$codex_desktop_home_dir/.local/share/applications/$codex_desktop_target_file" + if ! codex_desktop_entry_is_legacy_generated "$codex_desktop_user_entry"; then + continue + fi + + codex_desktop_backup_target="$(codex_desktop_next_backup_path "$codex_desktop_user_entry")" + runuser -u "$codex_desktop_user_name" -- mv \ + "$codex_desktop_user_entry" "$codex_desktop_backup_target" >/dev/null 2>&1 || true + runuser -u "$codex_desktop_user_name" -- sh -c ' + if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database "$1" >/dev/null 2>&1 || true + fi + ' sh "$codex_desktop_home_dir/.local/share/applications" >/dev/null 2>&1 || true + done +} diff --git a/packaging/linux/codex-desktop.desktop b/packaging/linux/codex-desktop.desktop index bce60717..776c6569 100644 --- a/packaging/linux/codex-desktop.desktop +++ b/packaging/linux/codex-desktop.desktop @@ -8,9 +8,13 @@ Type=Application Categories=Development; MimeType=x-scheme-handler/codex;x-scheme-handler/codex-browser-sidebar; StartupNotify=true -StartupWMClass=Codex -X-GNOME-WMClass=Codex -Actions=CheckForUpdates;InstallReadyUpdate; +StartupWMClass=codex-desktop +X-GNOME-WMClass=codex-desktop +Actions=new-window;CheckForUpdates;InstallReadyUpdate; + +[Desktop Action new-window] +Name=New Window +Exec=env BAMF_DESKTOP_FILE_HINT=/usr/share/applications/codex-desktop.desktop CHROME_DESKTOP=codex-desktop.desktop CODEX_MULTI_LAUNCH=1 /usr/bin/codex-desktop --new-instance [Desktop Action CheckForUpdates] Name=Check for Updates diff --git a/packaging/linux/codex-desktop.install b/packaging/linux/codex-desktop.install index 02f83b4b..8b25ef29 100644 --- a/packaging/linux/codex-desktop.install +++ b/packaging/linux/codex-desktop.install @@ -1,13 +1,21 @@ SERVICE_HELPER="/opt/codex-desktop/update-builder/packaging/linux/codex-update-manager-user-service.sh" +DESKTOP_ENTRY_DOCTOR="/opt/codex-desktop/.codex-linux/codex-desktop-entry-doctor.sh" if [ -f "$SERVICE_HELPER" ]; then # shellcheck source=/opt/codex-desktop/update-builder/packaging/linux/codex-update-manager-user-service.sh . "$SERVICE_HELPER" fi +if [ -f "$DESKTOP_ENTRY_DOCTOR" ]; then + # shellcheck source=/opt/codex-desktop/.codex-linux/codex-desktop-entry-doctor.sh + . "$DESKTOP_ENTRY_DOCTOR" +fi post_install() { if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database /usr/share/applications >/dev/null 2>&1 || true fi + if [ -f "$DESKTOP_ENTRY_DOCTOR" ]; then + codex_desktop_repair_system_package_shadow_entries codex-desktop || true + fi if [ -f "$SERVICE_HELPER" ]; then codex_ensure_user_service_running || true fi diff --git a/packaging/linux/codex-desktop.spec b/packaging/linux/codex-desktop.spec index 53df3eea..e4932f9d 100644 --- a/packaging/linux/codex-desktop.spec +++ b/packaging/linux/codex-desktop.spec @@ -51,6 +51,11 @@ cp -a "__RPM_STAGING_DIR__/." "%{buildroot}/" if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database /usr/share/applications >/dev/null 2>&1 || true fi +DESKTOP_ENTRY_DOCTOR=/opt/__PACKAGE_NAME__/.codex-linux/codex-desktop-entry-doctor.sh +if [ -f "$DESKTOP_ENTRY_DOCTOR" ]; then + . "$DESKTOP_ENTRY_DOCTOR" + codex_desktop_repair_system_package_shadow_entries __PACKAGE_NAME__ || true +fi %if __PACKAGE_WITH_UPDATER__ SERVICE_HELPER=/opt/__PACKAGE_NAME__/update-builder/packaging/linux/codex-update-manager-user-service.sh diff --git a/packaging/linux/codex-update-manager.postinst b/packaging/linux/codex-update-manager.postinst index bbb5f60a..7a738f63 100644 --- a/packaging/linux/codex-update-manager.postinst +++ b/packaging/linux/codex-update-manager.postinst @@ -2,10 +2,16 @@ set -eu SERVICE_HELPER="/opt/codex-desktop/update-builder/packaging/linux/codex-update-manager-user-service.sh" +DESKTOP_ENTRY_DOCTOR="/opt/codex-desktop/.codex-linux/codex-desktop-entry-doctor.sh" if [ -f "$SERVICE_HELPER" ]; then # shellcheck source=/opt/codex-desktop/update-builder/packaging/linux/codex-update-manager-user-service.sh . "$SERVICE_HELPER" codex_ensure_user_service_running || true fi +if [ -f "$DESKTOP_ENTRY_DOCTOR" ]; then + # shellcheck source=/opt/codex-desktop/.codex-linux/codex-desktop-entry-doctor.sh + . "$DESKTOP_ENTRY_DOCTOR" + codex_desktop_repair_system_package_shadow_entries codex-desktop || true +fi exit 0 diff --git a/scripts/build-deb.sh b/scripts/build-deb.sh index 53f8c2a5..4c897e64 100755 --- a/scripts/build-deb.sh +++ b/scripts/build-deb.sh @@ -86,7 +86,10 @@ CONTROL fi chmod 0644 "$PKG_ROOT/DEBIAN/control" if package_with_updater_enabled; then - sed -e "s|/opt/codex-desktop|/opt/$PACKAGE_NAME|g" "$POSTINST_TEMPLATE" > "$PKG_ROOT/DEBIAN/postinst" + sed \ + -e "s|/opt/codex-desktop|/opt/$PACKAGE_NAME|g" \ + -e "s|codex_desktop_repair_system_package_shadow_entries codex-desktop|codex_desktop_repair_system_package_shadow_entries $PACKAGE_NAME|g" \ + "$POSTINST_TEMPLATE" > "$PKG_ROOT/DEBIAN/postinst" cp "$PRERM_TEMPLATE" "$PKG_ROOT/DEBIAN/prerm" cp "$POSTRM_TEMPLATE" "$PKG_ROOT/DEBIAN/postrm" chmod 0755 "$PKG_ROOT/DEBIAN/postinst" "$PKG_ROOT/DEBIAN/prerm" "$PKG_ROOT/DEBIAN/postrm" diff --git a/scripts/build-pacman.sh b/scripts/build-pacman.sh index 4a24f1bf..effdd4c2 100755 --- a/scripts/build-pacman.sh +++ b/scripts/build-pacman.sh @@ -92,6 +92,7 @@ main() { "$PKGBUILD_TEMPLATE" >"$build_root/PKGBUILD" if package_with_updater_enabled; then sed -e "s|/opt/codex-desktop|/opt/$PACKAGE_NAME|g" \ + -e "s|codex_desktop_repair_system_package_shadow_entries codex-desktop|codex_desktop_repair_system_package_shadow_entries $PACKAGE_NAME|g" \ "$INSTALL_HOOKS" >"$build_root/${PACKAGE_NAME}.install" else write_no_updater_pacman_install_hooks "$build_root/${PACKAGE_NAME}.install" diff --git a/scripts/lib/package-common.sh b/scripts/lib/package-common.sh index cb01e6df..f87ef4c7 100755 --- a/scripts/lib/package-common.sh +++ b/scripts/lib/package-common.sh @@ -88,21 +88,53 @@ render_desktop_entry() { display_name="$(sed_escape_replacement "${PACKAGE_DISPLAY_NAME:-Codex Desktop}")" comment="$(sed_escape_replacement "${PACKAGE_COMMENT:-Run Codex Desktop on Linux}")" - sed \ - -e "s/codex-desktop/$package_name/g" \ - -e "s/^Name=.*/Name=$display_name/g" \ - -e "s/^Comment=.*/Comment=$comment/g" \ - "$DESKTOP_TEMPLATE" > "$rendered_target" + awk \ + -v package_name="$package_name" \ + -v display_name="$display_name" \ + -v comment="$comment" ' + BEGIN { in_desktop_entry = 0 } + /^\[Desktop Entry\]$/ { + in_desktop_entry = 1 + gsub(/codex-desktop/, package_name) + print + next + } + /^\[/ { + in_desktop_entry = 0 + } + { + gsub(/codex-desktop/, package_name) + if (in_desktop_entry && /^Name=/) { + print "Name=" display_name + next + } + if (in_desktop_entry && /^Comment=/) { + print "Comment=" comment + next + } + print + } + ' "$DESKTOP_TEMPLATE" > "$rendered_target" if package_with_updater_enabled; then mv "$rendered_target" "$target" else awk ' + BEGIN { actions_rewritten = 0 } /^\[Desktop Action CheckForUpdates\]$/ { skip = 1; next } /^\[Desktop Action InstallReadyUpdate\]$/ { skip = 1; next } /^\[/ { skip = 0 } skip { next } - /^Actions=/ { next } + /^Actions=/ { + print "Actions=new-window;" + actions_rewritten = 1 + next + } { print } + END { + if (actions_rewritten == 0) { + print "Actions=new-window;" + } + } ' "$rendered_target" > "$target" rm -f "$rendered_target" fi @@ -219,6 +251,13 @@ SCRIPT chmod 0644 "$target" } +render_desktop_entry_doctor_helper() { + local target="$1" + + cp "$REPO_DIR/packaging/linux/codex-desktop-entry-doctor.sh" "$target" + chmod 0644 "$target" +} + write_no_updater_deb_postinst() { local target="$1" local package_name @@ -233,11 +272,17 @@ if command -v update-desktop-database >/dev/null 2>&1; then fi CLEANUP_HELPER="/opt/$package_name/.codex-linux/codex-no-updater-transition-cleanup.sh" +DESKTOP_ENTRY_DOCTOR="/opt/$package_name/.codex-linux/codex-desktop-entry-doctor.sh" if [ -f "\$CLEANUP_HELPER" ]; then # shellcheck source=/opt/$package_name/.codex-linux/codex-no-updater-transition-cleanup.sh . "\$CLEANUP_HELPER" codex_no_updater_cleanup_update_manager_service || true fi +if [ -f "\$DESKTOP_ENTRY_DOCTOR" ]; then + # shellcheck source=/opt/$package_name/.codex-linux/codex-desktop-entry-doctor.sh + . "\$DESKTOP_ENTRY_DOCTOR" + codex_desktop_repair_system_package_shadow_entries $package_name || true +fi exit 0 SCRIPT @@ -272,6 +317,7 @@ write_no_updater_pacman_install_hooks() { package_name="$(sed_escape_replacement "$PACKAGE_NAME")" cat > "$target" <