├── .gitignore ├── exclude.txt ├── .github ├── CONTRIBUTING.md └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── CODE_OF_CONDUCT.md ├── README.md └── shrink-backup /.gitignore: -------------------------------------------------------------------------------- 1 | /exclude.txt 2 | -------------------------------------------------------------------------------- /exclude.txt: -------------------------------------------------------------------------------- 1 | /lost+found 2 | /proc/* 3 | /sys/* 4 | /dev/* 5 | /tmp/* 6 | /run/* 7 | /mnt/* 8 | /media/* 9 | /var/swap 10 | /snap/* 11 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | All pull requests have to be made to the testing branch. 2 | Please try to follow the standards in the script, naming conversion, indentation (2 in bash) etc. 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context**
20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve 4 | title: 'Bug in shrink-backup' 5 | labels: bug 6 | assignees: UnconnectedBedna 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior. 15 | 16 | **Harware (please complete the following information):** 17 | - Hardware: [f.ex. raspberrypi 4] 18 | - OS: [f.ex. rpi bookworm OR armbian for opi5] 19 | 20 | **Additional context** 21 | Add any other context about the problem here. 22 | For example output of `lsblk` or `df -h` 23 | 24 | **Log** 25 | Run in debug mode by using `-l` option and please provide the output of `shrink-backup.log` 26 | Copy/paste the **complete log of the backup that fails** (not the entire log file, there could be multiple backups), not just the parts you think could be useful. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024-present, Marcus Johansson 4 | https://github.com/UnconnectedBedna 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shrink-backup 2 | 3 | _I made this script because I wanted a universal method of backing up my SBC:s into small img files as fast as possible (with rsync), indepentent of what os is in use._ 4 | 5 | shrink-backup is a very fast utility for backing up your SBC:s into minimal bootable img files for easy restore with autoexpansion at boot. 6 | 7 | Can backup any device with or without a `boot` partition as long as the filesystem is `ext4`, [`f2fs`](#f2fs) or [`btrfs`](#btrfs) (with subvolumes). 8 | 9 | Supports backing up `root` & `boot` (if existing) partitions. Data from other partitions will be written to `root` if not [excluded](#-t-excludetxt). 10 | For [`btrfs`](#btrfs), all existing top level 5 subvolumes in `/etc/fstab` will be created with new backups, nested subvolumes will be created and can also be removed/added in an [update](#image-update) of the backup img. 11 | Please read [Info](#info) section for more information. 12 | 13 | Autoexpansion tested & supported on following operating systems: 14 | - Raspberry Pi OS (trixie and older) 15 | - Armbian 16 | - Manjaro-arm 17 | - DietPi 18 | - ArchLinuxArm 19 | - Kali-arm 20 | - Ubuntu-server-arm (Ubuntu autoexpands by default, but that can be disabled with `-e` option) 21 | 22 | Autoexpansion does not work on [`f2fs`](#f2fs) due to filesystem limitations. 23 | Other operating systems will most likely work too, but autoexpansion will not. 24 | The script will report the operating system as "unknown" but that does not mean the script will fail. 25 | Feel free to make a [feature request](https://github.com/UnconnectedBedna/shrink-backup/issues/new/choose) if you use an operating system not on this list. 26 | 27 | Full functionality for usage inside [webmin](https://webmin.com/) (including "custom command" button). Thank you to [iliajie](https://github.com/iliajie) for helping out. ❤️ 28 | 29 | **Latest release:** [shrink-backup v1.4](https://github.com/UnconnectedBedna/shrink-backup/releases/download/v1.4/shrink-backup.v1.4.tar.gz) 30 | [**Testing branch:**](https://github.com/UnconnectedBedna/shrink-backup/tree/testing) If you want to use the absolute latest version. There might be bugs. 31 | 32 | **Very fast restore thanks to minimal size of img file.** 33 | 34 | Default device that will be backed up is determined by scanning what disk-device `root` resides on. 35 | This means that **if** `boot` is a partition, that partition must be on the **same device and before the `root` partition**. 36 | The script considers everything on the device before `root` as the bootsector. 37 | 38 | Backing up/restoring, to/from: usb-stick `/dev/sdX` with Raspberry pi os has been tested and works. Ie, writing an sd-card img to a usb-stick and vice versa works. 39 | 40 | **Ultra-fast incremental backups to existing img files.** 41 | 42 | See [wiki](https://github.com/UnconnectedBedna/shrink-backup/wiki) for information about [installation methods](https://github.com/UnconnectedBedna/shrink-backup/wiki/Installing), usage and examples. 43 | [Ideas and feedback](https://github.com/UnconnectedBedna/shrink-backup/discussions) is always appreciated, whether it's positive or negative. Please just keep it civil. :) 44 | If you find a bug or think something is missing in the script, please file a [bug report or Feature request](https://github.com/UnconnectedBedna/shrink-backup/issues/new/choose) 45 | 46 | **To restore a backup, simply "burn" the img file to a device using your favorite method.** 47 | 48 | When booting a restored image with autoresize active, on some operating systems a reboot will occur after resizing is made (you will be informed at the end of the script if your operating system is affected by this), **please wait until the the reboot sequence has occurred.** The login prompt _may_ very well become visible before the autoresize function has rebooted. 49 | 50 |
51 | 52 | ## Usage 53 | ``` 54 | shrink-backup -h 55 | Script for creating an .img file and subsequently keeing it updated (-U), autoexpansion is enabled by default 56 | Directory where .img file is created is automatically excluded in backup 57 | ######################################################################## 58 | Usage: sudo shrink-backup [options] imagefile.img [extra space (MiB)] 59 | -U Update existing img file (rsync to existing img) 60 | Optional [extra space] extends img root partition 61 | -a Autocalculate root size partition, [extra space] is ignored. 62 | When used in combination with -U: 63 | Expand if partition is >=256MiB smaller than autocalculated recommended minimum 64 | Shrink if partition is >=512MiB bigger than autocalculated recommended minimum 65 | -t Use exclude.txt in same folder as script to set excluded directories 66 | One directory per line: "/dir" or "/dir/*" to only exclude contents 67 | -y Disable prompts in script (please use this option with care!) 68 | -e Disable autoexpansion on root filesystem when image is booted 69 | -l Write debug messages to logfile shrink-backup.log located in same directory as script 70 | -z Make script zoom at light-speed, only question prompts might slow it down 71 | Can be combined with -y for UNSAFE ultra-mega-superduper-speed 72 | -q --quiet Do not print rsync copy process 73 | --no-color Run script without color formatted text 74 | --fix Try to fix the img file if -a fails with a "broken pipe" error 75 | Will activate rsync options --delete-before & --fsync 76 | --rsync Define custom rsync line manually. Will print rsync line for user to edit 77 | --loop [img] Loop img file and exit, works in combination with -l & -z 78 | If optional [extra space] is defined, the img file will be extended with the amount before looping 79 | NOTE that only the file gets truncated, no partitions 80 | Useful if you for example want to manually manage the partitions 81 | --chroot [img] Use systemd-nspawn. Loop img file, mount to temp directory, enter chroot environment and drop to shell 82 | This will let you make changes in a chroot environment directly on the img file 83 | For example update with package manager or rebuild initramfs 84 | The script will keep running in the background 85 | Type 'exit' when done. Script will unmount, remove temp directory/loop and exit 86 | --f2fs Convert root filesystem on img from ext4 to f2fs 87 | Only works on new img file, not in combination with -U 88 | Will make backups of fstab & cmdline.txt to: fstab.shrink-backup.bak & cmdline.txt.shrink-backup.bak 89 | Then change ext4 to f2fs in both files and add discard to options on root partition in fstab 90 | --version Print version and exit 91 | -h --help Show this help snippet 92 | ######################################################################## 93 | Examples: 94 | sudo shrink-backup -a /path/to/backup.img (create img, resize2fs calcualtes size) 95 | sudo shrink-backup -e -y /path/to/backup.img 1024 (create img, ignore prompts, do not autoexpand, add 1024MiB extra space) 96 | sudo shrink-backup -Utl /path/to/backup.img (update img backup, use exclude.txt and write log to shrink-backup.log) 97 | sudo shrink-backup -U /path/to/backup.img 1024 (update img backup, expand img size/root partition with 1024MiB) 98 | sudo shrink-backup -Ua /path/to/backup.img (update img backup, resize2fs calculates and resizes img file if needed) 99 | sudo shrink-backup -Ua --fix /path/to/backup.img 1024 (update img backup, automatically resizes img file if needed, fix img free space) 100 | sudo shrink-backup -l --loop /path/to/backup.img 1024 (write to log file, expand IMG FILE (not partition) by 1024MiB and loop) 101 | ``` 102 | 103 | #### `-t` (exclude.txt) 104 | The folder where the img file is created will **ALWAYS be excluded in the backup.**
105 | If `-t` option is selected, `exclude.txt` **MUST exist** (but can be empty) within the **directory where the script is located** or the script will exit with an error. 106 | 107 | > [!NOTE] 108 | > If installed using `curl`, location and name of file is different. See [install with curl](https://github.com/UnconnectedBedna/shrink-backup/wiki/Installing#curl---shrink-backup-install-script) for information. 109 | 110 | Use one directory per line in `exclude.txt`. 111 | `/directory/*` = create directory but exclude content. 112 | `/directory` = exclude the directory completely. 113 | `/directory*` = exclude all directories starting with name `/directory`. 114 | 115 | > [!NOTE] 116 | > `btrfs` uses another file to exclude subvolumes, see [btrfs](#btrfs) for information. 117 | 118 | The paths must be absolute, ie `~/directory` will not work. 119 | 120 | If `-t` is **NOT** selected the following will be excluded: 121 | ``` 122 | /lost+found 123 | /proc/* 124 | /sys/* 125 | /dev/* 126 | /tmp/* 127 | /run/* 128 | /mnt/* 129 | /media/* 130 | /var/swap 131 | /snap/* 132 | ``` 133 | Please read [info](#info) section for more information. 134 |
135 | #### `-l` (Log file) 136 | Use `-l` to write debug info into `shrink-backup.log` file located in the same directory as the script. 137 | Please provide this file if filing a [bug report](https://github.com/UnconnectedBedna/shrink-backup/issues/new/choose) 138 | 139 | > [!NOTE] 140 | > If installed using `curl`, location and name of file is different. See [install with curl](https://github.com/UnconnectedBedna/shrink-backup/wiki/Installing#curl---shrink-backup-install-script) for information. 141 |
142 | 143 | #### `-z` (Zoom speed) 144 | The `-z` "zoom" option simply removes the one second sleep at each info prompt to give the user time to read. 145 | By using the option, you save 15-25s when running the script. 146 | 147 | > [!CAUTION] 148 | > When used in combination with `-y` **warnings will also be bypassed! PLEASE use with care!** 149 | > The script will for example overwrite files without asking for confirmation! 150 |
151 | 152 | #### `--fix` (Broken pipe) 153 | Add `--fix` to your options if a backup fails during `rsync` with a "broken pipe" error. This will make `rsync` use options `--delete-before` & `--fsync`. 154 | You can also manually add `[extra space]` instead of using `-Ua --fix` to solve this. 155 | 156 | **Example:** `sudo shrink-backup -Ua --fix /path/to/backup.img` 157 | 158 | The reason it happens is because `rsync` normally deletes files during the backup, not creating a file-list > removing files from img before starting to copy. 159 | So if you have removed and added new data on the system you backup from, there is a risk `rsync` tries to copy the new data before deleting data from the img, hence completely filling the img. 160 | 161 | Using `--fix` configures `rsync` create a file-list and delete data **before** starting to transfer new data. This also means the backup takes a little longer. 162 | Having a "broken pipe" error during backup has in my experience never broken an img backup after either using `--fix` or adding `[extra space]` while updating the backup with `-U`. 163 |
164 |
165 | #### `--loop` (Loop img file) 166 | Use `--loop` to loop an img file to your `/dev`. 167 | This functionality works on any linux system, just use the script on any img type file (not limited to `.img` extension files) anywhere available to the computer. 168 | 169 | **Example:** `sudo shrink-backup --loop /path/to/backup.img` 170 | 171 | If used in combination with `[extra space]` the amount in MiB will be added to the **IMG FILE, NOT any partition.** 172 | 173 | **Example:** `sudo shrink-backup --loop /path/to/backup.img 1024` 174 | 175 | With this you can for example run: `sudo gparted /dev/loop0` (use correct `loop` it got assigned to) if you have a graphical interface to manually manage the img partitions in a graphical interface with `gparted`. 176 | You can ofc use any partition manager for this. 177 | If you added `[extra space]` this will then show up as unpartitioned space at the end of the device where you can create partition(s) and manually copy data to by mounting the new `loop` partition(s) that will become visible in `lsblk`. 178 | If you do this, don't forget to create or update the img with `-e` (disable autoexpansion) first. Autoexpansion will not work since the space will be occupied by your manually managed partition(s). 179 | You can still update the img file with `-U` as long as the img `root` partition is big enough to hold the data from the device you backup from, but make sure to [exclude](#-t-excludetxt) your manually managed partition(s) or they will be copied to the img `root` partition. 180 | 181 | To remove the loop: `sudo losetup -d /dev/loop0`, (use correct `loop` it got assigned to) 182 | To remind yourself: `lsblk /dev/loop*` (if you forgot what `loop` it got assigned to) 183 |
184 |
185 | #### --chroot 186 | Use `systemd-nspawn` to chroot into an img created from the system you are currently running. 187 | Works on all supported filesystems but you have to run the command on the system you created the img from, the script looks at the system you are running to create mountpoint(s) and chroot into. 188 | A dependency check will be made and if you lack any of the packages needed the script will ask if you want to install them. 189 | Then a temp directory will be created inside `/tmp` to use as mountpoint, loop the img and mount partitions from the loop appropriately. 190 | 191 | **Example:** `sudo shrink-backup --chroot /path/to/backup.img` 192 | 193 | With this you can for example experiment with things you don't want to do on your running system, do changes to kernel, rebuild initramfs, update/install packages etc. 194 | You will run as `root` user inside the img so unless you `su` to another user there is no reason to use `sudo`. 195 | 196 | When done, type `exit`. The script will unmount img, remove loop, delete temp directory and exit shrink-backup. 197 | You can then write the img file and test if things work without risking corruption of the system you run. 198 | 199 | Systemd logs will not register changes made on the img though because the log-id:s on the mounted img will be the same as on the system you run (but can probably be made to work, if you feel like helping out, creating pr:s to the testing branch of shrink-backup would be highly appreciated) 200 |
201 |
202 | #### `--f2fs` (Convert `ext4` into `f2fs` on img file) 203 | > [!IMPORTANT] 204 | > ONLY use this for **CONVERTING** filesystem into img file, **if you already have `f2fs` on the system you backup from, do not use this option.** 205 | > The script will detect what filesystem is used on `root` and act accordingly. 206 | 207 | Only supported for Raspberry Pi OS with new backups, not when using `-U`. 208 | 209 | Autoexpansion at boot is not supported for `f2fs` (there is no way of resizing a mounted `f2fs` filesystem, unlike with `ext4`) so resizing root partition have to be made manually after writing img to sd-card. 210 | Resize operations (when updating backup with `-U`) is not available for `f2fs` _as of now_. 211 | 212 | The script will make backups of `fstab` & `cmdline.txt` into `fstab.shrink-backup.bak` & `cmdline.txt.shrink-backup.bak` on the img. 213 | It will then change from `ext4` to `f2fs` in `fstab` & `cmdline.txt` and add `discard` to the options on the `root` partition in `fstab`. 214 | 215 | Please read information about [`f2fs`](#f2fs) further down. 216 |
217 |
218 | ### Info 219 | 220 | The script works on any device as long as root filesystem is `ext4`, [`f2fs`](#f2fs) or [`btrfs`](#btrfs) with subvolumes. 221 | Since the script uses `lsblk` to crosscheck with `/etc/fstab` to figure out where `root` resides it does not matter what device it is located on. 222 | 223 | If `boot` is a partition, that partition must be on the **same device and before the `root` partition**. 224 | The script considers everything on the device before `root` as the bootsector. 225 | 226 | Even if you forget to disable autoexpansion on a non supported OS, the backup will not fail, it will just skip creating the autoresize script. 227 | 228 | > [!IMPORTANT] 229 | > **Rsync WILL cross filesystem boundries, so make sure you [exclude](#-t-excludetxt) external mounts and other partitions unless you want them included in the `root` partition of the img backup.** (separate `/home` for example) 230 | 231 | - The script will **ONLY** create `boot` (if exits) and `root` partitions on the img file. 232 | - The script will **ONLY** look at your `root` partition when calculating sizes. 233 | 234 | **Not excluding other mounts will copy that data to the img `root` partition, not create more partitions,** so make sure to [manually add](#manually-configure-size) `[extra space]` if you do this. 235 | [`btrfs`](#btrfs) is an exception, all subvolumes (unless [excluded](#exclude_btrfstxt)) will be created. 236 | 237 | See [--loop](#--loop-loop-img-file) for how to manually include more partitions on the img. 238 |
239 |
240 | #### Applications used in the script: 241 | - fdisk 242 | - sfdisk 243 | - dd 244 | - parted 245 | - e2fsck 246 | - truncate 247 | - mkfs.ext4/f2fs/btrfs (depends on fileystem used) 248 | - rsync 249 | - gdisk (sgdisk is only required if the partition table is GPT, the script will inform you) 250 | 251 |
252 | 253 | ## Image creation 254 | 255 | The easiest way to create a backup is to let the script configure the size. 256 | To create a backup img using recommended size, use the `-a` option and the path to the img file. 257 | 258 | **Example:** `sudo shrink-backup -a /path/to/backup.img` 259 | 260 | The script will set the img size by requesting recommended minimum size from `e2fsck` or `du` (`e2fsck` does not work on `f2fs` f.ex). 261 | This is not the absolute smallest size you can achieve but is the "safest" way to create a "smallest possible" img file. 262 | If the size of the filesystem you are backing up from does not increase too much, you can most likely keep it updated with the [update function](#image-update) (`-U`) of the script. 263 |
264 |
265 | ### Manually configure size 266 | 267 | To manually configure size use `[extra space]` 268 | Space is added on top of `df` reported "used space", not the size of the partition. 269 | `[extra space]` is in **MiB**, so if you want to add **1G**, add **1024**. 270 | 271 | **Example:** `sudo shrink-backup /path/to/backup.img 1024` 272 |
273 |
274 | ### Smallest image possible 275 | 276 | To get the absolute smallest img file possible, do **NOT** use `-a` option, instead set `[extra space]` to `0` 277 | 278 | **Example:** `sudo shrink-backup /path/to/backup.img 0` 279 | 280 | This will instruct the script to get the used space from `df` and add 128MiB "*wiggle room*". 281 | If you are like me, doing a lot of testing, rewriting the sd-card multiple times when experimenting, the extra time it takes each "burn" will add up pretty fast. 282 | 283 | **Example:** 284 | ``` 285 | -rw-r--r-- 1 root root 3.7G Jul 22 21:27 test.img # file created with -a 286 | -rw-r--r-- 1 root root 3.3G Jul 22 22:37 test0.img # file created with 0 287 | ``` 288 | 289 | > [!IMPORTANT] 290 | > Because of how filesystems work, `df` is never a true representation of what will actually fit in a created img file. 291 | > Each file, no matter the size, will take up one block of the filesystem, so if you have a LOT of very small files (running `docker` f.ex) the "0 added space method" might fail during rsync. Increase the 0 a little bit and retry. 292 | > 293 | > Using this method also means you have VERY little free space on the img file after creation. 294 | > If the filesystem you back up from increases in size, an update (`-U`) of the img file might fail with lack of space. 295 |
296 | 297 | ### Order of operations - Image creation 298 | 1. Uses `lsblk` & `/etc/fstab` to figure out the correct disk device to back up. 299 | 2. Reads the block sizes of the system's `root` (and `boot` if it exists) partition. 300 | 3. Uses `dd` to create the boot part of the system + a few megabytes to include the filesystem on root. (this _can_ be a partition) 301 | 4. Uses `df` and/or `resize2fs` (depends on filesystem) to calculate sizes by analyzing the system's `root` partition. (For btrfs: `btrfs filesystem du` + 192MiB is used instead of `resize2fs`) 302 | 5. Uses `truncate` to resize img file. 303 | 6. Loops the img file. 304 | 7. Removes and recreates the `root` partition on the loop of the img file. 305 | 8. Creates the `root` filesystem on loop of the img file with the same `UUID` and `LABEL` as the system you are backing up from. 306 | 9. Creates a temp directory and mounts img file `root` partition from loop. 307 | 10. Checks if `boot` partition exists, if true, checks `fstab` and creates directory on `root` and mounts accordingly from loop. 308 | 11. Uses `rsync` to sync filesystems. 309 | 12. Tries to create autoresize scripts if supported on OS and not disabled with `-e`. 310 | 13. Unmounts and removes temp directory and file (file created for `rsync` log output). 311 | 312 |
313 | 314 | ## Image update 315 | 316 | To update an existing img file simply use the `-U` option and the path to the img file. 317 | 318 | **Example:** `sudo shrink-backup -U /path/to/backup.img` 319 |
320 |
321 | ### Autoresizing img when updating 322 | 323 | If `-a` is used in combination with `-U`, the script will compare the `root` partition on the img file to the size `resize2fs` recommends as minimum (or `du` calculations depending on filesystem). 324 | 325 | **Example:** `sudo shrink-backup -Ua /path/to/backup.img` 326 | 327 | > [!NOTE] 328 | > - The **img file** `root` **partition** needs to be **>=256MB smaller** than `resize2fs` recommended minimum (or `du` calculations) to be **expanded**. 329 | > - The **img file** `root` **partition** needs to be **>=512MB bigger** than `resize2fs` recommended minimum (or `du` calculations) to be **shrunk**. 330 | > 331 | > This is to protect from unnecessary resizing operations most likely not needed. 332 | 333 | Using combination `-Ua` on an img that has become overfilled works, if not use [`--fix`](#--fix-broken-pipe) or manually add `[extra space]` and retry. 334 |
335 |
336 | ### Manually resizing img when updating 337 | 338 | Only expansion is possible with this method. 339 | If `[extra space]` is used in combination with `-U`, the `root` partition of the img file will be expanded by that amount. 340 | `[extra space]` is in **MiB**, so if you want to add **1G**, add **1024**. 341 | 342 | **Example:** `sudo shrink-backup /path/to/backup.img 1024` 343 | 344 | **No checks are being performed to make sure the data you want to back up will actually fit.** 345 | 346 | Resizing operations are not supported with [`f2fs`](#f2fs). 347 |
348 |
349 | ### Order of operations - Image update 350 | 1. Loops the img file. 351 | 2. Probes the loop of the img file for information about partitions. 352 | 3. If `-a` is selected, calculates sizes by comparing `root` used space on system and img file by using `fdisk` & `resize2fs` (or `du` depending on filesystem). 353 | 4. Expands filesystem on img file if requested (`-a`) and conditions were met in point 3, or if _manually added_ `[extra space]` is used. 354 | 5. Creates temp directory and mounts `root` partition from loop. 355 | 6. Checks if `boot` partition exists, if true, checks `fstab` and mounts accordingly from loop. 356 | 7. Uses `rsync` to sync filesystems. 357 | 8. Shrinks filesystem on img file if requested (`-a`) and conditions were met in point 3. 358 | 9. Tries to create autoresize scripts if supported on OS and not disabled with `-e`. 359 | 10. Unmounts and removes temp directory and file (file created for `rsync` log output). 360 | 361 |
362 | 363 | ## f2fs 364 | 365 | The script will detect `f2fs` on `root` automatically and act accordingly. 366 | 367 | > [!NOTE] 368 | > **Do NOT USE [`--f2fs`](#--f2fs-Convert-ext4-into-f2fs-on-img-file) unless you are converting from a `ext4` filesystem (on your system) into `f2fs` on the img file.** 369 | 370 | Autoexpansion at boot is not possible with `f2fs`. User will have to manually expand img to cover entire storage media (f.ex sd-card) before booting a restored img. 371 | Resizing img `root` partition while updating (`-U`) is not possible with `f2fs` _as of now_. User will have to create a new backup if img runs out of space. 372 | This is something planned to be implemented further down the line. 373 | 374 |
375 | 376 | ## btrfs 377 | 378 | **ALL testing has been done on Manjaro-arm** 379 | 380 | > [!NOTE] 381 | > **THIS IS NOT A CLONE, IT IS A BACKUP OF REQUIRED FILES FOR A BOOTABLE BTRFS SYSTEM!** 382 | > Deduplication will not follow from the system you backup from to the img. 383 | > The script does NOT utilize `btrfs send|recieve` 384 | > The script utilizes `rsync` witch is significantly faster, but that means deduplication does not work. 385 | 386 | All options in script will work just as on `ext4`. The script will detect `btrfs` and act accordingly. 387 | 388 | Top level 5 subvolumes are checked for in `/etc/fstab` and are created (only at img creation) and mounted accordingly. 389 | Mount options will be preserved (if you for example changed compression). 390 | Top level 5 subvolumes NOT in `/etc/fstab` will not be created. 391 | This means that for example top level 5 snapshots (unless mounted in fstab) will not be included. 392 | If you however have the snapshots mounted in `/etc/fstab` they will be created as subvolumes and files copied as "normal" files and take up space, NOT deduplicated! 393 | Simply mounting the snapshot somewhere will not create the snapshot, only copy the files like from a normal directory. 394 | 395 | The initial report window will show all top and nested subvolumes that will be included, **make sure these are correct before pressing y**. 396 | 397 | > [!IMPORTANT] 398 | > You should not use `subvolid` in mount options in `/etc/fstab`. This is because the subvolumes created on the img might get different subvolid and therefore fail to mount. 399 | > You can also not use `UUID`, `PARTUUID` is ok. This is because the linux kernel will not let you have two btrfs filesystems with the same `UUID` mounted at the same time. 400 | > The preferred option is `subvol=@` (or `subvol=/@` for example for root, using leading slash or not does not matter) 401 | > 402 | > This means that if you restore an img, you can not update that same img with `-U` from that restored system, the img mounting will fail during backup due to both having the same `UUID`. 403 | > If you have restored a backup, create a new backup from that system that can then be updated. 404 | 405 | All nested subvolumes will be created unless excluded in a file called `exclude_btrfs.txt`, please see below. 406 | Nested subvolumes can also be both created and removed with an [update](#image-update) of the backup. 407 | Autoresize function has only been tested and works on **Manjaro-arm**. 408 | 409 | When creating an img, the initial report window will tell you what volumes will be created. **Make sure these are correct before pressing Y.** 410 |
411 |
412 | ### exclude_btrfs.txt 413 | 414 | Because of how snapshots are named, a special file for excluding is necessary. `rsync` uses `PATTERNS` witch means undesired results can occur in certain situations. 415 | 416 | If for example a nested subvolume exists at `/temp`, the name of that subvolume will be `temp` (without leading `/`), and by excluding that in `exclude.txt`, `rsync` will interpret that as a `PATTERN`, therefore exclude and delete ALL directories called `temp` on the entire img. 417 | 418 | If you want to exclude subvolumes, both top level 5 and nested subvolumes, create the file `exclude_btrfs.txt` in the same directory as `shrink-backup` and add subvolumes, one per line. 419 | Wildcards (`*`) will not work in this scenario, but regex will, see tip below. 420 | 421 | > [!NOTE] 422 | > If installed using `curl`, the location to be used is different. See [install with curl](https://github.com/UnconnectedBedna/shrink-backup/wiki/Installing#curl---shrink-backup-install-script) for information. 423 | 424 | For example: 425 | ``` 426 | @subvolume1 427 | @home/user/nested_volume 428 | ``` 429 | 430 | There is no need to use `-t` option in this scenario (unless you also want to exclude other files/directories), the script will detect the existence of the file and add the paths for the subvolumes to be excluded and deleted on the img with `rsync`. 431 | 432 | > [!TIP] 433 | > For detecting subvolumes in `exclude_btrfs.txt`, regex is used (`grep -xE`) 434 | > So if you for example have a collection of nested subvolumes or snapshots somewhere, all starting with the same directory name, let's say a date, you can choose witch to exclude by date and exclude them all: 435 | > ``` 436 | > path/to/snapshots/2025(.+) 437 | > ``` 438 | > The path to be used is what is shown by using `sudo btrfs subvolume list /`, ie the subvolume without a leading `/`, so if you have nested subvolumes in for example `@home`, the path will be: 439 | > ``` 440 | > @home/path/to/subvolume 441 | > ``` 442 | > And if they are nested subvolumes on root (`@`), the path will be: 443 | > ``` 444 | > path/to/subvolume 445 | > ``` 446 | > To exclude an entire top level 5 subvolume, the path will be: 447 | > ``` 448 | > @subvolume 449 | > ``` 450 | > The initial report window will show all top and nested subvolumes by path and how they should be excluded if so is desired. 451 | 452 |
453 | Fun fact about shrink-backup & btrfs 454 | I used the script to create a backup of my Arch linux (BTW) desktop installation using btrfs with grub as bootloader (separate `/home` subvolume included in the image)
455 | Wrote that image to a usb stick, and it booted and autoexpanded without problems.
456 |
457 | I do NOT recommend using shrink-backup as your main backup software for a desktop computer! 458 | Use proper backup software for that. 459 |
460 | 461 |
462 | 463 | **Thank you for using shrink-backup** ❤️❤️ 464 | 465 | *"A backup is not really a backup until it has been restored"* 466 | -------------------------------------------------------------------------------- /shrink-backup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # shrink-backup 4 | # Version 1.4 5 | # Backup tool for creating and updating .img files (with autoexpansion function) on various linux operating systems 6 | # 7 | # Copyright (c) 2024-present, Marcus Johansson 8 | # https://github.com/UnconnectedBedna/shrink-backup 9 | # All rights reserved. 10 | # 11 | # This source code is licensed under the BSD-style license found in the 12 | # LICENSE file in the root directory of this source tree. 13 | ############################################################################## 14 | 15 | 16 | 17 | function debug() { 18 | local log_level="$1" 19 | local log_message="$2" 20 | if [ "$DEBUG" == true ]; then 21 | if [ "$log_level" == 'BREAK' ]; then 22 | echo '-------------------------------------------------------------------------------------' >> "$LOG_FILE" 23 | else 24 | if [ $log_level == 'INFO' ]; then 25 | echo -e "$(date +"%Y-%m-%d %H:%M:%S") [$log_level] - $log_message" >> "$LOG_FILE" 26 | elif [ $log_level == 'ERROR' ]; then 27 | echo -e "$(date +"%Y-%m-%d %H:%M:%S") [$log_level] - $log_message" >> "$LOG_FILE" 28 | elif [ $log_level == 'WARNING' ]; then 29 | echo -e "$(date +"%Y-%m-%d %H:%M:%S") [$log_level] - $log_message" >> "$LOG_FILE" 30 | else 31 | echo -e "$(date +"%Y-%m-%d %H:%M:%S") [$log_level] - $log_message" >> "$LOG_FILE" 32 | fi 33 | fi 34 | fi 35 | return 0 36 | } 37 | 38 | 39 | 40 | # Function to clean up resources on script exit or termination 41 | function cleanup() { 42 | # exit 0 = cleanup after script 43 | # exit 1 = early/clean error 44 | # exit 2 = help, version & looprun 45 | # exit 3 = later/normal error 46 | # exit 4 = aborted by user 47 | # exit 5 = rsync error 48 | # exit 10 = stopped by user (ctrl+c) 49 | local exit_code="$?" 50 | 51 | # Kill tail operation hindering unmounting of boot partition & delete lock file 52 | debug 'DEBUG' "Killing tail operation and removing ${BOOT_PATH}/shrink-backup.lock" 53 | pkill tail 54 | rm "$BOOT_PATH"/shrink-backup.lock 2>/dev/null 55 | 56 | if [ "$exit_code" -ne 1 ] && [ "$exit_code" -ne 2 ]; then 57 | case $exit_code in 58 | 0) debug 'DEBUG' 'Cleanup function called with exit 0';; 59 | 4) debug 'WARNING' 'Script aborted by user, cleanup exit 4';; 60 | 5) debug 'ERROR' 'Script failed during rsync, cleanup exit 5';; 61 | 10) echo -e "\n${Yellow}!! Script aborted by user with ctrl+c..."; debug 'WARNING' 'Script stopped by user with ctrl+c, cleanup exit 10';; 62 | *) echo -e "${Yellow}!! Cleanup function called with non-zero exit code, something went wrong!!!"; debug 'ERROR' "Cleanup function called with non zero exit code: exit $exit_code";; 63 | esac 64 | 65 | echo -e "${White}## ${IWhite}Exiting and cleaning up..." 66 | echo -e "${White}## ${IWhite}Please stand by..." 67 | 68 | # Unmount img boot 69 | if [ -n "$BOOT_PATH" ] && [ -n "$TMP_DIR" ] && grep -qs "${TMP_DIR}${BOOT_PATH} " /proc/mounts; then 70 | # Loop until the mount point is not busy 71 | while true; do 72 | # Check if the mount point is busy 73 | if ! umount "${TMP_DIR}${BOOT_PATH}" && grep -qs "${TMP_DIR}${BOOT_PATH} " /proc/mounts; then 74 | # If it is, sleep for 5 seconds and try again 75 | echo -e "${Yellow}!! ${Green}${TMP_DIR}${BOOT_PATH} ${Yellow}is busy, retrying in 5 seconds..." 76 | debug 'DEBUG' "${TMP_DIR}${BOOT_PATH} is busy, retrying in 5 seconds" 77 | sleep 5 78 | else 79 | debug 'DEBUG' "Unmounting boot partition in cleanup function: umount ${TMP_DIR}${BOOT_PATH}" 80 | break 81 | fi 82 | done 83 | fi 84 | 85 | # btrfs 86 | if [ "$FSTYPE" == 'btrfs' ]; then 87 | # Non-root subvolumes 88 | for subvol in "${LOCAL_TOP_SUBVOLUMES[@]}"; do 89 | if grep -q "$subvol" /etc/fstab && [[ "$subvol" != '@' ]]; then 90 | path=$(cat /etc/fstab | grep "$subvol" | awk '{print $2}') 91 | if [ -n "$TMP_DIR" ] && grep -qs "${TMP_DIR}${path} " /proc/mounts; then 92 | while true; do 93 | if ! umount "${TMP_DIR}${path}" && grep -qs "${TMP_DIR}${path} " /proc/mounts; then 94 | echo -e "${Yellow}!! ${Green}${TMP_DIR}${path} ${Yellow}is busy, retrying in 5 seconds..." 95 | debug 'DEBUG' "${TMP_DIR}${path} is busy, retrying in 5 seconds" 96 | sleep 5 97 | else 98 | debug 'DEBUG' "Unmounting subvolume $subvol in cleanup function: umount ${TMP_DIR}${path}" 99 | break 100 | fi 101 | done 102 | fi 103 | fi 104 | done 105 | 106 | # Root subvolume 107 | if [ -n "$TMP_DIR" ] && grep -qs "${TMP_DIR} " /proc/mounts; then 108 | while true; do 109 | if ! umount "${TMP_DIR}" && grep -qs "${TMP_DIR} " /proc/mounts; then 110 | echo -e "${Yellow}!! ${Green}${TMP_DIR} ${Yellow}is busy, retrying in 5 seconds..." 111 | debug 'DEBUG' "${TMP_DIR} is busy, retrying in 5 seconds" 112 | sleep 5 113 | else 114 | debug 'DEBUG' "Unmounting root subvolume (@) in cleanup function: umount ${TMP_DIR}" 115 | break 116 | fi 117 | done 118 | fi 119 | 120 | # All other filesystems 121 | else 122 | # Unmount img root 123 | if [ -n "$TMP_DIR" ] && grep -qs "$TMP_DIR " /proc/mounts; then 124 | while true; do 125 | if ! umount "${TMP_DIR}" && grep -qs "$TMP_DIR " /proc/mounts; then 126 | echo -e "${Yellow}!! ${Green}${TMP_DIR} ${Yellow}is busy, retrying in 5 seconds..." 127 | debug 'DEBUG' "${TMP_DIR} is busy, retrying in 5 seconds" 128 | sleep 5 129 | else 130 | debug 'DEBUG' "Unmounting root partition in cleanup function: umount ${TMP_DIR}" 131 | break 132 | fi 133 | done 134 | fi 135 | fi 136 | 137 | # Remove loop 138 | if losetup "$LOOP" &>/dev/null; then 139 | while true; do 140 | if ! losetup -d "$LOOP"; then 141 | echo -e "${Yellow}!! ${Green}${LOOP} ${Yellow}is busy, retrying in 5 seconds" 142 | debug 'DEBUG' "${LOOP} is busy, retrying in 5 seconds" 143 | sleep 5 144 | else 145 | debug 'DEBUG' "Removing loop in cleanup function: losetup -d $LOOP" 146 | break 147 | fi 148 | done 149 | fi 150 | 151 | # Remove temp directly 152 | if [ -d "$TMP_DIR" ]; then 153 | debug 'DEBUG' "Removing temp directory in cleanup function: rm -rf $TMP_DIR" 154 | rm -rf "$TMP_DIR" 155 | fi 156 | 157 | # Remove temp file 158 | if [ -f "$tmp_file" ]; then 159 | debug 'DEBUG' "Removing temp file in cleanup function: rm $tmp_file" 160 | rm "$tmp_file" 161 | fi 162 | 163 | echo -e "${White}## ${Green}Done." 164 | echo -e "${White}## ${IWhite}Elapsed time: ${Green}$(date -d@$SECONDS -u +%M.%S)${NC}" 165 | debug 'INFO' "Elapsed time: $(date -d@$SECONDS -u +%M.%S)" 166 | debug 'DEBUG' 'Exiting script' 167 | fi 168 | if [ "$DEBUG" == true ]; then 169 | echo '#####################################################################################' >> "$LOG_FILE" 170 | fi 171 | } 172 | trap cleanup EXIT SIGTERM 173 | trap "exit 10" SIGINT 174 | 175 | 176 | 177 | # Function to pause script execution and prompt for user input 178 | function pause() { 179 | read -p "$*" 180 | } 181 | # Uncomment the following line to enable the pause function 182 | #pause 'Press [any] key to continue...' 183 | 184 | 185 | 186 | # Set default variables 187 | unset diff_small 188 | VERSION='1.4' 189 | INSTALL_METHOD='default' 190 | PROMPTS=true 191 | DEBUG=false 192 | EXCLUDE_FILE=false 193 | AUTOEXPAND=true 194 | AUTORESIZE_RUN=false 195 | UPDATE=false 196 | if [ "$INSTALL_METHOD" == 'curl' ]; then 197 | LOG_FILE='/var/log/shrink-backup.log' 198 | EXCLUDE_FILE_LOCATION='/usr/local/etc/shrink-backup.conf' 199 | EXCLUDE_FILE_LOCATION_BTRFS='/usr/local/etc/shrink-backup_btrfs.conf' 200 | else 201 | LOG_FILE="$(dirname $0)/shrink-backup.log" 202 | EXCLUDE_FILE_LOCATION="$(dirname $0)/exclude.txt" 203 | EXCLUDE_FILE_LOCATION_BTRFS="$(dirname $0)/exclude_btrfs.txt" 204 | fi 205 | AUTORESIZE_WARNING=false 206 | ZOOM=false 207 | LOOPRUN=false 208 | CHROOTRUN=false 209 | RSYNC_DELETE='--delete' # gets set to --delete-before if --fix is selected 210 | RSYNC_TTY=true # gets set to false if -q|--quiet is selected 211 | RSYNC_CUSTOM=false 212 | F2FS_CONVERSION=false 213 | BTRFS_CONVERSION=false 214 | #STARTLINE="$0 $*" # will produce double // if script is in $PATH 215 | STARTLINE="$(dirname $0)/$(basename $0) $*" # will produce absolute path if script is in $PATH 216 | #STARTLINE="$(realpath $0) $*" # same as bove, but will ALWYAS produce absolute path 217 | 218 | # If no TTY is available (for example running custom command in webmin) 219 | if [ -t 0 ]; then 220 | TTY_AVAILABILITY='/dev/tty' 221 | 222 | # Colors 223 | White='\033[0;37m' # white 224 | IWhite='\033[0;97m' # high intensity white 225 | Red='\033[0;91m' # high intensity red 226 | Blue='\033[0;94m' # high intensity blue 227 | Green='\033[0;92m' # high intensity green 228 | Yellow='\033[0;93m' # high intensity yellow 229 | Purple='\033[0;95m' # high intensity purple 230 | NC='\033[0m' # Text Reset - No colors 231 | COLORS=true 232 | else 233 | TTY_AVAILABILITY='/dev/null' 234 | COLORS=false 235 | fi 236 | 237 | 238 | 239 | # Print version and exit 240 | version() { 241 | echo -e "${Purple}######################################################################### 242 | # # 243 | # ${Green}shrink-backup version $VERSION ${Purple}# 244 | # # 245 | # ${IWhite}Copyright (c) 2024-present, Marcus Johansson ${Purple}# 246 | # ${White}https://github.com/UnconnectedBedna/shrink-backup ${Purple}# 247 | # ${White}All rights reserved. ${Purple}# 248 | # # 249 | # ${White}This source code is licensed under the BSD-style license found in the ${Purple}# 250 | # ${White}LICENSE file in the root directory of this source tree. ${Purple}# 251 | # # 252 | #########################################################################" 253 | exit 2 254 | } 255 | 256 | 257 | 258 | # Display help information and exit 259 | help() { 260 | local help 261 | read -r -d '' help << EOM 262 | ${IWhite}Script for creating an .img file and subsequently keeing it updated (-U), autoexpansion is enabled by default 263 | Directory where .img file is created is automatically excluded in backup 264 | ###################################################################################################################### 265 | Usage: ${Green}sudo $(basename "$0") [options] imagefile.img [extra space (MiB)] 266 | -U ${IWhite}Update existing img file (rsync to existing img) 267 | Optional [extra space] extends img root partition 268 | ${Green}-a ${IWhite}Autocalculate root size partition, [extra space] is ignored. 269 | When used in combination with -U: 270 | Expand if partition is >=256MiB smaller than autocalculated recommended minimum 271 | Shrink if partition is >=512MiB bigger than autocalculated recommended minimum 272 | ${Green}-t ${IWhite}Use exclude.txt in same folder as script to set excluded directories 273 | One directory per line: "/dir" or "/dir/*" to only exclude contents 274 | Wildcards work, f.ex "/dir*" will eclude all directories starting with "/dir" 275 | ${Green}-y ${IWhite}Disable prompts in script (please use this option with care!) 276 | ${Green}-e ${IWhite}Disable autoexpansion on root filesystem when image is booted 277 | ${Green}-l ${IWhite}Write debug messages to logfile shrink-backup.log located in same directory as script 278 | ${Green}-z ${IWhite}Make script zoom at light-speed, only question prompts might slow it down 279 | Can be combined with -y for UNSAFE ultra-mega-superduper-speed 280 | ${Green}-q --quiet ${IWhite}Do not print rsync copy process 281 | ${Green}--no-color ${IWhite}Run script without color formatted text 282 | ${Green}--fix ${IWhite}Try to fix the img file if -a fails with a "broken pipe" error 283 | Will activate rsync options --delete-before & --fsync 284 | ${Green}--rsync ${IWhite}Define custom rsync line manually. Will print rsync line for user to edit 285 | ${Green}--loop [img] ${IWhite}Loop img file and exit, works in combination with -l & -z 286 | If optional [extra space] is defined, the img file will be extended with the amount before looping 287 | NOTE that only the file gets truncated, no partitions 288 | Useful if you for example want to manually manage the partitions 289 | ${Green}--chroot [img] ${IWhite}Use systemd-nspawn. Loop img file, mount to temp directory, enter chroot environment and drop to shell 290 | This will let you make changes in a chroot environment directly on the img file 291 | For example update with package manager or rebuild initramfs 292 | The script will keep running in the background 293 | Type 'exit' when done. Script will unmount, remove temp directory/loop and exit 294 | ${Green}--f2fs ${IWhite}Convert root filesystem on img from ext4 to f2fs 295 | Only works on new img file, not in combination with -U 296 | Will make backups of fstab & cmdline.txt to: fstab.shrink-backup.bak & cmdline.txt.shrink-backup.bak 297 | Then change ext4 to f2fs in both files and add discard to options on root partition in fstab 298 | ${Green}--version ${IWhite}Print version and exit 299 | ${Green}-h --help ${IWhite}Show this help snippet 300 | ###################################################################################################################### 301 | Examples: 302 | ${Green}sudo $(basename "$0") -a /path/to/backup.img ${White}(create img, automatically set size) 303 | ${Green}sudo $(basename "$0") -e -y /path/to/backup.img 1024 ${White}(create img, ignore prompts, do NOT autoexpand, add 1024MiB extra space) 304 | ${Green}sudo $(basename "$0") -Utl /path/to/backup.img ${White}(update img backup, use exclude.txt and write log to shrink-backup.log) 305 | ${Green}sudo $(basename "$0") -U /path/to/backup.img 1024 ${White}(update img backup, expand img size/root partition with 1024MiB) 306 | ${Green}sudo $(basename "$0") -Ua /path/to/backup.img ${White}(update img backup, automatically resizes img file if needed) 307 | ${Green}sudo $(basename "$0") -Ua --fix /path/to/backup.img 1024 ${White}(update img backup, automatically resizes img file if needed, fix img free space) 308 | ${Green}sudo $(basename "$0") -l --loop /path/to/backup.img 1024 ${White}(write to log file, expand IMG FILE (not partition) by 1024MiB, loop then exit script) 309 | EOM 310 | echo -e "$help" 311 | exit 2 312 | } 313 | 314 | 315 | 316 | # Parse command-line options 317 | options=$(getopt -o Uatyelzhq --long help,version,quiet,fix,f2fs,no-color,rsync,loop,chroot: -- "$@") 318 | [ $? -eq 0 ] || { 319 | echo -e "${Red}Invalid options provided" 320 | help 321 | } 322 | eval set -- "$options" 323 | while true; do 324 | case "$1" in 325 | -h|--help) help; break;; 326 | --version) version; break;; 327 | -U) UPDATE=true; shift;; 328 | -a) AUTORESIZE_RUN=true; shift;; 329 | -t) EXCLUDE_FILE=true; shift;; 330 | -y) PROMPTS=false; shift;; 331 | -e) AUTOEXPAND=false; shift;; 332 | -l) DEBUG=true; shift;; 333 | -z) ZOOM=true; shift;; 334 | -q|--quiet) RSYNC_TTY=false; shift;; 335 | --fix) RSYNC_DELETE='--delete-before --fsync'; shift;; 336 | --f2fs) F2FS_CONVERSION=true; shift;; 337 | --no-color) unset White IWhite Red Blue Green Yellow Purple NC; COLORS=false; debug 'DEBUG' '--no-color selected by user, unsetting color variables'; shift;; 338 | --rsync) RSYNC_CUSTOM=true; shift;; 339 | --loop) LOOPRUN=true; IMG_FILE="$3"; shift;; 340 | --chroot) CHROOTRUN=true; IMG_FILE="$2"; shift;; 341 | --) break;; 342 | *) shift;; 343 | esac 344 | done 345 | 346 | # Process non-option arguments 347 | while [ "$#" -gt 0 ]; do 348 | if [[ "$1" =~ ^- ]]; then 349 | shift 350 | # If --loop or --chroot is used, IMG_FILE is already set 351 | elif [ -n "$IMG_FILE" ]; then 352 | ADDED_SPACE="$2" 353 | break 354 | else 355 | IMG_FILE="$1" 356 | ADDED_SPACE="$2" 357 | break 358 | fi 359 | done 360 | 361 | # Set width 362 | COLS=85 363 | if [ $(echo "$IMG_FILE" | wc -m) -gt $(( COLS - 19 )) ]; then 364 | COLS=$(( $(echo "$IMG_FILE" | wc -m) + 20 )) 365 | fi 366 | if [ -t 0 ] && [ $(tput cols) -lt "$COLS" ]; then # tput will fail in webmin 367 | COLS=$(tput cols) 368 | fi 369 | BREAK="$(printf %${COLS}s | tr ' ' '#')" 370 | 371 | # Verify script is running as root 372 | if [ "$EUID" -ne 0 ]; then 373 | echo -e "${Red}!! THIS SCRIPT MUST BE RUNNING AS ROOT! (WITH SUDO)" 374 | help 375 | fi 376 | 377 | 378 | 379 | # loop img file and exit 380 | function looprun() { 381 | debug 'DEBUG' "IMG_FILE=$IMG_FILE" 382 | debug 'DEBUG' "ADDED_SPACE=$ADDED_SPACE" 383 | if [ "$ADDED_SPACE" -ne 0 ]; then 384 | debug 'INFO' 'ADDED_SPACE not 0, truncating img file' 385 | if [ "$PROMPTS" == true ]; then 386 | echo -e "${Yellow}!! ${Red}WARNING! ${Yellow}You have requested to add ${Green}${ADDED_SPACE}MiB ${Yellow}to the img file (NOT partition)" 387 | debug 'INFO' 'Do you want to continue with the truncate operation? [y/n]' 388 | while true; do 389 | if [ "$COLORS" == true ]; then 390 | read -r -p $'\e[0;37m## \e[0;97mDo you want to continue with the truncate operation? \e[0;92m[y/n] \e[0m' input 391 | else 392 | read -r -p '## Do you want to continue with the truncate operation? [y/n] ' input 393 | fi 394 | case $input in 395 | [Yy]) break;; 396 | [Nn]) echo -e "${Red}!! Aborting..."; exit 4;; 397 | *) echo -e "${Yellow}!! ${Red}ERROR! ${Yellow}Please enter ${Green}'y' ${Yellow}or ${Green}'n'${Yellow}"; debug 'WARNING' "ERROR, please enter 'y' or 'n'";; 398 | esac 399 | done 400 | debug 'INFO' 'Y or y pressed to confirm' 401 | echo -e "${White}## ${IWhite}Truncating file by adding ${Green}${ADDED_SPACE}MiB" 402 | $SLEEPING 403 | else 404 | echo -e "${White}## ${IWhite}Truncating file by adding ${Green}${ADDED_SPACE}MiB" 405 | $SLEEPING 406 | fi 407 | debug 'DEBUG' "Running: truncate -s +\$(( $ADDED_SPACE * 1024 * 1024 )) $IMG_FILE" 408 | if ! output=$(truncate -s +$(( ADDED_SPACE * 1024 * 1024 )) "$IMG_FILE" 2>&1); then 409 | echo -e "${Yellow}$output\n${Red}!! TRUNCATE FAILED!!!" 410 | debug 'BREAK' 411 | debug 'ERROR' "TRUNCATE FAILED:\n$output\n-------------------------------------------------------------------------------------" 412 | exit 1 413 | fi 414 | fi 415 | 416 | debug 'INFO' 'Running function: do_loop' 417 | do_loop 418 | 419 | echo -e "${White}## ${Green}$IMG_FILE ${IWhite}is looped to ${Green}$LOOP" 420 | echo -e "${Purple}$BREAK" 421 | readarray -t looprun_output < <(lsblk $LOOP) 422 | for i in "${looprun_output[@]}"; do 423 | #echo -e "${Purple}# ${Green}${i} ${Purple}$(printf "%+$(( COLS - 3 - $(echo ${i} | wc -m ) ))s" '#')" 424 | echo -e "${Purple}# ${Green}${i}" 425 | done 426 | echo -e "${Purple}$BREAK" 427 | echo -e "${White}## ${Green}Done." 428 | echo -e "${White}## ${IWhite}To remove loop, type: ${Green}sudo losetup -d ${LOOP}" 429 | debug 'DEBUG' 'Exiting script, exit 2' 430 | if [ "$DEBUG" == true ]; then 431 | echo '#####################################################################################' >> "$LOG_FILE" 432 | fi 433 | exit 2 434 | } 435 | 436 | 437 | 438 | function chrootrun() { 439 | 440 | check_dependencies 441 | do_loop 442 | 443 | # Assumes last partition is root 444 | partprobe "$LOOP" 445 | sleep 2 446 | FSTYPE="$(lsblk -no fstype $LOOP | tail -1)" 447 | debug 'DEBUG' "FSTYPE=$FSTYPE" 448 | # First index (LSBLK_PARTITIONS[0]) will be the loop itself 449 | LSBLK_PARTITIONS=($(lsblk -no path $LOOP)) 450 | debug 'DEBUG' "LSBLK_PARTITIONS=$(echo ${LSBLK_PARTITIONS[@]})" 451 | 452 | # Check for temp directory and create if needed 453 | if ! [ -d "$TMP_DIR" ]; then 454 | echo -e "${White}## ${IWhite}Creating temp directory..." 455 | $SLEEPING 456 | debug 'INFO' 'Creating temp directory' 457 | debug 'DEBUG' "Running: mktemp -d -t chroot-XXX" 458 | TMP_DIR=$(mktemp -d -t chroot-XXX) 459 | debug 'DEBUG' "TMP_DIR=$TMP_DIR" 460 | fi 461 | 462 | # ext4 or f2fs 463 | if [ "$FSTYPE" == 'ext4' ] || [ "$FSTYPE" == 'f2fs' ]; then 464 | # Assumes root is on last partition 465 | count=$(( ${#LSBLK_PARTITIONS[@]} - 1 )) # ${#LSBLK_PARTITIONS[@]} gives count, -1 removes device so only partitions are counted 466 | echo -e "${White}## ${IWhite}Mounting img ${Green}root (/) ${IWhite}partition" 467 | $SLEEPING 468 | debug 'INFO' 'Mounting img root partition' 469 | debug 'DEBUG' "Running: mount ${LSBLK_PARTITIONS[$count]} $TMP_DIR" 470 | if ! output=$(mount "${LSBLK_PARTITIONS[$count]}" "$TMP_DIR" 2>&1); then 471 | echo -e "${Yellow}$output\n${Red}!! IMG ROOT MOUNT FAILED!!!" 472 | debug 'BREAK' 473 | debug 'ERROR' "IMG ROOT MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 474 | exit 3 475 | fi 476 | 477 | # Remount with flags from fstab 478 | fstab=($(cat "$TMP_DIR"/etc/fstab | grep ' / ')) 479 | echo -e "${White}## ${IWhite}Remounting img ${Green}root (/) ${IWhite}partition with flags from fstab" 480 | $SLEEPING 481 | debug 'INFO' 'Remounting img root partition with flags from fstab' 482 | debug 'DEBUG' "Running: mount -o remount,${fstab[3]} $TMP_DIR" 483 | if ! output=$(mount -o remount,${fstab[3]} "$TMP_DIR" 2>&1); then 484 | echo -e "${Yellow}$output\n${Red}!! REMOUNTING IMG ROOT PARTITION FAILED!!!" 485 | debug 'BREAK' 486 | debug 'ERROR' "REMOUNTING IMG ROOT PARTITION FAILED:\n$output\n-------------------------------------------------------------------------------------" 487 | exit 3 488 | fi 489 | 490 | # Mount boot if exists 491 | if grep -q 'boot' $TMP_DIR/etc/fstab; then 492 | (( count -- )) 493 | debug 'INFO' 'Boot partition found in fstab, mounting img boot partition' 494 | fstab=($(cat "$TMP_DIR"/etc/fstab | grep 'boot')) 495 | echo -e "${White}## ${IWhite}Mounting img ${Green}${fstab[1]} ${IWhite}from fstab" 496 | $SLEEPING 497 | debug 'DEBUG' "Running: mount -o ${fstab[3]} ${LSBLK_PARTITIONS[$count]} ${TMP_DIR}${fstab[1]}" 498 | if ! output=$(mount -o "${fstab[3]}" "${LSBLK_PARTITIONS[$count]}" "${TMP_DIR}${fstab[1]}" 2>&1); then 499 | echo -e "${Yellow}$output\n${Red}!! IMG BOOT MOUNT FAILED!!!" 500 | debug 'BREAK' 501 | debug 'ERROR' "IMG BOOT MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 502 | exit 3 503 | fi 504 | fi 505 | 506 | # btrfs 507 | elif [ "$FSTYPE" == 'btrfs' ]; then 508 | # Assumes root subvolume is @ and is on second partition 509 | echo -e "${White}## ${IWhite}Mounting img ${Green}@ ${IWhite}subvolume" 510 | $SLEEPING 511 | debug 'INFO' 'Mounting img @ subvolume' 512 | debug 'DEBUG' "Running: mount -o subvol=@ ${LSBLK_PARTITIONS[2]} $TMP_DIR" 513 | if ! output=$(mount -o subvol=@ "${LSBLK_PARTITIONS[2]}" "$TMP_DIR" 2>&1); then 514 | echo -e "${Yellow}$output\n${Red}!! IMG ROOT SUBVOLUME (@) MOUNT FAILED!!!" 515 | debug 'BREAK' 516 | debug 'ERROR' "IMG ROOT SUBVOLUME (@) MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 517 | exit 3 518 | fi 519 | 520 | # Remount with flags from fstab 521 | fstab=($(cat "$TMP_DIR"/etc/fstab | grep ' / ')) 522 | echo -e "${White}## ${IWhite}Remounting img ${Green}@ ${IWhite}subvolume with flags from fstab" 523 | $SLEEPING 524 | debug 'INFO' 'Remounting img @ subvolume with flags from fstab' 525 | debug 'DEBUG' "Running: mount -o remount,${fstab[3]} $TMP_DIR" 526 | if ! output=$(mount -o remount,${fstab[3]} "$TMP_DIR" 2>&1); then 527 | echo -e "${Yellow}$output\n${Red}!! REMOUNTING IMG ROOT SUBVOLUME (@) FAILED!!!" 528 | debug 'BREAK' 529 | debug 'ERROR' "REMOUNTING IMG ROOT SUBVOLUME (@) FAILED:\n$output\n-------------------------------------------------------------------------------------" 530 | exit 3 531 | fi 532 | 533 | # Assumes boot exists and is on first partition 534 | fstab=($(cat "$TMP_DIR"/etc/fstab | grep 'boot')) 535 | debug 'INFO' 'Mounting img boot partition' 536 | echo -e "${White}## ${IWhite}Mounting img ${Green}${fstab[1]} ${IWhite}from fstab" 537 | $SLEEPING 538 | debug 'DEBUG' "Running: mount -o ${fstab[3]} ${LSBLK_PARTITIONS[1]} ${TMP_DIR}${fstab[1]}" 539 | if ! output=$(mount -o "${fstab[3]}" "${LSBLK_PARTITIONS[1]}" "${TMP_DIR}${fstab[1]}" 2>&1); then 540 | echo -e "${Yellow}$output\n${Red}!! IMG BOOT MOUNT FAILED!!!" 541 | debug 'BREAK' 542 | debug 'ERROR' "IMG BOOT MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 543 | exit 3 544 | fi 545 | 546 | # Filter out @ and subvolumes not in fstab 547 | IMG_SUBVOLUMES=($(btrfs subvol list $TMP_DIR | awk '{print $9}')) 548 | echo -e "${White}## ${IWhite}Subvolumes found on img ${Green}${IMG_SUBVOLUMES[@]}" 549 | echo -e "${White}## ${IWhite}Filtering out subvolumes not present in fstab" 550 | $SLEEPING 551 | debug 'INFO' 'Filtering out @ and subvolumes not in fstab' 552 | debug 'DEBUG' "Subvolumes before filtering: $(echo ${IMG_SUBVOLUMES[@]})" 553 | for i in ${!IMG_SUBVOLUMES[@]}; do 554 | if ! $(grep -q ${IMG_SUBVOLUMES[i]} $TMP_DIR/etc/fstab) || [ $(echo ${IMG_SUBVOLUMES[i]}) == '@' ]; then 555 | unset IMG_SUBVOLUMES[i] 556 | fi 557 | done 558 | echo -e "${White}## ${IWhite}Subvolumes to mount: ${Green}${IMG_SUBVOLUMES[@]}" 559 | $SLEEPING 560 | debug 'DEBUG' "Subvolumes after filtering: ${IMG_SUBVOLUMES[@]}" 561 | # Mount remaining subvolumes 562 | debug 'INFO' 'Mounting remaining subvolumes' 563 | for subvol in ${IMG_SUBVOLUMES[@]}; do 564 | fstab=($(cat "$TMP_DIR"/etc/fstab | grep "$subvol")) 565 | echo -e "${White}## ${IWhite}Mounting img ${Green}${fstab[1]} ${IWhite}from fstab" 566 | $SLEEPING 567 | debug 'DEBUG' "Running: mount -o ${fstab[3]} ${LSBLK_PARTITIONS[2]} ${TMP_DIR}${fstab[1]}" 568 | if ! output=$(mount -o "${fstab[3]}" "${LSBLK_PARTITIONS[2]}" "${TMP_DIR}${fstab[1]}" 2>&1); then 569 | echo -e "${Yellow}$output\n${Red}!! SUBVOLUME MOUNT FAILED!!!" 570 | debug 'BREAK' 571 | debug 'ERROR' "IMG SUBVOLUME MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 572 | exit 3 573 | fi 574 | done 575 | fi 576 | 577 | # Enter chroot environment 578 | echo -e "${White}## ${IWhite}Running systemd-nspawn, type ${Green}exit ${IWhite}when done to exit chroot environment" 579 | echo -e "${White}## ${Yellow}YOU ARE RUNNING AS ROOT!!!${NC}" 580 | if systemctl is-active -q systemd-resolved.service; then 581 | debug 'DEBUG' "systemd-resolved.service is active, running: systemd-nspawn --resolv-conf=replace-stub -D $TMP_DIR bin/bash" 582 | systemd-nspawn --resolv-conf=replace-stub -D "$TMP_DIR" bin/bash 583 | else 584 | debug 'DEBUG' "systemd-resolved.service is not active, running: systemd-nspawn --resolv-conf=copy-host -D $TMP_DIR bin/bash" 585 | systemd-nspawn --resolv-conf=copy-host -D "$TMP_DIR" bin/bash 586 | fi 587 | exit 0 588 | } 589 | 590 | 591 | 592 | # Gather device information 593 | function dev_variables() { 594 | 595 | ADDED_SPACE=$(( ADDED_SPACE * 1024 * 1024 )) # bytes, ADDED_SPACE=0 if AUTORESIZE_RUN=true 596 | 597 | # Check if separate boot and root partition exists and set variables accordingly 598 | if grep -q 'boot' /etc/fstab; then 599 | BOOT_PARTITION=true 600 | LOCAL_DEV_BOOT_PATH=$(mount --fake | grep 'boot' | awk '{print $1}') 601 | LOCAL_DEV_ROOT_PATH=$(mount --fake | grep ' / ' | awk '{print $1}') 602 | debug 'DEBUG' "BOOT_PARTITION=$BOOT_PARTITION | LOCAL_DEV_BOOT_PATH=$LOCAL_DEV_BOOT_PATH | LOCAL_DEV_ROOT_PATH=$LOCAL_DEV_ROOT_PATH" 603 | LOCAL_BOOT_UUID=$(lsblk -no uuid "$LOCAL_DEV_BOOT_PATH") 604 | LOCAL_ROOT_UUID=$(lsblk -no uuid "$LOCAL_DEV_ROOT_PATH") 605 | LOCAL_ROOT_PARTUUID=$(lsblk -no partuuid "$LOCAL_DEV_ROOT_PATH") 606 | debug 'DEBUG' "LOCAL_BOOT_UUID=$LOCAL_BOOT_UUID | LOCAL_ROOT_UUID=$LOCAL_ROOT_UUID | LOCAL_ROOT_PARTUUID=$LOCAL_ROOT_PARTUUID" 607 | else 608 | debug 'INFO' 'No boot partition detected' 609 | BOOT_PARTITION=false 610 | LOCAL_DEV_ROOT_PATH=$(mount --fake | grep ' / ' | awk '{print $1}') 611 | debug 'DEBUG' "BOOT_PARTITION=$BOOT_PARTITION | LOCAL_DEV_ROOT_PATH=$LOCAL_DEV_ROOT_PATH" 612 | LOCAL_ROOT_UUID=$(lsblk -no uuid "$LOCAL_DEV_ROOT_PATH") 613 | LOCAL_ROOT_PARTUUID=$(lsblk -no partuuid "$LOCAL_DEV_ROOT_PATH") 614 | debug 'DEBUG' "LOCAL_ROOT_UUID=$LOCAL_ROOT_UUID | LOCAL_ROOT_PARTUUID=$LOCAL_ROOT_PARTUUID" 615 | fi 616 | LOCAL_ROOT_PARTN=$(blkid -s PART_ENTRY_NUMBER -o value -p "$LOCAL_DEV_ROOT_PATH") 617 | #LOCAL_ROOT_PARTN=$(parted -sm "$LOCAL_DEV_PATH" print | tail -1 | cut -d : -f 1) 618 | debug 'DEBUG' "LOCAL_ROOT_PARTN=$LOCAL_ROOT_PARTN" 619 | 620 | # Collect information about partition sizes 621 | debug 'INFO' 'Calculating size for dd to cover bootsector and adding 5MiB (5242880 bytes) to overlap into root (only used in img creation)' 622 | #LOCAL_BOOTSECTOR=$(lsblk --bytes -no size "$LOCAL_DEV_BOOT_PATH") # bytes, can not be used, will be 0 if no boot partition exists 623 | LOCAL_ROOT_START=$(fdisk -lo device,start "$LOCAL_DEV_PATH" | grep "$LOCAL_DEV_ROOT_PATH" | awk '{print $2}') # 512B blocks 624 | LOCAL_BOOTSECTOR=$(( LOCAL_ROOT_START - 1 )) # blocks, to set the actual bootsector, needed in case no boot partition exists 625 | LOCAL_ROOT_START=$(( LOCAL_ROOT_START * 512 )) # bytes 626 | LOCAL_BOOTSECTOR=$(( LOCAL_BOOTSECTOR * 512 )) # bytes 627 | LOCAL_DDBOOTSECTOR=$(( (LOCAL_BOOTSECTOR + 5242880) / 512 )) # 512B blocks, 5242880 = 5MiB in bytes 628 | debug 'DEBUG' "LOCAL_ROOT_START=$LOCAL_ROOT_START bytes | LOCAL_BOOTSECTOR=$LOCAL_BOOTSECTOR bytes | LOCAL_DDBOOTSECTOR=$LOCAL_DDBOOTSECTOR 512B blocks" 629 | 630 | # Set automated calculated size (btrfs_variables if btrfs filesystem) 631 | if [ "$AUTORESIZE_RUN" == true ] || [ "$UPDATE" == false ] || [ "$FSTYPE" == 'btrfs' ]; then 632 | debug 'INFO' 'Calculating recommended root size' 633 | BLOCKSIZE=$(stat -fc %s /) # bytes 634 | debug 'DEBUG' "BLOCKSIZE=${BLOCKSIZE} bytes" 635 | if [ "$FSTYPE" == 'ext4' ]; then 636 | debug 'INFO' 'ext4 filesystem detected, using resize2fs to set recommended root size' 637 | LOCAL_AUTORESIZE_MIN=$(resize2fs -P "$LOCAL_DEV_ROOT_PATH" 2>/dev/null | awk '{print $7}') # blocks 638 | LOCAL_AUTORESIZE_MIN=$(( LOCAL_AUTORESIZE_MIN * BLOCKSIZE )) # bytes 639 | debug 'DEBUG' "LOCAL_AUTORESIZE_MIN=${LOCAL_AUTORESIZE_MIN} bytes" 640 | elif [ "$FSTYPE" == 'f2fs' ]; then 641 | # Method 2, using "used space" straight up 642 | debug 'INFO' 'f2fs filesystem detected, using df (+512MiB) to set recommended root size' 643 | LOCAL_AUTORESIZE_DF=$(df / -k --sync --output=used | tail -1) # 1k blocks 644 | LOCAL_AUTORESIZE_MIN=$(( LOCAL_AUTORESIZE_DF * 1024 )) # bytes 645 | LOCAL_AUTORESIZE_MIN=$(( LOCAL_AUTORESIZE_MIN + 536870912 )) # adding 512MiB 646 | debug 'DEBUG' "LOCAL_AUTORESIZE_MIN=${LOCAL_AUTORESIZE_MIN} bytes" 647 | elif [ "$FSTYPE" == 'btrfs' ]; then 648 | debug 'INFO' 'Running function: btrfs_variables' 649 | btrfs_variables 650 | fi 651 | fi 652 | 653 | # Method 1, using the value of "size - available" 654 | #declare -a LOCAL_DF_OUTPUT=( $(df / -k --sync --output=size,avail | tail -1) ) # 1k blocks 655 | #LOCAL_USED_SPACE=$(( (${LOCAL_DF_OUTPUT[0]} - ${LOCAL_DF_OUTPUT[1]}) * 1024 )) # bytes, df is in 1k blocks, 0 is the first position in an array 656 | 657 | # Method 2, using "used space" straight up 658 | if [ "$FSTYPE" != 'btrfs' ]; then 659 | LOCAL_DF_OUTPUT=$(df / -k --sync --output=used | tail -1) # 1k blocks 660 | LOCAL_USED_SPACE=$(( LOCAL_DF_OUTPUT * 1024 )) # bytes, df is in 1k blocks 661 | fi 662 | 663 | # Method 3, using du 664 | #LOCAL_USED_SPACE=$(du -xsb / | awk '{print $1}') # bytes 665 | 666 | # Set recommended size if option is selected 667 | if [ "$AUTORESIZE_RUN" == true ]; then 668 | debug 'INFO' 'Setting TOTAL (space needed for files on root) to autocalculated size' 669 | TOTAL=$LOCAL_AUTORESIZE_MIN # bytes 670 | # Add 512MiB if f2fs conversion 671 | if [ "$F2FS_CONVERSION" == true ]; then 672 | debug 'INFO' 'Adding 512MiB because of f2fs conversion' 673 | TOTAL=$(( $TOTAL + 536870912 )) 674 | fi 675 | else 676 | debug 'INFO' 'Calculating TOTAL (space needed for files on root) by adding LOCAL_USED_SPACE and ADDED_SPACE' 677 | debug 'DEBUG' "LOCAL_USED_SPACE=${LOCAL_USED_SPACE} bytes | ADDED_SPACE=${ADDED_SPACE} bytes" 678 | TOTAL=$(( LOCAL_USED_SPACE + ADDED_SPACE )) # bytes 679 | fi 680 | debug 'DEBUG' "TOTAL=${TOTAL} bytes" 681 | TRUNCATE_TOTAL=$(( LOCAL_BOOTSECTOR + TOTAL )) # bytes 682 | debug 'INFO' 'Calculating .img file size by adding LOCAL_BOOTSECTOR to TOTAL' 683 | debug 'DEBUG' "TRUNCATE_TOTAL=${TRUNCATE_TOTAL} bytes" 684 | 685 | # Add 128MiB extra space if autocalculated size reports bigger minimum than created on new img 686 | if [ "$UPDATE" == false ] && [ "$TOTAL" -lt "$LOCAL_AUTORESIZE_MIN" ]; then 687 | debug 'WARNING' 'Adding WIGGLEROOM (128MiB) because manually requested ADDED_SPACE is less than autocaluclated size' 688 | AUTORESIZE_WARNING=true 689 | WIGGLEROOM=134217728 # 128MiB = 134217728B, 192MiB = 201326592B 690 | TRUNCATE_TOTAL=$(( TRUNCATE_TOTAL + WIGGLEROOM )) 691 | debug 'DEBUG' "AUTORESIZE_WARNING=$AUTORESIZE_WARNING | WIGGLEROOM=${WIGGLEROOM} bytes | TRUNCATE_TOTAL=${TRUNCATE_TOTAL} bytes" 692 | fi 693 | return 0 694 | } 695 | 696 | 697 | 698 | # Gather btrfs information 699 | function btrfs_variables() { 700 | 701 | debug 'INFO' 'Using btrfs fi us to get required size' 702 | declare -a BTRFS_USAGE=($(btrfs fi us -T --raw / | tail -1)) # bytes 703 | LOCAL_USED_SPACE=$(( BTRFS_USAGE[1] + BTRFS_USAGE[2] + BTRFS_USAGE[3] )) # adding Data single, Metadata single & System single 704 | debug 'DEBUG' "LOCAL_USED_SPACE=$LOCAL_USED_SPACE" 705 | if [ "$AUTORESIZE_RUN" == true ]; then 706 | debug 'INFO' 'Adding 512MiB to LOCAL_USED_SPACE as recommended minimum size' 707 | LOCAL_AUTORESIZE_MIN=$(( LOCAL_USED_SPACE + 536870912 )) 708 | debug 'DEBUG' "LOCAL_AUTORESIZE_MIN=$LOCAL_AUTORESIZE_MIN bytes" 709 | fi 710 | 711 | # Get subvolumes 712 | debug 'DEBUG' "Running: btrfs subvolume list / | grep ' 5 ' | awk '{print \$9}'" 713 | declare -ag LOCAL_TOP_SUBVOLUMES=( $(btrfs subvolume list / | grep ' 5 ' | awk '{print $9}') ) # -g = make variable global 714 | debug 'DEBUG' "LOCAL_TOP_SUBVOLUMES=$(echo ${LOCAL_TOP_SUBVOLUMES[@]})" 715 | debug 'DEBUG' "Running: btrfs subvolume list / --sort=path | grep -v ' 5 ' | awk '{print \$9}'" 716 | declare -ag LOCAL_NESTED_SUBVOLUMES=( $(btrfs subvolume list / --sort=path | grep -v ' 5 ' | awk '{print $9}') ) 717 | debug 'DEBUG' "LOCAL_NESTED_SUBVOLUMES=$(echo ${LOCAL_NESTED_SUBVOLUMES[@]})" 718 | 719 | # Filter out top subvolumes not in fstab 720 | debug 'INFO' 'Filtering out top level 5 subvolumes not existing in fstab' 721 | for i in ${!LOCAL_TOP_SUBVOLUMES[@]}; do 722 | subvol=${LOCAL_TOP_SUBVOLUMES[i]} 723 | if $(echo $subvol | grep -q -v -f /etc/fstab); then 724 | debug 'DEBUG' "Filtering out subvolume: $subvol" 725 | unset LOCAL_TOP_SUBVOLUMES[i] 726 | fi 727 | done 728 | debug 'DEBUG' "After filtering: LOCAL_TOP_SUBVOLUMES=$(echo ${LOCAL_TOP_SUBVOLUMES[@]})" 729 | 730 | # Filter out top subvolumes from exclude_btrfs.txt or shrink-backup_btrfs.conf 731 | if [ -f "$EXCLUDE_FILE_LOCATION_BTRFS" ]; then 732 | debug 'INFO' 'Filtering out top level 5 subvolumes to exclude from exclude_btrfs.txt or shrink-backup_btrfs.conf' 733 | for i in ${!LOCAL_TOP_SUBVOLUMES[@]}; do 734 | subvol=${LOCAL_TOP_SUBVOLUMES[i]} 735 | if $(echo $subvol | grep -q -f "$EXCLUDE_FILE_LOCATION_BTRFS"); then 736 | SUBVOLUME_EXCLUDE_PATHS+=( $(cat /etc/fstab | grep $subvol | awk 'print $2') ) 737 | debug 'DEBUG' "Adding $(cat /etc/fstab | grep $subvol | awk 'print $2') to SUBVOLUME_EXCLUDE_PATHS" 738 | debug 'DEBUG' "Filtering out subvolume: $subvol" 739 | unset LOCAL_TOP_SUBVOLUMES[i] 740 | fi 741 | done 742 | debug 'DEBUG' "After filtering: LOCAL_TOP_SUBVOLUMES=$(echo ${LOCAL_TOP_SUBVOLUMES[@]})" 743 | 744 | # Filter out nested subvolumes from exclude_btrfs.txt or shrink-backup_btrfs.conf 745 | debug 'INFO' 'Filtering out nested subvolumes to exclude from exclude_btrfs.txt or shrink-backup_btrfs.conf' 746 | for i in ${!LOCAL_NESTED_SUBVOLUMES[@]}; do 747 | subvol=${LOCAL_NESTED_SUBVOLUMES[i]} 748 | if $(echo $subvol | grep -qxE -f "$EXCLUDE_FILE_LOCATION_BTRFS"); then 749 | debug 'DEBUG' "Filtering out subvolume: $subvol" 750 | 751 | # Fix path if nested subvolume is in other location than root subvolume 752 | top_subvolumes="$(echo ${LOCAL_TOP_SUBVOLUMES[@]} | sed 's/ /|/g')" # convert spaces into | 753 | if echo "$subvol" | grep -qE "$top_subvolumes"; then 754 | debug 'INFO' 'Nested volume not under root, fixing path' 755 | for top_subvol in ${LOCAL_TOP_SUBVOLUMES[@]}; do 756 | if echo "$subvol" | grep -qw "$top_subvol"; then 757 | break 758 | fi 759 | done 760 | path="$(cat /etc/fstab | grep $top_subvol | awk '{print $2}')" 761 | subvol="$(echo $subvol | sed "s|$top_subvol|$path|g")" 762 | fi 763 | if [[ "$subvol" != /* ]]; then 764 | subvol="/$subvol" 765 | fi 766 | debug 'DEBUG' "Adding $subvol to SUBVOLUME_EXCLUDE_PATHS" 767 | SUBVOLUME_EXCLUDE_PATHS+=($subvol) 768 | unset LOCAL_NESTED_SUBVOLUMES[i] 769 | fi 770 | done 771 | if [ -n "$SUBVOLUME_EXCLUDE_PATHS" ]; then 772 | SUBVOLUME_EXCLUDE_PATHS="$(echo ${SUBVOLUME_EXCLUDE_PATHS[@]} | sed 's/ /,/g')" 773 | debug 'DEBUG' "SUBVOLUME_EXCLUDE_PATHS=$SUBVOLUME_EXCLUDE_PATHS)" 774 | fi 775 | debug 'DEBUG' "After filtering: LOCAL_NESTED_SUBVOLUMES=$(echo ${LOCAL_NESTED_SUBVOLUMES[@]})" 776 | fi 777 | return 0 778 | } 779 | 780 | 781 | 782 | # Gather image information 783 | function img_variables() { 784 | 785 | if [ "$UPDATE" == true ]; then 786 | debug 'DEBUG' "Running: stat -c %s $IMG_FILE" 787 | IMG_SIZE=$(stat -c %s "$IMG_FILE") 788 | debug 'DEBUG' "IMG_SIZE=$IMG_SIZE bytes" 789 | fi 790 | 791 | # Boot partition exists 792 | if grep -q 'boot' /etc/fstab; then 793 | 794 | # ext4 & f2fs 795 | if [ "$FSTYPE" == 'ext4' ] || [ "$FSTYPE" == 'f2fs' ]; then 796 | # I have no idea why, but if I do not put this sleep here, IMG_DEV_ROOT_PATH does not get set 797 | sleep 2 798 | #pause 'Press [Enter] key to continue...' 799 | IMG_DEV_BOOT_PATH=$(lsblk -no path,uuid "$LOOP" | grep "$LOCAL_BOOT_UUID" | awk '{print $1}') 800 | IMG_DEV_ROOT_PATH=$(lsblk -no path,uuid "$LOOP" | grep "$LOCAL_ROOT_UUID" | awk '{print $1}') 801 | debug 'DEBUG' "IMG_DEV_BOOT_PATH=$IMG_DEV_BOOT_PATH | IMG_DEV_ROOT_PATH=$IMG_DEV_ROOT_PATH" 802 | # 2s sleep is sometimes not enough, might have to do with slow network if img file is on a network share 803 | if ! [ -e "$IMG_DEV_BOOT_PATH" ] || ! [ -e "$IMG_DEV_ROOT_PATH" ]; then 804 | for (( i=1; i<=3; i++ )); do 805 | echo -e "${Yellow}!! ${Yellow}LOOP paths can not be set, retrying in 5 seconds..." 806 | debug 'WARNING' 'LOOP paths can not be set, retrying in 5 seconds' 807 | debug 'WARNING' "IMG_DEV_BOOT_PATH=$IMG_DEV_BOOT_PATH | IMG_DEV_ROOT_PATH=$IMG_DEV_ROOT_PATH" 808 | sleep 5 809 | IMG_DEV_BOOT_PATH=$(lsblk -no path,uuid "$LOOP" | grep "$LOCAL_BOOT_UUID" | awk '{print $1}') 810 | IMG_DEV_ROOT_PATH=$(lsblk -no path,uuid "$LOOP" | grep "$LOCAL_ROOT_UUID" | awk '{print $1}') 811 | if [ -e "$IMG_DEV_BOOT_PATH" ] && [ -e "$IMG_DEV_ROOT_PATH" ]; then 812 | echo -e "${White}## ${Green}LOOP paths found, resuming backup..." 813 | debug 'INFO' "LOOP paths found, resuming backup" 814 | debug 'DEBUG' "IMG_DEV_BOOT_PATH=$IMG_DEV_BOOT_PATH | IMG_DEV_ROOT_PATH=$IMG_DEV_ROOT_PATH" 815 | break 816 | fi 817 | done 818 | fi 819 | 820 | if ! [ -e "$IMG_DEV_BOOT_PATH" ] || ! [ -e "$IMG_DEV_ROOT_PATH" ]; then 821 | echo -e "${Red}!! LOOP PATHS CAN NOT BE SET!!!" 822 | debug 'ERROR' 'LOOP PATHS CAN NOT BE SET' 823 | exit 3 824 | fi 825 | 826 | # btrfs (old method) 827 | elif [ "$FSTYPE" == 'btrfs' ]; then 828 | IMG_DEV_BOOT_PATH="${LOOP}p1" 829 | IMG_DEV_ROOT_PATH="${LOOP}p2" 830 | debug 'DEBUG' "IMG_DEV_BOOT_PATH=$IMG_DEV_BOOT_PATH | IMG_DEV_ROOT_PATH=$IMG_DEV_ROOT_PATH" 831 | fi 832 | 833 | # No boot partition exists 834 | else 835 | # I have no idea why, but if I do not put this sleep here, IMG_DEV_ROOT_PATH does not get set 836 | sleep 2 837 | #pause 'Press [Enter] key to continue...' 838 | IMG_DEV_ROOT_PATH=$(lsblk -no path,uuid "$LOOP" | grep "$LOCAL_ROOT_UUID" | awk '{print $1}') 839 | debug 'DEBUG' "IMG_DEV_ROOT_PATH=$IMG_DEV_ROOT_PATH" 840 | # 2s sleep is sometimes not enough, might have to do with slow network if img file is on a network share 841 | if ! [ -e "$IMG_DEV_ROOT_PATH" ]; then 842 | for (( i=1; i<=3; i++ )); do 843 | echo -e "${Yellow}!! ${Yellow}LOOP paths can not be set, retrying in 5 seconds..." 844 | debug 'WARNING' 'LOOP paths can not be set, retrying in 5 seconds' 845 | debug 'WARNING' "IMG_DEV_ROOT_PATH=$IMG_DEV_ROOT_PATH" 846 | sleep 5 847 | IMG_DEV_ROOT_PATH=$(lsblk -no path,uuid "$LOOP" | grep "$LOCAL_ROOT_UUID" | awk '{print $1}') 848 | if [ -e "$IMG_DEV_ROOT_PATH" ]; then 849 | echo -e "${White}## ${Green}LOOP paths found, resuming backup..." 850 | debug 'INFO' "LOOP paths found, resuming backup" 851 | debug 'DEBUG' "IMG_DEV_ROOT_PATH=$IMG_DEV_ROOT_PATH" 852 | break 853 | fi 854 | done 855 | fi 856 | 857 | if ! [ -e "$IMG_DEV_ROOT_PATH" ]; then 858 | echo -e "${Red}!! LOOP PATHS CAN NOT BE SET!!!" 859 | debug 'ERROR' 'LOOP PATHS CAN NOT BE SET' 860 | exit 3 861 | fi 862 | fi 863 | return 0 864 | } 865 | 866 | 867 | 868 | # Set default RSYNC_LINE 869 | function rsync_line() { 870 | 871 | IMG_PATH=$(dirname "$IMG_FILE") 872 | debug 'INFO' 'Creating temp directory' 873 | debug 'DEBUG' "Running: mktemp -d -t backup-XXX" 874 | TMP_DIR=$(mktemp -d -t backup-XXX) 875 | debug 'DEBUG' "TMP_DIR=$TMP_DIR" 876 | debug 'INFO' 'Creating temp file to store rsync output' 877 | debug 'DEBUG' 'Running: mktemp -t rsync-XXX' 878 | tmp_file=$(mktemp -t rsync-XXX) 879 | debug 'DEBUG' "tmp_file=$tmp_file" 880 | if [ "$RSYNC_TTY" == false ]; then 881 | RSYNC_PATHS="/ $TMP_DIR 2>&1 | tee /dev/null > $tmp_file" 882 | else 883 | RSYNC_PATHS="/ $TMP_DIR 2>&1 | tee $TTY_AVAILABILITY > $tmp_file" 884 | fi 885 | debug 'DEBUG' "RSYNC_PATHS=$RSYNC_PATHS" 886 | 887 | if [ "$EXCLUDE_FILE" == true ]; then 888 | RSYNC_LINE="rsync -ahvHAX --exclude-from=$EXCLUDE_FILE_LOCATION --exclude={${IMG_PATH}/*,${TMP_DIR},${tmp_file},${BOOT_PATH}/shrink-backup.lock${SUBVOLUME_EXCLUDE_PATHS}} --info=progress2 --stats $RSYNC_DELETE --force --partial --delete-excluded --timeout=30" 889 | else 890 | RSYNC_LINE="rsync -ahvHAX --exclude={/lost+found,/proc/*,/sys/*,/dev/*,/tmp/*,/run/*,/mnt/*,/media/*,/var/swap,/snap/*,${IMG_PATH}/*,${BOOT_PATH}/shrink-backup.lock${SUBVOLUME_EXCLUDE_PATHS}} --info=progress2 --stats $RSYNC_DELETE --force --partial --delete-excluded --timeout=30" 891 | fi 892 | debug 'DEBUG' "RSYNC_LINE=$RSYNC_LINE" 893 | 894 | # If --rsync is selected, ask for user configuration 895 | if [ "$RSYNC_CUSTOM" == true ]; then 896 | echo -e "${Yellow}## Custom rsync line option selected" 897 | echo -e "${White}## ${IWhite}Default rsync line: ${Green}${RSYNC_LINE}" 898 | echo -e "${White}## ${IWhite}Edit line to use in script ${Yellow}(press ctrl+c to abort)${NC}" 899 | printf %${COLUMNS}s | tr ' ' '#' 900 | 901 | read -e -i "$RSYNC_LINE" input 902 | RSYNC_CUSTOM_LINE="$input" 903 | RSYNC_LINE="$input" 904 | debug 'DEBUG' "RSYNC_CUSTOM_LINE=$RSYNC_CUSTOM_LINE" 905 | fi 906 | 907 | # Add RSYNC_PATHS to RSYNC_LINE 908 | debug 'INFO' 'Combining RSYNC_LINE & RSYNC_PATHS' 909 | RSYNC_LINE="$(echo -e $RSYNC_LINE $RSYNC_PATHS)" 910 | debug 'DEBUG' "RSYNC_LINE=$RSYNC_LINE" 911 | return 0 912 | } 913 | 914 | 915 | 916 | # Print confirmation window 917 | function print_confirmation() { 918 | 919 | # Display information 920 | #echo -e "# ${IWhite}A backup will be created at ${Green}$IMG_FILE $(while [ $x -lt $(( COLS - 31 - $(echo ${IMG_FILE} | wc -m ) )) ]; do echo -n ' '; let x=$x+1; done; echo)${Purple}#" 921 | echo -e "${Purple}$BREAK" 922 | if [ "$PROMPTS" == false ]; then 923 | debug 'INFO' '-y selected by user. prompts are disabled' 924 | echo -e "# ${Yellow}DISABLE PROMPTS SELECTED (${Green}-y${Yellow}), NO WARNINGS ABOUT DELETION!!! ${Purple}$(printf "%+$(( COLS - 63 ))s" '#')" 925 | fi 926 | 927 | if [ "$UPDATE" == false ]; then 928 | echo -e "# ${IWhite}A backup will be created at: ${Purple}$(printf "%+$(( COLS - 31 ))s" '#')" 929 | else 930 | echo -e "# ${IWhite}Updating backup img: ${Purple}$(printf "%+$(( COLS - 23 ))s" '#')" 931 | fi 932 | 933 | echo -e "# ${Green}$IMG_FILE ${Purple}$(printf "%+$(( COLS - 2 - $(echo ${IMG_FILE} | wc -m ) ))s" '#')" 934 | echo -e "# ${Green}$FSTYPE ${IWhite}filesystem detected on root ${Purple}$(printf "%+$(( COLS - 30 - $(echo ${FSTYPE} | wc -m ) ))s" '#')" 935 | 936 | if [ "$RSYNC_DELETE" != '--delete' ] && [ "$UPDATE" == false ]; then 937 | echo -e "# ${Green}--fix ${IWhite}option selected, ${Green}rsync using --fsync option${Purple}$(printf "%+$(( COLS - 51 ))s" '#')" 938 | elif [ "$RSYNC_DELETE" != '--delete' ] && [ "$UPDATE" == true ]; then 939 | echo -e "# ${Green}--fix ${IWhite}option selected, ${Green}rsync using --delete-before & --fsync options${Purple}$(printf "%+$(( COLS - 70 ))s" '#')" 940 | fi 941 | 942 | if [ "$FSTYPE" == 'btrfs' ]; then 943 | echo -e "# ${Green}${#LOCAL_TOP_SUBVOLUMES[@]} ${IWhite}btrfs top volumes will be included ${Purple}$(printf "%+$(( COLS - 37 - $(echo ${#LOCAL_TOP_SUBVOLUMES[@]} | wc -m ) ))s" '#')" 944 | echo -e "${Purple}# ${IWhite}btrfs top volumes: ${Green}${LOCAL_TOP_SUBVOLUMES[@]} ${Purple}$(printf "%+$(( COLS - 21 - $(echo ${LOCAL_TOP_SUBVOLUMES[@]} | wc -m ) ))s" '#')" 945 | echo -e "# ${Green}${#LOCAL_NESTED_SUBVOLUMES[@]} ${IWhite}btrfs nested volumes will be included ${Purple}$(printf "%+$(( COLS - 40 - $(echo ${#LOCAL_NESTED_SUBVOLUMES[@]} | wc -m ) ))s" '#')" 946 | echo -e "${Purple}# ${IWhite}btrfs nested volumes: ${Green}${LOCAL_NESTED_SUBVOLUMES[@]} ${Purple}$(printf "%+$(( COLS - 24 - $(echo ${LOCAL_NESTED_SUBVOLUMES[@]} | wc -m ) ))s" '#')" 947 | elif [ "$FSTYPE" == 'f2fs' ]; then 948 | echo -e "# ${Yellow}Autoexpand filesystem at boot not available for ${Green}f2fs ${Purple}$(printf "%+$(( COLS - 54 ))s" '#')" 949 | if [ "$UPDATE" == true ]; then 950 | echo -e "# ${Yellow}Resize operations not available for ${Green}f2fs ${Purple}$(printf "%+$(( COLS - 43 ))s" '#')" 951 | fi 952 | # Will have exited before if both F2FS_CONVERSION & UPDATE are true 953 | elif [ "$F2FS_CONVERSION" == true ]; then 954 | echo -e "# ${Yellow}Converting filesystem into ${Green}f2fs ${Yellow}on img file ${Purple}$(printf "%+$(( COLS - 46 ))s" '#')" 955 | echo -e "# ${Yellow}Autoexpand filesystem at boot not available for ${Green}f2fs ${Purple}$(printf "%+$(( COLS - 55 ))s" '#')" 956 | fi 957 | 958 | if [ "$UPDATE" == true ] && [ "$RSYNC_DELETE" == '--delete-before' ]; then 959 | echo -e "# ${Green}--fix ${IWhite}option selected, rsync will delete files on img before copy ${Purple}$(printf "%+$(( COLS - 68 ))s" '#')" 960 | debug 'DEBUG' '--fix selected by user, rsync deleting files before copy' 961 | fi 962 | 963 | if [ "$RSYNC_CUSTOM" == true ]; then 964 | echo -e "# ${IWhite}Custom rsync line: ${Purple}$(printf "%+$(( COLS - 21 ))s" '#')" 965 | echo -e " ${Green}$RSYNC_CUSTOM_LINE" 966 | fi 967 | 968 | echo -e "${Purple}# $(printf %$(( COLS - 4 ))s | tr ' ' '-') #" 969 | echo -e "# ${IWhite}Write to logfile: | ${Green}$DEBUG ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${DEBUG} | wc -m ) ))s" '#')" 970 | echo -e "# ${IWhite}Zoom speed requested: | ${Green}$ZOOM ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${ZOOM} | wc -m ) ))s" '#')" 971 | echo -e "# ${IWhite}rsync tty output: | ${Green}$RSYNC_TTY ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${RSYNC_TTY} | wc -m ) ))s" '#')" 972 | echo -e "# ${IWhite}Autocalculate img root size: | ${Green}$AUTORESIZE_RUN ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${AUTORESIZE_RUN} | wc -m ) ))s" '#')" 973 | echo -e "# ${IWhite}Autoexpand filesystem at boot: | ${Green}$AUTOEXPAND ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${AUTOEXPAND} | wc -m ) ))s" '#')" 974 | echo -e "# ${IWhite}Use exclude.txt: | ${Green}$EXCLUDE_FILE ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${EXCLUDE_FILE} | wc -m ) ))s" '#')" 975 | # Will have exited before if both F2FS_CONVERSION & UPDATE are true 976 | if [ "$F2FS_CONVERSION" == true ]; then 977 | echo -e "# $(printf %$(( COLS - 4 ))s | tr ' ' '-') #" 978 | echo -e "# ${Yellow}!!WARNING!! --f2fs option selected. f2fs filesystem will be created on img file. ${Purple}$(printf "%+$(( COLS - 83 ))s" '#')" 979 | echo -e "# ${IWhite}This option is only for CONVERTING existing filesystem into f2fs on img file. ${Purple}$(printf "%+$(( COLS - 80 ))s" '#')" 980 | echo -e "# ${IWhite}This option is NOT needed for normal backups from f2fs on root. ${Purple}$(printf "%+$(( COLS - 66 ))s" '#')" 981 | echo -e "# ${Yellow}Only tested on Raspberry pi OS. ${Purple}$(printf "%+$(( COLS - 34 ))s" '#')" 982 | fi 983 | 984 | echo -e "${Purple}# $(printf %$(( COLS - 4 ))s | tr ' ' '-') #" 985 | echo -e "# ${Green}Device info: ${Purple}$(printf "%+$(( COLS - 15 ))s" '#')" 986 | echo -e "# ${IWhite}Boot partition: | ${Green}$BOOT_PARTITION ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${BOOT_PARTITION} | wc -m ) ))s" '#')" 987 | if [ "$BOOT_PARTITION" == true ]; then 988 | echo -e "# ${IWhite}Boot device: | ${Green}$LOCAL_DEV_BOOT_PATH ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${LOCAL_DEV_BOOT_PATH} | wc -m ) ))s" '#')" 989 | echo -e "# ${IWhite}Boot location: | ${Green}$BOOT_PATH ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${BOOT_PATH} | wc -m ) ))s" '#')" 990 | fi 991 | print_temp="$(( LOCAL_BOOTSECTOR / 1024 / 1024 ))" 992 | echo -e "# ${IWhite}Bootsector size: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 39 - $(echo ${print_temp} | wc -m ) ))s" '#')" 993 | echo -e "# ${IWhite}Root device: | ${Green}$LOCAL_DEV_ROOT_PATH ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${LOCAL_DEV_ROOT_PATH} | wc -m ) ))s" '#')" 994 | print_temp="$(( $(df / -k --sync --output=used | tail -1) / 1024 ))" 995 | echo -e "# ${IWhite}Estimated root usage: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 39 - $(echo ${print_temp} | wc -m ) ))s" '#')" 996 | 997 | if [ "$AUTORESIZE_RUN" == true ]; then 998 | print_temp="$(( LOCAL_AUTORESIZE_MIN / 1024 / 1024 ))" 999 | echo -e "# ${IWhite}Auto calculated root size: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 39 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1000 | if [ "$UPDATE" == false ]; then 1001 | print_temp="$(( TRUNCATE_TOTAL / 1024 / 1024 ))" 1002 | echo -e "# ${IWhite}Total img size: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 40 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1003 | else 1004 | print_temp="$(( IMG_SIZE / 1024 / 1024 ))" 1005 | echo -e "# ${IWhite}Old img size: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 39 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1006 | print_temp="$(( TRUNCATE_TOTAL / 1024 / 1024 ))" 1007 | echo -e "# ${IWhite}New img size: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 39 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1008 | if [ -z "$diff_small" ]; then 1009 | echo -e "# ${IWhite}Difference: | ${Green}$DIFFERENCE ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${DIFFERENCE} | wc -m ) ))s" '#')" 1010 | else 1011 | echo -e "# ${IWhite}Difference: | ${Green}$DIFFERENCE ${Purple}$(printf "%+$(( COLS - 36 - $(echo ${DIFFERENCE} | wc -m ) ))s" '#')" 1012 | fi 1013 | fi 1014 | 1015 | elif [ "$UPDATE" == false ] && [ "$AUTORESIZE_RUN" == false ]; then 1016 | print_temp="$(( TRUNCATE_TOTAL / 1024 / 1024 ))" 1017 | print_temp2="$(( ADDED_SPACE / 1024 / 1024 ))" 1018 | echo -e "# ${IWhite}Total img size: | ${Green}${print_temp}MiB ${IWhite}with ${Green}${print_temp2}MiB [extra space] ${IWhite}included ${Purple}$(printf "%+$(( COLS - 72 - $(echo ${print_temp} | wc -m ) - $(echo ${print_temp2} | wc -m ) ))s" '#')" 1019 | if [ "$AUTORESIZE_WARNING" == true ]; then 1020 | echo -e "${Purple}# $(printf %$(( COLS - 4 ))s | tr ' ' '-') #" 1021 | echo -e "# ${Yellow}!!WARNING!! Manually added space is smaller than calculated recommended minimum ${Purple}$(printf "%+$(( COLS - 82 ))s" '#')" 1022 | echo -e "# This does NOT mean the backup WILL fail, but CAN fail due to lack of space ${Purple}$(printf "%+$(( COLS - 77 ))s" '#')" 1023 | echo -e "# Consider using the -a option or manually adding more space ${Purple}$(printf "%+$(( COLS - 61 ))s" '#')" 1024 | print_temp="$(( LOCAL_AUTORESIZE_MIN / 1024 / 1024 ))" 1025 | echo -e "# ${IWhite}Calculated recommended minimum: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 40 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1026 | print_temp="$(( TOTAL / 1024 / 1024 ))" 1027 | echo -e "# ${IWhite}Requested root size: | ${Yellow}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 40 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1028 | fi 1029 | 1030 | elif [ "$UPDATE" == true ] && [ "$ADDED_SPACE" -eq 0 ]; then 1031 | print_temp="$(( IMG_SIZE / 1024 / 1024 ))" 1032 | echo -e "# ${IWhite}Total img size: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 40 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1033 | elif [ "$UPDATE" == true ] && [ "$ADDED_SPACE" -ne 0 ]; then 1034 | print_temp="$(( ADDED_SPACE / 1024 / 1024 ))" 1035 | echo -e "# ${IWhite}Added [extra space]: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 40 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1036 | print_temp="$(( IMG_SIZE / 1024 / 1024 ))" 1037 | echo -e "# ${IWhite}Old img size: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 40 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1038 | print_temp="$(( TRUNCATE_TOTAL / 1024 / 1024 ))" 1039 | echo -e "# ${IWhite}New img size: | ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 40 - $(echo ${print_temp} | wc -m ) ))s" '#')" 1040 | fi 1041 | 1042 | # Disabled prompts 1043 | if [ "$PROMPTS" == false ] && [ "$ZOOM" == false ]; then 1044 | echo -e "${Red}# !! PRESS CTRL+C WITHIN 5s TO CANCEL !! $(printf "%+$(( COLS - 41 ))s" '#')" 1045 | echo -e "${BREAK}" 1046 | i=5 1047 | while [ "$i" -gt 0 ]; do 1048 | echo -ne "\r${Red}## ${IWhite}Cancel? ${Green}${i}s" 1049 | sleep 1 1050 | ((i--)) 1051 | done 1052 | echo -e "\n${White}## ${IWhite}Starting backup..." 1053 | debug 'INFO' '5 seconds passed, user did not stop operation' 1054 | debug 'BREAK' 1055 | 1056 | # Confirm with user input 1057 | elif [ "$PROMPTS" == true ]; then 1058 | echo -e "${Purple}${BREAK}${IWhite}" 1059 | debug 'INFO' 'Do you want to continue? [y/n]' 1060 | while true; do 1061 | if [ "$COLORS" == true ]; then 1062 | read -r -p $'\e[0;37m## \e[0;97mDo you want to continue? \e[0;92m[y/n] \e[0m' input 1063 | else 1064 | read -r -p '## Do you want to continue? [y/n] ' input 1065 | fi 1066 | case $input in 1067 | [Yy]) debug 'INFO' 'Y or y pressed to confirm'; debug 'BREAK'; break;; 1068 | [Nn]) echo -e "${Red}!! Aborting..."; exit 4;; 1069 | *) echo -e "${Yellow}!! ${Red}ERROR! ${Yellow}Please enter ${Green}'y' ${Yellow}or ${Green}'n'${Yellow}"; debug 'WARNING' "ERROR, please enter 'y' or 'n'";; 1070 | esac 1071 | done 1072 | else 1073 | echo -e "${Purple}${BREAK}${IWhite}" 1074 | fi 1075 | return 0 1076 | } 1077 | 1078 | 1079 | 1080 | # Check for required dependencies 1081 | function check_dependencies() { 1082 | 1083 | # GPT 1084 | if [ "$PARTITION_TABLE" == 'gpt' ]; then 1085 | echo -e "${White}## ${Green}GPT partition table detected, ${Yellow}sgdisk needed, ${IWhite}checking for application..." 1086 | $SLEEPING 1087 | if ! command -v sgdisk > /dev/null 2>&1; then 1088 | echo -e "${Yellow}!! sgdisk is NOT installed..." 1089 | debug 'INFO' 'sgdisk not available on system' 1090 | 1091 | # Debian based 1092 | if command -v apt > /dev/null 2>&1; then 1093 | echo -e "${White}## ${Green}apt found, ${IWhite}trying to install gdisk..." 1094 | # Confirm with user input 1095 | if [ "$PROMPTS" == true ]; then 1096 | debug 'INFO' 'Do you want to use apt to install gdisk? (will upgrade system first) [y/n]' 1097 | while true; do 1098 | if [ "$COLORS" == true ]; then 1099 | read -r -p $'\e[0;37m## \e[0;93mDo you want to use apt to install gdisk? \e[0;97m(will upgrade system first) \e[0;92m[y/n] \e[0m' input 1100 | else 1101 | read -r -p '## Do you want to use apt to install gdisk? (will upgrade system first) [y/n] ' input 1102 | fi 1103 | case $input in 1104 | [Yy]) break;; 1105 | [Nn]) echo -e "${Red}!! Aborting..."; exit 4;; 1106 | *) echo -e "${Yellow}!! ${Red}ERROR! ${Yellow}Please enter ${Green}'y' ${Yellow}or ${Green}'n'${Yellow}"; debug 'WARNING' "ERROR, please enter 'y' or 'n'";; 1107 | esac 1108 | done 1109 | debug 'INFO' 'Y or y pressed to confirm' 1110 | debug 'BREAK' 1111 | debug 'DEBUG' 'Running: apt update -y && apt upgrade -y && apt install gdisk -y' 1112 | apt update -y && apt upgrade -y && apt install gdisk -y 1113 | else 1114 | debug 'DEBUG' 'Running: apt update -y && apt upgrade -y && apt install gdisk -y' 1115 | apt update -y && apt upgrade -y && apt install gdisk -y 1116 | fi 1117 | 1118 | # Arch based 1119 | elif command -v pacman > /dev/null 2>&1; then 1120 | echo -e "${White}## ${Green}pacman found, ${IWhite}trying to install gdisk..." 1121 | # Confirm with user input 1122 | if [ "$PROMPTS" == true ]; then 1123 | debug 'INFO' 'Do you want to use pacman to install gptfdisk? (will run pacman -Syu first) [y/n]' 1124 | while true; do 1125 | if [ "$COLORS" == true ]; then 1126 | read -r -p $'\e[0;37m## \e[0;93mDo you want to use pacman to install gptfdisk? \e[0;97m(will run pacman -Syu first) \e[0;92m[y/n] \e[0m' input 1127 | else 1128 | read -r -p '## Do you want to use pacman to install gptfdisk? (will run pacman -Syu first) [y/n] ' input 1129 | fi 1130 | case $input in 1131 | [Yy]) break;; 1132 | [Nn]) echo -e "${Red}!! Aborting..."; exit 4;; 1133 | *) echo -e "${Yellow}!! ${Red}ERROR! ${Yellow}Please enter ${Green}'y' ${Yellow}or ${Green}'n'${Yellow}"; debug 'WARNING' "ERROR, please enter 'y' or 'n'";; 1134 | esac 1135 | done 1136 | debug 'INFO' 'Y or y pressed to confirm' 1137 | debug 'BREAK' 1138 | debug 'DEBUG' 'Running: pacman -Syu --nocofirm && pacman -S --noconfirm gptfdisk' 1139 | pacman -Syu --noconfirm && pacman -S --noconfirm gptfdisk 1140 | else 1141 | debug 'DEBUG' 'Running: pacman -Syu --nocofirm && pacman -S --noconfirm gptfdisk' 1142 | pacman -Syu --noconfirm && pacman -S --noconfirm gptfdisk 1143 | fi 1144 | 1145 | # Installation failed or neither apt nor pacman is available 1146 | else 1147 | echo -e "${Red}!! Error! ${Yellow}Installing sgdisk failed! ${Green}Please install sgdisk manually and retry script..." 1148 | debug 'ERROR' 'Installing sgdisk failed, aborting exit 3' 1149 | echo -e "${Red}!! Aborting..." 1150 | exit 3 1151 | fi 1152 | echo -e "${White}## ${Green}sgdisk installed successfully, ${IWhite}resuming backup..." 1153 | debug 'INFO' 'gdisk installed successfully' 1154 | $SLEEPING 1155 | 1156 | # sgdisk already installed 1157 | else 1158 | echo -e "${White}## ${Green}sgdisk is available, ${IWhite}resuming backup..." 1159 | debug 'INFO' 'sgdisk is available on system' 1160 | $SLEEPING 1161 | fi 1162 | fi 1163 | 1164 | # f2fs conversion (only apt) 1165 | if [ "$F2FS_CONVERSION" == true ]; then 1166 | echo -e "${White}## ${Green}--f2fs option selected, mkfs.f2fs is needed, ${IWhite}checking for application..." 1167 | $SLEEPING 1168 | if ! command -v mkfs.f2fs > /dev/null 2>&1; then 1169 | echo -e "${Yellow}!! mkfs.f2fs is NOT installed..." 1170 | debug 'INFO' 'mkfs.f2fs (f2fs-tools) not available on system' 1171 | 1172 | # Debian based 1173 | if command -v apt > /dev/null 2>&1; then 1174 | echo -e "${White}## ${Green}apt found, ${IWhite}trying to install f2fs-tools..." 1175 | # Confirm with user input 1176 | if [ "$PROMPTS" == true ]; then 1177 | debug 'INFO' 'Do you want to use apt to install f2fs-tools? (will upgrade system first) [y/n]' 1178 | while true; do 1179 | if [ "$COLORS" == true ]; then 1180 | read -r -p $'\e[0;37m## \e[0;93mDo you want to use apt to install f2fs-tools? \e[0;97m(will upgrade system first) \e[0;92m[y/n] \e[0m' input 1181 | else 1182 | read -r -p '## Do you want to use apt to install f2fs-tools? (will upgrade system first) [y/n] ' input 1183 | fi 1184 | case $input in 1185 | [Yy]) break;; 1186 | [Nn]) echo -e "${Red}!! Aborting..."; exit 4;; 1187 | *) echo -e "${Yellow}!! ${Red}ERROR! ${Yellow}Please enter ${Green}'y' ${Yellow}or ${Green}'n'${Yellow}"; debug 'WARNING' "ERROR, please enter 'y' or 'n'";; 1188 | esac 1189 | done 1190 | debug 'INFO' 'Y or y pressed to confirm' 1191 | debug 'BREAK' 1192 | debug 'DEBUG' 'Running: apt update -y && apt upgrade -y && apt install f2fs-tools -y' 1193 | apt update -y && apt upgrade -y && apt install f2fs-tools -y 1194 | else 1195 | debug 'DEBUG' 'Running: apt update -y && apt upgrade -y && apt install f2fs-tools -y' 1196 | apt update -y && apt upgrade -y && apt install f2fs-tools -y 1197 | fi 1198 | 1199 | # Installation failed 1200 | else 1201 | echo -e "${Red}!! Error! ${Yellow}Installing mkfs.f2fs (f2fs-tools) failed! ${Green}Please install f2fs-tools manually and retry script..." 1202 | debug 'ERROR' 'Installing f2fs-tools failed, aborting exit 3' 1203 | echo -e "${Red}!! Aborting..." 1204 | exit 3 1205 | fi 1206 | 1207 | # mkfs.f2fs already installed 1208 | else 1209 | echo -e "${White}## ${Green}mkfs.f2fs is available, ${IWhite}resuming backup..." 1210 | debug 'INFO' 'mkfs.f2fs is available on system' 1211 | $SLEEPING 1212 | fi 1213 | fi 1214 | 1215 | # btrfs conversion (work in progress) 1216 | if [ "$BTRFS_CONVERSION" == true ]; then 1217 | echo 'btrfs' 1218 | fi 1219 | 1220 | # --chroot selected (only apt) 1221 | if [ "$CHROOTRUN" == true ]; then 1222 | echo -e "${White}## ${Green}--chrootrun ${IWhite}selected, ${Yellow}systemd-nspawn needed, ${IWhite}checking for application..." 1223 | $SLEEPING 1224 | if ! command -v systemd-nspawn > /dev/null 2>&1; then 1225 | echo -e "${Yellow}!! systemd-nspawn is NOT installed..." 1226 | debug 'INFO' 'systemd-nspawn not available on system' 1227 | 1228 | # Only needed on debian based systems, on arch systemd-nspawn is in systemd package 1229 | if command -v apt > /dev/null 2>&1; then 1230 | echo -e "${White}## ${Green}apt found, ${IWhite}trying to install systemd-container..." 1231 | # Confirm with user input 1232 | if [ "$PROMPTS" == true ]; then 1233 | debug 'INFO' 'Do you want to use apt to install systemd-container? (will upgrade system first) [y/n]' 1234 | while true; do 1235 | if [ "$COLORS" == true ]; then 1236 | read -r -p $'\e[0;37m## \e[0;93mDo you want to use apt to install systemd-container? \e[0;97m(will upgrade system first) \e[0;92m[y/n] \e[0m' input 1237 | else 1238 | read -r -p '## Do you want to use apt to install systemd-container? (will upgrade system first) [y/n] ' input 1239 | fi 1240 | case $input in 1241 | [Yy]) break;; 1242 | [Nn]) echo -e "${Red}!! Aborting..."; exit 4;; 1243 | *) echo -e "${Yellow}!! ${Red}ERROR! ${Yellow}Please enter ${Green}'y' ${Yellow}or ${Green}'n'${Yellow}"; debug 'WARNING' "ERROR, please enter 'y' or 'n'";; 1244 | esac 1245 | done 1246 | debug 'INFO' 'Y or y pressed to confirm' 1247 | debug 'BREAK' 1248 | debug 'DEBUG' 'Running: apt update -y && apt upgrade -y && apt install systemd-container -y' 1249 | apt update -y && apt upgrade -y && apt install systemd-container -y 1250 | else 1251 | debug 'DEBUG' 'Running: apt update -y && apt upgrade -y && apt install systemd-container -y' 1252 | apt update -y && apt upgrade -y && apt install systemd-container -y 1253 | fi 1254 | echo -e "${White}## ${Green}systemd-container installed successfully, ${IWhite}resuming..." 1255 | debug 'INFO' 'systemd-container installed successfully' 1256 | $SLEEPING 1257 | 1258 | # Installation failed 1259 | else 1260 | echo -e "${Red}!! Error! ${Yellow}Installing systemd-nspawn (systemd-container) failed! ${Green}Please install systemd-container manually and retry script..." 1261 | debug 'ERROR' 'Installing systemd-container failed, aborting exit 3' 1262 | echo -e "${Red}!! Aborting..." 1263 | exit 3 1264 | fi 1265 | 1266 | # systemd-nspawn already installed 1267 | else 1268 | echo -e "${White}## ${Green}systemd-nspawn is available, ${IWhite}resuming..." 1269 | debug 'INFO' 'systemd-nspawn is available on system' 1270 | $SLEEPING 1271 | fi 1272 | fi 1273 | return 0 1274 | } 1275 | 1276 | 1277 | 1278 | # loop img file 1279 | function do_loop() { 1280 | 1281 | # Find free loop 1282 | if [ -z "$LOOP" ]; then 1283 | LOOP=$(losetup -f) 1284 | debug 'DEBUG' "LOOP=$LOOP" 1285 | fi 1286 | 1287 | echo -e "${White}## ${IWhite}Looping img file..." 1288 | $SLEEPING 1289 | debug 'DEBUG' "Running: losetup -P $LOOP $IMG_FILE" 1290 | if ! output=$(losetup -P "$LOOP" "$IMG_FILE" 2>&1); then 1291 | echo -e "${Yellow}$output\n${Red}!! LOSETUP FAILED!!!" 1292 | debug 'BREAK' 1293 | debug 'ERROR' "LOSETUP FAILED:\n$output\n-------------------------------------------------------------------------------------" 1294 | exit 3 1295 | fi 1296 | return 0 1297 | } 1298 | 1299 | 1300 | 1301 | # Mount img file 1302 | function do_mount() { 1303 | 1304 | partprobe "$LOOP" 1305 | 1306 | # ext4 & f2fs 1307 | if [ "$FSTYPE" == 'ext4' ] || [ "$FSTYPE" == 'f2fs' ] || [ "$F2FS_CONVERSION" == true ]; then 1308 | echo -e "${White}## ${IWhite}Mounting img root partition..." 1309 | $SLEEPING 1310 | debug 'INFO' 'Mounting root partition from loop' 1311 | debug 'DEBUG' "Running: mount $IMG_DEV_ROOT_PATH $TMP_DIR" 1312 | if ! output=$(mount "$IMG_DEV_ROOT_PATH" "$TMP_DIR" 2>&1); then 1313 | echo -e "${Yellow}$output\n${Red}!! ROOT MOUNT FAILED!!!" 1314 | debug 'BREAK' 1315 | debug 'ERROR' "ROOT MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 1316 | exit 3 1317 | fi 1318 | 1319 | # btrfs 1320 | elif [ "$FSTYPE" == 'btrfs' ]; then 1321 | declare -a fstab=( $(cat /etc/fstab | grep ' / ') ) 1322 | echo -e "${White}## ${IWhite}Mounting root (${Green}@${IWhite}) subvolume..." 1323 | $SLEEPING 1324 | debug 'INFO' 'Mounting root (@) subvolume from loop' 1325 | debug 'DEBUG' "Running: mount -o ${fstab[3]} $IMG_DEV_ROOT_PATH $TMP_DIR" 1326 | if ! output=$(mount -o "${fstab[3]}" "$IMG_DEV_ROOT_PATH" "$TMP_DIR" 2>&1); then 1327 | echo -e "${Yellow}$output\n${Red}!! ROOT SUBVOLUME MOUNT FAILED!!!" 1328 | debug 'BREAK' 1329 | debug 'ERROR' "ROOT SUBVOLUME MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 1330 | exit 3 1331 | fi 1332 | 1333 | # Mount top subvolumes (except @). New top volumes will not be created during an update, if new top volumes have been created on device script will fail and exit 1334 | debug 'INFO' 'Mounting non-root subvolumes from loop (if existing)' 1335 | for subvol in "${LOCAL_TOP_SUBVOLUMES[@]}"; do 1336 | # Excluding root (@) subvolume and mounting all else in fstab 1337 | if grep -q "$subvol" /etc/fstab && [[ "$subvol" != '@' ]]; then 1338 | declare -a fstab=( $(cat /etc/fstab | grep "$subvol") ) 1339 | echo -e "${White}## ${IWhite}Mounting subvolume: ${Green}$subvol" 1340 | $SLEEPING 1341 | if ! [ -d "${TMP_DIR}${fstab[1]}" ]; then 1342 | debug 'DEBUG' "Directory for subvol $subvol did not exist, running: mkdir -p ${TMP_DIR}${fstab[1]}" 1343 | mkdir -p ${TMP_DIR}${fstab[1]} 1344 | fi 1345 | debug 'DEBUG' "Running: mount -o ${fstab[3]} $IMG_DEV_ROOT_PATH ${TMP_DIR}${fstab[1]}" 1346 | if ! output=$(mount -o "${fstab[3]}" "$IMG_DEV_ROOT_PATH" "${TMP_DIR}${fstab[1]}" 2>&1); then 1347 | echo -e "${Yellow}$output\n${Red}!! SUBVOLUME MOUNT FAILED!!!" 1348 | debug 'BREAK' 1349 | debug 'ERROR' "SUBVOLUME MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 1350 | exit 3 1351 | fi 1352 | fi 1353 | done 1354 | 1355 | # Check for differences in nested subvolumes & create/remove if necessary 1356 | if [[ $(echo $(btrfs subvolume list "$TMP_DIR" --sort=path | grep -v ' 5 ' | awk '{print $9}') ) != "${LOCAL_NESTED_SUBVOLUMES[@]}" ]]; then 1357 | echo -e "${White}## ${IWhite}Managing nested subvolumes..." 1358 | $SLEEPING 1359 | debug 'INFO' 'Managing nested subvolumes' 1360 | IMG_NESTED_SUBVOLUMES=( $(btrfs subvolume list "$TMP_DIR" --sort=path | grep -v ' 5 ' | awk '{print $9}') ) 1361 | debug 'DEBUG' "IMG_NESTED_SUBVOLUMES=$(echo ${IMG_NESTED_SUBVOLUMES[@]})" 1362 | 1363 | # Create new nested subvolume(s) if needed 1364 | for subvol in ${LOCAL_NESTED_SUBVOLUMES[@]}; do 1365 | if ! printf '%s\n' "${IMG_NESTED_SUBVOLUMES[@]}" | grep -qx $subvol; then 1366 | echo -e "${White}## ${IWhite}Creating nested subvolume: ${Green}$subvol" 1367 | $SLEEPING 1368 | # Fix path if nested subvolume is in other location than root subvolume 1369 | top_subvolumes="$(echo ${LOCAL_TOP_SUBVOLUMES[@]} | sed 's/ /|/g')" # convert spaces into | 1370 | if echo "$subvol" | grep -qE "$top_subvolumes"; then 1371 | debug 'INFO' 'Nested volume not under root, fixing path' 1372 | for top_subvol in ${LOCAL_TOP_SUBVOLUMES[@]}; do 1373 | if echo "$subvol" | grep -qw "$top_subvol"; then 1374 | break 1375 | fi 1376 | done 1377 | path="$(cat /etc/fstab | grep $top_subvol | awk '{print $2}' | sed 's/^\///')" # remove first slash 1378 | subvol="$(echo $subvol | sed "s|$top_subvol|$path|g")" 1379 | fi 1380 | # Remove directory if existing 1381 | if [ -d "${TMP_DIR}/${subvol}" ]; then 1382 | debug 'DEBUG' "Directory for nested volume exists, deleting: rm -rf ${TMP_DIR}/${subvol}" 1383 | rm -rf "${TMP_DIR}/${subvol}" 1384 | fi 1385 | debug 'DEBUG' "Running: btrfs subvolume create ${TMP_DIR}/$subvol" 1386 | if ! output=$(btrfs subvolume create -p "$TMP_DIR"/"$subvol" 2>&1); then 1387 | echo -e "${Yellow}$output\n${Red}!! CREATE NESTED SUBVOLUME FAILED!!!" 1388 | debug 'BREAK' 1389 | debug 'ERROR' "CREATE NESTED SUBVOLUME FAILED:\n$output\n-------------------------------------------------------------------------------------" 1390 | exit 3 1391 | fi 1392 | fi 1393 | IMG_NESTED_SUBVOLUMES=( $(btrfs subvolume list "$TMP_DIR" --sort=path | grep -v ' 5 ' | awk '{print $9}') ) 1394 | debug 'DEBUG' "IMG_NESTED_SUBVOLUMES=$(echo ${IMG_NESTED_SUBVOLUMES[@]})" 1395 | done 1396 | 1397 | # Delete nested img subvolume(s) if needed 1398 | for subvol in ${IMG_NESTED_SUBVOLUMES[@]}; do 1399 | if ! printf '%s\n' "${LOCAL_NESTED_SUBVOLUMES[@]}" | grep -qx $subvol; then 1400 | echo -e "${White}## ${IWhite}Deleting nested subvolume: ${Green}$subvol" 1401 | $SLEEPING 1402 | # Fix path if nested subvolume is in other location than root subvolume 1403 | top_subvolumes="$(echo ${LOCAL_TOP_SUBVOLUMES[@]} | sed 's/ /|/g')" # convert spaces into | 1404 | if echo "$subvol" | grep -qE "$top_subvolumes"; then 1405 | debug 'INFO' 'Nested volume not under root, fixing path' 1406 | for top_subvol in ${LOCAL_TOP_SUBVOLUMES[@]}; do 1407 | if echo "$subvol" | grep -qw "$top_subvol"; then 1408 | break 1409 | fi 1410 | done 1411 | path="$(cat /etc/fstab | grep $top_subvol | awk '{print $2}' | sed 's/^\///')" # remove first slash 1412 | subvol="$(echo $subvol | sed "s|$top_subvol|$path|g")" 1413 | fi 1414 | debug 'DEBUG' "Running: btrfs subvolume delete ${TMP_DIR}/$subvol" 1415 | if ! output=$(btrfs subvolume delete "$TMP_DIR"/"$subvol" 2>&1); then 1416 | echo -e "${Yellow}$output\n${Red}!! DELETE NESTED SUBVOLUME FAILED!!!" 1417 | debug 'BREAK' 1418 | debug 'ERROR' "DELETE NESTED SUBVOLUME FAILED:\n$output\n-------------------------------------------------------------------------------------" 1419 | exit 3 1420 | fi 1421 | fi 1422 | done 1423 | IMG_NESTED_SUBVOLUMES=( $(btrfs subvolume list "$TMP_DIR" --sort=path | grep -v ' 5 ' | awk '{print $9}') ) 1424 | debug 'DEBUG' "IMG_NESTED_SUBVOLUMES=$(echo ${IMG_NESTED_SUBVOLUMES[@]})" 1425 | fi 1426 | fi 1427 | 1428 | # Mount boot partition if exists 1429 | if [ -n "$BOOT_PATH" ]; then 1430 | echo -e "${White}## ${IWhite}Mounting img boot partition..." 1431 | $SLEEPING 1432 | debug 'INFO' 'Mounting boot inside root' 1433 | if ! [ -d ${TMP_DIR}${BOOT_PATH} ]; then 1434 | debug 'INFO' 'Boot directory did not exist on img' 1435 | debug 'DEBUG' "Running: mkdir -p ${TMP_DIR}${BOOT_PATH}" 1436 | mkdir -p ${TMP_DIR}${BOOT_PATH} 1437 | fi 1438 | debug 'DEBUG' "Running: mount $IMG_DEV_BOOT_PATH ${TMP_DIR}${BOOT_PATH}" 1439 | if ! output=$(mount "$IMG_DEV_BOOT_PATH" "${TMP_DIR}${BOOT_PATH}" 2>&1); then 1440 | echo -e "${Yellow}$output\n${Red}!! BOOT MOUNT FAILED!!!" 1441 | debug 'BREAK' 1442 | debug 'ERROR' "BOOT MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 1443 | exit 3 1444 | fi 1445 | fi 1446 | return 0 1447 | } 1448 | 1449 | 1450 | 1451 | # Check filesystem 1452 | function do_e2fsck() { 1453 | 1454 | if [ -n "$BOOT_PATH" ] && [ -n "$TMP_DIR" ] && grep -qs "${TMP_DIR}${BOOT_PATH} " /proc/mounts; then 1455 | debug 'INFO' 'Unmounting boot partition' 1456 | debug 'DEBUG' "Running: umount ${TMP_DIR}${BOOT_PATH}" 1457 | umount "${TMP_DIR}${BOOT_PATH}" 1458 | fi 1459 | 1460 | if [ -n "$TMP_DIR" ] && grep -qs "$TMP_DIR " /proc/mounts; then 1461 | debug 'INFO' 'Unmounting root partition' 1462 | debug 'DEBUG' "Running: umount $TMP_DIR" 1463 | umount "$TMP_DIR" 1464 | fi 1465 | 1466 | # Final check of filesystem 1467 | if [ "$*" == 'final' ]; then 1468 | echo -e "${White}## ${IWhite}Finalizing filesystem..." 1469 | $SLEEPING 1470 | 1471 | output=$(e2fsck -p -f -v "$IMG_DEV_ROOT_PATH" 2>&1) 1472 | echo "$output" 1473 | debug 'BREAK' 1474 | debug 'DEBUG' "Running: e2fsck -p -f -v $IMG_DEV_ROOT_PATH\n$output\n-------------------------------------------------------------------------------------" 1475 | $SLEEPING 1476 | 1477 | # Remounting if autoexpansion is requested 1478 | if [[ ( "$AUTOEXPAND" == true && "$OS" != 'unknown' ) || ( "$AUTOEXPAND" == false && "$OS" == 'ubuntu' ) ]]; then 1479 | echo -e "${White}## ${IWhite}Remounting for autoexpansion..." 1480 | debug 'INFO' 'Remounting for autoexpansion function' 1481 | debug 'INFO' 'Running function: do_mount' 1482 | $SLEEPING 1483 | do_mount 1484 | fi 1485 | 1486 | # Normal check 1487 | else 1488 | echo -e "${White}## ${IWhite}Checking img filesystem..." 1489 | $SLEEPING 1490 | 1491 | output=$(e2fsck -p -f "$IMG_DEV_ROOT_PATH" 2>&1) 1492 | echo "$output" 1493 | debug 'DEBUG' "Running: e2fsck -p -f $IMG_DEV_ROOT_PATH\n$output\n-------------------------------------------------------------------------------------" 1494 | $SLEEPING 1495 | fi 1496 | return 0 1497 | } 1498 | 1499 | 1500 | 1501 | # Resize image 1502 | function do_resize() { 1503 | 1504 | # Reading offset for img root partition 1505 | debug 'INFO' 'Using fdisk to find img root partition offset' 1506 | debug 'DEBUG' "Running: fdisk -lo start $IMG_FILE | tail -1" 1507 | IMG_ROOT_START=$(fdisk -lo start "$IMG_FILE" | tail -1 | awk '{print $1}') # blocks, 521B block size 1508 | debug 'DEBUG' "IMG_ROOT_START=$IMG_ROOT_START blocks" 1509 | IMG_ROOT_START=$(( IMG_ROOT_START * 512 )) # bytes 1510 | debug 'DEBUG' "IMG_ROOT_START=${IMG_ROOT_START}B" 1511 | 1512 | # Converting TOTAL > TOTALK (bytes > kibibytes) 1513 | debug 'INFO' 'Converting TOTAL > TOTALK (bytes > kibibytes)' 1514 | TOTALK=$(( TOTAL / 1024 )) # kibibytes 1515 | debug 'DEBUG' "TOTAL=${TOTAL}B | TOTALK=${TOTALK}KiB" 1516 | 1517 | # Gather information 1518 | debug 'INFO' 'Using parted to fetch root partition number' 1519 | debug 'DEBUG' "Running: parted -sm "$LOOP" print | tail -1 | cut -d : -f 1" 1520 | IMG_ROOT_PARTN=$(parted -sm "$LOOP" print | tail -1 | cut -d : -f 1) 1521 | debug 'DEBUG' "IMG_ROOT_PARTN=$IMG_ROOT_PARTN" 1522 | 1523 | # Check img filesystem 1524 | if [ $FSTYPE == 'ext4' ]; then 1525 | debug 'INFO' 'Running function: do_e2fsck' 1526 | do_e2fsck 1527 | fi 1528 | 1529 | 1530 | # Expanding 1531 | if [ "$*" == 'expand' ]; then 1532 | 1533 | echo -e "${White}## ${Yellow}Expanding img filesystem..." 1534 | $SLEEPING 1535 | 1536 | # Removing loop for truncate to take effect 1537 | echo -e "${White}## ${IWhite}Removing loop..." 1538 | $SLEEPING 1539 | debug 'INFO' 'Removing loop for truncate to take effect' 1540 | debug 'DEBUG' "Running: losetup -d $LOOP" 1541 | losetup -d "$LOOP" 1542 | 1543 | echo -e "${White}## ${Yellow}Expanding image file..." 1544 | $SLEEPING 1545 | debug 'INFO' "Using truncate to resize img file to $(( TRUNCATE_TOTAL / 1024 / 1024 ))MiB" 1546 | debug 'DEBUG' "Running: truncate --size=$TRUNCATE_TOTAL $IMG_FILE" 1547 | if ! output=$(truncate --size="$TRUNCATE_TOTAL" "$IMG_FILE" 2>&1); then 1548 | echo -e "${Yellow}$output\n${Red}!! TRUNCATE FAILED!!!" 1549 | debug 'BREAK' 1550 | debug 'ERROR' "TRUNCATE FAILED:\n$output\n-------------------------------------------------------------------------------------" 1551 | exit 3 1552 | fi 1553 | 1554 | # Loop img file 1555 | debug 'INFO' 'Re-looping img file to fetch new img size' 1556 | debug 'INFO' 'Running function: do_loop' 1557 | do_loop 1558 | 1559 | echo -e "${White}## ${IWhite}Removing partition..." 1560 | $SLEEPING 1561 | #debug 'INFO' 'Using sfdisk to remove root partition' 1562 | debug 'INFO' 'Using parted to remove root partition' 1563 | #debug 'DEBUG' "Running: sfdisk --delete -f $LOOP $IMG_ROOT_PARTN" 1564 | debug 'DEBUG' "Running: parted -s $LOOP rm $IMG_ROOT_PARTN" 1565 | #debug 'DEBUG' "Running: printf 'Ignore\\\n'$IMG_ROOT_PARTN | parted $LOOP rm $IMG_ROOT_PARTN ---pretend-input-tty" 1566 | 1567 | #if ! output=$(sfdisk --delete -f "$LOOP" "$IMG_ROOT_PARTN" 2>&1); then # might fail if img size is very big 1568 | if ! output=$(parted -s "$LOOP" rm "$IMG_ROOT_PARTN" 2>&1); then # for some reason this line works here but not when creating img 1569 | #if ! output=$(printf 'Ignore\n'$IMG_ROOT_PARTN | parted $LOOP rm $IMG_ROOT_PARTN ---pretend-input-tty 2>&1); then # raspberry pi os does not like this method, keep for memory 1570 | #echo -e "${Yellow}$output\n${Red}!! SFDISK FAILED!!!" 1571 | echo -e "${Yellow}$output\n${Red}!! PARTED FAILED!!!" 1572 | debug 'BREAK' 1573 | #debug 'ERROR' "SFDISK FAILED:\n$output\n-------------------------------------------------------------------------------------" 1574 | debug 'ERROR' "PARTED FAILED:\n$output\n-------------------------------------------------------------------------------------" 1575 | exit 3 1576 | fi 1577 | 1578 | echo -e "${White}## ${IWhite}Recreating partition..." 1579 | $SLEEPING 1580 | debug 'INFO' 'Using parted to recreate root partition' 1581 | debug 'DEBUG' "Running: parted -s -a none $LOOP unit B mkpart primary ext4 $IMG_ROOT_START 100%" 1582 | if ! output=$(parted -s -a none "$LOOP" unit B mkpart primary ext4 "$IMG_ROOT_START" 100% 2>&1); then 1583 | echo -e "${Yellow}$output\n${Red}!! PARTED FAILED!!!" 1584 | debug 'BREAK' 1585 | debug 'ERROR' "PARTED FAILED:\n$output\n-------------------------------------------------------------------------------------" 1586 | exit 3 1587 | fi 1588 | 1589 | echo -e "${White}## ${Yellow}Expanding filesystem..." 1590 | $SLEEPING 1591 | debug 'INFO' 'Using resize2fs to expand filesystem' 1592 | debug 'BREAK' 1593 | debug 'DEBUG' "Running: resize2fs -p -f $IMG_DEV_ROOT_PATH" 1594 | if ! output=$(resize2fs -p -f "$IMG_DEV_ROOT_PATH" 2>&1); then 1595 | echo -e "${Yellow}$output\n${Red}!! RESIZE2FS FAILED!!!" 1596 | debug 'BREAK' 1597 | debug 'ERROR' "RESIZE2FS FAILED:\n$output\n-------------------------------------------------------------------------------------" 1598 | exit 3 1599 | fi 1600 | debug 'DEBUG' "$output\n-------------------------------------------------------------------------------------" 1601 | 1602 | # Check img filesystem 1603 | if [ $FSTYPE == 'ext4' ]; then 1604 | debug 'INFO' 'Running function: do_e2fsck' 1605 | do_e2fsck 1606 | fi 1607 | 1608 | 1609 | # Shrinking 1610 | elif [ "$*" == 'shrink' ]; then 1611 | 1612 | echo -e "${White}## ${Yellow}Shrinking filesystem..." 1613 | $SLEEPING 1614 | debug 'INFO' 'Using resize2fs to shrink filesystem' 1615 | debug 'BREAK' 1616 | debug 'DEBUG' "Running: resize2fs -p -f $IMG_DEV_ROOT_PATH ${TOTALK}K" 1617 | if ! output=$(resize2fs -p -f "$IMG_DEV_ROOT_PATH" "$TOTALK"K 2>&1 | tee "$TTY_AVAILABILITY"); then 1618 | echo -e "${Red}$output\n${Red}!! RESIZE2FS FAILED!!!" 1619 | debug 'BREAK' 1620 | debug 'ERROR' "RESIZE2FS FAILED:\n$output\n-------------------------------------------------------------------------------------" 1621 | exit 3 1622 | fi 1623 | debug 'DEBUG' "$output\n-------------------------------------------------------------------------------------" 1624 | 1625 | echo -e "${White}## ${Yellow}Shrinking partition..." 1626 | $SLEEPING 1627 | debug 'INFO' 'Using parted to shrink partition' 1628 | debug 'BREAK' 1629 | #debug 'DEBUG' "Running: parted -s -a none $LOOP unit B resizepart $IMG_ROOT_PARTN $TRUNCATE_TOTAL" 1630 | debug 'DEBUG' "Running: printf 'Yes\\\n' | parted -a none $LOOP unit B resizepart $IMG_ROOT_PARTN $TRUNCATE_TOTAL ---pretend-input-tty" 1631 | 1632 | #if ! output=$(parted -s -a none "$LOOP" unit B resizepart "$IMG_ROOT_PARTN" "$TRUNCATE_TOTAL" 2>&1); then # tested may 2024 and does not work, still asking for user confirmation 1633 | if ! output=$(printf 'Yes\n' | parted -a none "$LOOP" unit B resizepart "$IMG_ROOT_PARTN" "$TRUNCATE_TOTAL" ---pretend-input-tty 2>&1); then 1634 | echo -e "${Yellow}$output\n${Red}!! PARTED FAILED!!!" 1635 | debug 'BREAK' 1636 | debug 'ERROR' "PARTED FAILED:\n$output\n-------------------------------------------------------------------------------------" 1637 | exit 3 1638 | fi 1639 | debug 'DEBUG' "$output\n-------------------------------------------------------------------------------------" 1640 | 1641 | echo -e "${White}## ${Yellow}Shrinking img file..." 1642 | $SLEEPING 1643 | debug 'INFO' "Using truncate to shrink img file to $(( TRUNCATE_TOTAL / 1024 / 1024 ))MiB" 1644 | debug 'DEBUG' "Running: truncate --size=$TRUNCATE_TOTAL $IMG_FILE" 1645 | if ! output=$(truncate --size="$TRUNCATE_TOTAL" "$IMG_FILE" 2>&1); then 1646 | echo -e "${Yellow}$output\n${Red}!! TRUNCATE FAILED!!!" 1647 | debug 'BREAK' 1648 | debug 'ERROR' "TRUNCATE FAILED:\n$output\n-------------------------------------------------------------------------------------" 1649 | exit 3 1650 | fi 1651 | fi 1652 | return 0 1653 | } 1654 | 1655 | 1656 | 1657 | # rsync to img file 1658 | function do_rsync() { 1659 | 1660 | echo -e "${White}## ${IWhite}Backing up files..." 1661 | $SLEEPING 1662 | if [ "$TTY_AVAILABILITY" == '/dev/null' ] || [ "$RSYNC_TTY" == false ]; then 1663 | echo -e "${White}## ${Yellow}This might take some time, please stand by..." 1664 | fi 1665 | debug 'DEBUG' "Backing up to ${IMG_PATH}${IMG_FILE}" 1666 | 1667 | # Run rsync 1668 | eval "$RSYNC_LINE" 1669 | 1670 | # Get the exit status of rsync from PIPESTATUS 1671 | if [ "${PIPESTATUS[0]}" -ne 0 ] && [ "${PIPESTATUS[0]}" -ne 23 ]; then # code 23 = rsync warning: some files vanished before they could be transferred (code 24) at main.c 1672 | output=$(tail -16 "$tmp_file") 1673 | echo -e "${Yellow}$output\n${Red}!! RSYNC FAILED!!!" 1674 | debug 'BREAK' 1675 | debug 'ERROR' "RSYNC FAILED:\nPIPESTATUS=${PIPESTATUS[0]}\n$output\n-------------------------------------------------------------------------------------" 1676 | exit 5 1677 | elif [ "${PIPESTATUS[0]}" -eq 23 ]; then 1678 | echo -e "${White}## ${Yellow}Warning, rsync code 23, some files vanished before they could be transferred (code 24) at main.c" 1679 | echo -e "${White}## ${Yellow}This does NOT mean the backup failed" 1680 | $SLEEPING 1681 | debug 'WARNING' 'rsync code 23, some files vanished before they could be transferred (code 24) at main.c' 1682 | fi 1683 | 1684 | echo -e "${White}## ${Green}Rsync done..." 1685 | echo -e "${White}## ${IWhite}Please stand by..." 1686 | output=$(tail -16 "$tmp_file") 1687 | debug 'BREAK' 1688 | debug 'DEBUG' "Rsync report:\n$output\n-------------------------------------------------------------------------------------" 1689 | debug 'INFO' 'Rsync done' 1690 | sleep 4 1691 | return 0 1692 | } 1693 | 1694 | 1695 | 1696 | # Edit config files for conversion 1697 | function do_conversion() { 1698 | 1699 | # Create partition 1700 | if [ "$*" == 'create_partition' ]; then 1701 | 1702 | # f2fs 1703 | if [ "$F2FS_CONVERSION" == true ]; then 1704 | debug 'INFO' '--f2fs selected by user, creating f2fs partition on img file' 1705 | debug 'DEBUG' "Running: parted -s -a none $LOOP unit B mkpart primary f2fs $LOCAL_ROOT_START 100%" 1706 | if ! output=$(parted -s -a none "$LOOP" unit B mkpart primary f2fs "$LOCAL_ROOT_START" 100% 2>&1); then 1707 | echo -e "${Yellow}$output\n${Red}!! PARTED FAILED!!!" 1708 | debug 'BREAK' 1709 | debug 'ERROR' "PARTED FAILED:\n$output\n-------------------------------------------------------------------------------------" 1710 | exit 3 1711 | fi 1712 | fi 1713 | 1714 | # Format filesystem 1715 | elif [ "$*" == 'format_filesystem' ]; then 1716 | 1717 | # f2fs 1718 | if [ "$F2FS_CONVERSION" == true ]; then 1719 | debug 'INFO' "Using mkfs.f2fs to format root filesystem" 1720 | if ! output=$(mkfs.f2fs -f -U "$UUID" -l "$LABEL" "$IMG_DEV_ROOT_PATH" 2>&1 | tee "$TTY_AVAILABILITY" ); then 1721 | echo -e "${Yellow}$output\n${Red}!! MKFS.F2FS FAILED!!!" 1722 | debug 'BREAK' 1723 | debug 'ERROR' "MKFS.F2FS FAILED:\n$output\n-------------------------------------------------------------------------------------" 1724 | exit 3 1725 | fi 1726 | debug 'BREAK' 1727 | debug 'DEBUG' "Running: mkfs.f2fs -f -U $UUID -l $LABEL $IMG_DEV_ROOT_PATH\n$output\n-------------------------------------------------------------------------------------" 1728 | $SLEEPING 1729 | 1730 | debug 'INFO' 'Running function: do_mount' 1731 | do_mount 1732 | fi 1733 | 1734 | # Edit config files 1735 | elif [ "$*" == 'edit_configs' ]; then 1736 | 1737 | # f2fs 1738 | if [ "$F2FS_CONVERSION" == true ]; then 1739 | echo -e "${White}## ${IWhite}Editing img configurations for f2fs..." 1740 | $SLEEPING 1741 | echo -e "${White}## ${IWhite}Backups will be created: ${Green}/etc/fstab.shrink-backup.bak ${IWhite}& ${Green}${BOOT_PATH}/cmdline.txt.shrink-backup.bak" 1742 | debug 'INFO' 'Configuring fstab and cmdline.txt on img file for f2fs' 1743 | $SLEEPING 1744 | 1745 | # Create backups 1746 | debug 'INFO' 'Backing up fstab & cmdline.txt into fstab.shrink-backup.bak & cmdline.shrink-backup.bak on img' 1747 | cp ${TMP_DIR}/etc/fstab ${TMP_DIR}/etc/fstab.shrink-backup.bak 1748 | cp ${TMP_DIR}${BOOT_PATH}/cmdline.txt ${TMP_DIR}${BOOT_PATH}/cmdline.txt.shrink-backup.bak 1749 | 1750 | # fstab 1751 | debug 'DEBUG' "Old root line in fstab=$(cat ${TMP_DIR}/etc/fstab | grep ' / ')" 1752 | fstab_options=$(cat ${TMP_DIR}/etc/fstab | grep ' / ' | awk '{print $4}') 1753 | debug 'DEBUG' "Running: sed -i -e \"/${LOCAL_ROOT_PARTUUID}/s/ext4/f2fs/\" -e \"/${LOCAL_ROOT_PARTUUID}/s/${fstab_options}/${fstab_options},discard/\" ${TMP_DIR}/etc/fstab" 1754 | sed -i -e "/${LOCAL_ROOT_PARTUUID}/s/ext4/f2fs/" -e "/$LOCAL_ROOT_PARTUUID/s/${fstab_options}/${fstab_options},discard/" ${TMP_DIR}/etc/fstab 1755 | debug 'DEBUG' "New root line in fstab=$(cat ${TMP_DIR}/etc/fstab | grep ' / ')" 1756 | 1757 | # cmdline.txt 1758 | debug 'INFO' "Old line in cmdline.txt: $(cat ${TMP_DIR}${BOOT_PATH}/cmdline.txt | grep 'console')" 1759 | debug 'DEBUG' "Running: sed -i 's/rootfstype=ext4/rootfstype=f2fs/' ${TMP_DIR}${BOOT_PATH}/cmdline.txt" 1760 | sed -i 's/rootfstype=ext4/rootfstype=f2fs/' "${TMP_DIR}${BOOT_PATH}"/cmdline.txt 1761 | debug 'DEBUG' "New line in cmdline.txt: $(cat ${TMP_DIR}${BOOT_PATH}/cmdline.txt | grep 'console')" 1762 | fi 1763 | fi 1764 | return 0 1765 | } 1766 | 1767 | 1768 | 1769 | # Create a backup img file 1770 | function make_img() { 1771 | 1772 | debug 'INFO' 'Running function: dev_variables' 1773 | # btrfs_variables is run within dev_variables 1774 | dev_variables 1775 | debug 'INFO' 'Running function: rsync_line' 1776 | rsync_line 1777 | print_confirmation 1778 | 1779 | if [ "$PROMPTS" == true ] && [ -f "$IMG_FILE" ]; then 1780 | debug 'WARNING' "$IMG_FILE ALREADY EXISTS!" 1781 | echo -e "${Yellow}!! ${Red}WARNING!!! WARNING!!! WARNING!!!" 1782 | echo -e "${Yellow}!! ${Green}$IMG_FILE" 1783 | echo -e "${Yellow}!! FILE ALREADY EXISTS!!!" 1784 | debug 'WARNING' 'Do you want to overwrite? [y/n]' 1785 | while true; do 1786 | if [ "$COLORS" == true ]; then 1787 | read -r -p $'\e[0;93m!! \e[0;97mDo you want to overwrite? \e[0;92m[y/n] \e[0m' input 1788 | else 1789 | read -r -p '!! Do you want to overwrite? [y/n] ' input 1790 | fi 1791 | case $input in 1792 | [Yy]) debug 'WARNING' 'Overwrite confirmed by user'; break;; 1793 | [Nn]) echo -e "${Red}!! Aborting..."; exit 4;; 1794 | *) echo -e "${Yellow}!! ${Red}ERROR! ${Yellow}Please enter ${Green}'y' ${Yellow}or ${Green}'n'${Yellow}"; debug 'WARNING' "ERROR, please enter 'y' or 'n'";; 1795 | esac 1796 | done 1797 | fi 1798 | 1799 | # Delete existing file if user validation above passed 1800 | if [ -f "$IMG_FILE" ]; then 1801 | debug 'WARNING' "Removing: $IMG_FILE" 1802 | echo -e "${Yellow}!! Removing old img file..." 1803 | rm "$IMG_FILE" 1804 | $SLEEPING 1805 | fi 1806 | 1807 | # Create and dd bootsector 1808 | echo -e "${White}## ${IWhite}Creating bootsector..." 1809 | if [ "$TTY_AVAILABILITY" == '/dev/null' ]; then 1810 | echo '## This might take some time, please stand by...' 1811 | fi 1812 | $SLEEPING 1813 | debug 'INFO' 'Using dd to create bootsector' 1814 | debug 'DEBUG' "Running: dd bs=512 count=$LOCAL_DDBOOTSECTOR if=$LOCAL_DEV_PATH of=$IMG_FILE conv=noerror,sync status=progress" 1815 | if ! output=$(dd bs=512 count=$LOCAL_DDBOOTSECTOR if="$LOCAL_DEV_PATH" of="$IMG_FILE" conv=noerror,fsync status=progress 2>&1 | tee "$TTY_AVAILABILITY") && sync; then 1816 | echo -e "${Yellow}$output\n${Red}!! DD TO LOCAL_BOOTSECTOR FAILED!!!" 1817 | debug 'BREAK' 1818 | debug 'ERROR' "DD TO LOCAL_BOOTSECTOR FAILED:\n$output\n-------------------------------------------------------------------------------------" 1819 | exit 3 1820 | fi 1821 | output=$(echo "$output" | tail -3 ) 1822 | debug 'BREAK' 1823 | debug 'DEBUG' "dd completed:\n$output\n-------------------------------------------------------------------------------------" 1824 | $SLEEPING 1825 | 1826 | # Truncate file to correct size 1827 | echo -e "${White}## ${IWhite}Resizing img file..." 1828 | $SLEEPING 1829 | debug 'INFO' "Using truncate to resize img file to $(( TRUNCATE_TOTAL / 1024 / 1024 ))MiB" 1830 | debug 'DEBUG' "Running: truncate --size=$TRUNCATE_TOTAL $IMG_FILE" 1831 | if ! output=$(truncate --size="$TRUNCATE_TOTAL" "$IMG_FILE" 2>&1); then 1832 | echo -e "${Yellow}$output\n${Red}!! TRUNCATE FAILED!!!" 1833 | debug 'BREAK' 1834 | debug 'ERROR' "TRUNCATE FAILED:\n$output\n-------------------------------------------------------------------------------------" 1835 | exit 3 1836 | fi 1837 | $SLEEPING 1838 | 1839 | # Loop img file 1840 | debug 'INFO' 'Running function: do_loop' 1841 | do_loop 1842 | 1843 | # Remove partition 1844 | echo -e "${White}## ${IWhite}Removing root partition..." 1845 | $SLEEPING 1846 | 1847 | # GPT 1848 | if [ $PARTITION_TABLE == 'gpt' ]; then 1849 | debug 'INFO' 'Using sgdisk to remove root partition' 1850 | debug 'DEBUG' "Running: sgdisk $LOOP -d $LOCAL_ROOT_PARTN" 1851 | if ! output=$(sgdisk "$LOOP" -d "$LOCAL_ROOT_PARTN" 2>&1); then 1852 | echo -e "${Yellow}$output\n${Red}!! SGDISK FAILED!!!" 1853 | debug 'BREAK' 1854 | debug 'ERROR' "SGDISK FAILED:\n$output\n-------------------------------------------------------------------------------------" 1855 | exit 3 1856 | fi 1857 | 1858 | # MBR 1859 | else 1860 | # Keep all of this for memory, things needed to get this to work seems to change back and forth over time 1861 | debug 'INFO' 'Using sfdisk to remove root partition' 1862 | #debug 'INFO' 'Using parted to remove root partition' 1863 | debug 'DEBUG' "Running: sfdisk --delete -f $LOOP $LOCAL_ROOT_PARTN" 1864 | #debug 'DEBUG' "Running: parted -s $LOOP rm $LOCAL_ROOT_PARTN" 1865 | #debug 'DEBUG' "Running: printf 'Ignore\\\n'$LOCAL_ROOT_PARTN | parted $LOOP rm $LOCAL_ROOT_PARTN ---pretend-input-tty" 1866 | 1867 | if ! output=$(sfdisk --delete -f "$LOOP" "$LOCAL_ROOT_PARTN" 2>&1); then # might fail if img size is very big 1868 | #if ! output=$(parted -s "$LOOP" rm "$LOCAL_ROOT_PARTN" 2>&1); then # retry this after -f got removed, before = does not work, still asking for user confirmation even though --script and -f (automatically answer "fix" to exceptions in script mode) is used. faults with: "Error: Can't have a partition outside the disk!". for some reason this line works in the resizing function 1869 | #if ! output=$(printf 'Ignore\n'$LOCAL_ROOT_PARTN | parted $LOOP rm $LOCAL_ROOT_PARTN ---pretend-input-tty 2>&1); then # raspberry pi os does not like this method, keep for memory 1870 | echo -e "${Yellow}$output\n${Red}!! SFDISK FAILED!!!" 1871 | #echo -e "${Yellow}$output\n${Red}!! PARTED FAILED!!!" 1872 | debug 'BREAK' 1873 | debug 'ERROR' "SFDISK FAILED:\n$output\n-------------------------------------------------------------------------------------" 1874 | #debug 'ERROR' "PARTED FAILED:\n$output\n-------------------------------------------------------------------------------------" 1875 | exit 3 1876 | fi 1877 | fi 1878 | $SLEEPING 1879 | 1880 | # Recreate partition 1881 | echo -e "${White}## ${IWhite}Recreating root partition..." 1882 | $SLEEPING 1883 | debug 'INFO' 'Using parted to recreate root partition' 1884 | 1885 | # Converting 1886 | if [ "$F2FS_CONVERSION" == true ]; then 1887 | debug 'INFO' "Running function: do_conversion 'create_partition'" 1888 | do_conversion 'create_partition' 1889 | 1890 | # ext4 or f2fs on root 1891 | elif [ "$FSTYPE" == 'ext4' ] || [ "$FSTYPE" == 'f2fs' ]; then 1892 | debug 'DEBUG' "Running: parted -s -a none $LOOP unit B mkpart primary $LOCAL_ROOT_START 100%" 1893 | # Figure out why I am not using below line for ALL filesystems including converting 1894 | #if ! output=$(parted -s -a none "$LOOP" unit B mkpart primary "$FSTYPE" "$LOCAL_ROOT_START" 100% 2>&1); then 1895 | if ! output=$(parted -s -a none "$LOOP" unit B mkpart primary "$LOCAL_ROOT_START" 100% 2>&1); then 1896 | echo -e "${Yellow}$output\n${Red}!! PARTED FAILED!!!" 1897 | debug 'BREAK' 1898 | debug 'ERROR' "PARTED FAILED:\n$output\n-------------------------------------------------------------------------------------" 1899 | exit 3 1900 | fi 1901 | 1902 | # btrfs on root 1903 | elif [ "$FSTYPE" == 'btrfs' ]; then 1904 | debug 'DEBUG' "Running: parted -s -a none $LOOP unit B mkpart primary btrfs $LOCAL_ROOT_START 100%" 1905 | if ! output=$(parted -s -a none "$LOOP" unit B mkpart primary btrfs "$LOCAL_ROOT_START" 100% 2>&1); then 1906 | echo -e "${Yellow}$output\n${Red}!! PARTED FAILED!!!" 1907 | debug 'BREAK' 1908 | debug 'ERROR' "PARTED FAILED:\n$output\n-------------------------------------------------------------------------------------" 1909 | exit 3 1910 | fi 1911 | fi 1912 | $SLEEPING 1913 | 1914 | # Get img variables 1915 | debug 'INFO' 'Running function: img_variables' 1916 | img_variables 1917 | 1918 | # Format filesystem 1919 | echo -e "${White}## ${IWhite}Formatting filesystem..." 1920 | debug 'INFO' 'Formatting filesystem' 1921 | $SLEEPING 1922 | LABEL=$(lsblk -no label "$LOCAL_DEV_ROOT_PATH") 1923 | UUID=$(lsblk -no uuid "$LOCAL_DEV_ROOT_PATH") 1924 | debug 'DEBUG' "LABEL=$LABEL | UUID=$UUID" 1925 | 1926 | # Converting 1927 | if [ "$F2FS_CONVERSION" == true ]; then 1928 | debug 'INFO' "Running function: do_conversion 'format_filesystem'" 1929 | do_conversion 'format_filesystem' 1930 | 1931 | # f2fs 1932 | elif [ "$FSTYPE" == 'f2fs' ]; then 1933 | debug 'INFO' "Using mkfs.f2fs to format root filesystem" 1934 | if ! output=$(mkfs.f2fs -f -U "$UUID" -l "$LABEL" "$IMG_DEV_ROOT_PATH" 2>&1 | tee "$TTY_AVAILABILITY" ); then 1935 | echo -e "${Yellow}$output\n${Red}!! MKFS.F2FS FAILED!!!" 1936 | debug 'BREAK' 1937 | debug 'ERROR' "MKFS.F2FS FAILED:\n$output\n-------------------------------------------------------------------------------------" 1938 | exit 3 1939 | fi 1940 | debug 'BREAK' 1941 | debug 'DEBUG' "Running: mkfs.f2fs -f -U $UUID -l $LABEL $IMG_DEV_ROOT_PATH\n$output\n-------------------------------------------------------------------------------------" 1942 | $SLEEPING 1943 | 1944 | debug 'INFO' 'Running function: do_mount' 1945 | do_mount 1946 | 1947 | # ext4 1948 | elif [ "$FSTYPE" == 'ext4' ]; then 1949 | debug 'INFO' "Using mkfs.ext4 to format root filesystem" 1950 | if ! output=$(mkfs.ext4 -U "$UUID" -L "$LABEL" "$IMG_DEV_ROOT_PATH" 2>&1 | tee "$TTY_AVAILABILITY" ); then 1951 | echo -e "${Yellow}$output\n${Red}!! MKFS.EXT4 FAILED!!!" 1952 | debug 'BREAK' 1953 | debug 'ERROR' "MKFS.EXT4 FAILED:\n$output\n-------------------------------------------------------------------------------------" 1954 | exit 3 1955 | fi 1956 | debug 'BREAK' 1957 | debug 'DEBUG' "Running: mkfs.$FSTYPE -U $UUID -L $LABEL $IMG_DEV_ROOT_PATH\n$output\n-------------------------------------------------------------------------------------" 1958 | $SLEEPING 1959 | 1960 | debug 'INFO' 'Running function: do_e2fsck' 1961 | do_e2fsck 1962 | 1963 | debug 'INFO' 'Running function: do_mount' 1964 | do_mount 1965 | 1966 | # btrfs 1967 | elif [ "$FSTYPE" == 'btrfs' ]; then 1968 | debug 'INFO' 'Using mkfs.btrfs to format root filesystem' 1969 | partprobe "$LOOP" 1970 | # btrfs does work with having the same UUID on 2 filesystems at the same time but the PARTUUID is the same anyway since it's transferred with dd 1971 | if ! output=$(mkfs.btrfs -f -m single -L "$LABEL" -v "$IMG_DEV_ROOT_PATH" 2>&1 | tee "$TTY_AVAILABILITY" ); then 1972 | echo -e "${Yellow}$output\n${Red}!! MKFS.BTRFS FAILED!!!" 1973 | $SLEEPING 1974 | debug 'BREAK' 1975 | debug 'ERROR' "MKFS.BTRFS FAILED:\n$output\n-------------------------------------------------------------------------------------" 1976 | exit 3 1977 | fi 1978 | debug 'BREAK' 1979 | debug 'DEBUG' "Running: mkfs.btrfs -f -m single -L $LABEL -v $IMG_DEV_ROOT_PATH\n$output\n-------------------------------------------------------------------------------------" 1980 | 1981 | # Mount btrfs filesystem and create volumes 1982 | echo -e "${White}## ${IWhite}Creating btrfs subvolumes..." 1983 | $SLEEPING 1984 | debug 'INFO' 'Mounting btrfs filesystem and creating btrfs subvolumes' 1985 | debug 'DEBUG' "Running: mount -o noatime,compress=zstd $IMG_DEV_ROOT_PATH $TMP_DIR" 1986 | if ! output=$(mount -o noatime,compress=zstd "$IMG_DEV_ROOT_PATH" "$TMP_DIR" 2>&1); then 1987 | echo -e "${Yellow}$output\n${Red}!! BTRFS FILESYSTEM MOUNT FAILED!!!" 1988 | debug 'BREAK' 1989 | debug 'ERROR' "BTRFS FILESYSTEM MOUNT FAILED:\n$output\n-------------------------------------------------------------------------------------" 1990 | exit 3 1991 | fi 1992 | 1993 | # Create top level subvolumes 1994 | for subvol in "${LOCAL_TOP_SUBVOLUMES[@]}"; do 1995 | echo -e "${White}## ${IWhite}Creating subvolume: ${Green}$subvol" 1996 | $SLEEPING 1997 | debug 'DEBUG' "Running: btrfs subvolume create ${TMP_DIR}/${subvol}" 1998 | if ! output=$(btrfs subvolume create "$TMP_DIR"/"$subvol" 2>&1); then 1999 | echo -e "${Yellow}$output\n${Red}!! CREATE SUBVOLUME FAILED!!!" 2000 | debug 'BREAK' 2001 | debug 'ERROR' "CREATE SUBVOLUME FAILED:\n$output\n-------------------------------------------------------------------------------------" 2002 | exit 3 2003 | fi 2004 | done 2005 | 2006 | # Nested volumes are created within do_mount function 2007 | debug 'INFO' 'Unmounting btrfs filesystem & running function: do_mount' 2008 | umount "$TMP_DIR" 2009 | do_mount 2010 | fi 2011 | 2012 | # Copy files 2013 | debug 'INFO' 'Backing up files' 2014 | debug 'INFO' 'Running function: do_rsync' 2015 | do_rsync 2016 | 2017 | # Edit config files for conversion 2018 | if [ "$F2FS_CONVERSION" == true ]; then 2019 | debug 'INFO' "Running function: do_conversion 'edit_configs'" 2020 | do_conversion 'edit_configs' 2021 | fi 2022 | 2023 | # Final check of created img file 2024 | if [ "$FSTYPE" == 'ext4' ] && [ "$F2FS_CONVERSION" == false ]; then 2025 | debug 'INFO' "Running function: do_e2fsck 'final'" 2026 | do_e2fsck 'final' 2027 | fi 2028 | return 0 2029 | } 2030 | 2031 | 2032 | 2033 | # Update existing backup img file 2034 | function update_img() { 2035 | 2036 | # Make sure img file exists 2037 | if ! [ -f "$IMG_FILE" ]; then 2038 | echo -e "${Red}!! ERROR! ${Green}$IMG_FILE ${Yellow}does not exist!" 2039 | debug 'ERROR' "$IMG_FILE does not exist, exit 3" 2040 | exit 3 2041 | fi 2042 | 2043 | debug 'INFO' 'Running function: dev_variables' 2044 | # btrfs_variables is run within dev_variables 2045 | dev_variables 2046 | debug 'INFO' 'Running function: rsync_line' 2047 | rsync_line 2048 | debug 'INFO' 'Running function: do_loop' 2049 | do_loop 2050 | debug 'INFO' 'Running function: img_variables' 2051 | img_variables 2052 | 2053 | # Check if resizing should be performed 2054 | if [ "$AUTORESIZE_RUN" == true ]; then 2055 | debug 'DEBUG' "Running: fdisk --bytes -lo device,size "$LOOP" | grep "$IMG_DEV_ROOT_PATH" | awk '{print \$2}'" 2056 | IMG_ROOT_SIZE=$(fdisk --bytes -lo device,size "$LOOP" | grep "$IMG_DEV_ROOT_PATH" | awk '{print $2}' ) 2057 | diff=$(( (LOCAL_AUTORESIZE_MIN - IMG_ROOT_SIZE) / 1024 / 1024 )) 2058 | [[ "$diff" != '-'* ]] && diff="+${diff}" # add a + in case the value is not negative for formatting reasons 2059 | debug 'DEBUG' "IMG_ROOT_SIZE=$IMG_ROOT_SIZE bytes | LOCAL_AUTORESIZE_MIN=$LOCAL_AUTORESIZE_MIN bytes | diff=${diff}MiB" 2060 | 2061 | if [ "$IMG_ROOT_SIZE" -lt "$LOCAL_AUTORESIZE_MIN" ] && (( LOCAL_AUTORESIZE_MIN - IMG_ROOT_SIZE >= 268435456 )); then # 256MiB in bytes 2062 | DIFFERENCE="${diff}MiB, Expanding img filesystem" 2063 | RESIZE_FUNCTION='expand' 2064 | elif [ "$IMG_ROOT_SIZE" -gt "$LOCAL_AUTORESIZE_MIN" ] && (( IMG_ROOT_SIZE - LOCAL_AUTORESIZE_MIN >= 536870912 )); then # 512MiB in bytes 2065 | DIFFERENCE="${diff}MiB, Shrinking img filesystem" 2066 | RESIZE_FUNCTION='shrink' 2067 | else 2068 | if [[ "$diff" != '-'* ]]; then 2069 | DIFFERENCE="Too small (${diff}MiB), not expanding filesystem (must be >=+256MiB)" 2070 | diff_small=true 2071 | else 2072 | DIFFERENCE="Too small (${diff}MiB), not shrinking filesystem (must be >=-512MiB)" 2073 | diff_small=true 2074 | fi 2075 | debug 'DEBUG' "DIFFERENCE=$DIFFERENCE" 2076 | fi 2077 | # Change TRUNCATE_TOTAL to be based on img file instead of local root if [extra space] is provided 2078 | elif [ "$ADDED_SPACE" -ne 0 ]; then 2079 | debug 'INFO' 'Manually added space provided by user, calculating img size (TRUNCATE_TOTAL) by adding ADDED_SPACE to IMG_SIZE' 2080 | TRUNCATE_TOTAL=$(( ADDED_SPACE + IMG_SIZE )) 2081 | debug 'DEBUG' "TRUNCATE_TOTAL=${TRUNCATE_TOTAL} bytes" 2082 | fi 2083 | 2084 | print_confirmation 2085 | 2086 | # Resize if needed 2087 | if [ "$AUTORESIZE_RUN" == true ]; then 2088 | # Expanding 2089 | if [ "$RESIZE_FUNCTION" == 'expand' ]; then 2090 | debug 'INFO' "Running function: do_resize 'expand'" 2091 | do_resize 'expand' 2092 | 2093 | # Shrink then exit this function 2094 | elif [ "$RESIZE_FUNCTION" == 'shrink' ]; then 2095 | debug 'INFO' 'Running function: do_mount' 2096 | do_mount 2097 | debug 'INFO' 'Backing up files' 2098 | debug 'INFO' 'Running function: do_rsync' 2099 | do_rsync 2100 | debug 'INFO' "Running function: do_resize 'shrink'" 2101 | do_resize 'shrink' 2102 | if [ $FSTYPE == 'ext4' ]; then 2103 | debug 'INFO' "Running function: do_e2fsck 'final'" 2104 | do_e2fsck 'final' 2105 | fi 2106 | return 0 2107 | 2108 | else 2109 | debug 'INFO' 'Img root partition is <=256MiB smaller or <=512MiB bigger compared to auto calculated size, not resizing' 2110 | fi 2111 | fi 2112 | 2113 | # Expand img file if ADDED_SPACE not 0 2114 | if [ "$ADDED_SPACE" -ne 0 ]; then 2115 | debug 'INFO' "Running function: do_resize 'expand'" 2116 | do_resize 'expand' 2117 | fi 2118 | 2119 | debug 'INFO' 'Running function: do_mount' 2120 | do_mount 2121 | debug 'INFO' 'Backing up files' 2122 | debug 'INFO' 'Running function: do_rsync' 2123 | do_rsync 2124 | 2125 | # Final check of img filesystem 2126 | if [ $FSTYPE == 'ext4' ]; then 2127 | debug 'INFO' "Running function: do_e2fsck 'final'" 2128 | do_e2fsck 'final' 2129 | fi 2130 | return 0 2131 | } 2132 | 2133 | 2134 | 2135 | # Enable autoexpansion for Manjaro 2136 | function autoexpansion_manjaro() { 2137 | if ! [ -d "${TMP_DIR}/etc/systemd/system/basic.target.wants" ]; then 2138 | debug 'DEBUG' "Systemd basic.target.wants directory does not exist, running: mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants" 2139 | mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants 2140 | fi 2141 | 2142 | # Creating autoexpansion systemd unit file expand-fs.service 2143 | debug 'DEBUG' 'Systemd unit expand-fs.service does not exist, creating it' 2144 | cat << EOF > "${TMP_DIR}/etc/systemd/system/expand-fs.service" 2145 | [Unit] 2146 | Description=Extend root partition and resize ext4 file system 2147 | After=local-fs.target 2148 | Wants=local-fs.target 2149 | 2150 | [Service] 2151 | Type=oneshot 2152 | ExecStart=/bin/bash -c "/usr/bin/resize-fs || exit 0" 2153 | ExecStop=/bin/bash -c "/usr/bin/rm /etc/systemd/system/basic.target.wants/expand-fs.service && /usr/bin/rm /etc/systemd/system/expand-fs.service && /usr/bin/reboot -f || exit 0" 2154 | 2155 | [Install] 2156 | WantedBy=basic.target 2157 | EOF 2158 | 2159 | if ! [ -L "${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service" ]; then 2160 | debug 'DEBUG' "Enabling systemd service by creating symlink: ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service" 2161 | ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service 2162 | fi 2163 | 2164 | echo -e "${White}## ${Green}Manjaro-arm filesystem autoresizing at boot..." 2165 | debug 'INFO' 'Manjaro-arm filesystem autoresizing at boot' 2166 | $SLEEPING 2167 | return 0 2168 | } 2169 | 2170 | 2171 | 2172 | # Enable autoexpansion for Armbian 2173 | function autoexpansion_armbian() { 2174 | if ! test -L "${TMP_DIR}/etc/systemd/system/basic.target.wants/armbian-resize-filesystem.service"; then 2175 | debug 'DEBUG' "Enabling systemd service by creating symlink: ln -s /lib/systemd/system/armbian-resize-filesystem.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/armbian-resize-filesystem.service" 2176 | ln -s /lib/systemd/system/armbian-resize-filesystem.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/armbian-resize-filesystem.service 2177 | fi 2178 | echo -e "${White}## ${Green}Armbian filesystem autoresizing at boot..." 2179 | debug 'INFO' 'Armbian filesystem autoresizing at boot' 2180 | $SLEEPING 2181 | return 0 2182 | } 2183 | 2184 | 2185 | 2186 | # Enable autoexpansion for Raspberry pi 2187 | function autoexpansion_rpi() { 2188 | if ! [ -d "${TMP_DIR}/etc/systemd/system/basic.target.wants" ]; then 2189 | debug 'DEBUG' "Systemd basic.target.wants directory does not exist, running: mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants" 2190 | mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants 2191 | fi 2192 | 2193 | # Creating autoexpansion systemd unit file expand-fs.service 2194 | debug 'DEBUG' 'Creating systemd unit expand-fs.service' 2195 | cat << EOF > "${TMP_DIR}/etc/systemd/system/expand-fs.service" 2196 | [Unit] 2197 | Description=Extend root partition and resize ext4 file system 2198 | After=local-fs.target 2199 | Wants=local-fs.target 2200 | 2201 | [Service] 2202 | Type=oneshot 2203 | ExecStart=/bin/bash -c "/expand-fs.sh || exit 0" 2204 | ExecStop=/bin/bash -c "/usr/bin/rm /etc/systemd/system/basic.target.wants/expand-fs.service && /usr/bin/rm /expand-fs.sh && /usr/bin/rm /etc/systemd/system/expand-fs.service && /usr/sbin/reboot -f || exit 0" 2205 | 2206 | [Install] 2207 | WantedBy=basic.target 2208 | EOF 2209 | 2210 | # Enable service by creating symlink 2211 | if ! [ -L "${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service" ]; then 2212 | debug 'DEBUG' "Enabling systemd service by creating symlink: ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service" 2213 | ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service 2214 | fi 2215 | 2216 | # Creating script for autoexpansion 2217 | debug 'DEBUG' "Creating expansion script ${TMP_DIR}/expand-fs.sh" 2218 | cat << EOF2 > "${TMP_DIR}/expand-fs.sh" 2219 | #!/usr/bin/bash 2220 | LOCAL_DEV_ROOT_PATH=\$(findmnt -n -o SOURCE /) 2221 | LOCAL_DEV_PATH=/dev/\$(lsblk -no pkname "\$LOCAL_DEV_ROOT_PATH") 2222 | LOCAL_ROOT_PARTN=\$(blkid -s PART_ENTRY_NUMBER -o value -p "\$LOCAL_DEV_ROOT_PATH") 2223 | LOCAL_ROOT_START=\$(fdisk -lo start "\$LOCAL_DEV_PATH" | tail -1 | awk '{print \$1}') # blocks, 512B block size 2224 | LOCAL_ROOT_START=\$(( LOCAL_ROOT_START * 512 )) # bytes 2225 | 2226 | sfdisk --delete -f "\$LOCAL_DEV_PATH" "\$LOCAL_ROOT_PARTN" 2227 | parted -s -a none "\$LOCAL_DEV_PATH" unit B mkpart primary "\$LOCAL_ROOT_START" 100% 2228 | 2229 | resize2fs -f "\$LOCAL_DEV_ROOT_PATH" 2230 | sync 2231 | exit 0 2232 | EOF2 2233 | 2234 | debug 'DEBUG' 'Making expand-fs.sh executable' 2235 | chmod +x ${TMP_DIR}/expand-fs.sh 2236 | 2237 | if [ "$OS" == 'dietpi' ]; then 2238 | echo -e "${White}## ${Green}DietPi filesystem autoresizing at boot..." 2239 | debug 'INFO' 'DietPi filesystem autoresizing at boot' 2240 | else 2241 | echo -e "${White}## ${Green}Raspberry pi filesystem autoresizing at boot..." 2242 | debug 'INFO' 'Raspberry pi filesystem autoresizing at boot' 2243 | fi 2244 | $SLEEPING 2245 | return 0 2246 | } 2247 | 2248 | 2249 | 2250 | # Enable autoexpansion for ArchLinuxArm 2251 | function autoexpansion_arch() { 2252 | if ! [ -d "${TMP_DIR}/etc/systemd/system/basic.target.wants" ]; then 2253 | debug 'DEBUG' "Systemd basic.target.wants directory does not exist, running: mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants" 2254 | mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants 2255 | fi 2256 | 2257 | # Creating autoexpansion systemd unit file expand-fs.service 2258 | debug 'DEBUG' 'Creating systemd unit expand-fs.service' 2259 | cat << EOF > "${TMP_DIR}/etc/systemd/system/expand-fs.service" 2260 | [Unit] 2261 | Description=Extend root partition and resize ext4 file system 2262 | After=local-fs.target 2263 | Wants=local-fs.target 2264 | 2265 | [Service] 2266 | Type=oneshot 2267 | ExecStart=/bin/bash -c "/expand-fs.sh || exit 0" 2268 | ExecStop=/bin/bash -c "/usr/bin/rm /etc/systemd/system/basic.target.wants/expand-fs.service && /usr/bin/rm /expand-fs.sh && /usr/bin/rm /etc/systemd/system/expand-fs.service && /usr/bin/reboot -f || exit 0" 2269 | 2270 | [Install] 2271 | WantedBy=basic.target 2272 | EOF 2273 | 2274 | # Enable service by creating symlink 2275 | if ! [ -L "${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service" ]; then 2276 | debug 'DEBUG' "Enabling systemd service by creating symlink: ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service" 2277 | ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service 2278 | fi 2279 | 2280 | # Creating script for autoexpansion 2281 | debug 'DEBUG' "Creating expansion script ${TMP_DIR}/expand-fs.sh" 2282 | cat << EOF2 > "${TMP_DIR}/expand-fs.sh" 2283 | #!/usr/bin/bash 2284 | LOCAL_DEV_ROOT_PATH=\$(findmnt -n -o SOURCE /) 2285 | LOCAL_DEV_PATH=/dev/\$(lsblk -no pkname "\$LOCAL_DEV_ROOT_PATH") 2286 | LOCAL_ROOT_PARTN=\$(blkid -s PART_ENTRY_NUMBER -o value -p "\$LOCAL_DEV_ROOT_PATH") 2287 | LOCAL_ROOT_START=\$(fdisk -lo start "\$LOCAL_DEV_PATH" | tail -1 | awk '{print \$1}') # blocks, 512B block size 2288 | LOCAL_ROOT_START=\$(( LOCAL_ROOT_START * 512 )) # bytes 2289 | 2290 | sfdisk --delete -f "\$LOCAL_DEV_PATH" "\$LOCAL_ROOT_PARTN" 2291 | parted -s -a none "\$LOCAL_DEV_PATH" unit B mkpart primary "\$LOCAL_ROOT_START" 100% 2292 | resize2fs -f "\$LOCAL_DEV_ROOT_PATH" 2293 | sync 2294 | exit 0 2295 | EOF2 2296 | 2297 | debug 'DEBUG' 'Making /expand-fs.sh executable' 2298 | chmod +x ${TMP_DIR}/expand-fs.sh 2299 | 2300 | echo -e "${White}## ${Green}ArchLinux-arm filesystem autoresizing at boot..." 2301 | debug 'INFO' 'ArchLinux-arm filesystem autoresizing at boot' 2302 | $SLEEPING 2303 | return 0 2304 | } 2305 | 2306 | 2307 | 2308 | # Enable autoexpansion for Kali-ARM 2309 | function autoexpansion_kali() { 2310 | if ! test -L "${TMP_DIR}/etc/systemd/system/basic.target.wants/rpi-resizerootfs.service"; then 2311 | debug 'DEBUG' "Enabling systemd service by creating symlink: ln -s /etc/systemd/system/rpi-resizerootfs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/rpi-resizerootfs.service" 2312 | ln -s /etc/systemd/system/rpi-resizerootfs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/rpi-resizerootfs.service 2313 | fi 2314 | echo -e "${White}## ${Green}Kali-ARM filesystem autoresizing at boot..." 2315 | debug 'INFO' 'Kali-ARM filesystem autoresizing at boot' 2316 | $SLEEPING 2317 | return 0 2318 | } 2319 | 2320 | 2321 | 2322 | # Print result 2323 | function print_result() { 2324 | 2325 | AFTER_SIZE=$(stat -c %s "$IMG_FILE") 2326 | AFTER_SIZE=$(( AFTER_SIZE / 1024 / 1024 )) 2327 | IMG_ROOT_SIZE=$(fdisk --bytes -lo device,size "$LOOP" | grep "$IMG_DEV_ROOT_PATH" | awk '{print $2}' ) 2328 | 2329 | echo -e "${White}## ${Green}Backup done." 2330 | echo -e "${Purple}${BREAK}" 2331 | echo -e "# ${IWhite}Backup location: ${Green}${IMG_FILE} ${Purple}$(printf "%+$(( COLS - 19 - $(echo ${IMG_FILE} | wc -m ) ))s" '#')" 2332 | echo -e "# ${IWhite}Write to logfile: ${Green}$DEBUG ${Purple}$(printf "%+$(( COLS - 20 - $(echo ${DEBUG} | wc -m ) ))s" '#')" 2333 | echo -e "# ${IWhite}Autoexpand filesystem at boot: ${Green}$AUTOEXPAND ${Purple}$(printf "%+$(( COLS - 33 - $(echo ${AUTOEXPAND} | wc -m ) ))s" '#')" 2334 | echo -e "# ${IWhite}Use exclude.txt: ${Green}$EXCLUDE_FILE ${Purple}$(printf "%+$(( COLS - 19 - $(echo ${EXCLUDE_FILE} | wc -m ) ))s" '#')" 2335 | echo -e "# ${IWhite}Boot partition: ${Green}$BOOT_PARTITION ${Purple}$(printf "%+$(( COLS - 18 - $(echo ${BOOT_PARTITION} | wc -m ) ))s" '#')" 2336 | print_temp="$(( LOCAL_BOOTSECTOR / 1024 / 1024 ))" 2337 | echo -e "# ${IWhite}Bootsector size: ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 22 - $(echo ${print_temp} | wc -m ) ))s" '#')" 2338 | print_temp="$(( $(df / -k --sync --output=used | tail -1) / 1024 ))" 2339 | echo -e "# ${IWhite}Estimated root usage: ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 27 - $(echo ${print_temp} | wc -m ) ))s" '#')" 2340 | 2341 | # New backup 2342 | if [ "$UPDATE" == false ]; then 2343 | debug 'INFO' 'Img file created' 2344 | if [ "$AUTORESIZE_RUN" == true ]; then 2345 | print_temp="$(( LOCAL_AUTORESIZE_MIN / 1024 / 1024 ))" 2346 | echo -e "# ${IWhite}Image size: ${Green}${AFTER_SIZE}MiB ${IWhite}with a ${Green}root partition ${IWhite}of ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 45 - $(echo ${AFTER_SIZE} | wc -m ) - $(echo ${print_temp} | wc -m ) ))s" '#')" 2347 | debug 'DEBUG' "$IMG_FILE is ${AFTER_SIZE}MiB with a root partition of $(( LOCAL_AUTORESIZE_MIN / 1024 / 1024 ))MiB" 2348 | else 2349 | print_temp="$(( ADDED_SPACE / 1024 / 1024 ))" 2350 | echo -e "# ${IWhite}Added [extra space]: ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 26 - $(echo ${print_temp} | wc -m ) ))s" '#')" 2351 | print_temp=$(( IMG_ROOT_SIZE / 1024 / 1024 )) 2352 | echo -e "# ${IWhite}Image size: ${Green}${AFTER_SIZE}MiB ${IWhite}with a ${Green}root partition ${IWhite}of ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 45 - $(echo ${AFTER_SIZE} | wc -m ) - $(echo ${print_temp} | wc -m ) ))s" '#')" 2353 | debug 'DEBUG' "$IMG_FILE is ${AFTER_SIZE}MiB with a root partition of ${print_temp}MiB including $(( ADDED_SPACE / 1024 / 1024 ))MiB [extra space]" 2354 | fi 2355 | 2356 | # Updated backup 2357 | else 2358 | debug 'INFO' 'Img file updated' 2359 | if [ "$ADDED_SPACE" -ne 0 ]; then 2360 | print_temp="$(( ADDED_SPACE / 1024 / 1024 ))" 2361 | echo -e "# ${IWhite}Added [extra space]: ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 26 - $(echo ${print_temp} | wc -m ) ))s" '#')" 2362 | fi 2363 | print_temp=$(( IMG_ROOT_SIZE / 1024 / 1024 )) 2364 | echo -e "# ${IWhite}Image size: ${Green}${AFTER_SIZE}MiB ${IWhite}with a ${Green}root partition ${IWhite}of ${Green}${print_temp}MiB ${Purple}$(printf "%+$(( COLS - 45 - $(echo ${AFTER_SIZE} | wc -m ) - $(echo ${print_temp} | wc -m ) ))s" '#')" 2365 | debug 'INFO' "$IMG_FILE is ${AFTER_SIZE}MiB" 2366 | fi 2367 | 2368 | if [ "$AUTOEXPAND" == true ] && [ "$OS" != 'armbian' ] && [ "$OS" != 'kali' ] && [ "$OS" != 'ubuntu' ]; then 2369 | echo -e "# ${Yellow}Please wait for the system to reboot after restoring an image with autoexpansion ${Purple}$(printf "%+$(( COLS - 83 ))s" '#')" 2370 | fi 2371 | if [ "$F2FS_CONVERSION" == true ]; then 2372 | echo -e "# ${Green}Filesystem converted to f2fs on img ${Purple}$(printf "%+$(( COLS - 38 ))s" '#')" 2373 | fi 2374 | echo -e "${Purple}${BREAK}" 2375 | debug 'INFO' 'Backup done' 2376 | debug 'BREAK' 2377 | return 0 2378 | } 2379 | 2380 | 2381 | 2382 | # Check if debugging is requested 2383 | if [ "$DEBUG" == true ]; then 2384 | echo -e "${White}## ${IWhite}Debugging requested, writing to log file: ${Green}$LOG_FILE" 2385 | debug 'INFO' "Debugging requested, writing to log file $LOG_FILE" 2386 | fi 2387 | 2388 | debug 'INFO' "Version=$VERSION" 2389 | debug 'INFO' "Script started with: $STARTLINE" 2390 | 2391 | # Make sure absolute path is used on img file 2392 | if ! [[ "$IMG_FILE" =~ ^[/|.] ]]; then 2393 | debug 'INFO' 'Absolute path not provided for img file, adding pwd to variable' 2394 | IMG_FILE="$(pwd)/$IMG_FILE" 2395 | elif [[ "$IMG_FILE" =~ ^[.] ]]; then 2396 | debug 'INFO' 'Path to img file starting with ".", removing dot adding pwd to variable' 2397 | IMG_FILE="$(pwd)${IMG_FILE:1}" 2398 | fi 2399 | 2400 | # Set zoom type 2401 | if [ "$ZOOM" == false ]; then 2402 | SLEEPING='sleep 1' 2403 | echo -e "${White}## ${IWhite}Zoom speed NOT requested..." 2404 | debug 'INFO' "Zoom speed NOT requested, setting SLEEPING=$SLEEPING | ZOOM=$ZOOM" 2405 | $SLEEPING 2406 | else 2407 | SLEEPING='' 2408 | echo -e "${White}## ${IWhite}Zoom speed requested..." 2409 | debug 'INFO' "Zoom speed requested, setting SLEEPING to empty variable | ZOOM=$ZOOM" 2410 | fi 2411 | 2412 | # Check if the image file has the correct extension 2413 | if [ "$LOOPRUN" == false ] && [[ "$IMG_FILE" != *.img ]]; then 2414 | echo -e "${Red}!! ERROR! ${Yellow}File must have ${Green}.img ${Yellow}extension" 2415 | debug 'ERROR' 'File must have .img extension, exit 1' 2416 | exit 1 2417 | fi 2418 | 2419 | # Setting ADDED_SPACE to 0 if AUTORESIZE_RUN option is enabled 2420 | if [ "$AUTORESIZE_RUN" == true ] && [ "$LOOPRUN" == false ]; then 2421 | if [ -n "$ADDED_SPACE" ]; then 2422 | echo -e "${Yellow}!! ${Green}-a ${Yellow}used in combination with ${Green}[extra space]" 2423 | echo -e "${Yellow}!! ${Green}[extra space] (${ADDED_SPACE}MiB) ${Yellow}is ingored!" 2424 | $SLEEPING 2425 | fi 2426 | ADDED_SPACE=0 2427 | debug 'INFO' '-a selected by user, setting ADDED_SPACE to 0 (non-zero value)' 2428 | fi 2429 | 2430 | # Setting ADDED_SPACE to 0 if update is requested and variable ADDED_SPACE is a zero value 2431 | if [ $UPDATE == true ] && [ -z "$ADDED_SPACE" ] && [ "$LOOPRUN" == false ]; then 2432 | ADDED_SPACE=0 2433 | debug 'INFO' '-U selected, -a not selected or [extra space] not provided by user, setting ADDED_SPACE to 0 (non-zero value)' 2434 | fi 2435 | 2436 | # Setting ADDED_SPACE to 0 if --loop is selected without providing [extra space] 2437 | if [ "$LOOPRUN" == true ] && [ -z "$ADDED_SPACE" ]; then 2438 | ADDED_SPACE=0 2439 | debug 'INFO' '--loop selected and [extra space] not provided by user, setting ADDED_SPACE to 0 (non-zero value)' 2440 | fi 2441 | 2442 | # Setting ADDED_SPACE to 0 if --chroot is selected 2443 | if [ "$CHROOTRUN" == true ]; then 2444 | ADDED_SPACE=0 2445 | debug 'INFO' '--chroot selected, setting ADDED_SPACE to 0 (non-zero value)' 2446 | fi 2447 | 2448 | # Requesting user to input [extra space] if not provided while making new image 2449 | if [ "$UPDATE" == false ] && [ -z "$ADDED_SPACE" ]; then 2450 | debug 'INFO' 'New image without -a option and no [extra space] defined by user, requesting user input' 2451 | echo -e "${White}## ${IWhite}New image requested without ${Yellow}-a ${IWhite}option and no provided ${Yellow}[extra space]" 2452 | if [ "$COLORS" == true ]; then 2453 | read -r -p $'\e[0;37m## \e[0;97mPlease input requested \e[0;92m[extra space] in MiB \e[0;97m(0 is valid): ' ADDED_SPACE 2454 | else 2455 | read -r -p '## Please input requested [extra space] in MiB (0 is valid): ' ADDED_SPACE 2456 | fi 2457 | debug 'DEBUG' "User requested ${ADDED_SPACE}MiB as ADDED_SPACE" 2458 | fi 2459 | 2460 | # Regular expression for whole numbers 2461 | RE='^[0-9]+$' 2462 | 2463 | # Validate the added space argument as a whole number 2464 | if ! [[ "$ADDED_SPACE" =~ $RE ]]; then 2465 | debug 'WARNING' 'User defined ADDED_SPACE is not a regualar expression (whole number)' 2466 | debug 'DEBUG' "ADDED_SPACE=$ADDED_SPACE" 2467 | COUNTER=0 2468 | while ! [[ "$ADDED_SPACE" =~ $RE ]] 2469 | do 2470 | if [ "$COUNTER" -gt 0 ]; then 2471 | echo -e "${Yellow}!! ${Red}ERROR!" 2472 | debug 'WARNING' 'ERROR, user input ADDED_SPACE not regular expression' 2473 | fi 2474 | echo -e "${Yellow}!! [extra space] space must be a whole number" 2475 | echo -e "${Yellow}## How much space in MiB should be added to the image? (0 is valid)" 2476 | read ADDED_SPACE 2477 | (( COUNTER++ )) 2478 | #(( COUNTER += 1 )) 2479 | debug 'DEBUG' "User requested ${ADDED_SPACE}MiB as ADDED_SPACE" 2480 | done 2481 | typeset -i ADDED_SPACE 2482 | fi 2483 | 2484 | # If boot exists in fstab, set BOOT_PATH and make sure boot is mounted 2485 | if grep -q 'boot' /etc/fstab; then 2486 | debug 'INFO' 'Separate boot partition detected' 2487 | BOOT_PATH=$(cat /etc/fstab | grep 'boot' | awk '{print $2}') # Used in cleanup function 2488 | debug 'DEBUG' "BOOT_PATH=$BOOT_PATH" 2489 | if ! mount --fake | grep -q 'boot'; then 2490 | echo -e "${Red}!! Boot found in fstab but partition not mounted..." 2491 | debug 'WARNING' 'Boot found in fstab but partition not mounted' 2492 | $SLEEPING 2493 | fstab=($(cat /etc/fstab | grep 'boot')) 2494 | if [ "$PROMPTS" == true ]; then 2495 | while true; do 2496 | if [ "$COLORS" == true ]; then 2497 | debug 'INFO' 'Do you want to mount boot partition?' 2498 | read -r -p $'\e[0;37m## \e[0;97mDo you want to mount boot partition? \e[0;92m[y/n] \e[0m' input 2499 | else 2500 | read -r -p '## Do you want to mount boot partition? [y/n] ' input 2501 | fi 2502 | case $input in 2503 | [Yy]) debug 'DEBUG' "Y or y pressed to confirm, running: mount ${fstab[0]} ${fstab[1]}"; echo -e "${Yellow}!! Mounting boot partition to ${fstab[1]}..."; mount "${fstab[0]}" "${fstab[1]}"; break;; 2504 | [Nn]) echo -e "${Red}!! Aborting..."; exit 4;; 2505 | *) echo -e "${Yellow}!! ${Red}ERROR! ${Yellow}Please enter ${Green}'y' ${Yellow}or ${Green}'n'${Yellow}"; debug 'WARNING' "ERROR, please enter 'y' or 'n'";; 2506 | esac 2507 | done 2508 | else 2509 | echo -e "${Yellow}!! Mounting boot partition to ${fstab[1]}..." 2510 | debug 'DEBUG' "Running: mount ${fstab[0]} ${fstab[1]}" 2511 | mount "${fstab[0]}" "${fstab[1]}" 2512 | fi 2513 | sleep 1 2514 | fi 2515 | 2516 | # Create a lock file on boot partition and tail it to hinder unmount operations 2517 | touch "$BOOT_PATH"/shrink-backup.lock 2518 | tail -f "$BOOT_PATH"/shrink-backup.lock > /dev/null & 2519 | debug 'DEBUG' "${BOOT_PATH}/shrink-backup.lock created, running: tail -f ${BOOT_PATH}/shrink-backup.lock > /dev/null &" 2520 | fi 2521 | 2522 | # If --loop is requested, execute looprun function. Will exit script within the function 2523 | if [ "$LOOPRUN" == true ]; then 2524 | debug 'INFO' 'Running function: looprun' 2525 | looprun 2526 | fi 2527 | 2528 | # If --chroot is requested, execute chrootrun function. Will exit script within the function 2529 | if [ "$CHROOTRUN" == true ]; then 2530 | debug 'INFO' 'Running function: chrootrun' 2531 | chrootrun 2532 | fi 2533 | 2534 | echo -e "${White}## ${IWhite}Scanning filesystem and calculating..." 2535 | 2536 | # Check what filesystem root is using and set LOCAL_DEV_PATH & PARTITION_TABLE 2537 | FSTYPE="$(mount --fake | grep ' / ' | awk '{print $5}')" 2538 | debug 'INFO' "$FSTYPE root filesystem detected" 2539 | debug 'DEBUG' "FSTYPE=$FSTYPE" 2540 | if [ "$FSTYPE" == 'ext4' ] || [ "$FSTYPE" == 'f2fs' ]; then 2541 | LOCAL_DEV_PTUUID=$(lsblk -no mountpoint,ptuuid | grep '/ ' | awk '{print $2}') 2542 | elif [ "$FSTYPE" == 'btrfs' ]; then 2543 | #LOCAL_DEV_PTUUID=$(lsblk -no fsroots,ptuuid | grep '/ ' | awk '{print $2}') 2544 | LOCAL_DEV_PTUUID=$(lsblk -no path,ptuuid $(mount --fake | grep ' / ' | awk '{print $1}') | awk '{print $2}') 2545 | fi 2546 | LOCAL_DEV_PATH=$(lsblk -no ptuuid,type,path | grep "$LOCAL_DEV_PTUUID" | grep 'disk' | awk '{print $3}' | head -1) 2547 | debug 'DEBUG' "LOCAL_DEV_PTUUID=$LOCAL_DEV_PTUUID | LOCAL_DEV_PATH=$LOCAL_DEV_PATH" 2548 | PARTITION_TABLE=$(parted "$LOCAL_DEV_PATH" print | grep -i 'Partition Table' | awk '{print $3}') 2549 | #PARTITION_TABLE=$(blkid "$LOCAL_DEV_PATH" | sed -n 's|^.*PTTYPE="\(\S\+\)".*|\1|p') 2550 | 2551 | if [ "$FSTYPE" == 'f2fs' ] || [ "$F2FS_CONVERSION" == true ]; then 2552 | if [ "$FSTYPE" == 'f2fs' ] && [ "$F2FS_CONVERSION" == true ]; then 2553 | echo -e "${Red}!! ERROR! ${Green}--f2fs ${Yellow}selected with existing ${Green}f2fs filesystem${Yellow}, conversion not needed" 2554 | echo -e "${Red}!! Run script without ${Green}--f2fs" 2555 | debug 'ERROR' '--f2fs selected with existing f2fs filesystem, conversion not needed, exit 1' 2556 | exit 1 2557 | elif [ "$F2FS_CONVERSION" == true ] && [ "$UPDATE" == true ]; then 2558 | echo -e "${Red}!! ERROR! ${Green}--f2fs ${Yellow}selected in combination with ${Green}-U${Yellow}, conversion not possible" 2559 | echo -e "${Red}!! ${Yellow}Create new img to enable ${Green}f2fs conversion" 2560 | debug 'ERROR' '--f2fs selected in combination with -U, operation not supported, exit 1' 2561 | exit 1 2562 | elif [ "$FSTYPE" == 'f2fs' ] && [ "$UPDATE" == true ] && [ "$AUTORESIZE_RUN" == true ]; then 2563 | echo -e "${Red}!! ERROR! ${Green}-U ${Yellow}selected in combination with ${Green}-a${Yellow}, operation not supported" 2564 | echo -e "${Red}!! ${Yellow}Only creation of new img backups support ${Green}operations with -a when using f2fs on root" 2565 | debug 'ERROR' '-U selected in combination with -a on f2fs filesystem, operation not supported, exit 1' 2566 | exit 1 2567 | elif [ "$FSTYPE" == 'f2fs' ] && [ "$UPDATE" == true ] && [ "$ADDED_SPACE" -ne 0 ]; then 2568 | echo -e "${Red}!! ERROR! ${Green}-U ${Yellow}selected in combination with ${Green}[extra space]${Yellow}, operation not supported" 2569 | echo -e "${Red}!! ${Yellow}Only creation of new img backups support ${Green}operations with [extra space] when using f2fs on root" 2570 | debug 'ERROR' '-U selected in combination with [extra space] on f2fs filesystem, operation not supported, exit 1' 2571 | exit 1 2572 | fi 2573 | debug 'INFO' 'f2fs filesystem or conversion to f2fs requested, disabling autoexpansion' 2574 | AUTOEXPAND=false 2575 | fi 2576 | 2577 | # Detect OS 2578 | if [ -e /etc/apt/sources.list.d/dietpi.list ]; then 2579 | echo -e "${White}## ${Green}DietPi detected" 2580 | OS='dietpi' 2581 | debug 'INFO' 'DietPi detected' 2582 | $SLEEPING 2583 | elif [ -e /etc/apt/sources.list.d/raspi.list ] || [ -e /etc/apt/sources.list.d/raspi.sources ]; then 2584 | echo -e "${White}## ${Green}Raspberry pi detected" 2585 | OS='rpi' 2586 | debug 'INFO' 'Raspberry pi detected' 2587 | $SLEEPING 2588 | elif [ -e /etc/armbian-release ] || grep -qsi 'armbian' /etc/os-release; then 2589 | echo -e "${White}## ${Green}Armbian detected" 2590 | OS='armbian' 2591 | debug 'INFO' 'Armbian detected' 2592 | $SLEEPING 2593 | elif grep -qsi 'manjaro' /etc/os-release; then 2594 | echo -e "${White}## ${Green}Manjaro-arm detected" 2595 | OS='manjaro-arm' 2596 | debug 'INFO' 'Manjaro-arm detected' 2597 | $SLEEPING 2598 | elif grep -qsi 'archlinuxarm' /etc/os-release; then 2599 | echo -e "${White}## ${Green}ArchLinux-arm detected" 2600 | OS='archlinux-arm' 2601 | debug 'INFO' 'ArchLinux-arm detected' 2602 | $SLEEPING 2603 | elif grep -qsi 'kali' /etc/os-release; then 2604 | echo -e "${White}## ${Green}Kali-arm detected" 2605 | OS='kali' 2606 | debug 'INFO' 'Kali-arm detected' 2607 | $SLEEPING 2608 | elif grep -qsi 'ubuntu' /etc/os-release; then 2609 | echo -e "${White}## ${Green}Ubuntu detected" 2610 | OS='ubuntu' 2611 | debug 'INFO' 'Ubuntu detected' 2612 | $SLEEPING 2613 | else 2614 | echo -e "${Yellow}!! Unknown OS, no autoexpansion available" 2615 | OS='unknown' 2616 | debug 'INFO' 'Unknown OS' 2617 | $SLEEPING 2618 | fi 2619 | 2620 | # Enter variables into logfile 2621 | if [ "$DEBUG" == true ]; then 2622 | debug 'BREAK' 2623 | debug 'DEBUG' "INSTALL_METHOD=$INSTALL_METHOD" 2624 | debug 'DEBUG' "OS=$OS" 2625 | debug 'DEBUG' "IMG_FILE=$IMG_FILE" 2626 | debug 'DEBUG' "PARTITION_TABLE=$PARTITION_TABLE" 2627 | debug 'DEBUG' "UPDATE=$UPDATE" 2628 | debug 'DEBUG' "AUTORESIZE_RUN=$AUTORESIZE_RUN" 2629 | debug 'DEBUG' "PROMPTS=$PROMPTS" 2630 | debug 'DEBUG' "RSYNC_TTY=$RSYNC_TTY" 2631 | debug 'DEBUG' "EXCLUDE_FILE=$EXCLUDE_FILE" 2632 | debug 'DEBUG' "AUTOEXPAND=$AUTOEXPAND" 2633 | debug 'DEBUG' "RSYNC_DELETE=$RSYNC_DELETE" 2634 | debug 'DEBUG' "RSYNC_CUSTOM=$RSYNC_CUSTOM" 2635 | debug 'DEBUG' "F2FS_CONVERSION=$F2FS_CONVERSION" 2636 | debug 'DEBUG' "TTY_AVAILABILITY=$TTY_AVAILABILITY" 2637 | debug 'DEBUG' "ADDED_SPACE=$ADDED_SPACE" 2638 | debug 'BREAK' 2639 | fi 2640 | 2641 | # Check if exclude.txt exists when usage is requested 2642 | if [ "$EXCLUDE_FILE" == true ]; then 2643 | debug 'INFO' "-t selected by user, using $EXCLUDE_FILE_LOCATION" 2644 | if ! [ -f "$EXCLUDE_FILE_LOCATION" ]; then 2645 | echo -e "${Red}!! ERROR! ${Green}-t selected but ${Yellow}$EXCLUDE_FILE_LOCATION does not exist!" 2646 | debug 'ERROR' "$EXCLUDE_FILE_LOCATION does not exist, exit 1" 2647 | exit 1 2648 | fi 2649 | debug 'DEBUG' "$EXCLUDE_FILE_LOCATION exists" 2650 | else 2651 | debug 'INFO' '-t NOT selected by user, using default exclude directories' 2652 | fi 2653 | 2654 | # Check dependencies 2655 | debug 'INFO' 'Running function: check_dependencies' 2656 | check_dependencies 2657 | 2658 | # Create or update image 2659 | if [ "$UPDATE" != true ]; then 2660 | debug 'INFO' 'Running function: make_img' 2661 | debug 'BREAK' 2662 | make_img 2663 | else 2664 | debug 'INFO' '-U selected by user, running function: update_img' 2665 | debug 'BREAK' 2666 | update_img 2667 | fi 2668 | 2669 | # Run autoexpansion 2670 | if [ "$AUTOEXPAND" == true ]; then 2671 | debug 'INFO' 'Running autoexpand function' 2672 | $SLEEPING 2673 | case "$OS" in 2674 | dietpi) echo -e "${White}## ${IWhite}Enabling autoexpansion for ${Green}DietPi..."; debug 'INFO' 'Running function: autoexpansion_rpi'; autoexpansion_rpi;; 2675 | rpi) echo -e "${White}## ${IWhite}Enabling autoexpansion for ${Green}Raspberry pi..."; debug 'INFO' 'Running function: autoexpansion_rpi'; autoexpansion_rpi;; 2676 | armbian) echo -e "${White}## ${IWhite}Enabling autoexpansion for ${Green}Armbian..."; debug 'INFO' 'Running function: autoexpansion_armbian'; autoexpansion_armbian;; 2677 | manjaro-arm) echo -e "${White}## ${IWhite}Enabling autoexpansion for ${Green}Manjaro-arm..."; debug 'INFO' 'Running function: autoexpansion_manjaro'; autoexpansion_manjaro;; 2678 | archlinux-arm) echo -e "${White}## ${IWhite}Enabling autoexpansion for ${Green}ArchLinux-arm..."; debug 'INFO' 'Running function: autoexpansion_arch'; autoexpansion_arch;; 2679 | kali) echo -e "${White}## ${IWhite}Enabling autoexpansion for ${Green}Kali-ARM..."; debug 'INFO' 'Running function: autoexpansion_kali'; autoexpansion_kali;; 2680 | ubuntu) echo -e "${White}## ${Green}Making sure autoexpansion is enabled in /etc/cloud/cloud.cfg on img"; debug 'INFO' "Making sure autoexpansion is enabled in ${TMP_DIR}/etc/cloud/cloud.cfg" 2681 | sed -i 's/^[# ]*- growpart/ - growpart/' ${TMP_DIR}/etc/cloud/cloud.cfg 2682 | sed -i 's/^[# ]*- resizefs/ - resizefs/' ${TMP_DIR}/etc/cloud/cloud.cfg;; 2683 | unknown) echo -e "${Yellow}!! No autoexpand option available for this OS!"; debug 'WARNING' 'No autoexpand option available for this OS'; AUTOEXPAND='failed';; 2684 | esac 2685 | elif [ "$AUTOEXPAND" == false ] && [ "$OS" == 'ubuntu' ]; then 2686 | debug 'INFO' "Disable autoexpansion requested & Ubuntu detected, making sure autoexpansion is disabled in ${TMP_DIR}/etc/cloud/cloud.cfg" 2687 | echo -e "${White}## ${Green}Making sure autoexpansion is disabled in /etc/cloud/cloud.cfg on img" 2688 | $SLEEPING 2689 | sed -i 's/^[ ]*- growpart/# - growpart/' ${TMP_DIR}/etc/cloud/cloud.cfg 2690 | sed -i 's/^[ ]*- resizefs/# - resizefs/' ${TMP_DIR}/etc/cloud/cloud.cfg 2691 | fi 2692 | 2693 | print_result 2694 | 2695 | exit 0 2696 | --------------------------------------------------------------------------------