dar-backup 0.6.17__py3-none-any.whl → 0.6.19__py3-none-any.whl
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.
- dar_backup/Changelog.md +232 -0
- dar_backup/README.md +1117 -0
- dar_backup/__about__.py +1 -1
- dar_backup/clean_log.py +14 -7
- dar_backup/cleanup.py +41 -43
- dar_backup/command_runner.py +59 -9
- dar_backup/dar_backup.py +156 -41
- dar_backup/dar_backup_systemd.py +119 -0
- dar_backup/installer.py +39 -23
- dar_backup/manager.py +210 -89
- dar_backup/rich_progress.py +101 -0
- dar_backup/util.py +289 -46
- {dar_backup-0.6.17.dist-info → dar_backup-0.6.19.dist-info}/METADATA +212 -27
- dar_backup-0.6.19.dist-info/RECORD +21 -0
- {dar_backup-0.6.17.dist-info → dar_backup-0.6.19.dist-info}/entry_points.txt +1 -0
- dar_backup-0.6.17.dist-info/RECORD +0 -17
- {dar_backup-0.6.17.dist-info → dar_backup-0.6.19.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.17.dist-info → dar_backup-0.6.19.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import subprocess
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
SERVICE_TEMPLATE = """[Unit]
|
|
6
|
+
Description=dar-backup {mode}
|
|
7
|
+
StartLimitIntervalSec=120
|
|
8
|
+
StartLimitBurst=1
|
|
9
|
+
|
|
10
|
+
[Service]
|
|
11
|
+
Type=oneshot
|
|
12
|
+
TimeoutSec=infinity
|
|
13
|
+
RemainAfterExit=no
|
|
14
|
+
ExecStart=/bin/bash -c '{exec_command}'
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
TIMER_TEMPLATE = """[Unit]
|
|
18
|
+
Description=dar-backup {mode} timer
|
|
19
|
+
|
|
20
|
+
[Timer]
|
|
21
|
+
OnCalendar={calendar}
|
|
22
|
+
Persistent=true
|
|
23
|
+
|
|
24
|
+
[Install]
|
|
25
|
+
WantedBy=timers.target
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
CLEANUP_SERVICE_TEMPLATE = """[Unit]
|
|
29
|
+
Description=cleanup up old DIFF & INCR backups
|
|
30
|
+
StartLimitIntervalSec=120
|
|
31
|
+
StartLimitBurst=1
|
|
32
|
+
|
|
33
|
+
[Service]
|
|
34
|
+
Type=oneshot
|
|
35
|
+
TimeoutSec=60
|
|
36
|
+
RemainAfterExit=no
|
|
37
|
+
ExecStart=/bin/bash -c '{exec_command}'
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
CLEANUP_TIMER = """[Unit]
|
|
41
|
+
Description=dar-cleanup DIFF & INCR timer
|
|
42
|
+
|
|
43
|
+
[Timer]
|
|
44
|
+
OnCalendar=*-*-* 21:07:00
|
|
45
|
+
|
|
46
|
+
[Install]
|
|
47
|
+
WantedBy=timers.target
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
TIMINGS = {
|
|
51
|
+
"FULL": "*-12-30 10:03:00",
|
|
52
|
+
"DIFF": "*-*-01 19:03:00",
|
|
53
|
+
"INCR": "*-*-04/3 19:03:00"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
FLAGS = {
|
|
57
|
+
"FULL": "-F",
|
|
58
|
+
"DIFF": "-D",
|
|
59
|
+
"INCR": "-I"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def build_exec_command(venv, flag, dar_path=None, tool='dar-backup'):
|
|
63
|
+
if dar_path:
|
|
64
|
+
return f"PATH={dar_path}:$PATH && . {venv}/bin/activate && {tool} {flag} --verbose --log-stdout"
|
|
65
|
+
return f". {venv}/bin/activate && {tool} {flag} --verbose --log-stdout"
|
|
66
|
+
|
|
67
|
+
def generate_service(mode, venv, dar_path):
|
|
68
|
+
exec_command = build_exec_command(venv, FLAGS[mode], dar_path)
|
|
69
|
+
return SERVICE_TEMPLATE.format(mode=mode, exec_command=exec_command)
|
|
70
|
+
|
|
71
|
+
def generate_timer(mode):
|
|
72
|
+
return TIMER_TEMPLATE.format(mode=mode, calendar=TIMINGS[mode])
|
|
73
|
+
|
|
74
|
+
def generate_cleanup_service(venv, dar_path):
|
|
75
|
+
exec_command = build_exec_command(venv, "", dar_path, tool='cleanup').strip()
|
|
76
|
+
return CLEANUP_SERVICE_TEMPLATE.format(exec_command=exec_command)
|
|
77
|
+
|
|
78
|
+
def write_unit_file(path, filename, content):
|
|
79
|
+
file_path = path / filename
|
|
80
|
+
file_path.write_text(content)
|
|
81
|
+
print(f"Generated {filename}")
|
|
82
|
+
|
|
83
|
+
def enable_and_start_unit(unit_name):
|
|
84
|
+
subprocess.run(["systemctl", "--user", "enable", unit_name], check=False)
|
|
85
|
+
subprocess.run(["systemctl", "--user", "start", unit_name], check=False)
|
|
86
|
+
|
|
87
|
+
def write_unit_files(venv, dar_path, install=False):
|
|
88
|
+
output_path = Path.home() / ".config/systemd/user" if install else Path.cwd()
|
|
89
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
|
|
91
|
+
for mode in FLAGS:
|
|
92
|
+
service_name = f"dar-{mode.lower()}-backup.service"
|
|
93
|
+
timer_name = f"dar-{mode.lower()}-backup.timer"
|
|
94
|
+
write_unit_file(output_path, service_name, generate_service(mode, venv, dar_path))
|
|
95
|
+
write_unit_file(output_path, timer_name, generate_timer(mode))
|
|
96
|
+
print(f" → Fires on: {TIMINGS[mode]}")
|
|
97
|
+
|
|
98
|
+
write_unit_file(output_path, "dar-cleanup.service", generate_cleanup_service(venv, dar_path))
|
|
99
|
+
write_unit_file(output_path, "dar-cleanup.timer", CLEANUP_TIMER)
|
|
100
|
+
print(f" → Fires on: *-*-* 21:07:00")
|
|
101
|
+
|
|
102
|
+
if install:
|
|
103
|
+
for mode in FLAGS:
|
|
104
|
+
enable_and_start_unit(f"dar-{mode.lower()}-backup.timer")
|
|
105
|
+
enable_and_start_unit("dar-cleanup.timer")
|
|
106
|
+
subprocess.run(["systemctl", "--user", "daemon-reexec"], check=False)
|
|
107
|
+
subprocess.run(["systemctl", "--user", "daemon-reload"], check=False)
|
|
108
|
+
print("Systemd `dar-backup` units and timers installed and user daemon reloaded.")
|
|
109
|
+
|
|
110
|
+
def main():
|
|
111
|
+
parser = argparse.ArgumentParser(description="Generate systemd service and timer units for dar-backup.")
|
|
112
|
+
parser.add_argument("--venv", required=True, help="Path to the Python venv with dar-backup")
|
|
113
|
+
parser.add_argument("--dar-path", help="Optional path to dar binary's directory")
|
|
114
|
+
parser.add_argument("--install", action="store_true", help="Install the units to ~/.config/systemd/user")
|
|
115
|
+
args = parser.parse_args()
|
|
116
|
+
write_unit_files(args.venv, args.dar_path, install=args.install)
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
main()
|
dar_backup/installer.py
CHANGED
|
@@ -63,6 +63,7 @@ BACKUP_DEFINITION = '''
|
|
|
63
63
|
--cache-directory-tagging
|
|
64
64
|
'''
|
|
65
65
|
|
|
66
|
+
|
|
66
67
|
def main():
|
|
67
68
|
parser = argparse.ArgumentParser(
|
|
68
69
|
description="Set up `dar-backup` on your system.",
|
|
@@ -72,7 +73,6 @@ def main():
|
|
|
72
73
|
action="store_true",
|
|
73
74
|
help="Deploy a simple config file, use ~/dar-backup/ for log file, archives and restore tests."
|
|
74
75
|
)
|
|
75
|
-
|
|
76
76
|
parser.add_argument(
|
|
77
77
|
"-v", "--version",
|
|
78
78
|
action="version",
|
|
@@ -85,38 +85,54 @@ def main():
|
|
|
85
85
|
errors = []
|
|
86
86
|
if os.path.exists(CONFIG_DIR):
|
|
87
87
|
errors.append(f"Config directory '{CONFIG_DIR}' already exists.")
|
|
88
|
-
|
|
89
88
|
if os.path.exists(DAR_BACKUP_DIR):
|
|
90
89
|
errors.append(f"Directory '{DAR_BACKUP_DIR}' already exists.")
|
|
91
90
|
|
|
92
|
-
if
|
|
91
|
+
if errors:
|
|
93
92
|
for error in errors:
|
|
94
93
|
print(f"Error: {error}")
|
|
95
|
-
sys.exit(1)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
os.makedirs(DAR_BACKUP_DIR, exist_ok=False)
|
|
98
|
+
os.makedirs(os.path.join(DAR_BACKUP_DIR, "backups"), exist_ok=False)
|
|
99
|
+
os.makedirs(os.path.join(DAR_BACKUP_DIR, "restore"), exist_ok=False)
|
|
100
|
+
os.makedirs(CONFIG_DIR, exist_ok=False)
|
|
101
|
+
os.makedirs(os.path.join(CONFIG_DIR, "backup.d"), exist_ok=False)
|
|
102
|
+
print(f"Directories created: `{DAR_BACKUP_DIR}` and `{CONFIG_DIR}`")
|
|
103
|
+
|
|
104
|
+
script_dir = Path(__file__).parent
|
|
105
|
+
source_file = script_dir / "dar-backup.conf"
|
|
106
|
+
destination_file = Path(CONFIG_DIR) / "dar-backup.conf"
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
shutil.copy2(source_file, destination_file)
|
|
110
|
+
print(f"Config file deployed to {destination_file}")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f"Error: Could not copy config file: {e}")
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
backup_definition = BACKUP_DEFINITION.replace("@@HOME_DIR@@", os.path.expanduser("~"))
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
with open(os.path.join(CONFIG_DIR, "backup.d", "default"), "w") as f:
|
|
120
|
+
f.write(backup_definition)
|
|
121
|
+
print(f"Default backup definition file deployed to {os.path.join(CONFIG_DIR, 'backup.d', 'default')}")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f"Error: Could not write default backup definition: {e}")
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"Installation failed: {e}")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
115
129
|
print("1. Now run `manager --create` to create the catalog database.")
|
|
116
130
|
print("2. Then you can run `dar-backup --full-backup` to create a backup.")
|
|
117
131
|
print("3. List backups with `dar-backup --list`")
|
|
118
132
|
print("4. List contents of a backup with `dar-backup --list-contents <backup-name>`")
|
|
119
133
|
|
|
134
|
+
sys.exit(0)
|
|
135
|
+
|
|
120
136
|
|
|
121
137
|
if __name__ == "__main__":
|
|
122
138
|
main()
|