flatback 1.0__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.
- flatback-1.0/PKG-INFO +96 -0
- flatback-1.0/README.md +85 -0
- flatback-1.0/flatback/__init__.py +1 -0
- flatback-1.0/flatback/__main__.py +48 -0
- flatback-1.0/flatback/core.py +264 -0
- flatback-1.0/flatback.egg-info/PKG-INFO +96 -0
- flatback-1.0/flatback.egg-info/SOURCES.txt +12 -0
- flatback-1.0/flatback.egg-info/dependency_links.txt +1 -0
- flatback-1.0/flatback.egg-info/entry_points.txt +2 -0
- flatback-1.0/flatback.egg-info/requires.txt +1 -0
- flatback-1.0/flatback.egg-info/top_level.txt +1 -0
- flatback-1.0/pyproject.toml +19 -0
- flatback-1.0/setup.cfg +4 -0
- flatback-1.0/setup.py +22 -0
flatback-1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flatback
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Backup and restore Flatpak applications for offline transfer
|
|
5
|
+
Home-page: https://github.com/jfhack/flatback
|
|
6
|
+
Project-URL: Homepage, https://github.com/jfhack/flatback
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pyyaml
|
|
10
|
+
Dynamic: home-page
|
|
11
|
+
|
|
12
|
+
# Flatback
|
|
13
|
+
|
|
14
|
+
This is a simple script utility to back up and restore Flatpak applications for offline transfer.
|
|
15
|
+
|
|
16
|
+
Packages a Flatpak app together with its runtime into a single `.tar.gz` archive (or directory), including a YAML manifest. Ideal for moving apps to offline computers or preserving deprecated packages.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pip install flatback
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires `flatpak` to be installed on the system.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Backup
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
flatback backup org.popular.emulator
|
|
32
|
+
flatback backup org.popular.emulator -o emulator-backup.tar.gz
|
|
33
|
+
flatback backup com.slack.Slack --no-tar
|
|
34
|
+
flatback backup com.google.Chrome --system
|
|
35
|
+
flatback backup org.popular.emulator --data
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Restore
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
flatback restore org.popular.emulator.tar.gz
|
|
42
|
+
flatback restore emulator-backup.tar.gz
|
|
43
|
+
flatback restore com.slack.Slack/
|
|
44
|
+
flatback restore com.google.Chrome.tar.gz --system
|
|
45
|
+
flatback restore org.popular.emulator.tar.gz --data
|
|
46
|
+
flatback restore org.popular.emulator.tar.gz --no-data
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## How It Works
|
|
50
|
+
|
|
51
|
+
**Backup** bundles the app and its runtime into `.flatpak` files using `flatpak build-bundle`, then packages everything with a `flatback.yml` manifest into a `.tar.gz`.
|
|
52
|
+
|
|
53
|
+
**Restore** extracts the archive, reads the manifest, and installs the runtime and app bundles using `flatpak install --bundle`.
|
|
54
|
+
|
|
55
|
+
### Manifest Example (`flatback.yml`)
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
app:
|
|
59
|
+
app_id: org.popular.emulator
|
|
60
|
+
arch: x86_64
|
|
61
|
+
branch: stable
|
|
62
|
+
installation: user
|
|
63
|
+
origin: flathub
|
|
64
|
+
app_bundle: app_org.popular.emulator_x86_64_stable.flatpak
|
|
65
|
+
flatback_version: '1.0'
|
|
66
|
+
has_data: true
|
|
67
|
+
runtime:
|
|
68
|
+
app_id: org.kde.Platform
|
|
69
|
+
arch: x86_64
|
|
70
|
+
branch: 5.15-23.08
|
|
71
|
+
installation: user
|
|
72
|
+
origin: flathub
|
|
73
|
+
runtime_bundle: runtime_org.kde.Platform_x86_64_5.15-23.08.flatpak
|
|
74
|
+
version: mainline-0-1734
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Arguments
|
|
78
|
+
|
|
79
|
+
### `flatback backup`
|
|
80
|
+
|
|
81
|
+
| Argument | Description |
|
|
82
|
+
|-|-|
|
|
83
|
+
| `app_id` | Flatpak application ID (e.g. `org.popular.emulator`) |
|
|
84
|
+
| `-o`, `--output` | Output path (default: `<app_id>.tar.gz`) |
|
|
85
|
+
| `--no-tar` | Save as directory instead of tar.gz |
|
|
86
|
+
| `--data` | Include user data (`~/.var/app/<app_id>`) in backup |
|
|
87
|
+
| `--system` | Use system installation instead of user |
|
|
88
|
+
|
|
89
|
+
### `flatback restore`
|
|
90
|
+
|
|
91
|
+
| Argument | Description |
|
|
92
|
+
|-|-|
|
|
93
|
+
| `source` | Path to backup `.tar.gz` or directory |
|
|
94
|
+
| `--data` | Restore user data without prompting |
|
|
95
|
+
| `--no-data` | Skip user data restore without prompting |
|
|
96
|
+
| `--system` | Install to system instead of user |
|
flatback-1.0/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Flatback
|
|
2
|
+
|
|
3
|
+
This is a simple script utility to back up and restore Flatpak applications for offline transfer.
|
|
4
|
+
|
|
5
|
+
Packages a Flatpak app together with its runtime into a single `.tar.gz` archive (or directory), including a YAML manifest. Ideal for moving apps to offline computers or preserving deprecated packages.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install flatback
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires `flatpak` to be installed on the system.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Backup
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
flatback backup org.popular.emulator
|
|
21
|
+
flatback backup org.popular.emulator -o emulator-backup.tar.gz
|
|
22
|
+
flatback backup com.slack.Slack --no-tar
|
|
23
|
+
flatback backup com.google.Chrome --system
|
|
24
|
+
flatback backup org.popular.emulator --data
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Restore
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
flatback restore org.popular.emulator.tar.gz
|
|
31
|
+
flatback restore emulator-backup.tar.gz
|
|
32
|
+
flatback restore com.slack.Slack/
|
|
33
|
+
flatback restore com.google.Chrome.tar.gz --system
|
|
34
|
+
flatback restore org.popular.emulator.tar.gz --data
|
|
35
|
+
flatback restore org.popular.emulator.tar.gz --no-data
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## How It Works
|
|
39
|
+
|
|
40
|
+
**Backup** bundles the app and its runtime into `.flatpak` files using `flatpak build-bundle`, then packages everything with a `flatback.yml` manifest into a `.tar.gz`.
|
|
41
|
+
|
|
42
|
+
**Restore** extracts the archive, reads the manifest, and installs the runtime and app bundles using `flatpak install --bundle`.
|
|
43
|
+
|
|
44
|
+
### Manifest Example (`flatback.yml`)
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
app:
|
|
48
|
+
app_id: org.popular.emulator
|
|
49
|
+
arch: x86_64
|
|
50
|
+
branch: stable
|
|
51
|
+
installation: user
|
|
52
|
+
origin: flathub
|
|
53
|
+
app_bundle: app_org.popular.emulator_x86_64_stable.flatpak
|
|
54
|
+
flatback_version: '1.0'
|
|
55
|
+
has_data: true
|
|
56
|
+
runtime:
|
|
57
|
+
app_id: org.kde.Platform
|
|
58
|
+
arch: x86_64
|
|
59
|
+
branch: 5.15-23.08
|
|
60
|
+
installation: user
|
|
61
|
+
origin: flathub
|
|
62
|
+
runtime_bundle: runtime_org.kde.Platform_x86_64_5.15-23.08.flatpak
|
|
63
|
+
version: mainline-0-1734
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Arguments
|
|
67
|
+
|
|
68
|
+
### `flatback backup`
|
|
69
|
+
|
|
70
|
+
| Argument | Description |
|
|
71
|
+
|-|-|
|
|
72
|
+
| `app_id` | Flatpak application ID (e.g. `org.popular.emulator`) |
|
|
73
|
+
| `-o`, `--output` | Output path (default: `<app_id>.tar.gz`) |
|
|
74
|
+
| `--no-tar` | Save as directory instead of tar.gz |
|
|
75
|
+
| `--data` | Include user data (`~/.var/app/<app_id>`) in backup |
|
|
76
|
+
| `--system` | Use system installation instead of user |
|
|
77
|
+
|
|
78
|
+
### `flatback restore`
|
|
79
|
+
|
|
80
|
+
| Argument | Description |
|
|
81
|
+
|-|-|
|
|
82
|
+
| `source` | Path to backup `.tar.gz` or directory |
|
|
83
|
+
| `--data` | Restore user data without prompting |
|
|
84
|
+
| `--no-data` | Skip user data restore without prompting |
|
|
85
|
+
| `--system` | Install to system instead of user |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from .core import Backup, Restore
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_args():
|
|
7
|
+
parser = argparse.ArgumentParser(
|
|
8
|
+
prog="flatback",
|
|
9
|
+
description="Backup and restore Flatpak applications for offline transfer",
|
|
10
|
+
)
|
|
11
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
12
|
+
|
|
13
|
+
backup = sub.add_parser("backup", help="Backup a Flatpak application")
|
|
14
|
+
backup.add_argument("app_id", help="Flatpak application ID (e.g. org.yuzu_emu.yuzu)")
|
|
15
|
+
backup.add_argument("-o", "--output", help="Output path (default: <app_id>.tar.gz)")
|
|
16
|
+
backup.add_argument("--no-tar", action="store_true", help="Save as directory instead of tar.gz")
|
|
17
|
+
backup.add_argument("--data", action="store_true", help="Include user data (~/.var/app/<app_id>) in backup")
|
|
18
|
+
backup.add_argument("--system", action="store_true", help="Use system installation instead of user")
|
|
19
|
+
|
|
20
|
+
restore = sub.add_parser("restore", help="Restore a Flatpak application from backup")
|
|
21
|
+
restore.add_argument("source", help="Path to backup tar.gz or directory")
|
|
22
|
+
restore.add_argument("--data", action="store_true", default=None, help="Restore user data without prompting")
|
|
23
|
+
restore.add_argument("--no-data", action="store_true", help="Skip user data restore without prompting")
|
|
24
|
+
restore.add_argument("--system", action="store_true", help="Install to system instead of user")
|
|
25
|
+
|
|
26
|
+
return parser.parse_args()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main():
|
|
30
|
+
args = parse_args()
|
|
31
|
+
installation = "system" if args.system else "user"
|
|
32
|
+
|
|
33
|
+
if args.command == "backup":
|
|
34
|
+
bk = Backup(args.app_id, installation)
|
|
35
|
+
bk.create(output_path=args.output, as_tar=not args.no_tar, include_data=args.data)
|
|
36
|
+
|
|
37
|
+
elif args.command == "restore":
|
|
38
|
+
restore_data = None
|
|
39
|
+
if args.data:
|
|
40
|
+
restore_data = True
|
|
41
|
+
elif args.no_data:
|
|
42
|
+
restore_data = False
|
|
43
|
+
rs = Restore(installation, restore_data=restore_data)
|
|
44
|
+
rs.load(args.source)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
main()
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import yaml
|
|
3
|
+
import shutil
|
|
4
|
+
import tarfile
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from . import __version__
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FlatpakRef:
|
|
12
|
+
def __init__(self, app_id, arch="x86_64", branch="stable", origin="flathub", installation="user"):
|
|
13
|
+
self.app_id = app_id
|
|
14
|
+
self.arch = arch
|
|
15
|
+
self.branch = branch
|
|
16
|
+
self.origin = origin
|
|
17
|
+
self.installation = installation
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def ref(self):
|
|
21
|
+
return f"{self.app_id}/{self.arch}/{self.branch}"
|
|
22
|
+
|
|
23
|
+
def as_dict(self):
|
|
24
|
+
return {
|
|
25
|
+
"app_id": self.app_id,
|
|
26
|
+
"arch": self.arch,
|
|
27
|
+
"branch": self.branch,
|
|
28
|
+
"origin": self.origin,
|
|
29
|
+
"installation": self.installation,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_dict(cls, d):
|
|
34
|
+
return cls(**d)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FlatpakQuery:
|
|
38
|
+
def __init__(self, installation="user"):
|
|
39
|
+
self._install_flag = f"--{installation}"
|
|
40
|
+
|
|
41
|
+
def _run(self, args):
|
|
42
|
+
result = subprocess.run(
|
|
43
|
+
["flatpak"] + args,
|
|
44
|
+
capture_output=True, text=True, check=True,
|
|
45
|
+
)
|
|
46
|
+
return result.stdout.strip()
|
|
47
|
+
|
|
48
|
+
def app_info(self, app_id):
|
|
49
|
+
raw = self._run(["info", self._install_flag, app_id])
|
|
50
|
+
info = {}
|
|
51
|
+
for line in raw.splitlines():
|
|
52
|
+
if ":" in line:
|
|
53
|
+
key, _, val = line.partition(":")
|
|
54
|
+
info[key.strip()] = val.strip()
|
|
55
|
+
return info
|
|
56
|
+
|
|
57
|
+
def app_ref(self, app_id):
|
|
58
|
+
info = self.app_info(app_id)
|
|
59
|
+
ref_str = info.get("Ref", "")
|
|
60
|
+
parts = ref_str.split("/")
|
|
61
|
+
return FlatpakRef(
|
|
62
|
+
app_id=parts[1] if len(parts) > 1 else app_id,
|
|
63
|
+
arch=parts[2] if len(parts) > 2 else "x86_64",
|
|
64
|
+
branch=parts[3] if len(parts) > 3 else "stable",
|
|
65
|
+
origin=info.get("Origin", "flathub"),
|
|
66
|
+
installation="user" if self._install_flag == "--user" else "system",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def runtime_ref(self, app_id):
|
|
70
|
+
raw = self._run(["info", self._install_flag, "--show-runtime", app_id])
|
|
71
|
+
parts = raw.split("/")
|
|
72
|
+
return FlatpakRef(
|
|
73
|
+
app_id=parts[0],
|
|
74
|
+
arch=parts[1] if len(parts) > 1 else "x86_64",
|
|
75
|
+
branch=parts[2] if len(parts) > 2 else "stable",
|
|
76
|
+
origin="flathub",
|
|
77
|
+
installation="user" if self._install_flag == "--user" else "system",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def repo_path(self, installation="user"):
|
|
81
|
+
if installation == "user":
|
|
82
|
+
return Path.home() / ".local/share/flatpak/repo"
|
|
83
|
+
return Path("/var/lib/flatpak/repo")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class BundleBuilder:
|
|
87
|
+
def __init__(self, query: FlatpakQuery):
|
|
88
|
+
self.query = query
|
|
89
|
+
|
|
90
|
+
def build_bundle(self, ref: FlatpakRef, output_path: Path, is_runtime=False):
|
|
91
|
+
repo = self.query.repo_path(ref.installation)
|
|
92
|
+
args = [
|
|
93
|
+
"flatpak", "build-bundle",
|
|
94
|
+
str(repo), str(output_path),
|
|
95
|
+
ref.app_id, ref.branch,
|
|
96
|
+
f"--arch={ref.arch}",
|
|
97
|
+
]
|
|
98
|
+
if is_runtime:
|
|
99
|
+
args.append("--runtime")
|
|
100
|
+
subprocess.run(args, check=True)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Backup:
|
|
104
|
+
MANIFEST = "flatback.yml"
|
|
105
|
+
DATA_DIR = "data"
|
|
106
|
+
|
|
107
|
+
def __init__(self, app_id, installation="user"):
|
|
108
|
+
self.app_id = app_id
|
|
109
|
+
self.installation = installation
|
|
110
|
+
self.query = FlatpakQuery(installation)
|
|
111
|
+
self.builder = BundleBuilder(self.query)
|
|
112
|
+
|
|
113
|
+
def _bundle_name(self, ref: FlatpakRef, is_runtime=False):
|
|
114
|
+
prefix = "runtime" if is_runtime else "app"
|
|
115
|
+
return f"{prefix}_{ref.app_id}_{ref.arch}_{ref.branch}.flatpak"
|
|
116
|
+
|
|
117
|
+
def _user_data_path(self):
|
|
118
|
+
return Path.home() / ".var" / "app" / self.app_id
|
|
119
|
+
|
|
120
|
+
def create(self, output_path=None, as_tar=True, include_data=False):
|
|
121
|
+
app_ref = self.query.app_ref(self.app_id)
|
|
122
|
+
runtime_ref = self.query.runtime_ref(self.app_id)
|
|
123
|
+
app_info = self.query.app_info(self.app_id)
|
|
124
|
+
|
|
125
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
126
|
+
tmp_dir = Path(tmp)
|
|
127
|
+
|
|
128
|
+
app_bundle = self._bundle_name(app_ref)
|
|
129
|
+
runtime_bundle = self._bundle_name(runtime_ref, is_runtime=True)
|
|
130
|
+
|
|
131
|
+
print(f"Bundling app: {app_ref.ref}")
|
|
132
|
+
self.builder.build_bundle(app_ref, tmp_dir / app_bundle)
|
|
133
|
+
|
|
134
|
+
print(f"Bundling runtime: {runtime_ref.ref}")
|
|
135
|
+
self.builder.build_bundle(runtime_ref, tmp_dir / runtime_bundle, is_runtime=True)
|
|
136
|
+
|
|
137
|
+
has_data = False
|
|
138
|
+
if include_data:
|
|
139
|
+
has_data = self._copy_user_data(tmp_dir)
|
|
140
|
+
|
|
141
|
+
manifest = {
|
|
142
|
+
"app": app_ref.as_dict(),
|
|
143
|
+
"runtime": runtime_ref.as_dict(),
|
|
144
|
+
"app_bundle": app_bundle,
|
|
145
|
+
"runtime_bundle": runtime_bundle,
|
|
146
|
+
"version": app_info.get("Version", "unknown"),
|
|
147
|
+
"flatback_version": __version__,
|
|
148
|
+
"has_data": has_data,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
with open(tmp_dir / self.MANIFEST, "w") as f:
|
|
152
|
+
yaml.dump(manifest, f, default_flow_style=False)
|
|
153
|
+
|
|
154
|
+
default_name = self.app_id
|
|
155
|
+
if as_tar:
|
|
156
|
+
out = Path(output_path or f"{default_name}.tar.gz")
|
|
157
|
+
self._pack_tar(tmp_dir, out)
|
|
158
|
+
else:
|
|
159
|
+
out = Path(output_path or default_name)
|
|
160
|
+
if out.exists():
|
|
161
|
+
shutil.rmtree(out)
|
|
162
|
+
shutil.copytree(tmp_dir, out)
|
|
163
|
+
|
|
164
|
+
print(f"Backup saved to: {out}")
|
|
165
|
+
return out
|
|
166
|
+
|
|
167
|
+
def _copy_user_data(self, tmp_dir):
|
|
168
|
+
src = self._user_data_path()
|
|
169
|
+
if not src.exists():
|
|
170
|
+
print(f"No user data found at: {src}")
|
|
171
|
+
return False
|
|
172
|
+
dst = tmp_dir / self.DATA_DIR
|
|
173
|
+
print(f"Copying user data: {src}")
|
|
174
|
+
shutil.copytree(src, dst, symlinks=True)
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
def _pack_tar(self, source_dir, tar_path):
|
|
178
|
+
with tarfile.open(tar_path, "w:gz") as tar:
|
|
179
|
+
for item in source_dir.iterdir():
|
|
180
|
+
tar.add(item, arcname=item.name)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class Restore:
|
|
184
|
+
MANIFEST = "flatback.yml"
|
|
185
|
+
DATA_DIR = "data"
|
|
186
|
+
|
|
187
|
+
def __init__(self, installation="user", restore_data=None):
|
|
188
|
+
self.installation = installation
|
|
189
|
+
self._install_flag = f"--{installation}"
|
|
190
|
+
self._restore_data = restore_data
|
|
191
|
+
|
|
192
|
+
def load(self, source_path):
|
|
193
|
+
source = Path(source_path)
|
|
194
|
+
if source.is_file() and (source.suffix == ".gz" or source.name.endswith(".tar.gz")):
|
|
195
|
+
self._load_from_tar(source)
|
|
196
|
+
elif source.is_dir():
|
|
197
|
+
self._load_from_dir(source)
|
|
198
|
+
else:
|
|
199
|
+
raise ValueError(f"Unsupported source: {source}")
|
|
200
|
+
|
|
201
|
+
def _load_from_tar(self, tar_path):
|
|
202
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
203
|
+
tmp_dir = Path(tmp)
|
|
204
|
+
with tarfile.open(tar_path, "r:gz") as tar:
|
|
205
|
+
tar.extractall(tmp_dir, filter="data")
|
|
206
|
+
self._load_from_dir(tmp_dir)
|
|
207
|
+
|
|
208
|
+
def _load_from_dir(self, directory):
|
|
209
|
+
manifest_path = directory / self.MANIFEST
|
|
210
|
+
if not manifest_path.exists():
|
|
211
|
+
raise FileNotFoundError(f"Manifest not found: {manifest_path}")
|
|
212
|
+
|
|
213
|
+
with open(manifest_path) as f:
|
|
214
|
+
manifest = yaml.safe_load(f)
|
|
215
|
+
|
|
216
|
+
runtime_bundle = directory / manifest["runtime_bundle"]
|
|
217
|
+
app_bundle = directory / manifest["app_bundle"]
|
|
218
|
+
|
|
219
|
+
runtime_ref = FlatpakRef.from_dict(manifest["runtime"])
|
|
220
|
+
app_ref = FlatpakRef.from_dict(manifest["app"])
|
|
221
|
+
|
|
222
|
+
print(f"Installing runtime: {runtime_ref.ref}")
|
|
223
|
+
self._install_bundle(runtime_bundle)
|
|
224
|
+
|
|
225
|
+
print(f"Installing app: {app_ref.ref}")
|
|
226
|
+
self._install_bundle(app_bundle)
|
|
227
|
+
|
|
228
|
+
if manifest.get("has_data"):
|
|
229
|
+
self._handle_user_data(directory, app_ref.app_id)
|
|
230
|
+
|
|
231
|
+
print(f"Restore complete: {app_ref.app_id}")
|
|
232
|
+
|
|
233
|
+
def _handle_user_data(self, directory, app_id):
|
|
234
|
+
data_dir = directory / self.DATA_DIR
|
|
235
|
+
if not data_dir.exists():
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
should_restore = self._restore_data
|
|
239
|
+
if should_restore is None:
|
|
240
|
+
answer = input("Backup contains user data. Restore it? [y/N] ").strip().lower()
|
|
241
|
+
should_restore = answer in ("y", "yes")
|
|
242
|
+
|
|
243
|
+
if not should_restore:
|
|
244
|
+
print("Skipping user data restore")
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
dst = Path.home() / ".var" / "app" / app_id
|
|
248
|
+
if dst.exists():
|
|
249
|
+
print(f"Replacing existing user data: {dst}")
|
|
250
|
+
shutil.rmtree(dst)
|
|
251
|
+
print(f"Restoring user data to: {dst}")
|
|
252
|
+
shutil.copytree(data_dir, dst, symlinks=True)
|
|
253
|
+
|
|
254
|
+
def _install_bundle(self, bundle_path):
|
|
255
|
+
result = subprocess.run(
|
|
256
|
+
["flatpak", "install", self._install_flag, "--bundle", "-y", str(bundle_path)],
|
|
257
|
+
capture_output=True, text=True,
|
|
258
|
+
)
|
|
259
|
+
if result.returncode != 0:
|
|
260
|
+
if "already installed" in result.stderr:
|
|
261
|
+
print(f"Already installed, skipping: {bundle_path.name}")
|
|
262
|
+
else:
|
|
263
|
+
print(result.stderr)
|
|
264
|
+
raise subprocess.CalledProcessError(result.returncode, result.args)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flatback
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Backup and restore Flatpak applications for offline transfer
|
|
5
|
+
Home-page: https://github.com/jfhack/flatback
|
|
6
|
+
Project-URL: Homepage, https://github.com/jfhack/flatback
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pyyaml
|
|
10
|
+
Dynamic: home-page
|
|
11
|
+
|
|
12
|
+
# Flatback
|
|
13
|
+
|
|
14
|
+
This is a simple script utility to back up and restore Flatpak applications for offline transfer.
|
|
15
|
+
|
|
16
|
+
Packages a Flatpak app together with its runtime into a single `.tar.gz` archive (or directory), including a YAML manifest. Ideal for moving apps to offline computers or preserving deprecated packages.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pip install flatback
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires `flatpak` to be installed on the system.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Backup
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
flatback backup org.popular.emulator
|
|
32
|
+
flatback backup org.popular.emulator -o emulator-backup.tar.gz
|
|
33
|
+
flatback backup com.slack.Slack --no-tar
|
|
34
|
+
flatback backup com.google.Chrome --system
|
|
35
|
+
flatback backup org.popular.emulator --data
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Restore
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
flatback restore org.popular.emulator.tar.gz
|
|
42
|
+
flatback restore emulator-backup.tar.gz
|
|
43
|
+
flatback restore com.slack.Slack/
|
|
44
|
+
flatback restore com.google.Chrome.tar.gz --system
|
|
45
|
+
flatback restore org.popular.emulator.tar.gz --data
|
|
46
|
+
flatback restore org.popular.emulator.tar.gz --no-data
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## How It Works
|
|
50
|
+
|
|
51
|
+
**Backup** bundles the app and its runtime into `.flatpak` files using `flatpak build-bundle`, then packages everything with a `flatback.yml` manifest into a `.tar.gz`.
|
|
52
|
+
|
|
53
|
+
**Restore** extracts the archive, reads the manifest, and installs the runtime and app bundles using `flatpak install --bundle`.
|
|
54
|
+
|
|
55
|
+
### Manifest Example (`flatback.yml`)
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
app:
|
|
59
|
+
app_id: org.popular.emulator
|
|
60
|
+
arch: x86_64
|
|
61
|
+
branch: stable
|
|
62
|
+
installation: user
|
|
63
|
+
origin: flathub
|
|
64
|
+
app_bundle: app_org.popular.emulator_x86_64_stable.flatpak
|
|
65
|
+
flatback_version: '1.0'
|
|
66
|
+
has_data: true
|
|
67
|
+
runtime:
|
|
68
|
+
app_id: org.kde.Platform
|
|
69
|
+
arch: x86_64
|
|
70
|
+
branch: 5.15-23.08
|
|
71
|
+
installation: user
|
|
72
|
+
origin: flathub
|
|
73
|
+
runtime_bundle: runtime_org.kde.Platform_x86_64_5.15-23.08.flatpak
|
|
74
|
+
version: mainline-0-1734
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Arguments
|
|
78
|
+
|
|
79
|
+
### `flatback backup`
|
|
80
|
+
|
|
81
|
+
| Argument | Description |
|
|
82
|
+
|-|-|
|
|
83
|
+
| `app_id` | Flatpak application ID (e.g. `org.popular.emulator`) |
|
|
84
|
+
| `-o`, `--output` | Output path (default: `<app_id>.tar.gz`) |
|
|
85
|
+
| `--no-tar` | Save as directory instead of tar.gz |
|
|
86
|
+
| `--data` | Include user data (`~/.var/app/<app_id>`) in backup |
|
|
87
|
+
| `--system` | Use system installation instead of user |
|
|
88
|
+
|
|
89
|
+
### `flatback restore`
|
|
90
|
+
|
|
91
|
+
| Argument | Description |
|
|
92
|
+
|-|-|
|
|
93
|
+
| `source` | Path to backup `.tar.gz` or directory |
|
|
94
|
+
| `--data` | Restore user data without prompting |
|
|
95
|
+
| `--no-data` | Skip user data restore without prompting |
|
|
96
|
+
| `--system` | Install to system instead of user |
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
flatback/__init__.py
|
|
5
|
+
flatback/__main__.py
|
|
6
|
+
flatback/core.py
|
|
7
|
+
flatback.egg-info/PKG-INFO
|
|
8
|
+
flatback.egg-info/SOURCES.txt
|
|
9
|
+
flatback.egg-info/dependency_links.txt
|
|
10
|
+
flatback.egg-info/entry_points.txt
|
|
11
|
+
flatback.egg-info/requires.txt
|
|
12
|
+
flatback.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyyaml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flatback
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "flatback"
|
|
7
|
+
version = "1.0"
|
|
8
|
+
description = "Backup and restore Flatpak applications for offline transfer"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pyyaml",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://github.com/jfhack/flatback"
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
flatback = "flatback.__main__:main"
|
flatback-1.0/setup.cfg
ADDED
flatback-1.0/setup.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
5
|
+
long_description = fh.read()
|
|
6
|
+
|
|
7
|
+
setup(
|
|
8
|
+
name="flatback",
|
|
9
|
+
version="1.0",
|
|
10
|
+
packages=find_packages(),
|
|
11
|
+
entry_points={
|
|
12
|
+
"console_scripts": [
|
|
13
|
+
"flatback = flatback.__main__:main",
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
url="https://github.com/jfhack/flatback",
|
|
17
|
+
install_requires=[
|
|
18
|
+
"pyyaml",
|
|
19
|
+
],
|
|
20
|
+
long_description=long_description,
|
|
21
|
+
long_description_content_type="text/markdown",
|
|
22
|
+
)
|