feat(backups): 主人和管理员可删除单个备份,归档由 reaper 下一轮删除、异地副本随下次同步删除,恢复可能在读时拒绝
This commit is contained in:
23 files changed
+969
-45
No files matched your search
@@ -3803,6 +3803,46 @@ paths:
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
/api/v1/backups/{id}:
|
||||
delete:
|
||||
tags: [backups]
|
||||
operationId: deleteBackup
|
||||
summary: Delete one world backup (admin, or the user who owned the world).
|
||||
description: >-
|
||||
The backup leaves every list, restore and the backup budget at once;
|
||||
the reaper's next daily run deletes the archive and the off-site copy's
|
||||
next sync removes the bucket's copy. A user gets 404 for a backup
|
||||
outside their scope, as their list never shows it. Refused while a
|
||||
restore on the backup's server may still read it.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
security: [{ sessionCookie: [] }]
|
||||
parameters:
|
||||
- { name: id, in: path, required: true, schema: { type: string } }
|
||||
responses:
|
||||
'200':
|
||||
description: The backup is deleted; its archive goes at the reaper's next run.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [id, status]
|
||||
properties:
|
||||
id: { type: string }
|
||||
status: { type: string, const: expired }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'404':
|
||||
description: No present backup with this id in the caller's scope (no_backup).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'409':
|
||||
description: A restore running on the backup's server may be reading it (restore_in_progress).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
/api/v1/servers/{name}/restore-backup:
|
||||
post:
|
||||
tags: [backups]
|
||||
|
||||
@@ -1082,6 +1082,32 @@ the error its container exited on under Recent operations on the server's
|
||||
backup page. [GO-TESTED: `TestBackupNow`, `TestCheckRoom`,
|
||||
`TestReaperConfigManualKeys`, `TestLatestJobsExplainsFailures`]
|
||||
|
||||
### Deleting one backup
|
||||
|
||||
The delete button on a backup row (`DELETE /api/v1/backups/{id}`) takes that backup
|
||||
out of every list, every restore and the `max_local_bytes` count at once: its row
|
||||
turns `expired`, with `expires_at` pulled back to the moment of the delete. An admin
|
||||
may delete any backup, a user only one of a world they owned (the scope that lists
|
||||
it); any other id is `404 no_backup`. While a restore on that server may be reading
|
||||
the archive (a restore Job still running, or a safety snapshot whose restore of this
|
||||
backup has yet to start) the delete is `409 restore_in_progress`. The audit action
|
||||
is `backup.delete`, with the backup id, former owner and size in its payload.
|
||||
|
||||
The archive stays on the backup volume until the reaper's next daily run, whose
|
||||
retention pass deletes every `expired` row's archive whatever its `expires_at`; the
|
||||
off-site copy goes at the sync after the delete. Until that run an admin can take
|
||||
the delete back, with the id from the audit entry; clearing `offsite_at` has the
|
||||
next sync copy it off site again in case its copy is already gone:
|
||||
|
||||
```sh
|
||||
sudo k3s kubectl -n felis exec deploy/felis-postgres -c postgres -- psql -U postgres felis -c \
|
||||
"UPDATE world_backups SET status = 'present', expires_at = now() + interval '30 days', offsite_at = NULL
|
||||
WHERE id = '<backup id>' AND status = 'expired'"
|
||||
```
|
||||
|
||||
[GO-TESTED: `TestDeleteBackup`, `TestExpiredBackupsDeleted`,
|
||||
`TestSyncExpiresOnlyPastRetention`; PG-TESTED: `TestOwnerDeletedBackup`]
|
||||
|
||||
### Every world at once: `felis backup-now`
|
||||
|
||||
A world lives only in its volume, and the off-site copy holds only its archives.
|
||||
|
||||
Reference in new issue
Block a user