mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-29 08:07:37 +09:00
An extension with something worth backing up had to hand-roll it — no policy, no cron mirror, no run history, no retention, no offsite copy, no verification, no restore, and no place in the Protection panel. A registered kind inherits every one of those, because everything downstream of the target_type switches is already generic: the run lifecycle, retention (count, age, incremental-chain ancestry), the API blueprint, and the panel itself, which threads target_type through without ever switching on its value. No migration: target_type is an unconstrained String, and the allowed set lived purely in a Python tuple. A kind supplies resolve() and execute(), plus an optional restore(). Four switches had to learn about the registry, and the order matters in three of them: - validate_target_type is the keystone — every policy route funnels through it, so accepting a registered kind opens the whole blueprint at once. - _resolve_target and _execute_backup check the registry BEFORE their core branches, because both end in an application fallthrough that swallows any unrecognised type. - run_restore_job likewise. This is the sharp one: without it a plugin's archive would be unpacked over an app directory by core's else-branch. A kind that didn't implement restore is refused at request time with a reason, rather than queueing a job that takes the policy lock and runs a safety backup before failing. The registry normalises what plugins hand back — target_type is forced rather than defaulted, so a kind cannot describe itself as an 'application' and be restored as one; execute() must return (path, size, meta) with a real path; and the metadata retention and the panel read is filled in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
66 lines
2.5 KiB
Python
66 lines
2.5 KiB
Python
"""Plugin-facing SDK for backups.
|
|
|
|
from app.plugins_sdk import backups
|
|
|
|
def resolve(policy):
|
|
world = World.query.get(policy.target_id)
|
|
if not world:
|
|
raise ValueError('World not found')
|
|
return {'name': world.name, 'root_path': world.directory, 'world': world}
|
|
|
|
def execute(policy, target, kind):
|
|
path = tar_up(target['root_path'])
|
|
return path, os.path.getsize(path), {'world': target['name']}
|
|
|
|
def restore(policy, target, run, options):
|
|
untar(run.storage_path, target['root_path'])
|
|
|
|
backups.register('minecraft.world', resolve=resolve, execute=execute,
|
|
restore=restore)
|
|
|
|
Registering a kind means never writing backup plumbing again: the policy row
|
|
and its cron schedule, run history, retention (count and age, aware of
|
|
incremental chains), offsite copy, verification, restore, and the Protection
|
|
panel — which is already generic over the target type — all come from the
|
|
panel. Create a policy the way core does::
|
|
|
|
from app.services.backup_policy_service import BackupPolicyService
|
|
BackupPolicyService.get_or_create_policy('minecraft.world', world.id)
|
|
|
|
Then the operator schedules, runs, prunes and restores it from the UI.
|
|
|
|
``restore`` is optional but strongly encouraged. Without it the panel refuses
|
|
the restore up front and says so, rather than queueing a job that fails — and
|
|
critically, rather than falling through to core's application restore, which
|
|
would unpack your archive over an app directory.
|
|
"""
|
|
|
|
from app.services import backup_kind_registry
|
|
|
|
|
|
class BackupsSdk:
|
|
"""Stable backup surface for plugins."""
|
|
|
|
def register(self, target_type, resolve, execute, restore=None, replace=False):
|
|
"""Register a backup target type.
|
|
|
|
``resolve(policy) -> dict`` describes the live target (include
|
|
``name``, and ``root_path`` if it is a directory); raise if it is gone.
|
|
``execute(policy, target, kind) -> (storage_path, size_bytes, meta)``
|
|
produces the artifact. ``restore(policy, target, run, options)`` puts
|
|
one back.
|
|
|
|
Namespace the type after your plugin (``minecraft.world``); core's bare
|
|
words are reserved.
|
|
"""
|
|
return backup_kind_registry.register(
|
|
target_type, resolve=resolve, execute=execute, restore=restore,
|
|
replace=replace)
|
|
|
|
def kinds(self):
|
|
"""Backup target types registered by plugins."""
|
|
return backup_kind_registry.kinds()
|
|
|
|
|
|
backups = BackupsSdk()
|