Restore from Gitea ZIP snapshot (12.08.2026) after full instance reinstall

Git history was lost when the previous Gitea instance was wiped and
reinstalled due to an unresolved corruption bug — this commit is the
last known-good file content, exported before the reinstall. Prior
commit history is not recoverable through this path.
This commit is contained in:
Claude Sonnet 5
2026-08-12 19:24:01 +00:00
commit f386f74036
64 changed files with 13447 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
/**
* Rotates the AES-256-GCM master key used to encrypt stored mailbox
* credentials. Decrypts every stored IMAP/SMTP/CalDAV password under the
* old key and re-encrypts under a newly generated one, in a single
* transaction, then prints the new key so it can be written into
* /etc/ai-chief-of-staff/secrets.env.
*
* Usage: OLD_KEY=<base64> npx tsx scripts/rotate-credential-key.ts
* (reads the current key from CREDENTIALS_ENCRYPTION_KEY if OLD_KEY is unset)
*/
import crypto from "node:crypto";
import Database from "better-sqlite3";
import path from "node:path";
import { rotateCredential } from "../src/lib/crypto/credentials";
const dataDir = process.env.DATA_DIR ?? path.join(process.cwd(), "data");
const oldKeyB64 = process.env.OLD_KEY ?? process.env.CREDENTIALS_ENCRYPTION_KEY;
if (!oldKeyB64) {
console.error("Set OLD_KEY (or CREDENTIALS_ENCRYPTION_KEY) to the current base64-encoded key before running.");
process.exit(1);
}
const oldKey = Buffer.from(oldKeyB64, "base64");
if (oldKey.length !== 32) {
console.error("OLD_KEY must decode to exactly 32 bytes.");
process.exit(1);
}
const newKey = crypto.randomBytes(32);
const sqlite = new Database(path.join(dataDir, "db.sqlite"));
const rows = sqlite
.prepare(
"SELECT id, imap_password_enc, smtp_password_enc, caldav_password_enc FROM mailbox_credentials",
)
.all() as Array<{
id: string;
imap_password_enc: string | null;
smtp_password_enc: string | null;
caldav_password_enc: string | null;
}>;
const update = sqlite.prepare(
"UPDATE mailbox_credentials SET imap_password_enc = ?, smtp_password_enc = ?, caldav_password_enc = ? WHERE id = ?",
);
const rotateAll = sqlite.transaction(() => {
for (const row of rows) {
const imap = row.imap_password_enc ? rotateCredential(row.imap_password_enc, oldKey, newKey) : null;
const smtp = row.smtp_password_enc ? rotateCredential(row.smtp_password_enc, oldKey, newKey) : null;
const caldav = row.caldav_password_enc ? rotateCredential(row.caldav_password_enc, oldKey, newKey) : null;
update.run(imap, smtp, caldav, row.id);
}
});
rotateAll();
console.log(`Rotated ${rows.length} mailbox_credentials row(s).`);
console.log("\nNew key (update CREDENTIALS_ENCRYPTION_KEY in /etc/ai-chief-of-staff/secrets.env, then restart the service):\n");
console.log(newKey.toString("base64"));