├── .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 |
--------------------------------------------------------------------------------