diff --git a/ubuntu-to-mint-convert-v3.sh b/ubuntu-to-mint-convert-v3.sh index e94b420..56b6140 100644 --- a/ubuntu-to-mint-convert-v3.sh +++ b/ubuntu-to-mint-convert-v3.sh @@ -20,7 +20,7 @@ set -euo pipefail # ========================= # Version # ========================= -SCRIPT_VERSION="4.8" +SCRIPT_VERSION="4.9" # ========================= # Globals / Defaults @@ -58,6 +58,16 @@ SYSTEM_KEYRING="/usr/share/keyrings/linuxmint-repo.gpg" # Error handling ON_ERROR_BACKUP_HINT="" +on_err() { + local rc=$? + local line="${BASH_LINENO[0]:-unknown}" + echo "ERROR: FAILED at line ${line} (exit ${rc})." + echo "ERROR: Command: ${BASH_COMMAND}" + [[ -n "${ON_ERROR_BACKUP_HINT:-}" ]] && echo "${ON_ERROR_BACKUP_HINT}" + exit "$rc" +} +trap on_err ERR + # ========================= # Pretty output # ========================= @@ -100,7 +110,7 @@ Usage (command-first): sudo bash $0 convert --i-accept-the-risk [options] sudo bash $0 rollback /root/ubuntu-to-mint-backup-YYYYMMDD-HHMMSS -Usage (options-first ALSO supported in v${SCRIPT_VERSION}): +Usage (options-first ALSO supported): sudo bash $0 [options] doctor sudo bash $0 [options] plan sudo bash $0 [options] convert --i-accept-the-risk @@ -264,7 +274,7 @@ timeshift_snapshot_best_effort() { } # ========================= -# Mint repo key bootstrap (no keyservers) +# Keyrings # ========================= gpg_key_file_has_keyid() { local f="$1" @@ -272,6 +282,16 @@ gpg_key_file_has_keyid() { gpg --batch --with-colons --show-keys "$f" 2>/dev/null | awk -F: '$1=="pub"||$1=="sub"{print toupper($5)}' | grep -qx "$(echo "$keyid" | tr '[:lower:]' '[:upper:]')" } +ubuntu_archive_keyring_path() { + # Needed for plan mode temp APT root signature verification. + DEBIAN_FRONTEND=noninteractive apt-get install -y ubuntu-keyring >/dev/null 2>&1 || true + if [[ -r /usr/share/keyrings/ubuntu-archive-keyring.gpg ]]; then + echo "/usr/share/keyrings/ubuntu-archive-keyring.gpg" + return 0 + fi + die "Ubuntu archive keyring not found at /usr/share/keyrings/ubuntu-archive-keyring.gpg (install ubuntu-keyring?)" +} + mint_keyring_build_from_linuxmint_keyring_deb() { local out_keyring="$1" [[ -n "$out_keyring" ]] || die "mint_keyring_build_from_linuxmint_keyring_deb: missing output path" @@ -346,7 +366,7 @@ mint_keyring_build_from_linuxmint_keyring_deb() { info "Using key candidate: $chosen" if [[ "$chosen" == *.asc || "$chosen" == *.key ]]; then grep -q "BEGIN PGP PUBLIC KEY BLOCK" "$chosen" 2>/dev/null || { rm -rf "$tmpdir"; die "Selected key candidate is not armored PGP: $chosen"; } - gpg --batch --dearmor -o "$tmp_out" "$chosen" || { rm -rf "$tmpdir"; die "gpg dearmor failed for key candidate"; } + gpg --batch --dearmor -o "$tmp_out" "$chosen" else cp -a "$chosen" "$tmp_out" fi @@ -408,7 +428,9 @@ detect_ubuntu_mirrors() { write_mint_sources_system() { local list="/etc/apt/sources.list.d/official-package-repositories.list" - local keyring="$SYSTEM_KEYRING" + local mint_keyring="$SYSTEM_KEYRING" + local ubuntu_keyring + ubuntu_keyring="$(ubuntu_archive_keyring_path)" detect_ubuntu_mirrors @@ -418,13 +440,13 @@ write_mint_sources_system() { # Generated by ubuntu-to-mint-convert-v3.sh on $(date -Is) # # Linux Mint repository: -deb [signed-by=${keyring}] ${MINT_MIRROR%/} ${TARGET_MINT} main upstream import backport +deb [signed-by=${mint_keyring}] ${MINT_MIRROR%/} ${TARGET_MINT} main upstream import backport # Ubuntu base repositories: -deb ${UBUNTU_ARCHIVE_MIRROR%/} ${UBUNTU_BASE} main restricted universe multiverse -deb ${UBUNTU_ARCHIVE_MIRROR%/} ${UBUNTU_BASE}-updates main restricted universe multiverse -deb ${UBUNTU_ARCHIVE_MIRROR%/} ${UBUNTU_BASE}-backports main restricted universe multiverse -deb ${UBUNTU_SECURITY_MIRROR%/} ${UBUNTU_BASE}-security main restricted universe multiverse +deb [signed-by=${ubuntu_keyring}] ${UBUNTU_ARCHIVE_MIRROR%/} ${UBUNTU_BASE} main restricted universe multiverse +deb [signed-by=${ubuntu_keyring}] ${UBUNTU_ARCHIVE_MIRROR%/} ${UBUNTU_BASE}-updates main restricted universe multiverse +deb [signed-by=${ubuntu_keyring}] ${UBUNTU_ARCHIVE_MIRROR%/} ${UBUNTU_BASE}-backports main restricted universe multiverse +deb [signed-by=${ubuntu_keyring}] ${UBUNTU_SECURITY_MIRROR%/} ${UBUNTU_BASE}-security main restricted universe multiverse EOF if [[ -f /etc/apt/sources.list ]]; then @@ -490,7 +512,6 @@ session_name_for_edition() { } ensure_lightdm_on_boot() { - # Make LightDM the default display manager and ensure graphical boot. if [[ ! -d /run/systemd/system ]]; then warn "systemd not detected; cannot enable LightDM on boot in this environment." return 0 @@ -498,33 +519,23 @@ ensure_lightdm_on_boot() { info "Ensuring LightDM starts on boot (graphical.target + default display manager)..." - # Unmask LightDM in case something previously masked it systemctl unmask lightdm >/dev/null 2>&1 || true - - # Ensure the default DM marker is LightDM echo "/usr/sbin/lightdm" > /etc/X11/default-display-manager || true - # Debian/Ubuntu DM selection often uses update-alternatives if have_cmd update-alternatives; then update-alternatives --set x-display-manager /usr/sbin/lightdm >/dev/null 2>&1 || true fi - # Ensure graphical boot target systemctl set-default graphical.target >/dev/null 2>&1 || true systemctl enable graphical.target >/dev/null 2>&1 || true - - # Enable LightDM for boot systemctl enable lightdm >/dev/null 2>&1 || true - # Prevent GDM from stealing the greeter if systemctl list-unit-files | awk '{print $1}' | grep -qx 'gdm3.service'; then systemctl disable --now gdm3 >/dev/null 2>&1 || true systemctl mask gdm3 >/dev/null 2>&1 || true fi - # Restart the active DM if we can systemctl restart lightdm >/dev/null 2>&1 || true - ok "LightDM configured to start on boot." } @@ -533,21 +544,11 @@ ensure_lightdm_defaults() { sess="$(session_name_for_edition)" info "Configuring LightDM defaults for edition=${EDITION} (session=${sess})" - DEBIAN_FRONTEND=noninteractive apt-get install -y lightdm slick-greeter - # Keep Ubuntu's gdm3 installed (if present) but ensure it can't win. - if systemctl is-enabled gdm3 >/dev/null 2>&1; then - warn "Disabling gdm3 (keeping installed)." - systemctl disable --now gdm3 || true - systemctl mask gdm3 || true - if [[ -f /etc/gdm3/custom.conf ]]; then - sed -i 's/^[#[:space:]]*WaylandEnable=.*/WaylandEnable=false/' /etc/gdm3/custom.conf || true - grep -q '^WaylandEnable=' /etc/gdm3/custom.conf || echo 'WaylandEnable=false' >> /etc/gdm3/custom.conf - fi - fi - mkdir -p /etc/lightdm/lightdm.conf.d + + # Comment out any existing user-session settings (keep files) shopt -s nullglob for f in /etc/lightdm/lightdm.conf.d/*.conf; do [[ "$(basename "$f")" == "99-ubuntu2mint.conf" ]] && continue @@ -596,9 +597,7 @@ EOF chmod 644 "$dmrc" || true done - # This is the baked-in boot behavior you asked for: ensure_lightdm_on_boot - ok "LightDM defaults applied; default session set to ${sess}." } @@ -621,8 +620,7 @@ apply_mintupdate_icon_diversion() { # Post-install sanity checks # ========================= post_install_sanity() { - local out_file="$1" - [[ -n "$out_file" ]] || out_file="/tmp/post-convert-validation.txt" + local out_file="${1:-/tmp/post-convert-validation.txt}" { echo "Post-conversion validation ($(date -Is))" @@ -650,7 +648,7 @@ post_install_sanity() { } # ========================= -# Plan mode (temp APT root) +# Plan mode helpers # ========================= apt_tmp_run() { local tmpapt="$1"; shift @@ -665,6 +663,24 @@ apt_tmp_run() { apt-get "${extra[@]}" "$@" } +plan_fix_tmpapt_perms() { + local tmpapt="$1" + + # Allow _apt sandbox to traverse and write partial lists + chmod 755 "$tmpapt" || true + chmod 755 "$tmpapt"/{etc,usr,var} 2>/dev/null || true + chmod 755 "$tmpapt"/var/{lib,cache} 2>/dev/null || true + chmod 755 "$tmpapt"/var/lib/apt 2>/dev/null || true + chmod 755 "$tmpapt"/var/cache/apt 2>/dev/null || true + + if id _apt >/dev/null 2>&1; then + chown -R _apt:root "$tmpapt/var/lib/apt/lists/partial" 2>/dev/null || true + chown -R _apt:root "$tmpapt/var/cache/apt/archives/partial" 2>/dev/null || true + chmod 755 "$tmpapt/var/lib/apt/lists/partial" 2>/dev/null || true + chmod 755 "$tmpapt/var/cache/apt/archives/partial" 2>/dev/null || true + fi +} + plan_mode() { need_root ensure_no_apt_locks @@ -674,19 +690,29 @@ plan_mode() { local tmpapt tmpapt="$(mktemp -d)" - mkdir -p "${tmpapt}/etc/apt/sources.list.d" "${tmpapt}/etc/apt/preferences.d" "${tmpapt}/var/lib/apt/lists/partial" "${tmpapt}/var/cache/apt/archives/partial" "${tmpapt}/usr/share/keyrings" + mkdir -p "${tmpapt}/etc/apt/sources.list.d" \ + "${tmpapt}/etc/apt/preferences.d" \ + "${tmpapt}/var/lib/apt/lists/partial" \ + "${tmpapt}/var/cache/apt/archives/partial" \ + "${tmpapt}/usr/share/keyrings" + + plan_fix_tmpapt_perms "$tmpapt" local tmp_keyring="${tmpapt}/usr/share/keyrings/linuxmint-repo.gpg" mint_keyring_build_from_linuxmint_keyring_deb "$tmp_keyring" + local ubuntu_keyring + ubuntu_keyring="$(ubuntu_archive_keyring_path)" + detect_ubuntu_mirrors cat > "${tmpapt}/etc/apt/sources.list" < "${tmpapt}/etc/apt/preferences.d/50-linuxmint-conversion.pref" <<'EOF' @@ -723,14 +749,14 @@ EOF local rc=${PIPESTATUS[0]} set -e + rm -rf "$tmpapt" + if [[ $rc -ne 0 ]]; then warn "Plan simulation failed (exit $rc). Review: $plan_out" - rm -rf "$tmpapt" exit $rc fi ok "Plan completed successfully. Review: $plan_out" - rm -rf "$tmpapt" } # ========================= @@ -829,10 +855,6 @@ convert() { info "Reinstalling x11-common (fixes Xsession has_option issues)..." DEBIAN_FRONTEND=noninteractive apt-get $(apt_opts_common) install --reinstall x11-common || true - info "Ensuring GNOME RandR runtime libs present (best-effort)..." - DEBIAN_FRONTEND=noninteractive apt-get $(apt_opts_common) install \ - libgnome-rr-4-2t64 libgnome-desktop-4-2t64 libgnome-bg-4-2t64 || true - ensure_lightdm_defaults mkdir -p "$backup_dir"