-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathabsync
More file actions
executable file
·270 lines (218 loc) · 8.82 KB
/
Copy pathabsync
File metadata and controls
executable file
·270 lines (218 loc) · 8.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#!/usr/bin/env python3
import click
import locale
import os
import subprocess
import sys
from typing import List
from abackup import appcron
from abackup.prepare.copy import copy_most_recent_backup_file
from abackup.sync import Config
from abackup.sync.examine import perform_examine
from abackup.sync.sync import perform_rsync, perform_auto_sync
from abackup.sync.updatecron import perform_update_cron
@click.group()
@click.pass_context
@click.option("--no-log", flag_value=True, help="Disable logging")
@click.option("--debug", flag_value=True, help="Enable debug-level of logging")
@click.option(
"--config",
type=click.Path(exists=True),
default=os.path.join(os.path.expanduser("~"), ".abackup/sync.yml"),
help="Path to global configuration file, defaults to ~/.abackup/sync.yml",
)
@click.option("--user", help="User to run cron commands for")
def cli(ctx, no_log: bool, debug: bool, config: str, user: str):
"""absync - a script for syncing data between hosts"""
config_path = config
config = Config(config_path, no_log, debug)
cron = appcron.AppCronTab("absync", user, config.log)
absync_options = []
if debug:
absync_options.append("--debug")
if no_log:
absync_options.append("--no-log")
if config_path != os.path.join(os.path.expanduser("~"), ".abackup/sync.yml"):
absync_options.extend(["--config", config_path])
if user:
absync_options.extend(["--user", user])
ctx.obj = {"config": config, "cron": cron, "log": config.log, "absync_options": " ".join(absync_options)}
locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
@cli.command("log")
@click.pass_context
def log_command(ctx):
"""Display the log on the terminal"""
subprocess.run(["less", "+G", ctx.obj["config"].log_path])
@cli.command("stored-path")
@click.pass_context
@click.argument("name")
def stored_path_command(ctx, name: str):
"""Get the path to the stored data for NAME"""
config = ctx.obj["config"]
log = ctx.obj["log"]
log.info("--- stored-path lookup for {}".format(name))
if name in config.stored_data:
path = config.stored_data[name].path
log.info("{}: {}".format(name, path))
print(path, end="")
log.info("--- end stored-path")
else:
log.error("{} not present in config!".format(name))
print("{} not present in config!".format(name), file=sys.stderr)
log.info("--- end stored-path")
exit(1)
@cli.command("owned-path")
@click.pass_context
@click.argument("name")
def owned_path_command(ctx, name: str):
"""Get the path to the owned data for NAME"""
config = ctx.obj["config"]
log = ctx.obj["log"]
log.info("--- owned-path lookup for {}".format(name))
if name in config.owned_data:
path = config.owned_data[name].path
log.info("{}: {}".format(name, path))
print(path, end="")
log.info("--- end owned-path")
else:
log.error("{} not present in config!".format(name))
print("{} not present in config!".format(name), file=sys.stderr)
log.info("--- end owned-path")
exit(1)
@cli.command("examine")
@click.pass_context
@click.option(
"--data-dir", help="Data directory to examine. " "In not specified, all configured data directories are used."
)
def examine_command(ctx, data_dir: str):
"""Display sync information for configured data directories
This will print to the terminal: owned_data info, latest syncs, stored_data info, crontab jobs
"""
config = ctx.obj["config"]
cron = ctx.obj["cron"]
log = ctx.obj["log"]
perform_examine(config, cron, log)
@cli.command("update-cron")
@click.pass_context
def update_cron_command(ctx):
"""Update crontab with configured auto_sync settings
This will add or update the crontab entry with auto_sync settings for configured owned_data.
"""
config = ctx.obj["config"]
cron = ctx.obj["cron"]
log = ctx.obj["log"]
absync_options = ctx.obj["absync_options"]
log.info("--- Updating crontab")
log.info("{} data directory(s)".format(len(config.owned_data.keys())))
ret = perform_update_cron(config.owned_data, config.stored_data, absync_options, cron, log)
if ret:
log.info("--- Crontab updated.")
else:
log.critical("--- Crontab not updated!")
exit(1)
@cli.command("push")
@click.pass_context
@click.option("--delete", flag_value=True, help="Disable/enable deletion of data during sync.")
@click.option("--max-delete", type=int, help="Only allow deletion of this many files during sync.")
@click.option("--port", help="Port to use when connecting to remote.")
@click.argument("local-name")
@click.argument("destination")
def push_command(ctx, delete: bool, max_delete: int, port: int, local_name: str, destination: str):
"""Push LOCAL_NAME data to DESTINATION
This will copy data identified by LOCAL_NAME in the config file to DESTINATION.
LOCAL_NAME must be a a name of a configured owned_data path.
DESTINATION can be on a remote.
"""
config = ctx.obj["config"]
log = ctx.obj["log"]
log.info("--- push started")
ret = perform_rsync(config, local_name, destination, False, log, delete, max_delete, port)
if ret:
log.info("--- push finished.")
else:
log.critical("--- push failed!")
exit(1)
@cli.command("pull")
@click.pass_context
@click.option("--delete", flag_value=True, help="Disable/enable deletion of data during sync.")
@click.option("--max-delete", type=int, help="Only allow deletion of this many files during sync.")
@click.option("--port", help="Port to use when connecting to remote.")
@click.argument("stored-name")
@click.argument("source")
def pull_command(ctx, delete: bool, max_delete: int, port: int, stored_name: str, source: str):
"""Pull STORED_NAME data from SOURCE
This will copy data identified by STORED_NAME in the config file from SOURCE.
STORED_NAME must be a a name of a configured stored_data path.
SOURCE can be on a remote.
"""
config = ctx.obj["config"]
log = ctx.obj["log"]
log.info("--- pull started")
ret = perform_rsync(config, stored_name, source, True, log, delete, max_delete, port)
if ret:
log.info("--- pull finished.")
else:
log.critical("--- pull failed!")
exit(1)
@cli.command("auto")
@click.pass_context
@click.option("--data-name", help="owned_data or stored_data name to limit syncing.")
@click.option("--sync-name", help="Name of the auto sync (in auto_sync settings) to limit syncing.")
@click.option(
"--sync-type",
type=click.Choice(["manual", "auto"]),
default="manual",
help="Name for the type of sycning, used for logging. Defaults to 'manual'.",
)
@click.option(
"--notify",
type=click.Choice(["auto", "always", "never"]),
default="never",
help="Notification setting, auto: notify on failure, always: always notify, never: never notify. "
"Defaults to never.",
)
@click.option("--healthchecks", flag_value=True, help="Perform healthcheks if configured.")
def auto_command(ctx, data_name: str, sync_name: str, sync_type: str, notify: str, healthchecks: bool):
"""Run all the configured auto syncs.
This will run the sync for all owned_data and stored_data which has configured auto_sync settings.
"""
config = ctx.obj["config"]
log = ctx.obj["log"]
absync_options = ctx.obj["absync_options"]
log.info("--- auto sync started")
ret = perform_auto_sync(
config, absync_options, notify, log, data_name, sync_name, sync_type, do_healthchecks=healthchecks
)
if ret:
log.info("--- auto sync finished.")
else:
log.critical("--- auto sync failed!")
exit(1)
@cli.command("copy-most-recent")
@click.pass_context
@click.option("--overwrite", flag_value=True, help="Overwrite destinations if they exist.")
@click.argument("stored-name")
@click.argument("relative-path")
@click.argument("destinations", nargs=-1)
def copy_most_recent_command(ctx, overwrite: bool, stored_name: str, relative_path: bool, destinations: List[str]):
"""Copy most recent backup file of stored data.
stored-name: Name of the stored data path.
relative-path: Relative path to directory containing backup files.
"""
config = ctx.obj["config"]
log = ctx.obj["log"]
log.info("--- copy-most-recent {}:{}".format(stored_name, relative_path))
if stored_name in config.stored_data:
stored_path = config.stored_data[stored_name].path
ret = copy_most_recent_backup_file(os.path.join(stored_path, relative_path), destinations, log, overwrite)
if ret:
log.info("--- copy-most-recent finished.")
else:
log.critical("--- copy-most-recent failed!")
exit(1)
else:
log.error("{} not present in config!".format(stored_name))
log.critical("--- copy-most-recent failed!")
exit(1)
if __name__ == "__main__":
cli()