mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-23 06:26:51 +00:00
load the database file when encryption is off (#1137)
This commit is contained in:
@@ -156,8 +156,34 @@ async function initializeDatabaseAsync(): Promise<void> {
|
||||
}
|
||||
} else {
|
||||
assertDataDirIsNotMisconfigured(dataDir);
|
||||
memoryDatabase = new Database(":memory:");
|
||||
isNewDatabase = true;
|
||||
|
||||
// The database still lives in memory and is serialised out on every write;
|
||||
// turning encryption off only changes whether that file is ciphertext. It
|
||||
// has to be read back, or each restart starts empty and silently discards
|
||||
// everything the previous run saved.
|
||||
const existing = readPlainDatabaseFile();
|
||||
if (existing) {
|
||||
memoryDatabase = new Database(existing);
|
||||
databaseLogger.info("Loaded unencrypted database from disk", {
|
||||
operation: "db_load_plain",
|
||||
path: dbPath,
|
||||
bytes: existing.length,
|
||||
});
|
||||
} else {
|
||||
memoryDatabase = new Database(":memory:");
|
||||
isNewDatabase = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The plain database file, or null when there is nothing to restore. */
|
||||
function readPlainDatabaseFile(): Buffer | null {
|
||||
try {
|
||||
const contents = fs.readFileSync(dbPath);
|
||||
return contents.length > 0 ? contents : null;
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import Database from "better-sqlite3";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
/**
|
||||
* `DB_FILE_ENCRYPTION=false` used to mean "start empty, every time".
|
||||
*
|
||||
* The database lives in memory on every backend and is serialised to disk after
|
||||
* writes; the flag only decides whether that file is ciphertext. The plain
|
||||
* branch wrote `db.sqlite` faithfully and then never read it back, so each
|
||||
* restart began with an empty database and silently discarded everything the
|
||||
* previous run had saved. The data-dir guard made it worse by confirming a
|
||||
* database was present in DATA_DIR immediately before it was thrown away.
|
||||
*/
|
||||
describe("unencrypted database persistence", () => {
|
||||
let dataDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "termix-plain-db-"));
|
||||
vi.resetModules();
|
||||
process.env.DATA_DIR = dataDir;
|
||||
process.env.DB_FILE_ENCRYPTION = "false";
|
||||
process.env.ALLOW_EMPTY_DATA_DIR = "true";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.DATA_DIR;
|
||||
delete process.env.DB_FILE_ENCRYPTION;
|
||||
delete process.env.ALLOW_EMPTY_DATA_DIR;
|
||||
fs.rmSync(dataDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
/** A database file with one row, as a previous run would have left it. */
|
||||
function writeExistingDatabase(): void {
|
||||
const seed = new Database(":memory:");
|
||||
seed.exec("CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT)");
|
||||
seed
|
||||
.prepare("INSERT INTO settings (key, value) VALUES (?, ?)")
|
||||
.run("survives_restart", "yes");
|
||||
fs.writeFileSync(path.join(dataDir, "db.sqlite"), seed.serialize());
|
||||
seed.close();
|
||||
}
|
||||
|
||||
it("reads back what an earlier run wrote", async () => {
|
||||
writeExistingDatabase();
|
||||
|
||||
const db = await import("../../../database/db/index.js");
|
||||
await db.initializeDatabase();
|
||||
|
||||
const row = db
|
||||
.getSqlite()
|
||||
.prepare("SELECT value FROM settings WHERE key = ?")
|
||||
.get("survives_restart") as { value: string } | undefined;
|
||||
|
||||
expect(row?.value).toBe("yes");
|
||||
});
|
||||
|
||||
it("starts empty when there is no file yet", async () => {
|
||||
const db = await import("../../../database/db/index.js");
|
||||
await db.initializeDatabase();
|
||||
|
||||
// Startup creates its own tables; the point is that it does not throw on a
|
||||
// missing file and does not carry rows over from nowhere.
|
||||
const row = db
|
||||
.getSqlite()
|
||||
.prepare("SELECT COUNT(*) AS count FROM users")
|
||||
.get() as { count: number };
|
||||
|
||||
expect(row.count).toBe(0);
|
||||
});
|
||||
|
||||
it("ignores a zero-length file rather than failing to open it", async () => {
|
||||
fs.writeFileSync(path.join(dataDir, "db.sqlite"), "");
|
||||
|
||||
const db = await import("../../../database/db/index.js");
|
||||
await expect(db.initializeDatabase()).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user