A restore on the live pasture: its own scenario, and two fixes it found
tools/pasture/scenarios/restore.sh backs the pasture up from the administrator's endpoint, then alice_restore deletes a post and makes another, mastouser follows her, she blocks bob_restore and carol_restore is made; the restore, asked for from the endpoint, keeps every protective act (19 checks). town.sh renew <peer> signs a peer's town accounts in again, since a restore ends every session. It found that a backup listed media files already missing when it was made, so verifying it failed and the restore was refused: a manifest now lists only the files it holds (refusals are cut to five lines). And a local post made after the backup now comes back as a deleted row, as a deletion leaves it, so it answers 410 and its id is never given again; DeletedObject holds remote tombstones only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
9ad96f560d
commit
bdc8be4508
8 files changed
+133
-26
No files matched your search
@@ -148,11 +148,12 @@ namespace PrivaPub.Infrastructure.Backup
|
||||
Builders<BsonDocument>.Update.Set("ConsecutiveFailures", 0).Set("UnavailableUntil", BsonNull.Value), cancellationToken: token);
|
||||
}
|
||||
|
||||
// posts deleted since: deleted again, out of timelines and pins; local posts made since: 410
|
||||
// posts deleted since: deleted again, out of timelines and pins; local posts made since: there as deleted, as a
|
||||
// deletion leaves them, so they answer 410 and their ids and addresses are never given again
|
||||
static async Task Posts(IMongoDatabase database, IEnumerable<BsonDocument> previous, RestoreReport report, CancellationToken token)
|
||||
{
|
||||
var posts = database.GetCollection<BsonDocument>("Post");
|
||||
var gone = database.GetCollection<BsonDocument>("DeletedObject");
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var batch in previous.Where(p => IsSet(p, "DeletedAt") || IsLocal(p)).Chunk(500))
|
||||
{
|
||||
var ids = new BsonArray(batch.Select(p => p["_id"]));
|
||||
@@ -162,12 +163,18 @@ namespace PrivaPub.Infrastructure.Backup
|
||||
{
|
||||
if (!restored.TryGetValue(post["_id"], out var mine))
|
||||
{
|
||||
if (IsLocal(post) && post.GetValue("ObjectURI", BsonNull.Value) is BsonString uri)
|
||||
if (!IsLocal(post))
|
||||
continue;
|
||||
if (!IsSet(post, "DeletedAt"))
|
||||
{
|
||||
await gone.UpdateOneAsync(new BsonDocument("ObjectURI", uri), Builders<BsonDocument>.Update
|
||||
.SetOnInsert("ObjectURI", uri).SetOnInsert("DeletedAt", DateTime.UtcNow), new UpdateOptions { IsUpsert = true }, token);
|
||||
report.PostsGone++;
|
||||
post["DeletedAt"] = now;
|
||||
foreach (var field in new[] { "Text", "ContentHtml", "Title", "SpoilerText" })
|
||||
post[field] = BsonNull.Value;
|
||||
post["Media"] = new BsonArray();
|
||||
post["Revisions"] = new BsonArray();
|
||||
}
|
||||
await posts.InsertOneAsync(post, cancellationToken: token);
|
||||
report.PostsGone++;
|
||||
continue;
|
||||
}
|
||||
if (!IsSet(post, "DeletedAt") || IsSet(mine, "DeletedAt"))
|
||||
|
||||
@@ -111,20 +111,21 @@ namespace PrivaPub.Infrastructure.Backup
|
||||
}
|
||||
manifest.Consistent = replica;
|
||||
|
||||
manifest.Media.List = [.. media];
|
||||
if (!dbOnly)
|
||||
foreach (var relative in media)
|
||||
// the files it holds (or, db-only, lists); a row whose file was already gone is only counted
|
||||
foreach (var relative in media)
|
||||
{
|
||||
var source = new[] { context.MediaRoot, context.TrashRoot }.Select(root => Inside(root, relative)).FirstOrDefault(File.Exists);
|
||||
if (source == default)
|
||||
{
|
||||
var source = new[] { context.MediaRoot, context.TrashRoot }.Select(root => Inside(root, relative)).FirstOrDefault(File.Exists);
|
||||
if (source == default)
|
||||
{
|
||||
manifest.Media.Missing++;
|
||||
continue;
|
||||
}
|
||||
HardLink.LinkOrCopy(source, Inside(Path.Combine(partial, "media"), relative));
|
||||
manifest.Media.Files++;
|
||||
manifest.Media.Bytes += new FileInfo(source).Length;
|
||||
manifest.Media.Missing++;
|
||||
continue;
|
||||
}
|
||||
if (!dbOnly)
|
||||
HardLink.LinkOrCopy(source, Inside(Path.Combine(partial, "media"), relative));
|
||||
manifest.Media.List.Add(relative);
|
||||
manifest.Media.Files++;
|
||||
manifest.Media.Bytes += new FileInfo(source).Length;
|
||||
}
|
||||
|
||||
manifest.Write(partial);
|
||||
Directory.Move(partial, Path.Combine(context.BackupsRoot, id));
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace PrivaPub.Infrastructure.Backup
|
||||
{
|
||||
var problems = await Check(context, id, token);
|
||||
if (problems.Count > 0)
|
||||
return string.Join("; ", problems);
|
||||
return string.Join("; ", problems.Take(5)) + (problems.Count > 5 ? $"; and {problems.Count - 5} more" : string.Empty);
|
||||
var waiting = RestoreMarker.Read(context.BackupsRoot);
|
||||
if (waiting != default && waiting.Attempts < RestoreMarker.MaxAttempts)
|
||||
return $"the restore of {waiting.Backup} is already {waiting.State}";
|
||||
|
||||
Reference in new issue
Block a user