[fix] Backup timer

This commit is contained in:
ljf 2022-10-18 00:53:13 +02:00
parent 97ce3f188b
commit ad5c0f0cc6
No known key found for this signature in database
3 changed files with 171 additions and 43 deletions

View file

@ -1264,6 +1264,19 @@ backup:
--purge:
help: Remove all archives and data inside repository
action: store_true
### backup_repository_prune()
prune:
action_help: Prune archives in a backup repository
api: POST /backups/repository/<shortname>/prune
arguments:
shortname:
help: Name of the backup repository to prune
extra:
pattern: *pattern_backup_repository_shortname
--prefix:
help: Prefix on which we prune
nargs: "?"
timer:
subcategory_help: Manage backup timer
actions:
@ -1272,6 +1285,10 @@ backup:
list:
action_help: List backup timer
api: GET /backup/timer
arguments:
--full:
help: Show more details
action: store_true
### backup_timer_add()
add:
@ -1365,6 +1382,16 @@ backup:
extra:
pattern: *pattern_backup_timer_name
### backup_timer_run()
run:
action_help: Run a backup timer
api: POST /backup/timer/<name>/run
arguments:
name:
help: Backup timer to run
extra:
pattern: *pattern_backup_timer_name
#############################
# Settings #
#############################

View file

@ -1954,7 +1954,7 @@ def backup_repository_add(operation_logger, shortname, name=None, location=None,
return repository.set(
operation_logger=args.pop('operation_logger'),
args=urllib.parse.urlencode(args)
args=urllib.parse.urlencode(args, doseq=True)
)
@ -1977,6 +1977,20 @@ def backup_repository_remove(operation_logger, shortname, purge=False):
BackupRepository(shortname).remove(purge)
@is_unit_operation()
def backup_repository_prune(operation_logger, shortname, prefix=None, keep_hourly=0, keep_daily=10, keep_weekly=8, keep_monthly=8):
"""
Remove a backup repository
"""
BackupRepository(shortname).prune(
prefix=prefix,
keep_hourly=keep_hourly,
keep_daily=keep_daily,
keep_weekly=keep_weekly,
keep_monthly=keep_monthly,
)
#
# Timer subcategory
#
@ -1986,45 +2000,15 @@ class BackupTimer(ConfigPanel):
BackupRepository manage all repository the admin added to the instance
"""
entity_type = "backup_timer"
save_path_tpl = "/etc/systemd/system/backup_timer_{entity}.timer"
timer_name_tpl = "backup_{entity}"
save_path_tpl = "/etc/yunohost/backup/timer/{entity}.yml"
timer_path_tpl = "/etc/systemd/system/{timer_name}.timer"
service_path_tpl = "/etc/systemd/system/{timer_name}.service"
save_mode = "full"
@property
def service_path(self):
return self.save_path[:-len(".timer")] + ".service"
# TODO prefill apps and system question with good values
# TODO validate calendar entry
def _load_current_values(self):
# Inject defaults if needed (using the magic .update() ;))
self.values = self._get_default_values()
if os.path.exists(self.save_path) and os.path.isfile(self.save_path):
raise NotImplementedError() # TODO
if os.path.exists(self.service_path) and os.path.isfile(self.service_path):
raise NotImplementedError() # TODO
def _apply(self):
write_to_file(self.save_path, f"""[Unit]
Description=Run backup {self.entity} regularly
[Timer]
OnCalendar={self.values['schedule']}
[Install]
WantedBy=timers.target
""")
# TODO --system and --apps params
# TODO prune params
write_to_file(self.service_path, f"""[Unit]
Description=Run backup {self.entity}
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/yunohost backup create -n '{self.entity}' -r '{self.repositories}' --system --apps ; /usr/bin/yunohost backup prune -n '{self.entity}'
User=root
Group=root
""")
@classmethod
def list(cls, full=False):
"""
@ -2038,12 +2022,121 @@ Group=root
full_timers = {}
for timer in timers:
try:
full_timers.update(BackupTimer(timer).info())
full_timers[timer] = BackupTimer(timer).info()
except Exception as e:
logger.error(f"Unable to open timer {timer}: {e}")
return full_timers
@property
def timer_name(self):
return self.timer_name_tpl.format(entity=self.entity)
@property
def service_path(self):
return self.service_path_tpl.format(timer_name=self.timer_name)
@property
def timer_path(self):
return self.timer_path_tpl.format(timer_name=self.timer_name)
def _reload_systemd(self):
try:
check_output("systemctl daemon-reload")
except Exception as e:
logger.warning(f"Failed to reload daemon : {e}")
def _run_service_command(self, action, *args):
# TODO improve services to support timers
# See https://github.com/YunoHost/issues/issues/1519
try:
check_output(f"systemctl {action} {self.timer_name}.timer")
except Exception as e:
logger.warning(f"Failed to {action} {self.timer_name}.timer : {e}")
def _load_current_values(self):
super()._load_current_values()
# Search OnCalendar schedule property
if os.path.exists(self.timer_path) and os.path.isfile(self.timer_path):
with open(self.timer_path, 'r') as f:
for index, line in enumerate(f):
if line.startswith("OnCalendar="):
self.values["schedule"] = line[11:].strip()
break
else:
logger.debug(f"No OnCalendar property found in {self.timer_path}")
def _apply(self):
super()._apply()
# TODO Add RandomizedDelaySec for daily and other special event
write_to_file(self.timer_path, f"""[Unit]
Description=Run backup {self.entity} regularly
[Timer]
OnCalendar={self.values['schedule']}
[Install]
WantedBy=timers.target
""")
write_to_file(self.service_path, f"""[Unit]
Description=Run backup {self.entity}
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/yunohost backup timer run '{self.entity}'
User=root
Group=root
""")
self._reload_systemd()
self._run_service_command("reset-failed")
self.start()
def info(self):
return self.get(mode="export")
def remove(self):
self.stop()
rm(self.save_path, force=True)
rm(self.service_path, force=True)
rm(self.timer_path, force=True)
self._reload_systemd()
self._run_service_command("reset-failed")
logger.success(m18n.n("backup_timer_removed", timer=self.entity))
def start(self):
self._run_service_command("enable")
self._run_service_command("start")
def stop(self):
self._run_service_command("stop")
self._run_service_command("disable")
def run(self, operation_logger):
self._load_current_values()
backup_create(
operation_logger,
name=self.entity,
description=self.description,
repositories=self.repositories,
system=self.system,
apps=self.apps
)
for repository in self.repositories:
backup_repository_prune(
operation_logger,
shortname=repository,
prefix=self.entity,
keep_hourly=self.keep_hourly,
keep_daily=self.keep_daily,
keep_weekly=self.keep_weekly,
keep_monthly=self.keep_monthly,
)
def backup_timer_list(full=False):
"""
@ -2053,7 +2146,7 @@ def backup_timer_list(full=False):
def backup_timer_info(name):
return BackupTimer(name).get()
return BackupTimer(name).info()
@is_unit_operation()
@ -2079,7 +2172,7 @@ def backup_timer_add(
timer = BackupTimer(name, creation=True)
return timer.set(
operation_logger=args.pop('operation_logger'),
args=urllib.parse.urlencode(args)
args=urllib.parse.urlencode(args, doseq=True)
)
@ -2095,11 +2188,19 @@ def backup_timer_update(operation_logger, shortname, name=None,
@is_unit_operation()
def backup_timer_remove(operation_logger, shortname, purge=False):
def backup_timer_remove(operation_logger, name):
"""
Remove a backup timer
"""
BackupTimer(shortname).remove(purge)
BackupTimer(name).remove()
@is_unit_operation()
def backup_timer_run(operation_logger, name):
"""
Run a backup timer
"""
BackupTimer(name).run(operation_logger)
#

View file

@ -46,7 +46,7 @@ from yunohost.utils.system import disk_usage, binary_to_human
from yunohost.utils.network import get_ssh_public_key, SHF_BASE_URL
logger = getActionLogger('yunohost.repository')
REPOSITORIES_DIR = '/etc/yunohost/repositories'
REPOSITORIES_DIR = '/etc/yunohost/backup/repositories'
CACHE_INFO_DIR = "/var/cache/yunohost/repositories/{repository}"
REPOSITORY_CONFIG_PATH = "/usr/share/yunohost/other/config_repository.toml"
MB_ALLOWED_TO_ORGANIZE = 10