├── debian ├── changelog ├── compat ├── control ├── copyright ├── install ├── rules └── source │ └── format └── usr ├── bin ├── mintupgrade └── mintupgrade-inhibit-power └── share └── linuxmint └── mintupgrade └── apt_destination_sources /debian/changelog: -------------------------------------------------------------------------------- 1 | mintupgrade (2020.07.18.1) tricia; urgency=medium 2 | 3 | * Fail on foreign packages even if they're critical 4 | 5 | -- Clement Lefebvre Sat, 18 Jul 2020 17:12:43 +0100 6 | 7 | mintupgrade (2020.07.18) tricia; urgency=medium 8 | 9 | [ Michael Webster ] 10 | * Update cache before checking for problematic packages. 11 | 12 | [ Clement Lefebvre ] 13 | * Foreign packages: Skip critical pkgs 14 | 15 | -- Clement Lefebvre Fri, 17 Jul 2020 23:56:58 +0100 16 | 17 | mintupgrade (2020.07.17.1) tricia; urgency=medium 18 | 19 | * Remove debug leftover 20 | 21 | -- Clement Lefebvre Fri, 17 Jul 2020 17:08:19 +0100 22 | 23 | mintupgrade (2020.07.17) tricia; urgency=medium 24 | 25 | * Colors: Switch yellow to orange 26 | * Fail if 3rd party repositories are detected 27 | * Detect foreign packages 28 | * Use apt-cache to detect 3rd party repositories 29 | 30 | -- Clement Lefebvre Fri, 17 Jul 2020 14:49:07 +0100 31 | 32 | mintupgrade (2020.07.11) tricia; urgency=medium 33 | 34 | * Don't crash is env doesn't have XDG_CURRENT_DESKTOP 35 | 36 | -- Clement Lefebvre Sat, 11 Jul 2020 10:24:09 +0100 37 | 38 | mintupgrade (2020.07.10) tricia; urgency=medium 39 | 40 | [ Michael Webster ] 41 | * Make sure apt sources.* are readable after restoring them. 42 | 43 | -- Clement Lefebvre Fri, 10 Jul 2020 16:32:37 +0100 44 | 45 | mintupgrade (2020.07.09.2) tricia; urgency=medium 46 | 47 | * Preserve ownership/permissions of new table files 48 | 49 | -- Clement Lefebvre Thu, 09 Jul 2020 23:23:05 +0100 50 | 51 | mintupgrade (2020.07.09.1) tricia; urgency=medium 52 | 53 | * Crypttab: Don't fail on empty lines 54 | 55 | -- Clement Lefebvre Thu, 09 Jul 2020 23:18:42 +0100 56 | 57 | mintupgrade (2020.07.09) tricia; urgency=medium 58 | 59 | [ Clement Lefebvre ] 60 | * Remove commented DMO checks 61 | * Remove commented LMDE plymouth checks 62 | * Remove prepare command line argument 63 | * Restore sources after mintupgrade download 64 | * restore-source: Update cache when explicitly restoring sources 65 | * Remove unused variable 66 | * Don't fail when /etc/crypttab doesn't exist 67 | * Disable encrypted swap before the upgrade 68 | * Enrypted swap: Exit on error 69 | * Encrypted swap: Fix non-existing backup_dir 70 | * Make swapoff non-fatal 71 | 72 | [ Michael Webster ] 73 | * Don't write new fstab and crypttab before we've passed all possible modifications, and fix their names. 74 | * Use a different method of detecting an encrypted swap path when LVM is in use. 75 | * Fix indentation. 76 | 77 | [ Clement Lefebvre ] 78 | * Tune pkg additions/removals 79 | 80 | -- Clement Lefebvre Thu, 09 Jul 2020 09:40:28 +0100 81 | 82 | mintupgrade (2020.07.08) tricia; urgency=medium 83 | 84 | [ Michael Webster ] 85 | * Make mkswap failing a non-fatal event. 86 | * Add a newline at the end of apt_destination_sources. 87 | * Add a missing declaration 'self.encrypted_swap'. 88 | 89 | -- Clement Lefebvre Wed, 08 Jul 2020 10:03:55 +0100 90 | 91 | mintupgrade (2020.07.07) tricia; urgency=medium 92 | 93 | [ Clement Lefebvre ] 94 | * Add the origin codename to the backup path 95 | 96 | [ Michael Webster ] 97 | * Start 19.3->20 upgrade 98 | * formatting improvements 99 | * Improve prompts 100 | * Add support for mate, xfce 101 | * Capture some additional hwe packages. 102 | * Fix more formatting, move initial cache update before checking for mandatory packages. 103 | * Add a command completion message, more formatting. 104 | * Check that the system isn't on battery. 105 | * Add some post-upgrade install files. 106 | * Improve upgrade process explanation. 107 | * Don't force the user to plug in. Add os-prober to required packages. 108 | * add meta-mint packages to important list. 109 | * more important packages. 110 | * Add cryptsetup-initramfs and cryptsetup-run 111 | * Disable encrypted swap if it exists. 112 | * Update grub, disable with cryptsetup 113 | 114 | -- Clement Lefebvre Tue, 07 Jul 2020 14:10:16 +0100 115 | 116 | mintupgrade (2020.03.19.1) debbie; urgency=medium 117 | 118 | * Move back libservlet3.1-java to CHECK_ABSENT 119 | 120 | -- Clement Lefebvre Thu, 19 Mar 2020 16:55:00 +0000 121 | 122 | mintupgrade (2020.03.19) debbie; urgency=medium 123 | 124 | * Improve CHECK_ABSENT and CHECK_PRESENT tests 125 | * Remove check on MDM 126 | * Update pkgs in CHECK_ABSENT 127 | * Add checks to ensure purge of deb-multimedia.org prior to upgrade 128 | * Edit the error message for dmo packages 129 | * Add mintsystem to CHECK_UP_TO_DATE 130 | * Update pkg additions/removals 131 | * Enable APT recommends post-upgrade 132 | * Update APT destination sources 133 | * Use Pre-removal for the problematic servlet pkg 134 | * DMO check: Also test source pkg name 135 | * Restore APT sources on failure or when user wants to stop 136 | * Check: Run apt update twice if it fails the first time 137 | * Fix typo on intel-microcode pkg 138 | * Add missing sudo to remove APT config file 139 | * Remove xplayer from IMPORTANT_PACKAGES 140 | * Remove memtest86+ 141 | * Reset the plymouth theme 142 | 143 | -- Clement Lefebvre Thu, 19 Mar 2020 11:08:42 +0000 144 | 145 | mintupgrade (2020.03.17) cindy; urgency=medium 146 | 147 | * Add package checks prior to upgrade 148 | * Add keyring packages to list of pkgs that have to be up to date 149 | * Rephrase MDM error 150 | * Forbid removal of important packages and warn when pkgs are kept back 151 | * Fix grub title via debian-system-adjustments as well 152 | * Disable mintsystem during the upgrade 153 | * Fix typo preventing fallback commands from being run 154 | * Prepare upgrade LMDE 3 -> LMDE 4 155 | 156 | -- Clement Lefebvre Tue, 17 Mar 2020 12:15:42 +0000 157 | 158 | mintupgrade (18.3.11) sylvia; urgency=medium 159 | 160 | * Don't block MESA 18.0.5 anymore. 161 | 162 | -- Clement Lefebvre Mon, 09 Jul 2018 12:32:10 +0200 163 | 164 | mintupgrade (18.3.10) sylvia; urgency=medium 165 | 166 | * Properly fail if MESA 18.0.5 is detected 167 | 168 | -- Clement Lefebvre Sat, 07 Jul 2018 03:56:47 +0200 169 | 170 | mintupgrade (18.3.9) sylvia; urgency=medium 171 | 172 | * Block the upgrade with MESA 18.0.5 173 | 174 | -- Clement Lefebvre Sat, 07 Jul 2018 03:16:40 +0200 175 | 176 | mintupgrade (18.3.8) sylvia; urgency=medium 177 | 178 | * Removals: Don't just remove, purge. 179 | * Purge gnome-user-share 180 | 181 | -- Clement Lefebvre Fri, 06 Jul 2018 18:35:06 +0200 182 | 183 | mintupgrade (18.3.7) sylvia; urgency=medium 184 | 185 | [ monsta ] 186 | * fix a couple of typos (#12) 187 | 188 | -- Clement Lefebvre Thu, 05 Jul 2018 11:54:13 +0200 189 | 190 | mintupgrade (18.3.6) sylvia; urgency=medium 191 | 192 | * Add libblockdev-crypto2 to installed additions. 193 | 194 | -- Clement Lefebvre Wed, 04 Jul 2018 22:45:01 +0200 195 | 196 | mintupgrade (18.3.5) sylvia; urgency=medium 197 | 198 | * Pre-upgrade: Suggest to reboot the computer after applying updates. 199 | 200 | -- Clement Lefebvre Tue, 03 Jul 2018 23:05:21 +0200 201 | 202 | mintupgrade (18.3.4) sylvia; urgency=medium 203 | 204 | * Preserve /etc/fstab 205 | 206 | -- Clement Lefebvre Tue, 03 Jul 2018 19:50:24 +0200 207 | 208 | mintupgrade (18.3.3) sylvia; urgency=medium 209 | 210 | * Fix missing sudo in system call 211 | 212 | -- Clement Lefebvre Mon, 02 Jul 2018 18:55:27 +0200 213 | 214 | mintupgrade (18.3.2) sylvia; urgency=medium 215 | 216 | * Post-upgrade: Adjust GRUB title 217 | 218 | -- Clement Lefebvre Mon, 02 Jul 2018 17:53:56 +0200 219 | 220 | mintupgrade (18.3.1) sylvia; urgency=medium 221 | 222 | * Add the ability to try a command multiple times 223 | * Reduce verbosity for most apt-get commands 224 | * Add a space after yes_no prompts 225 | * Kill screensaver processes 226 | * Add a message to indicate the upgrade is finished. 227 | * Check if the DM is MDM 228 | * Check for /etc/timeshift.json 229 | 230 | -- Clement Lefebvre Mon, 02 Jul 2018 13:56:16 +0200 231 | 232 | mintupgrade (18.3.0) sylvia; urgency=medium 233 | 234 | [ PaulXiCao ] 235 | * removing the reseting of the .bashrc file (#9) 236 | 237 | [ Clement Lefebvre ] 238 | * Update for the 18.3 -> 19 upgrade path 239 | 240 | -- Clement Lefebvre Sat, 30 Jun 2018 19:04:44 +0200 241 | 242 | mintupgrade (17.3.11) rosa; urgency=medium 243 | 244 | * Use Dpkg::Options::=--force-overwrite 245 | 246 | -- Clement Lefebvre Tue, 02 Aug 2016 12:08:31 +0200 247 | 248 | mintupgrade (17.3.10) rosa; urgency=medium 249 | 250 | * Remove bamfdaemon before the upgrade 251 | 252 | -- Clement Lefebvre Sat, 30 Jul 2016 16:30:09 +0200 253 | 254 | mintupgrade (17.3.9) rosa; urgency=medium 255 | 256 | * Additions: Add mint-y theme and icons 257 | * Added ristretto to the list of package removals 258 | * Don't ask confirmation for post-upgrade steps 259 | * Enable upgrades for Xfce edition 260 | 261 | -- Clement Lefebvre Sat, 30 Jul 2016 13:55:11 +0200 262 | 263 | mintupgrade (17.3.8) rosa; urgency=medium 264 | 265 | * Removals: Remove pkgs one by one 266 | 267 | -- Clement Lefebvre Wed, 13 Jul 2016 17:50:19 +0200 268 | 269 | mintupgrade (17.3.7) rosa; urgency=medium 270 | 271 | * Added missing sudo call in LSB restoration step 272 | 273 | -- Clement Lefebvre Wed, 13 Jul 2016 16:59:42 +0200 274 | 275 | mintupgrade (17.3.6) rosa; urgency=medium 276 | 277 | * Rephrased warning about 3rd party sources 278 | * Re-adjust lsb-release, issue and issue.net 279 | 280 | -- Clement Lefebvre Wed, 13 Jul 2016 16:42:48 +0200 281 | 282 | mintupgrade (17.3.5) rosa; urgency=medium 283 | 284 | * Prepare: Only check if the system is up to date if the sources don't point to the destination. 285 | 286 | -- Clement Lefebvre Wed, 13 Jul 2016 10:17:38 +0200 287 | 288 | mintupgrade (17.3.4) rosa; urgency=medium 289 | 290 | * Recreate ~/.bashrc 291 | * Prevent mintupgrade to be run as root or with sudo 292 | 293 | -- Clement Lefebvre Sat, 09 Jul 2016 21:24:44 +0200 294 | 295 | mintupgrade (17.3.3) rosa; urgency=medium 296 | 297 | * Update the cache and check mintupgrade version in prepare step 298 | * Prepare: Don't fail if user is already pointing at destination 299 | * Fixed typo 300 | * Don't fail on package removals when a package is not found 301 | 302 | -- Clement Lefebvre Sat, 09 Jul 2016 18:13:12 +0200 303 | 304 | mintupgrade (17.3.2) rosa; urgency=medium 305 | 306 | * Added more progress info and disabled Cinnamon/MATE screensavers 307 | 308 | -- Clement Lefebvre Sat, 09 Jul 2016 17:04:26 +0200 309 | 310 | mintupgrade (17.3.1) rosa; urgency=medium 311 | 312 | * Add notice when restoring the APT sources 313 | 314 | -- Clement Lefebvre Sat, 09 Jul 2016 15:15:41 +0200 315 | 316 | mintupgrade (17.3.0) rosa; urgency=low 317 | 318 | * Initial release 319 | 320 | -- Clement Lefebvre Fri, 08 Jul 2016 19:11:00 +0100 321 | 322 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: mintupgrade 2 | Section: admin 3 | Priority: optional 4 | Maintainer: Linux Mint 5 | Build-Depends: debhelper (>= 9) 6 | Standards-Version: 3.9.5 7 | 8 | Package: mintupgrade 9 | Architecture: all 10 | Depends: python3 (>= 3.3), 11 | perl, 12 | acpi, 13 | apt, 14 | aptitude, 15 | crudini, 16 | mint-common, 17 | mint-info, 18 | mintsystem, 19 | zenity, 20 | ${misc:Depends} 21 | Description: Linux Mint Release Upgrader 22 | Used to upgrade to a newer release. 23 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: mintupgrade 3 | Upstream-Contact: Linux Mint 4 | Source: https://github.com/linuxmint/mintupgrade 5 | 6 | Files: * 7 | Copyright: 2008-2016 Clement Lefebvre 8 | License: GPL-2.0+ 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | . 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | . 19 | You should have received a copy of the GNU General Public License 20 | along with this program. If not, see . 21 | . 22 | On Debian GNU/Linux systems, the complete text of the GNU General Public 23 | License version 2 can be found in '/usr/share/common-licenses/GPL-2'. 24 | -------------------------------------------------------------------------------- /debian/install: -------------------------------------------------------------------------------- 1 | usr 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh ${@} 5 | 6 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /usr/bin/mintupgrade: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys, os, apt 4 | import apt_pkg 5 | import subprocess 6 | import filecmp 7 | import platform 8 | import subprocess 9 | import tempfile 10 | import pathlib 11 | 12 | from gi.repository import GLib 13 | 14 | ORIGIN = "Linux Mint 19.3 'Tricia'" 15 | ORIGIN_CODENAME = "tricia" 16 | ORIGIN_BASE_CODENAME = "bionic" 17 | 18 | DESTINATION = "Linux Mint 20 'Ulyana'" 19 | DESTINATION_CODENAME = "ulyana" 20 | DESTINATION_BASE_CODENAME = "focal" 21 | 22 | SUPPORTED_EDITIONS = ["cinnamon", "mate", "xfce"] 23 | 24 | CHECK_ABSENT = [] 25 | 26 | CHECK_PRESENT = ["default-jre", "os-prober"] 27 | CHECK_UP_TO_DATE = ["mintupgrade", "apt", "dpkg", "linuxmint-keyring", "ubuntu-keyring", "mintsystem"] 28 | 29 | BACKUP_DIR = os.path.expanduser("~/Upgrade-Backup-%s" % ORIGIN_CODENAME) 30 | BACKUP_APT_SOURCES = os.path.join(BACKUP_DIR, "APT") 31 | BACKUP_FSTAB = os.path.join(BACKUP_DIR, "fstab") 32 | BACKUP_CRYPTTAB = os.path.join(BACKUP_DIR, "crypttab") 33 | 34 | PACKAGES_PRE_REMOVALS = [] 35 | 36 | PACKAGES_REMOVALS = [ 37 | "tomboy", 38 | "libxplayer-plparser18", 39 | "xplayer-common", 40 | "gksu", 41 | "memtest86+", 42 | "*hwe-18.04*", 43 | "linux-hwe*", 44 | "python3-tinycss", # 45 | "indicator-application", 46 | "grub2-theme-mint" 47 | ] 48 | 49 | PACKAGES_ADDITIONS = [ 50 | "neofetch", 51 | "ffmpegthumbnailer", 52 | "amd64-microcode", 53 | "intel-microcode", 54 | "celluloid", 55 | "drawing", 56 | "gnote", 57 | "adwaita-icon-theme-full", # 19.3->20 58 | "warpinator", # 59 | "alsa-topology-conf", # 60 | "alsa-ucm-conf", # 61 | "mesa-vdpau-drivers", # 62 | "mesa-vulkan-drivers", # 63 | "cryptsetup-initramfs", 64 | "cryptsetup-run", 65 | "libreoffice-gtk3", 66 | "gamemode" 67 | ] 68 | 69 | IMPORTANT_PACKAGES = [ 70 | "mint-meta-cinnamon", 71 | "mint-meta-mate", 72 | "mint-meta-xfce", 73 | "xreader", 74 | "xed", 75 | "xviewer", 76 | "pix", 77 | "mintsystem", 78 | "metacity", 79 | "nemo-preview", 80 | "mintdrivers", 81 | "mintupdate", 82 | "mintsources", 83 | "mintinstall" 84 | ] 85 | 86 | class bcolors: 87 | HEADER = '\033[95m' # Magenta 88 | OKBLUE = '\033[94m' # Light Blue 89 | OKGREEN = '\033[92m' # Light Green 90 | WARNING = '\033[38;5;202m' # Orange 91 | FAIL = '\033[91m' # Light Red 92 | ENDC = '\033[0m' 93 | BOLD = '\033[1m' 94 | UNDERLINE = '\033[4m' 95 | 96 | # fstab columns 97 | FSTAB_ID = 0 98 | FSTAB_MNTPT = 1 99 | FSTAB_TYPE = 2 100 | FSTAB_OPTS = 3 101 | 102 | CRYPTTAB_NAME = 0 103 | CRYPTTAB_PATH = 1 104 | CRYPTTAB_PW = 2 105 | CRYPTTAB_OPTS = 3 106 | 107 | # Don't allow any other commands until a 'check' has succeeded. 108 | CHECK_COMPLETE_PATH = pathlib.Path("/tmp/mintupgrade/check_completed") 109 | 110 | def ensure_check_completed(): 111 | if not CHECK_COMPLETE_PATH.exists(): 112 | print("Please run 'mintupgrade check' before attempting to execute any other commands.") 113 | exit(1) 114 | 115 | def set_check_completed(): 116 | CHECK_COMPLETE_PATH.parent.mkdir(exist_ok=True) 117 | CHECK_COMPLETE_PATH.touch() 118 | 119 | def rm_check_completed(): 120 | try: 121 | # < python 3.8 doesn't have missing_ok parameter 122 | CHECK_COMPLETE_PATH.unlink() 123 | except FileNotFoundError: 124 | pass 125 | 126 | try: 127 | CHECK_COMPLETE_PATH.parent.rmdir() 128 | except Exception as e: 129 | pass 130 | 131 | def timeshift_config_exists(): 132 | # Config file location has changed in timeshift 20.11.1 (mint 20 series): 133 | # https://github.com/teejee2008/timeshift/commit/55870b94d1402d2d2be71a2170d0ddb036f8f65a 134 | # Timeshift moves the file, not copies, so should be in one place OR the other. 135 | 136 | old_path = pathlib.Path("/etc/timeshift.json") 137 | new_path = pathlib.Path("/etc/timeshift/timeshift.json") 138 | 139 | return new_path.exists() or old_path.exists() 140 | 141 | def commented_out(line): 142 | return line.strip().startswith("#") 143 | 144 | def comment_out(line): 145 | if not commented_out(line): 146 | return "# %s" % line 147 | else: 148 | return line 149 | 150 | def uncomment(line): 151 | if not commented_out(line): 152 | return line 153 | else: 154 | return line.replace("#", "").lstrip() 155 | 156 | def write_tabfile(path, lines): 157 | tmp_filename = None 158 | 159 | with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: 160 | tmp_filename = f.name 161 | for line in lines: 162 | f.write(line) 163 | 164 | subprocess.run(["sudo", "cp", tmp_filename, path], check=True) 165 | 166 | # Returns a list of all active 3rd party repositories 167 | def get_third_party_repositories(): 168 | origins = set() 169 | output = subprocess.getoutput("apt-cache policy") 170 | for chunk in output.split("release "): 171 | if "origin" in chunk: 172 | origin = None 173 | label = None 174 | name = None 175 | for line in chunk.split("\n"): 176 | line = line.strip() 177 | if "o=" in line and "l=" in line: 178 | for part in line.split(","): 179 | if (part.startswith("o=")): 180 | name = part.replace("o=", "") 181 | elif (part.startswith("l=")): 182 | label = part.replace("l=", "") 183 | elif line.startswith("origin "): 184 | origin = line.replace("origin ", "") 185 | 186 | if name != None and name.lower() not in ("ubuntu", "canonical", "linuxmint"): 187 | origins.add("%s (%s) - %s" % (label, name, origin)) 188 | return origins 189 | 190 | # Returns a tuple containing two lists 191 | # The first list is a list of orphaned packages (packages which have no origin) 192 | # The second list is a list of packages which version is not the official version (this does not 193 | # include packages which simply aren't up to date) 194 | def get_foreign_packages(find_orphans=True, find_downgradable_packages=True): 195 | orphan_packages = [] 196 | downgradable_packages = [] 197 | 198 | cache = apt.Cache() 199 | 200 | for key in cache.keys(): 201 | pkg = cache[key] 202 | if (pkg.is_installed): 203 | installed_version = pkg.installed.version 204 | 205 | # Find packages which aren't downloadable 206 | if (pkg.candidate == None) or (not pkg.candidate.downloadable): 207 | if find_orphans: 208 | downloadable = False 209 | for version in pkg.versions: 210 | if version.downloadable: 211 | downloadable = True 212 | if not downloadable: 213 | orphan_packages.append([pkg, installed_version]) 214 | if (pkg.candidate != None): 215 | if find_downgradable_packages: 216 | best_version = None 217 | archive = None 218 | for version in pkg.versions: 219 | if not version.downloadable: 220 | continue 221 | for origin in version.origins: 222 | if origin.origin != None and origin.origin.lower() in ("ubuntu", "canonical", "linuxmint"): 223 | if best_version is None: 224 | best_version = version 225 | archive = origin.archive 226 | else: 227 | if version.policy_priority > best_version.policy_priority: 228 | best_version = version 229 | archive = origin.archive 230 | elif version.policy_priority == best_version.policy_priority: 231 | # same priorities, compare version 232 | return_code = subprocess.call(["dpkg", "--compare-versions", version.version, "gt", best_version.version]) 233 | if return_code == 0: 234 | best_version = version 235 | archive = origin.archive 236 | 237 | if best_version != None and installed_version != best_version and pkg.candidate.version != best_version.version: 238 | downgradable_packages.append([pkg, installed_version, best_version, archive]) 239 | 240 | return (orphan_packages, downgradable_packages) 241 | 242 | class MintUpgrade(): 243 | 244 | def __init__(self, mode): 245 | self.reversible = True 246 | self.command = mode 247 | 248 | # Check the Mint info file 249 | if not os.path.exists("/etc/linuxmint/info"): 250 | self.fail("Missing file '/etc/linuxmint/info'.") 251 | 252 | # Check the edition 253 | self.mint_codename = 'unknown' 254 | self.mint_edition = 'unknown' 255 | self.mint_meta = 'unknown' 256 | with open("/etc/linuxmint/info", "r") as info: 257 | for line in info: 258 | line = line.strip() 259 | if "EDITION=" in line: 260 | self.mint_edition = line.split('=')[1].replace('"', '').split()[0] 261 | self.mint_meta = "mint-meta-%s" % self.mint_edition.lower() 262 | if "CODENAME=" in line: 263 | self.mint_codename = line.split('=')[1].replace('"', '').split()[0] 264 | self.points_to_destination = False 265 | if os.path.exists("/etc/apt/sources.list.d/official-package-repositories.list"): 266 | with open("/etc/apt/sources.list.d/official-package-repositories.list") as sources: 267 | for line in sources: 268 | if DESTINATION_CODENAME in line: 269 | self.points_to_destination = True 270 | break 271 | 272 | self.check_ac() 273 | 274 | def check_ac(self): 275 | status = subprocess.getoutput("acpi -a") 276 | if "off-line" in status: 277 | messages = ["You should connect this computer to a power source before attempting to upgrade."] 278 | self.continue_yes_no(messages, False) 279 | os.system("clear") 280 | 281 | def restore_sources(self): 282 | self.reversible = False 283 | self.progress("Restoring your backed up APT sources") 284 | print("") 285 | if os.path.exists(BACKUP_APT_SOURCES): 286 | self.check_command("sudo mkdir -p /etc/apt/sources.list.d", "Failed to restore APT sources") 287 | self.check_command("sudo rm -rf /etc/apt/sources.list.d/*", "Failed to restore APT sources") 288 | self.check_command("sudo cp -R %s/* /etc/apt/" % BACKUP_APT_SOURCES, "Failed to restore APT sources") 289 | self.check_command("sudo chmod -R a+r /etc/apt/sources.*", "Failed to restore APT sources") 290 | self.check_command("sudo rm -rf '%s'" % BACKUP_APT_SOURCES, "Failed to restore APT sources") 291 | if self.command == "restore-sources": 292 | self.progress("Updating cache") 293 | print("") 294 | os.system("DEBIAN_PRIORITY=critical sudo apt-get update") 295 | 296 | def prepare(self): 297 | messages = [] 298 | messages.append("Executing '%s'. This will perform the following:" % self.command) 299 | messages.append("") 300 | messages.append("1 - Your repositories will be switched to point to %s and" % DESTINATION) 301 | messages.append(" any 3rd party repositories will be removed. A backup of your APT sources") 302 | messages.append(" will be written to %s." % BACKUP_APT_SOURCES) 303 | if self.command == "check": 304 | messages.append("") 305 | messages.append("2 - The upgrade will be simulated so impacted packages can be evaluated.") 306 | messages.append("") 307 | messages.append("Your sources will be restored to %s at the end of this command." % ORIGIN) 308 | elif self.command == "download": 309 | messages.append("") 310 | messages.append("2 - The packages and updates to perform the upgrade will be downloaded.") 311 | messages.append("") 312 | messages.append("Your sources will be restored to %s at the end of this command." % ORIGIN) 313 | elif self.command == "upgrade": 314 | messages.append("") 315 | messages.append("2 - The packages and updates to perform the upgrade will be downloaded.") 316 | messages.append("") 317 | messages.append("3 - Upon confirmation, the upgrade will be performed. It is very important not to interrupt this") 318 | messages.append(" step. You may be required to interact or re-authenticate during the upgrade.") 319 | 320 | self.continue_yes_no(messages, False) 321 | 322 | # Check codename 323 | self.progress("Checking your Linux Mint codename") 324 | if self.mint_codename != ORIGIN_CODENAME and self.mint_codename != DESTINATION_CODENAME: 325 | self.fail("Your version of Linux Mint is '%s'. Only %s can be upgraded to %s." % (self.mint_codename.capitalize(), ORIGIN, DESTINATION)) 326 | 327 | bits, linkage = platform.architecture() 328 | if "32" in bits: 329 | self.fail("You are on a 32-bit system. It cannot be upgraded to %s." % DESTINATION) 330 | 331 | # Check edition 332 | self.progress("Checking your Linux Mint edition") 333 | if self.mint_edition.lower() not in SUPPORTED_EDITIONS: 334 | self.fail("Your edition of Linux Mint is '%s'. It cannot be upgraded to %s." % (self.mint_edition, DESTINATION)) 335 | 336 | # Check for timeshift configuration 337 | self.progress("Checking your Timeshift configuration") 338 | if not timeshift_config_exists(): 339 | self.fail("Please set up system snapshots. If anything goes wrong with the upgrade, snapshots will allow you to restore your operating system. Install and configure Timeshift, and create a snapshot before proceeding with the upgrade.") 340 | 341 | self.progress("Updating cache") 342 | print("") 343 | os.system("DEBIAN_PRIORITY=critical sudo apt-get update") 344 | cache = apt.Cache() 345 | 346 | self.progress("Checking for broken packages") 347 | if cache.broken_count > 0: 348 | # offer to fix? 349 | self.fail("You have broken packages - please run 'apt install -f' to resolve any issues and try to upgrade again.") 350 | 351 | self.progress("Running autoremove to remove unused packages") 352 | self.check_command("sudo apt-get --purge autoremove --yes -o APT::AutoRemove::SuggestsImportant=false", "Failed to autoremove unused packages.") 353 | 354 | # Check APT sources (no 3rd party repositories) 355 | self.progress("Checking your APT repositories") 356 | third_party_repositories = get_third_party_repositories() 357 | if len(third_party_repositories) > 0: 358 | self.fail("The following 3rd party repositories were detected. Disable them and refresh your APT cache.\n\n - %s\n" % ("\n - ".join(sorted(third_party_repositories)))) 359 | 360 | # Check foreign packages 361 | self.progress("Checking your APT packages") 362 | (orphans, foreigns) = get_foreign_packages() 363 | if len(foreigns) > 0: 364 | print("") 365 | for foreign in foreigns: 366 | (pkg, installed_version, best_version, archive) = foreign 367 | print(" - %s: %s, should be %s (from %s)" % (pkg.name, installed_version, best_version.version, archive)) 368 | self.fail("The packages above have incorrect versions. They can be downgraded using 'Software Sources -> Maintenance -> Downgrade Foreign Packages'.") 369 | 370 | if len(orphans) > 0: 371 | print("") 372 | for orphan in orphans: 373 | (pkg, installed_version) = orphan 374 | print(" - %s" % pkg.name) 375 | self.continue_yes_no(["The packages above are not from the official repositories. If you experience issues, or for a safe upgrade, do not continue, remove them with 'Software Sources -> Maintenance -> Remove Foreign Packages' and re-run the upgrade."], False) 376 | 377 | # Check for encrypted SWAP (https://bugs.launchpad.net/ubuntu/+source/cryptsetup/+bug/1802617) 378 | self.progress("Checking for encrypted swap") 379 | encrypted_swap = False 380 | existing_swap_in_fstab = False 381 | new_crypttab = [] 382 | new_fstab = [] 383 | blkdev_name = None 384 | blkdev_real_id = None 385 | if os.path.exists("/etc/crypttab") and os.path.exists("/etc/fstab"): 386 | with open("/etc/crypttab", "r") as f: 387 | for line in f: 388 | if not commented_out(line): 389 | entry = line.split() 390 | if len(entry) > 3 and "swap" in entry[CRYPTTAB_OPTS]: 391 | encrypted_swap = True 392 | blkdev_name = entry[CRYPTTAB_NAME] 393 | blkdev_real_id = entry[CRYPTTAB_PATH] 394 | line = comment_out(line) 395 | new_crypttab.append(line) 396 | 397 | if encrypted_swap: 398 | # Detect LVM, fix path for searching fstab and restoring swap space. 399 | if len(subprocess.getoutput("sudo lvs")) > 0: 400 | print("\n LVM Detected") 401 | for line in subprocess.getoutput("sudo cryptsetup status %s" % blkdev_name).splitlines(): 402 | if "device:" not in line: 403 | continue 404 | 405 | blkdev_real_id = line.replace("device:", "").strip() 406 | break 407 | 408 | if blkdev_name != None: 409 | with open("/etc/fstab", "r") as f: 410 | # We're looking for a) the /dev/mapper name from crypttab and 411 | # b) a line with an id that matches the crypttab entry's path - 412 | # if we find one we can uncomment it (this happens if it was a 413 | # partition and not a file). 414 | for line in f: 415 | entry = uncomment(line).split() 416 | if len(entry) >= 6: 417 | if entry[FSTAB_ID] == ("/dev/mapper/%s" % blkdev_name): 418 | # found the /dev/mapper entry from crypttab 419 | line = comment_out(line) 420 | elif entry[FSTAB_ID] == blkdev_real_id and "swap" in entry[FSTAB_TYPE]: 421 | # This is either the original entry for the swap partition that was 422 | # found during installation, which was commented out when encryption 423 | # was setup, or it's an uncommented entry for a swapfile. Either 424 | # way, we enable it. 425 | line = uncomment(line) 426 | existing_swap_in_fstab = True 427 | new_fstab.append(line) 428 | 429 | messages = [] 430 | messages.append("Your swap space is encrypted. An issue in %s currently" % DESTINATION) 431 | messages.append("prevents the use of encrypted swap.") 432 | messages.append("") 433 | messages.append("This swap space will now be disabled.") 434 | self.continue_yes_no(messages, restore=False) 435 | 436 | self.progress("Disabling encrypted swap partition") 437 | print("") 438 | try: 439 | os.system("mkdir -p %s" % BACKUP_DIR) 440 | subprocess.run(["sudo", "swapoff", "-a"], check=False) 441 | subprocess.run(["sudo", "cryptsetup", "remove", blkdev_name], check=False) 442 | subprocess.run(["cp", "/etc/crypttab", BACKUP_CRYPTTAB], check=True) 443 | subprocess.run(["cp", "/etc/fstab", BACKUP_FSTAB], check=True) 444 | 445 | if existing_swap_in_fstab: 446 | print("Running mkswap to reinitialize the swap space. This will fail if you're using") 447 | print("a swap partition, and that's ok.\n") 448 | subprocess.run(["sudo", "mkswap", blkdev_real_id], check=False) 449 | else: 450 | messages = [] 451 | messages.append("A 2GB swap file (/swapfile) can be created.") 452 | print ("") 453 | for message in messages: 454 | print ("%s %s%s" % (bcolors.WARNING, message, bcolors.ENDC)) 455 | answer = None 456 | while (answer not in ["y", "yes", "n", "no"]): 457 | print ("") 458 | answer = input("%s Create a swap file? [y/n]:%s " % (bcolors.OKGREEN, bcolors.ENDC)).lower() 459 | if answer in ["y", "yes"]: 460 | print ("") 461 | new_fstab.append("\n") 462 | new_fstab.append("# Added by mintupgrade\n") 463 | new_fstab.append( 464 | "/swapfile none swap sw 0 0\n" 465 | ) 466 | subprocess.run(["sudo", "fallocate", "-l", "2G", "/swapfile"], check=True) 467 | subprocess.run(["sudo", "chmod", "600", "/swapfile"], check=True) 468 | subprocess.run(["sudo", "mkswap", "/swapfile"], check=True) 469 | 470 | write_tabfile("/etc/crypttab", new_crypttab) 471 | write_tabfile("/etc/fstab", new_fstab) 472 | subprocess.run(["sudo", "update-initramfs", "-u"]) 473 | subprocess.run(["sudo", "update-grub"]) 474 | os.system("sudo /usr/lib/linuxmint/mintsystem/mint-adjust.py") 475 | self.warn("The encrypted swap was successfully disabled. Reboot your computer to make\n" 476 | " sure the new configuration is functional before proceeding with the upgrade.") 477 | sys.exit(0) 478 | except Exception as e: 479 | messages = [] 480 | messages.append("Something went wrong trying to update your swap configuration: %s" % e) 481 | messages.append("Abort the upgrade and check /etc/fstab and /etc/crypttab.") 482 | messages.append("Your original files are stored in %s and %s" % (BACKUP_FSTAB, BACKUP_CRYPTTAB)) 483 | messages.append("Restore the latest system snapshot to go back to your original configuration.") 484 | self.critical_warn(messages) 485 | sys.exit(1) 486 | 487 | if not self.points_to_destination: 488 | # Check packages 489 | 490 | self.progress("Checking packages") 491 | cache = apt.Cache() 492 | need_to_remove = [] 493 | for pkg_name in CHECK_ABSENT: 494 | if pkg_name in cache.keys(): 495 | pkg = cache[pkg_name] 496 | if pkg.is_installed: 497 | need_to_remove.append(pkg.name) 498 | if len(need_to_remove) > 0: 499 | self.fail("The following packages create issues with this upgrade, please remove them:\n\n %s\n" % ", ".join(need_to_remove)) 500 | need_to_install = [] 501 | for pkg_name in CHECK_PRESENT: 502 | if pkg_name in cache.keys(): 503 | pkg = cache[pkg_name] 504 | if not pkg.is_installed: 505 | need_to_install.append(pkg.name) 506 | if len(need_to_install) > 0: 507 | self.fail("The following packages are required for a smooth install, please install them:\n\n %s\n" % ", ".join(need_to_install)) 508 | 509 | # Check that we're up to date 510 | self.progress("Checking if Linux Mint is up to date") 511 | for pkg in CHECK_UP_TO_DATE: 512 | if pkg in cache: 513 | pkg = cache[pkg] 514 | if pkg.is_installed and pkg.installed.version != pkg.candidate.version: 515 | self.fail("Your operating system is not up to date. Please apply available updates and reboot the computer.") 516 | 517 | # Switch to the destination APT sources 518 | if not os.path.exists(BACKUP_APT_SOURCES): 519 | self.progress("Backing up your APT sources") 520 | os.system("mkdir -p %s" % BACKUP_APT_SOURCES) 521 | os.system("cp -R /etc/apt/sources.* %s/" % BACKUP_APT_SOURCES) 522 | self.progress("Setting up the repositories for %s" % DESTINATION) 523 | print("") 524 | if os.path.exists("/etc/apt/sources.list"): 525 | self.check_command("sudo truncate --size 0 /etc/apt/sources.list", "Failed to configure APT sources") 526 | self.check_command("sudo mkdir -p /etc/apt/sources.list.d", "Failed to configure APT sources") 527 | self.check_command("sudo rm -rf /etc/apt/sources.list.d/*", "Failed to configure APT sources") 528 | self.check_command("sudo cp /usr/share/linuxmint/mintupgrade/apt_destination_sources /etc/apt/sources.list.d/official-package-repositories.list", "Failed to configure APT sources") 529 | self.try_command(2, 'DEBIAN_PRIORITY=critical sudo apt-get update', []) 530 | 531 | def check(self): 532 | messages = [] 533 | messages.append("APT will now calculate the package changes necessary to upgrade to %s.\n" % DESTINATION) 534 | messages.append("If conflicts are detected and APT is unable to perform the upgrade, take note of the packages") 535 | messages.append("causing the issue, remove them, and restart the upgrade.\n") 536 | messages.append("Pay close attention to what appears on the screen, particularly the packages being REMOVED. Take") 537 | messages.append("note of any you may wish to reinstall after the upgrade.") 538 | self.continue_press_enter(messages) 539 | self.progress("Simulating an upgrade") 540 | print("") 541 | os.system('DEBIAN_PRIORITY=critical sudo apt-get dist-upgrade -o Dpkg::Options::="--force-confnew" -o Dpkg::Options::="--force-overwrite" --assume-no') 542 | 543 | cache = apt.Cache() 544 | cache.upgrade(True) 545 | changes = cache.get_changes() 546 | incorrect_removals = [] 547 | kept_packages = [] 548 | for pkg in changes: 549 | if pkg.is_installed: 550 | if pkg.marked_keep: 551 | kept_packages.append(pkg.name) 552 | elif pkg.marked_delete and pkg.name in IMPORTANT_PACKAGES: 553 | incorrect_removals.append(pkg.name) 554 | if len(incorrect_removals) > 0: 555 | self.restore_sources() 556 | self.fail("Performing the upgrade would remove the following important packages:\n\n %s\n" % ", ".join(sorted(incorrect_removals))) 557 | if len(kept_packages) > 0: 558 | self.warn("The following packages will be kept back during the upgrade:\n\n %s\n\n" 559 | " This might or might not indicate a problem. Check the APT output above to decide\n" 560 | " whether to continue with the upgrade. If this OK you might need to update them\n" 561 | " manually after the upgrade." % ", ".join(sorted(kept_packages))) 562 | 563 | self.progress("Checking disk space requirements") 564 | self.check_disk_space_requirements(cache) 565 | 566 | if self.command == "check": 567 | self.warn("Command '%s' completed successfully" % self.command) 568 | 569 | set_check_completed() 570 | 571 | def download(self): 572 | self.progress("Downloading upgrade packages") 573 | print("") 574 | self.check_command("DEBIAN_PRIORITY=critical sudo apt-get dist-upgrade --download-only --yes", "Failed to download packages for the upgrade.") 575 | 576 | if self.command == "download": 577 | self.warn("Command '%s' completed successfully" % self.command) 578 | 579 | def upgrade(self): 580 | self.progress("Disabling screensaver and power management") 581 | self.disable_pm() 582 | 583 | # temporarily allow libpam to auto-restart without asking the user 584 | libpam0g_restart_val = "false" 585 | o = subprocess.check_output("sudo debconf-show libpam0g | grep libraries\/restart-without-asking", shell=True) 586 | if "true" in o.decode(): 587 | libpam0g_restart_val = "true" 588 | 589 | os.system('echo "libpam0g libraries/restart-without-asking select true" | sudo debconf-set-selections') 590 | 591 | self.progress("Saving /etc/fstab") 592 | os.system("cp /etc/fstab %s" % BACKUP_FSTAB) 593 | 594 | self.progress("Removing blacklisted packages") 595 | for removal in PACKAGES_PRE_REMOVALS: 596 | os.system('sudo apt-get remove --yes %s' % removal) # The return code indicates a failure if some packages were not found, so ignore it. 597 | 598 | messages = [] 599 | messages.append("APT will now perform the upgrade to %s." % DESTINATION) 600 | self.continue_yes_no(messages) 601 | 602 | self.progress("Performing upgrade") 603 | print("") 604 | self.reversible = False 605 | 606 | # Disable mintsystem during the upgrade 607 | os.system("sudo crudini --set /etc/linuxmint/mintSystem.conf global enabled False") 608 | 609 | fallback_commands = [] 610 | fallback_commands.append("sudo dpkg --configure -a") 611 | fallback_commands.append("sudo apt-get install -fyq") 612 | 613 | result = self.try_command(5, 'DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical sudo apt-get upgrade -fyq -o Dpkg::Options::="--force-confnew" -o Dpkg::Options::="--force-overwrite"', fallback_commands) 614 | if not result: 615 | self.progress("An issue was detected during the upgrade, running the upgrade in manual mode.") 616 | self.check_command('sudo apt-get upgrade -o Dpkg::Options::="--force-confnew" -o Dpkg::Options::="--force-overwrite"', "Failed to upgrade some of the packages. Please review the error message, use APT to fix the situation and try again.") 617 | 618 | result = self.try_command(5, 'DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical sudo apt-get dist-upgrade -fyq -o Dpkg::Options::="--force-confnew" -o Dpkg::Options::="--force-overwrite"', fallback_commands) 619 | if not result: 620 | self.progress("An issue was detected during the upgrade, running dist-upgrade in manual mode.") 621 | self.check_command('sudo apt-get dist-upgrade -o Dpkg::Options::="--force-confnew" -o Dpkg::Options::="--force-overwrite"', "Failed to dist-upgrade some of the packages. Please review the error message, use APT to fix the situation and try again.") 622 | 623 | self.progress("Re-installing the meta-package for your edition of Linux Mint") 624 | self.check_command('sudo apt-get install --yes %s' % self.mint_meta, "Failed to install %s" % self.mint_meta) 625 | 626 | # Enable APT recommends 627 | self.progress("Re-enabling APT recommends") 628 | if os.path.exists("/etc/apt/apt.conf.d/00recommends"): 629 | os.system("sudo rm -f /etc/apt/apt.conf.d/00recommends") 630 | 631 | self.progress("Re-installing the multimedia codecs") 632 | self.check_command('sudo apt-get install --yes mint-meta-codecs', "Failed to install mint-meta-codecs") 633 | 634 | self.progress("Installing new packages") 635 | self.check_command('sudo apt-get install --yes %s' % " ".join(PACKAGES_ADDITIONS), "Failed to install additional packages.") 636 | 637 | self.progress("Removing obsolete packages") 638 | for removal in PACKAGES_REMOVALS: 639 | os.system('sudo apt-get purge --yes %s' % removal) # The return code indicates a failure if some packages were not found, so ignore it. 640 | 641 | self.progress("Running autoremove to remove unused packages") 642 | self.check_command("sudo apt-get --purge autoremove --yes -o APT::AutoRemove::SuggestsImportant=false", "Failed to autoremove unused packages.") 643 | 644 | self.progress("Performing system adjustments") 645 | os.system("sudo rm -f /etc/systemd/logind.conf") 646 | os.system("apt install --reinstall -o Dpkg::Options::=\"--force-confmiss\" systemd") 647 | os.system("sudo rm -f /etc/polkit-1/localauthority/50-local.d/com.ubuntu.enable-hibernate.pkla") 648 | 649 | if os.path.exists("/usr/share/ubuntu-system-adjustments/systemd/adjust-grub-title"): 650 | os.system("sudo /usr/share/ubuntu-system-adjustments/systemd/adjust-grub-title") 651 | elif os.path.exists("/usr/share/debian-system-adjustments/systemd/adjust-grub-title"): 652 | os.system("sudo /usr/share/debian-system-adjustments/systemd/adjust-grub-title") 653 | 654 | # Re-enable mintsystem 655 | os.system("sudo crudini --set /etc/linuxmint/mintSystem.conf global enabled True") 656 | os.system("sudo /usr/lib/linuxmint/mintsystem/mint-adjust.py") 657 | 658 | # Restore /etc/fstab if it was changed 659 | if not filecmp.cmp('/etc/fstab', BACKUP_FSTAB): 660 | os.system("cp /etc/fstab %s.upgraded" % BACKUP_FSTAB) 661 | os.system("sudo cp %s /etc/fstab" % BACKUP_FSTAB) 662 | self.warn("A package modified /etc/fstab during the upgrade. To ensure a successful boot, the\n" 663 | " upgrader restored your original /etc/fstab and saved the modified file in \n" 664 | " %s.upgraded." % BACKUP_FSTAB) 665 | 666 | # restore libpam0g restart value 667 | os.system('echo "libpam0g libraries/restart-without-asking select %s" | sudo debconf-set-selections' % libpam0g_restart_val) 668 | 669 | self.progress("The upgrade is finished. Reboot the computer with \"sudo reboot\" when ready.") 670 | 671 | def disable_pm(self): 672 | os.system("killall cinnamon-screensaver") 673 | os.system("killall mate-screensaver") 674 | os.system("killall light-locker") 675 | 676 | inhibit_failed = False 677 | 678 | current_desktop = os.getenv("XDG_CURRENT_DESKTOP") 679 | if current_desktop != None: 680 | current_desktop = current_desktop.lower().replace("x-", "") # X-Cinnamon 681 | p = subprocess.Popen(["mintupgrade-inhibit-power", str(os.getpid()), current_desktop]) 682 | 683 | try: 684 | # process should wait until we exit. If it exits early, it failed. 685 | p.wait(3) 686 | inhibit_failed = True 687 | except: 688 | pass 689 | else: 690 | inhibit_failed = True 691 | 692 | if inhibit_failed: 693 | messages = [] 694 | messages.append("Could not inhibit session. You should disable power management for the duration") 695 | messages.append(" of this upgrade, and refrain from logging out or switching users.") 696 | self.continue_yes_no(messages, False) 697 | 698 | def check_disk_space_requirements(self, cache): 699 | download_size = 0.0 700 | additional_space_needed = 0.0 701 | 702 | # get download size 703 | pm = apt_pkg.PackageManager(cache._depcache) 704 | fetcher = apt_pkg.Acquire() 705 | 706 | # this may fail, but you'll still get the download size, vs cache.required_download 707 | try: 708 | pm.get_archives(fetcher, cache._list, cache._records) 709 | except: 710 | pass 711 | 712 | download_size = fetcher.fetch_needed 713 | 714 | # additional space needed when all finished 715 | additional_space_needed = cache._depcache.usr_size 716 | 717 | # gather mount info so we calculate free space correctly. 718 | mounted = [] 719 | mnt_map = {} 720 | fs_free = {} 721 | with open("/proc/mounts") as mounts: 722 | for line in mounts: 723 | try: 724 | (what, where, fs, options, a, b) = line.split() 725 | except ValueError as e: 726 | # print("line '%s' in /proc/mounts not understood (%s)" % (line, e)) 727 | continue 728 | if not where in mounted: 729 | mounted.append(where) 730 | # make sure mounted is sorted by longest path 731 | mounted.sort(key=len, reverse=True) 732 | 733 | class FreeSpace(object): 734 | " helper class that represents the free space on each mounted fs " 735 | def __init__(self, initialFree): 736 | self.initial_free = initialFree 737 | self.free = initialFree 738 | self.need = 0 739 | 740 | def make_fs_id(d): 741 | """ return 'id' of a directory so that directories on the 742 | same filesystem get the same id (simply the mount_point) 743 | """ 744 | for mount_point in mounted: 745 | if d.startswith(mount_point): 746 | return mount_point 747 | return "/" 748 | 749 | archivedir = apt_pkg.config.find_dir("Dir::Cache::archives") 750 | 751 | for d in ["/", "/usr", "/boot", archivedir, "/tmp/"]: 752 | d = os.path.realpath(d) 753 | fs_id = make_fs_id(d) 754 | if os.path.exists(d): 755 | st = os.statvfs(d) 756 | free = st.f_bavail * st.f_frsize 757 | else: 758 | # print("directory '%s' does not exists" % d) 759 | free = 0 760 | if fs_id in mnt_map: 761 | # print("Dir %s mounted on %s" % (d, mnt_map[fs_id])) 762 | fs_free[d] = fs_free[mnt_map[fs_id]] 763 | else: 764 | # print("Free space on %s: %s" % (d, free)) 765 | mnt_map[fs_id] = d 766 | fs_free[d] = FreeSpace(free) 767 | 768 | # sum up space requirements 769 | for (dir, size) in [ 770 | (archivedir, download_size), 771 | ("/usr", additional_space_needed), 772 | # plus 50M safety buffer in /usr 773 | ("/usr", 50 * 1024 * 1024), # buffer 774 | ("/boot", 50 * 1024 * 1024), # buffer - should we calculate real kernel/initramfs space required? 775 | ("/tmp", 5 * 1024 * 1024), # /tmp for dkms LP: #427035 776 | ("/", 10 * 1024 * 1024), # more buffer / 777 | ]: 778 | # we are ensuring we have more than enough free space not less 779 | if size < 0: 780 | continue 781 | dir = os.path.realpath(dir) 782 | # print("dir '%s' needs '%s' of '%s' (%f)" % (dir, size, fs_free[dir], fs_free[dir].free)) 783 | fs_free[dir].free -= size 784 | fs_free[dir].need += size 785 | 786 | # check for space required violations 787 | insufficient = False 788 | 789 | ok_messages = {} 790 | problem_messages = {} 791 | for dir in fs_free: 792 | free_needed_str = GLib.format_size(fs_free[dir].need) 793 | initial_free_str = GLib.format_size(fs_free[dir].initial_free) 794 | 795 | if fs_free[dir].free < 0: 796 | free_at_least_str = GLib.format_size(abs(fs_free[dir].free) + 1024 * 1024 * 10) 797 | 798 | problem_messages[make_fs_id(dir)] = "You need %s on '%s' but only have %s. You must free an additional %s." \ 799 | % (free_needed_str, make_fs_id(dir), initial_free_str, free_at_least_str) 800 | else: 801 | ok_messages[make_fs_id(dir)] = "You need %s on '%s' for the upgrade process, and have %s free - looks good." \ 802 | % (free_needed_str, make_fs_id(dir), initial_free_str) 803 | 804 | if len(ok_messages) > 0: 805 | for msg in list(ok_messages.values()): 806 | self.progress(msg) 807 | 808 | if len(problem_messages) > 0: 809 | self.critical_warn(list(problem_messages.values())) 810 | self.fail("Please free up the required disk space and try again.") 811 | 812 | def check_command(self, command, message): 813 | ret = os.system(command) 814 | if ret != 0: 815 | self.fail(message) 816 | 817 | def try_command(self, num_times, command, fallback_commands): 818 | success = False 819 | for i in range(num_times): 820 | ret = os.system(command) 821 | if ret == 0: 822 | return True 823 | self.progress("Error detected on try #%d..." % (i+1)) 824 | if (i+1) < num_times: 825 | self.progress("Retrying...") 826 | for fallback_command in fallback_commands: 827 | self.progress ("Running fallback command '%s'" % fallback_command) 828 | os.system(fallback_command) 829 | 830 | def fail(self, message): 831 | print ("") 832 | print ("------------------------------------------------") 833 | print ("%s!! ERROR: %s%s" % (bcolors.FAIL, message, bcolors.ENDC)) 834 | print ("!! Exiting.") 835 | print ("------------------------------------------------") 836 | if self.reversible: 837 | self.restore_sources() 838 | sys.exit(1) 839 | 840 | def critical_warn(self, messages): 841 | print("") 842 | print("%s!! WARNING:%s\n" % (bcolors.WARNING, bcolors.ENDC)) 843 | for line in messages: 844 | print ("%s %s%s" % (bcolors.WARNING, line, bcolors.ENDC)) 845 | print("") 846 | 847 | def continue_press_enter(self, messages): 848 | print ("") 849 | for message in messages: 850 | print ("%s %s%s" % (bcolors.WARNING, message, bcolors.ENDC)) 851 | print("") 852 | answer = input("%s Press Enter to continue%s" % (bcolors.OKGREEN, bcolors.ENDC)) 853 | 854 | def continue_yes_no(self, messages, restore=True): 855 | print ("") 856 | for message in messages: 857 | print ("%s %s%s" % (bcolors.WARNING, message, bcolors.ENDC)) 858 | answer = None 859 | while (answer not in ["y", "yes", "n", "no"]): 860 | print ("") 861 | answer = input("%s Do you want to continue? [y/n]:%s " % (bcolors.OKGREEN, bcolors.ENDC)).lower() 862 | if answer in ["n", "no"]: 863 | print ("") 864 | print (" Exiting...") 865 | if self.reversible and restore: 866 | self.restore_sources() 867 | else: 868 | print ("") 869 | sys.exit(0) 870 | 871 | def progress(self, message): 872 | print ("") 873 | print ("%s + %s...%s" % (bcolors.HEADER, message, bcolors.ENDC)) 874 | 875 | def warn(self, message): 876 | print ("") 877 | print ("%s + %s%s" % (bcolors.WARNING, message, bcolors.ENDC)) 878 | print ("") 879 | 880 | def usage(): 881 | print ("") 882 | print ("%sUsage:%s mintupgrade command" % (bcolors.HEADER, bcolors.ENDC)) 883 | print ("") 884 | print ("%sCommands:%s" % (bcolors.HEADER, bcolors.ENDC)) 885 | print (" help - prints this usage note") 886 | print (" check - checks the upgrade to %s. You should run this first." % DESTINATION) 887 | print (" download - downloads the packages for the upgrade to %s" % DESTINATION) 888 | print (" upgrade - upgrades to %s, performing all necessary steps." % DESTINATION) 889 | print (" restore-sources - restores the backed up APT sources (only use this") 890 | print (" command if you're still running %s)" % ORIGIN) 891 | print ("") 892 | sys.exit(0) 893 | 894 | if __name__ == '__main__': 895 | 896 | if os.getuid() == 0: 897 | print ("") 898 | print ("Please don't run this command as root or with elevated privileges.") 899 | print ("") 900 | sys.exit(1) 901 | 902 | os.system("clear") 903 | 904 | if len(sys.argv) != 2: 905 | usage() 906 | command = sys.argv[1] 907 | if command == "help": 908 | usage() 909 | 910 | upgrader = MintUpgrade(command) 911 | 912 | if command == "restore-sources": 913 | upgrader.restore_sources() 914 | elif command == "check": 915 | rm_check_completed() 916 | upgrader.prepare() 917 | upgrader.check() 918 | upgrader.restore_sources() 919 | elif command == "download": 920 | ensure_check_completed() 921 | upgrader.prepare() 922 | upgrader.download() 923 | upgrader.restore_sources() 924 | elif command == "upgrade": 925 | ensure_check_completed() 926 | upgrader.prepare() 927 | upgrader.download() 928 | upgrader.upgrade() 929 | rm_check_completed() 930 | else: 931 | usage() 932 | -------------------------------------------------------------------------------- /usr/bin/mintupgrade-inhibit-power: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | import psutil 4 | import time 5 | 6 | from gi.repository import GLib, Gio 7 | 8 | desktop = sys.argv[2] 9 | bus = Gio.bus_get_sync(Gio.BusType.SESSION) 10 | 11 | # 1: Inhibit logging out 12 | # 2: Inhibit user switching 13 | # 4: Inhibit suspending the session or computer 14 | # 8: Inhibit the session being marked as idle 15 | 16 | FLAGS = 1 | 2 | 4 | 8 17 | 18 | if desktop == "xfce": 19 | name = "org.freedesktop.PowerManagement" 20 | path = "/org/freedesktop/PowerManagement/Inhibit" 21 | iface = "org.freedesktop.PowerManagement.Inhibit" 22 | args = GLib.Variant("(ss)", ("mintupgrade", "Performing a system upgrade")) 23 | else: 24 | name = "org.gnome.SessionManager" 25 | path = "/org/gnome/SessionManager" 26 | iface = "org.gnome.SessionManager" 27 | args = GLib.Variant("(susu)", ("mintupgrade", 0, "Performing a system upgrade", FLAGS)) 28 | 29 | try: 30 | bus.call_sync(name, 31 | path, 32 | iface, 33 | "Inhibit", 34 | args, 35 | None, 36 | Gio.DBusCallFlags.NONE, 37 | 2000, 38 | None) 39 | except Exception as e: 40 | exit(1) 41 | # print(e) 42 | 43 | while psutil.pid_exists(int(sys.argv[1])): 44 | time.sleep(1) 45 | 46 | exit(0) -------------------------------------------------------------------------------- /usr/share/linuxmint/mintupgrade/apt_destination_sources: -------------------------------------------------------------------------------- 1 | # Do not edit this file manually, use Software Sources instead. 2 | 3 | deb http://packages.linuxmint.com ulyana main upstream import backport #id:linuxmint_main 4 | 5 | deb http://archive.ubuntu.com/ubuntu focal main restricted universe multiverse 6 | deb http://archive.ubuntu.com/ubuntu focal-updates main restricted universe multiverse 7 | deb http://archive.ubuntu.com/ubuntu focal-backports main restricted universe multiverse 8 | 9 | deb http://security.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse 10 | deb http://archive.canonical.com/ubuntu/ focal partner 11 | --------------------------------------------------------------------------------