nextcloud-fix-encryption-co.../fix-conflicts.ts

154 lines
5.8 KiB
TypeScript
Raw Normal View History

2023-01-25 22:36:55 +00:00
import { join } from "https://deno.land/std@0.173.0/path/mod.ts";
2023-01-25 20:49:22 +00:00
(async () => {
2023-01-25 22:36:55 +00:00
const mainDir = '/home/ben/Nextcloud';
const backupDir = '/home/ben/Nextcloud.bak';
2023-01-25 20:49:22 +00:00
const DRY_RUN = Deno.env.get('MOIST_RUN') === '1' ? false : true;
2023-01-25 20:49:22 +00:00
const BATCH_SIZE = 16;
const ENCRYPTED_CONTENT_STRING = 'HBEGIN:oc_encryption_module:OC_DEFAULT_MODULE:cipher';
const CONFLICTED_COPY_REGEX = /.*( \(conflicted copy \d{4}-\d{2}-\d{2} \d+\))(\.[^\.]*)?/;
const p = Deno.run({
2023-01-25 22:36:55 +00:00
cmd: ['bash', '-c', `find . -type f | grep conflicted\\ copy`],
2023-01-25 20:49:22 +00:00
stdout: 'piped',
2023-01-25 22:36:55 +00:00
cwd: backupDir
2023-01-25 20:49:22 +00:00
});
const output = await p.output();
p.close();
const outputStr = (new TextDecoder()).decode(output);
2023-01-25 22:40:57 +00:00
const fileNames = [...new Set(outputStr.split('\n').filter(s => !!s))];
2023-01-25 20:49:22 +00:00
const totalFiles = fileNames.length;
let filesSuccess = 0;
2023-01-25 22:36:55 +00:00
let fileData: string[] = [];
2023-01-25 20:49:22 +00:00
for (let i = 0; i < totalFiles; i += BATCH_SIZE) {
fileData = [
...fileData,
2023-01-25 22:40:57 +00:00
...(await Promise.all(fileNames.slice(i, i + BATCH_SIZE).map(async (relativeConflictedFileName) => {
const conflictedFileName = join(backupDir, relativeConflictedFileName);
2023-01-25 20:49:22 +00:00
const conflictedNameMatch = conflictedFileName.match(CONFLICTED_COPY_REGEX);
if (!conflictedNameMatch) {
return `[${conflictedFileName}] encountered regex error`;
}
const conflictString = conflictedNameMatch[1];
2023-01-25 22:40:57 +00:00
const nonConflictedFileName = join(mainDir, relativeConflictedFileName.replace(conflictString, ''));
2023-01-25 20:49:22 +00:00
const conflictedFile = await Deno.open(conflictedFileName, { read: true });
const conflictedFileInfo = await Deno.fstat(conflictedFile.rid);
const normalFile = await Deno.open(nonConflictedFileName, { read: true }).catch(() => null);
if (!normalFile) {
if (!DRY_RUN) {
// Only the conflicted file exists, just move it to the non-conflict filename
try {
await Deno.copyFile(conflictedFileName, nonConflictedFileName);
} catch (err) {
console.error(err);
}
2023-01-25 20:49:22 +00:00
}
return `[${conflictedFileName}] only conflicted exists. Copy to non-conflict filename`;
2023-01-25 20:49:22 +00:00
}
const normalFileInfo = await Deno.fstat(normalFile.rid);
conflictedFile.close();
normalFile.close();
const shaP = Deno.run({
cmd: ['sha256sum', nonConflictedFileName, conflictedFileName],
stdout: 'piped',
});
const shaOutputStr = (new TextDecoder()).decode(await shaP.output());
const shaOutputs = shaOutputStr.split('\n');
const normalSha = shaOutputs[0].split(' ')[0];
const conflictedSha = shaOutputs[1].split(' ')[0];
shaP.close();
if (normalSha === conflictedSha) {
if (!DRY_RUN) {
// They're the same, remove the conflicted file
// await Deno.remove(conflictedFileName);
2023-01-25 20:49:22 +00:00
}
return `[${conflictedFileName}] conflict and non-conflict have the same contents, do nothing`;
2023-01-25 20:49:22 +00:00
}
const headP = Deno.run({
cmd: ['head', '-c', '64', nonConflictedFileName, conflictedFileName],
stdout: 'piped',
});
const headOutputStr = (new TextDecoder()).decode(await headP.output());
const headOutputs = headOutputStr.split('\n');
const normalHead = headOutputs[1].split(' ')[0];
const conflictedHead = headOutputs[3].split(' ')[0];
headP.close();
const normalEncrypted = normalHead.startsWith(ENCRYPTED_CONTENT_STRING);
const conflictedEncrypted = conflictedHead.startsWith(ENCRYPTED_CONTENT_STRING);
if (normalEncrypted && conflictedEncrypted) {
console.error("Both files encrypted differently!");
console.error(nonConflictedFileName, conflictedFileName);
if (!DRY_RUN) {
// Whatever, deleted the conflicted one
// await Deno.remove(conflictedFileName);
2023-01-25 20:49:22 +00:00
}
return `[${conflictedFileName}] both are encrypted but with different contents. Do nothing`;
2023-01-25 20:49:22 +00:00
}
if (normalEncrypted) {
if (!DRY_RUN) {
const ddP = Deno.run({
cmd: ['dd', `if=${conflictedFileName}`, `of=${nonConflictedFileName}`],
});
const status = await ddP.status();
if (status.code !== 0) {
console.error(`Could not replace contents of file! ${conflictedFileName} > ${nonConflictedFileName}`);
}
ddP.close();
2023-01-25 20:49:22 +00:00
}
return `[${conflictedFileName}] the non-conflict is encrypted. Replace its contents with the non-encrypted stuff`;
2023-01-25 20:49:22 +00:00
}
if (conflictedEncrypted) {
if (!DRY_RUN) {
// delete conflicted
// await Deno.remove(conflictedFileName);
2023-01-25 20:49:22 +00:00
}
return `[${conflictedFileName}] the conflict is encrypted. Do nothing`;
2023-01-25 20:49:22 +00:00
}
if (normalFileInfo.mtime > conflictedFileInfo.mtime) {
if (!DRY_RUN) {
// delete conflicted
// await Deno.remove(conflictedFileName);
2023-01-25 20:49:22 +00:00
}
return `[${conflictedFileName}] conflict is older than the non-conflict. Do nothing`;
2023-01-25 20:49:22 +00:00
}
if (!DRY_RUN) {
const ddP = Deno.run({
cmd: ['dd', `if=${conflictedFileName}`, `of=${nonConflictedFileName}`],
});
const status = await ddP.status();
if (status.code !== 0) {
console.error(`Could not replace contents of file! ${conflictedFileName} > ${nonConflictedFileName}`);
}
ddP.close();
2023-01-25 20:49:22 +00:00
}
return `[${conflictedFileName}] non-conflict is older than the conflict. Replace its contents with the newer stuff`;
2023-01-25 20:49:22 +00:00
}).map(p => p.then((msg) => {
filesSuccess++;
console.log(`[${filesSuccess}/${totalFiles}] ${msg}`);
return msg;
})))),
];
}
// console.log(fileData);
})();