partial-backup 0.0.2__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {partial_backup-0.0.2 → partial_backup-0.1.1}/.github/workflows/python-app.yml +4 -1
- partial_backup-0.1.1/.github/workflows/python-publish.yml +29 -0
- partial_backup-0.1.1/PKG-INFO +169 -0
- partial_backup-0.1.1/README.md +146 -0
- partial_backup-0.1.1/partial_backup/__main__.py +48 -0
- partial_backup-0.1.1/partial_backup/_version.py +24 -0
- partial_backup-0.1.1/partial_backup/backup.py +541 -0
- partial_backup-0.1.1/partial_backup.egg-info/PKG-INFO +169 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/partial_backup.egg-info/SOURCES.txt +4 -1
- partial_backup-0.1.1/partial_backup.egg-info/scm_file_list.json +24 -0
- partial_backup-0.1.1/partial_backup.egg-info/scm_version.json +8 -0
- partial_backup-0.1.1/tests/test_backup.py +138 -0
- partial_backup-0.0.2/.github/workflows/python-publish.yml +0 -29
- partial_backup-0.0.2/PKG-INFO +0 -121
- partial_backup-0.0.2/README.md +0 -98
- partial_backup-0.0.2/partial_backup/__main__.py +0 -28
- partial_backup-0.0.2/partial_backup/_version.py +0 -21
- partial_backup-0.0.2/partial_backup/backup.py +0 -288
- partial_backup-0.0.2/partial_backup.egg-info/PKG-INFO +0 -121
- {partial_backup-0.0.2 → partial_backup-0.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/.github/pull_request_template.md +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/.github/workflows/codeql.yml +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/.github/workflows/markdown-lint.yml +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/.gitignore +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/.pre-commit-config.yaml +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/CONTRIBUTING.md +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/markdownlint-config.json +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/partial_backup/__init__.py +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/partial_backup/mbdb.py +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/partial_backup.egg-info/dependency_links.txt +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/partial_backup.egg-info/entry_points.txt +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/partial_backup.egg-info/requires.txt +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/partial_backup.egg-info/top_level.txt +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/pyproject.toml +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/requirements.txt +0 -0
- {partial_backup-0.0.2 → partial_backup-0.1.1}/setup.cfg +0 -0
|
@@ -5,7 +5,8 @@ on:
|
|
|
5
5
|
branches: [ "master" ]
|
|
6
6
|
paths:
|
|
7
7
|
- '.github/workflows/python-app.yml'
|
|
8
|
-
- '
|
|
8
|
+
- 'partial_backup/**'
|
|
9
|
+
- 'tests/**'
|
|
9
10
|
- 'requirements.txt'
|
|
10
11
|
- 'pyproject.toml'
|
|
11
12
|
release:
|
|
@@ -43,3 +44,5 @@ jobs:
|
|
|
43
44
|
python -m pip install -U '.[test]'
|
|
44
45
|
- name: Test show usage
|
|
45
46
|
run: partial-backup --help
|
|
47
|
+
- name: Run tests
|
|
48
|
+
run: python -m pytest tests
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# This workflow builds and uploads the Python package to PyPI when a release is created
|
|
2
|
+
|
|
3
|
+
name: Upload Python Package
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
release:
|
|
7
|
+
types: [ created ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
deploy:
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
# setuptools_scm needs full history + tags to derive the release version
|
|
18
|
+
# from the tag; a shallow clone yields a PyPI-rejected local version
|
|
19
|
+
# like 0.1.dev1+g<hash>.
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v7
|
|
23
|
+
- name: Build distributions
|
|
24
|
+
run: uv build
|
|
25
|
+
- name: Publish to PyPI
|
|
26
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
27
|
+
with:
|
|
28
|
+
packages-dir: dist
|
|
29
|
+
verbose: true
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: partial-backup
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Metadata-Aware Partial Backup Creator
|
|
5
|
+
Project-URL: Homepage, https://github.com/doronz88/partial-backup
|
|
6
|
+
Project-URL: Bug Reports, https://github.com/doronz88/partial-backup/issues
|
|
7
|
+
Keywords: ios,backup,partial-backup
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: rich
|
|
19
|
+
Requires-Dist: typer
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest; extra == "test"
|
|
22
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
23
|
+
|
|
24
|
+
# 🗂️ partial-backup: Metadata-Aware Partial Backup Creator
|
|
25
|
+
|
|
26
|
+
This utility scans a given directory in a structured *"unbacked"* format and generates a `Backup` object that can be
|
|
27
|
+
written back as a valid backup. It preserves directory and file metadata such as ownership (`user_`), group (`group_`),
|
|
28
|
+
and file mode (`mode_`), and skips common junk files.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 📦 Features
|
|
33
|
+
|
|
34
|
+
- Recursively scans a directory organized by *domain* (e.g., `HomeDomain`)
|
|
35
|
+
- Supports embedded metadata in directory names (`user_501_group_0_mode_755`)
|
|
36
|
+
- Preserves and serializes:
|
|
37
|
+
- File contents
|
|
38
|
+
- Directory structure
|
|
39
|
+
- Ownership and permissions
|
|
40
|
+
- Skips system-specific noise like `.DS_Store` and `Thumbs.db`
|
|
41
|
+
- Outputs a fully reconstructable `Backup` object
|
|
42
|
+
- Simple CLI using [Typer](https://typer.tiangolo.com/)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 🛠️ Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install partial-backup
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 🧪 Example Usage
|
|
53
|
+
|
|
54
|
+
Assume your source directory is structured like this:
|
|
55
|
+
|
|
56
|
+
```none
|
|
57
|
+
unback/HomeDomain/
|
|
58
|
+
└── Library
|
|
59
|
+
└── Preferences/
|
|
60
|
+
└── com.example.settings.plist
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
To generate the backup:
|
|
64
|
+
|
|
65
|
+
```shell
|
|
66
|
+
partial-backup unback/ output/
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
It will:
|
|
70
|
+
|
|
71
|
+
- Detect `HomeDomain`
|
|
72
|
+
- Extract user/group/mode/protection class from directories like `user_501_group_0_mode_755`
|
|
73
|
+
- List every parent directory, including the domain root, so the device can create them
|
|
74
|
+
- Write an iOS 10+ backup (`Manifest.db`) into `output/partial-backup/`
|
|
75
|
+
|
|
76
|
+
### Matching the device exactly
|
|
77
|
+
|
|
78
|
+
The device applies the owner, mode and data protection class listed in the backup, including to directories that
|
|
79
|
+
already exist. The built-in defaults follow a real iOS 27.2 backup, but for exact values take a small backup of the
|
|
80
|
+
same area from the target device and pass it as a reference:
|
|
81
|
+
|
|
82
|
+
```shell
|
|
83
|
+
pymobiledevice3 backup2 backup bk/ --full --only sms
|
|
84
|
+
partial-backup unback/ output/ --reference bk/<UDID>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Entries present in the reference keep its attributes (explicit metadata hints still win), and the device's own
|
|
88
|
+
`Info.plist`/`Manifest.plist` metadata and keybag are reused.
|
|
89
|
+
|
|
90
|
+
## ♻️ Restore the Backup
|
|
91
|
+
|
|
92
|
+
The generated backup is compatible with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3), and can be
|
|
93
|
+
restored to an iOS device without a full erase. Installed apps stay installed:
|
|
94
|
+
|
|
95
|
+
```shell
|
|
96
|
+
pymobiledevice3 backup2 restore output/ --source partial-backup --no-copy --system --no-remove --skip-apps
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> [!WARNING]
|
|
100
|
+
> A restore is not additive, even with `--no-remove`. On every restore the device replaces these paths with the
|
|
101
|
+
> backup's contents, deleting anything there the backup doesn't contain:
|
|
102
|
+
>
|
|
103
|
+
> - `HomeDomain`: `Library/Preferences`, `Library/AddressBook`, `Library/Calendar`, `Library/Notes`,
|
|
104
|
+
> `Library/SMS/sms.db`, `Library/Voicemail`, `Library/Cookies`, `Library/Safari/Bookmarks.db`,
|
|
105
|
+
> `Library/WebKit/Databases`, `Library/Caches/com.apple.WebAppCache`
|
|
106
|
+
> - `CameraRollDomain`: `Media/PhotoData` (the Photos library database)
|
|
107
|
+
> - every restored app container (`AppDomain-*`, `AppDomainGroup-*`, `AppDomainPlugin-*`), as a whole
|
|
108
|
+
>
|
|
109
|
+
> To change one of these on a device whose data matters, take a fresh backup of that device first and include its
|
|
110
|
+
> full current contents for each path, with your changes applied.
|
|
111
|
+
|
|
112
|
+
- `--system` is required unless `--reference` was used: `HomeDomain`, `MediaDomain` and the other domains in the
|
|
113
|
+
device's `MobileBackup.framework/Domains.plist` are "system domains", and the device skips them unless the host asks
|
|
114
|
+
for system files or the backup's `Lockdown.DeviceClass` matches the device.
|
|
115
|
+
- The device restores only what its `Domains.plist` allows. Paths outside a domain's rules (e.g. anything in
|
|
116
|
+
`MediaDomain` other than its `RelativePathsToBackupAndRestore`, such as `Media/Recordings` or
|
|
117
|
+
`Library/SMS/Attachments`) and some whole domains (e.g.
|
|
118
|
+
`SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles`) are skipped silently: the restore still
|
|
119
|
+
reports success.
|
|
120
|
+
- App containers (`AppDomain-<bundle id>`, `AppDomainGroup-<group id>`, `AppDomainPlugin-<bundle id>`) of installed
|
|
121
|
+
apps are restored too; their identifiers are listed in `Manifest.plist`'s `Applications` automatically, since the
|
|
122
|
+
device skips app domains that aren't listed there. A container of an app that isn't installed is set aside in
|
|
123
|
+
`/var/mobile/Library/SafeHarbor` instead.
|
|
124
|
+
- The device reboots when done and shows the post-restore Setup Assistant screens; backups are refused
|
|
125
|
+
(`MBErrorDomain/212`) until they are completed. `--no-reboot` doesn't avoid this, it only postpones it.
|
|
126
|
+
Developer Mode is turned off; re-enable it with `pymobiledevice3 amfi enable-developer-mode`.
|
|
127
|
+
|
|
128
|
+
For iOS 9 and older, `--legacy` writes the old `Manifest.mbdb` format directly into `output/`.
|
|
129
|
+
|
|
130
|
+
## 📁 Metadata Syntax
|
|
131
|
+
|
|
132
|
+
This tool supports optional metadata hints in folder names:
|
|
133
|
+
|
|
134
|
+
| Syntax | Meaning |
|
|
135
|
+
|--------------------------------------|---------------------------------------|
|
|
136
|
+
| `user_501` | File owned by user `501` |
|
|
137
|
+
| `group_20` | File belongs to group `20` |
|
|
138
|
+
| `mode_755` | File or directory mode |
|
|
139
|
+
| `class_4` | Data protection class |
|
|
140
|
+
| `user_501_group_20_mode_644_class_3` | All of the above |
|
|
141
|
+
|
|
142
|
+
These metadata markers are automatically removed from paths and applied to the entries below them. Without hints,
|
|
143
|
+
files are `0644`, directories `0755`, files use protection class `3`, and the owner is `mobile` (`501`) for domains
|
|
144
|
+
under `/var/mobile` (`HomeDomain`, `AppDomain-*`, ...) and `root` otherwise.
|
|
145
|
+
|
|
146
|
+
## ⛔ Ignored Files
|
|
147
|
+
|
|
148
|
+
The following files and folders are skipped automatically:
|
|
149
|
+
|
|
150
|
+
- `.DS_Store`
|
|
151
|
+
- `Thumbs.db`
|
|
152
|
+
- `desktop.ini`
|
|
153
|
+
- `.Spotlight-V100`
|
|
154
|
+
- `.Trashes`
|
|
155
|
+
|
|
156
|
+
## API Overview
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from pathlib import Path
|
|
160
|
+
from partial_backup.backup import Backup
|
|
161
|
+
|
|
162
|
+
backup = Backup.create_backup_from_directory(Path('/path/to/unback'))
|
|
163
|
+
backup.write_to_directory(Path('/path/to/output')) # -> /path/to/output/partial-backup
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 🙏 Acknowledgments
|
|
167
|
+
|
|
168
|
+
Huge thanks to [@JJTech0130](https://github.com/JJTech0130) for his insights and foundational work on the backup DB
|
|
169
|
+
implementation.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# 🗂️ partial-backup: Metadata-Aware Partial Backup Creator
|
|
2
|
+
|
|
3
|
+
This utility scans a given directory in a structured *"unbacked"* format and generates a `Backup` object that can be
|
|
4
|
+
written back as a valid backup. It preserves directory and file metadata such as ownership (`user_`), group (`group_`),
|
|
5
|
+
and file mode (`mode_`), and skips common junk files.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📦 Features
|
|
10
|
+
|
|
11
|
+
- Recursively scans a directory organized by *domain* (e.g., `HomeDomain`)
|
|
12
|
+
- Supports embedded metadata in directory names (`user_501_group_0_mode_755`)
|
|
13
|
+
- Preserves and serializes:
|
|
14
|
+
- File contents
|
|
15
|
+
- Directory structure
|
|
16
|
+
- Ownership and permissions
|
|
17
|
+
- Skips system-specific noise like `.DS_Store` and `Thumbs.db`
|
|
18
|
+
- Outputs a fully reconstructable `Backup` object
|
|
19
|
+
- Simple CLI using [Typer](https://typer.tiangolo.com/)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🛠️ Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install partial-backup
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 🧪 Example Usage
|
|
30
|
+
|
|
31
|
+
Assume your source directory is structured like this:
|
|
32
|
+
|
|
33
|
+
```none
|
|
34
|
+
unback/HomeDomain/
|
|
35
|
+
└── Library
|
|
36
|
+
└── Preferences/
|
|
37
|
+
└── com.example.settings.plist
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
To generate the backup:
|
|
41
|
+
|
|
42
|
+
```shell
|
|
43
|
+
partial-backup unback/ output/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
It will:
|
|
47
|
+
|
|
48
|
+
- Detect `HomeDomain`
|
|
49
|
+
- Extract user/group/mode/protection class from directories like `user_501_group_0_mode_755`
|
|
50
|
+
- List every parent directory, including the domain root, so the device can create them
|
|
51
|
+
- Write an iOS 10+ backup (`Manifest.db`) into `output/partial-backup/`
|
|
52
|
+
|
|
53
|
+
### Matching the device exactly
|
|
54
|
+
|
|
55
|
+
The device applies the owner, mode and data protection class listed in the backup, including to directories that
|
|
56
|
+
already exist. The built-in defaults follow a real iOS 27.2 backup, but for exact values take a small backup of the
|
|
57
|
+
same area from the target device and pass it as a reference:
|
|
58
|
+
|
|
59
|
+
```shell
|
|
60
|
+
pymobiledevice3 backup2 backup bk/ --full --only sms
|
|
61
|
+
partial-backup unback/ output/ --reference bk/<UDID>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Entries present in the reference keep its attributes (explicit metadata hints still win), and the device's own
|
|
65
|
+
`Info.plist`/`Manifest.plist` metadata and keybag are reused.
|
|
66
|
+
|
|
67
|
+
## ♻️ Restore the Backup
|
|
68
|
+
|
|
69
|
+
The generated backup is compatible with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3), and can be
|
|
70
|
+
restored to an iOS device without a full erase. Installed apps stay installed:
|
|
71
|
+
|
|
72
|
+
```shell
|
|
73
|
+
pymobiledevice3 backup2 restore output/ --source partial-backup --no-copy --system --no-remove --skip-apps
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> [!WARNING]
|
|
77
|
+
> A restore is not additive, even with `--no-remove`. On every restore the device replaces these paths with the
|
|
78
|
+
> backup's contents, deleting anything there the backup doesn't contain:
|
|
79
|
+
>
|
|
80
|
+
> - `HomeDomain`: `Library/Preferences`, `Library/AddressBook`, `Library/Calendar`, `Library/Notes`,
|
|
81
|
+
> `Library/SMS/sms.db`, `Library/Voicemail`, `Library/Cookies`, `Library/Safari/Bookmarks.db`,
|
|
82
|
+
> `Library/WebKit/Databases`, `Library/Caches/com.apple.WebAppCache`
|
|
83
|
+
> - `CameraRollDomain`: `Media/PhotoData` (the Photos library database)
|
|
84
|
+
> - every restored app container (`AppDomain-*`, `AppDomainGroup-*`, `AppDomainPlugin-*`), as a whole
|
|
85
|
+
>
|
|
86
|
+
> To change one of these on a device whose data matters, take a fresh backup of that device first and include its
|
|
87
|
+
> full current contents for each path, with your changes applied.
|
|
88
|
+
|
|
89
|
+
- `--system` is required unless `--reference` was used: `HomeDomain`, `MediaDomain` and the other domains in the
|
|
90
|
+
device's `MobileBackup.framework/Domains.plist` are "system domains", and the device skips them unless the host asks
|
|
91
|
+
for system files or the backup's `Lockdown.DeviceClass` matches the device.
|
|
92
|
+
- The device restores only what its `Domains.plist` allows. Paths outside a domain's rules (e.g. anything in
|
|
93
|
+
`MediaDomain` other than its `RelativePathsToBackupAndRestore`, such as `Media/Recordings` or
|
|
94
|
+
`Library/SMS/Attachments`) and some whole domains (e.g.
|
|
95
|
+
`SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles`) are skipped silently: the restore still
|
|
96
|
+
reports success.
|
|
97
|
+
- App containers (`AppDomain-<bundle id>`, `AppDomainGroup-<group id>`, `AppDomainPlugin-<bundle id>`) of installed
|
|
98
|
+
apps are restored too; their identifiers are listed in `Manifest.plist`'s `Applications` automatically, since the
|
|
99
|
+
device skips app domains that aren't listed there. A container of an app that isn't installed is set aside in
|
|
100
|
+
`/var/mobile/Library/SafeHarbor` instead.
|
|
101
|
+
- The device reboots when done and shows the post-restore Setup Assistant screens; backups are refused
|
|
102
|
+
(`MBErrorDomain/212`) until they are completed. `--no-reboot` doesn't avoid this, it only postpones it.
|
|
103
|
+
Developer Mode is turned off; re-enable it with `pymobiledevice3 amfi enable-developer-mode`.
|
|
104
|
+
|
|
105
|
+
For iOS 9 and older, `--legacy` writes the old `Manifest.mbdb` format directly into `output/`.
|
|
106
|
+
|
|
107
|
+
## 📁 Metadata Syntax
|
|
108
|
+
|
|
109
|
+
This tool supports optional metadata hints in folder names:
|
|
110
|
+
|
|
111
|
+
| Syntax | Meaning |
|
|
112
|
+
|--------------------------------------|---------------------------------------|
|
|
113
|
+
| `user_501` | File owned by user `501` |
|
|
114
|
+
| `group_20` | File belongs to group `20` |
|
|
115
|
+
| `mode_755` | File or directory mode |
|
|
116
|
+
| `class_4` | Data protection class |
|
|
117
|
+
| `user_501_group_20_mode_644_class_3` | All of the above |
|
|
118
|
+
|
|
119
|
+
These metadata markers are automatically removed from paths and applied to the entries below them. Without hints,
|
|
120
|
+
files are `0644`, directories `0755`, files use protection class `3`, and the owner is `mobile` (`501`) for domains
|
|
121
|
+
under `/var/mobile` (`HomeDomain`, `AppDomain-*`, ...) and `root` otherwise.
|
|
122
|
+
|
|
123
|
+
## ⛔ Ignored Files
|
|
124
|
+
|
|
125
|
+
The following files and folders are skipped automatically:
|
|
126
|
+
|
|
127
|
+
- `.DS_Store`
|
|
128
|
+
- `Thumbs.db`
|
|
129
|
+
- `desktop.ini`
|
|
130
|
+
- `.Spotlight-V100`
|
|
131
|
+
- `.Trashes`
|
|
132
|
+
|
|
133
|
+
## API Overview
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from pathlib import Path
|
|
137
|
+
from partial_backup.backup import Backup
|
|
138
|
+
|
|
139
|
+
backup = Backup.create_backup_from_directory(Path('/path/to/unback'))
|
|
140
|
+
backup.write_to_directory(Path('/path/to/output')) # -> /path/to/output/partial-backup
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 🙏 Acknowledgments
|
|
144
|
+
|
|
145
|
+
Huge thanks to [@JJTech0130](https://github.com/JJTech0130) for his insights and foundational work on the backup DB
|
|
146
|
+
implementation.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated, Optional
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from rich import print
|
|
6
|
+
|
|
7
|
+
from partial_backup.backup import DEFAULT_IDENTIFIER, Backup, ReferenceBackup
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_backup_directory(
|
|
11
|
+
source: Path,
|
|
12
|
+
output: Path,
|
|
13
|
+
identifier: Annotated[str, typer.Option(
|
|
14
|
+
help="Name of the backup directory created under OUTPUT; pass it to `backup2 restore --source`.")
|
|
15
|
+
] = DEFAULT_IDENTIFIER,
|
|
16
|
+
reference: Annotated[Optional[Path], typer.Option(
|
|
17
|
+
help="A real backup's <UDID> directory taken from the target device (e.g. "
|
|
18
|
+
"`pymobiledevice3 backup2 backup --only sms`). Its device metadata and the exact attributes of matching "
|
|
19
|
+
"entries are reused.")
|
|
20
|
+
] = None,
|
|
21
|
+
legacy: Annotated[bool, typer.Option(
|
|
22
|
+
help="Write the legacy iOS <= 9 format (Manifest.mbdb) directly into OUTPUT.")
|
|
23
|
+
] = False,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Construct a backup directory from a given source directory given in an `unback` format
|
|
27
|
+
"""
|
|
28
|
+
backup = Backup.create_backup_from_directory(source, ReferenceBackup.load(reference) if reference else None)
|
|
29
|
+
print(f"Constructing the following backup structure into: {output}")
|
|
30
|
+
print(backup.files)
|
|
31
|
+
if legacy:
|
|
32
|
+
backup.write_legacy_to_directory(output)
|
|
33
|
+
restore_source = "."
|
|
34
|
+
else:
|
|
35
|
+
backup.write_to_directory(output, identifier)
|
|
36
|
+
restore_source = identifier
|
|
37
|
+
print("[bold][green]Done![/green][/bold]")
|
|
38
|
+
print("[bold]Consider restoring this backup using:[/bold]")
|
|
39
|
+
print(f"[magenta]pymobiledevice3 backup2 restore {output} --source {restore_source} --no-copy --system "
|
|
40
|
+
f"--no-remove --skip-apps[/magenta]")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def cli() -> None:
|
|
44
|
+
typer.run(create_backup_directory)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
cli()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 1)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'gfa08fb7c2'
|