fix: multiple bug fixes
All checks were successful
Queue Release Build / prepare (push) Successful in 15s
Deploy Web Apps / deploy (push) Successful in 6m54s
Queue Release Build / build-windows (push) Successful in 16m6s
Queue Release Build / build-linux (push) Successful in 30m58s
Queue Release Build / finalize (push) Successful in 44s
All checks were successful
Queue Release Build / prepare (push) Successful in 15s
Deploy Web Apps / deploy (push) Successful in 6m54s
Queue Release Build / build-windows (push) Successful in 16m6s
Queue Release Build / build-linux (push) Successful in 30m58s
Queue Release Build / finalize (push) Successful in 44s
isolated users, db backup, weird disconnect issues for long voice sessions,
This commit is contained in:
@@ -18,7 +18,9 @@ Node/TypeScript signaling server for MetoYou / Toju. This package owns the publi
|
||||
|
||||
- The server loads the repository-root `.env` file on startup.
|
||||
- `SSL` can override the effective HTTP protocol, and `PORT` can override the effective port.
|
||||
- `DB_PATH` can override the SQLite database file location.
|
||||
- `data/variables.json` is normalized on startup and stores `klipyApiKey`, `releaseManifestUrl`, `serverPort`, `serverProtocol`, `serverHost`, and `linkPreview`.
|
||||
- Packaged server builds store `metoyou.sqlite` in the OS app-data directory by default so upgrades do not overwrite runtime data. On first start, the server copies forward legacy packaged databases that still live beside the executable.
|
||||
- When HTTPS is enabled, certificates are read from the repository `.certs/` directory.
|
||||
|
||||
## Structure
|
||||
|
||||
Binary file not shown.
@@ -17,7 +17,21 @@ import {
|
||||
ServerBanEntity
|
||||
} from '../entities';
|
||||
import { serverMigrations } from '../migrations';
|
||||
import { findExistingPath, resolveRuntimePath } from '../runtime-paths';
|
||||
import {
|
||||
findExistingPath,
|
||||
isPackagedRuntime,
|
||||
resolvePersistentDataPath,
|
||||
resolveRuntimePath
|
||||
} from '../runtime-paths';
|
||||
|
||||
const LEGACY_PACKAGED_DB_FILE = path.join(resolveRuntimePath('data'), 'metoyou.sqlite');
|
||||
const LEGACY_PACKAGED_DB_BACKUP = LEGACY_PACKAGED_DB_FILE + '.bak';
|
||||
|
||||
function resolveDefaultDbFile(): string {
|
||||
return isPackagedRuntime()
|
||||
? resolvePersistentDataPath('metoyou.sqlite')
|
||||
: LEGACY_PACKAGED_DB_FILE;
|
||||
}
|
||||
|
||||
function resolveDbFile(): string {
|
||||
const envPath = process.env.DB_PATH;
|
||||
@@ -26,7 +40,7 @@ function resolveDbFile(): string {
|
||||
return path.resolve(envPath);
|
||||
}
|
||||
|
||||
return path.join(resolveRuntimePath('data'), 'metoyou.sqlite');
|
||||
return resolveDefaultDbFile();
|
||||
}
|
||||
|
||||
const DB_FILE = resolveDbFile();
|
||||
@@ -37,6 +51,55 @@ const SQLITE_MAGIC = 'SQLite format 3\0';
|
||||
|
||||
let applicationDataSource: DataSource | undefined;
|
||||
|
||||
function restoreFromBackup(reason: string): Uint8Array | undefined {
|
||||
if (!fs.existsSync(DB_BACKUP)) {
|
||||
console.error(`[DB] ${reason}. No backup available - starting with a fresh database`);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const backup = new Uint8Array(fs.readFileSync(DB_BACKUP));
|
||||
|
||||
if (!isValidSqlite(backup)) {
|
||||
console.error(`[DB] ${reason}. Backup is also invalid - starting with a fresh database`);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
fs.copyFileSync(DB_BACKUP, DB_FILE);
|
||||
console.warn('[DB] Restored database from backup', DB_BACKUP);
|
||||
|
||||
return backup;
|
||||
}
|
||||
|
||||
async function migrateLegacyPackagedDatabase(): Promise<void> {
|
||||
if (process.env.DB_PATH || !isPackagedRuntime() || path.resolve(DB_FILE) === path.resolve(LEGACY_PACKAGED_DB_FILE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let migrated = false;
|
||||
|
||||
if (!fs.existsSync(DB_FILE)) {
|
||||
if (fs.existsSync(LEGACY_PACKAGED_DB_FILE)) {
|
||||
await fsp.copyFile(LEGACY_PACKAGED_DB_FILE, DB_FILE);
|
||||
migrated = true;
|
||||
} else if (fs.existsSync(LEGACY_PACKAGED_DB_BACKUP)) {
|
||||
await fsp.copyFile(LEGACY_PACKAGED_DB_BACKUP, DB_FILE);
|
||||
migrated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(DB_BACKUP) && fs.existsSync(LEGACY_PACKAGED_DB_BACKUP)) {
|
||||
await fsp.copyFile(LEGACY_PACKAGED_DB_BACKUP, DB_BACKUP);
|
||||
migrated = true;
|
||||
}
|
||||
|
||||
if (migrated) {
|
||||
console.log('[DB] Migrated packaged database files to:', DATA_DIR);
|
||||
console.log('[DB] Legacy packaged database location was:', LEGACY_PACKAGED_DB_FILE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when `data` looks like a valid SQLite file
|
||||
* (correct header magic and at least one complete page).
|
||||
@@ -56,8 +119,11 @@ function isValidSqlite(data: Uint8Array): boolean {
|
||||
* restore the backup before the server loads the database.
|
||||
*/
|
||||
function safeguardDbFile(): Uint8Array | undefined {
|
||||
if (!fs.existsSync(DB_FILE))
|
||||
return undefined;
|
||||
if (!fs.existsSync(DB_FILE)) {
|
||||
console.warn(`[DB] ${DB_FILE} is missing - checking backup`);
|
||||
|
||||
return restoreFromBackup('Database file missing');
|
||||
}
|
||||
|
||||
const data = new Uint8Array(fs.readFileSync(DB_FILE));
|
||||
|
||||
@@ -72,22 +138,7 @@ function safeguardDbFile(): Uint8Array | undefined {
|
||||
// The main file is corrupt or empty.
|
||||
console.warn(`[DB] ${DB_FILE} appears corrupt (${data.length} bytes) - checking backup`);
|
||||
|
||||
if (fs.existsSync(DB_BACKUP)) {
|
||||
const backup = new Uint8Array(fs.readFileSync(DB_BACKUP));
|
||||
|
||||
if (isValidSqlite(backup)) {
|
||||
fs.copyFileSync(DB_BACKUP, DB_FILE);
|
||||
console.warn('[DB] Restored database from backup', DB_BACKUP);
|
||||
|
||||
return backup;
|
||||
}
|
||||
|
||||
console.error('[DB] Backup is also invalid - starting with a fresh database');
|
||||
} else {
|
||||
console.error('[DB] No backup available - starting with a fresh database');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return restoreFromBackup(`Database file is invalid (${data.length} bytes)`);
|
||||
}
|
||||
|
||||
function resolveSqlJsConfig(): { locateFile: (file: string) => string } {
|
||||
@@ -132,6 +183,8 @@ export async function initDatabase(): Promise<void> {
|
||||
if (!fs.existsSync(DATA_DIR))
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
|
||||
await migrateLegacyPackagedDatabase();
|
||||
|
||||
const database = safeguardDbFile();
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
const PACKAGED_DATA_DIRECTORY_NAME = 'MetoYou Server';
|
||||
|
||||
type PackagedProcess = NodeJS.Process & { pkg?: unknown };
|
||||
|
||||
function uniquePaths(paths: string[]): string[] {
|
||||
@@ -21,6 +24,33 @@ export function resolveRuntimePath(...segments: string[]): string {
|
||||
return path.join(getRuntimeBaseDir(), ...segments);
|
||||
}
|
||||
|
||||
function resolvePackagedDataDirectory(): string {
|
||||
const homeDirectory = os.homedir();
|
||||
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
return path.join(
|
||||
process.env.APPDATA || path.join(homeDirectory, 'AppData', 'Roaming'),
|
||||
PACKAGED_DATA_DIRECTORY_NAME
|
||||
);
|
||||
case 'darwin':
|
||||
return path.join(homeDirectory, 'Library', 'Application Support', PACKAGED_DATA_DIRECTORY_NAME);
|
||||
default:
|
||||
return path.join(
|
||||
process.env.XDG_DATA_HOME || path.join(homeDirectory, '.local', 'share'),
|
||||
PACKAGED_DATA_DIRECTORY_NAME
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function resolvePersistentDataPath(...segments: string[]): string {
|
||||
if (!isPackagedRuntime()) {
|
||||
return resolveRuntimePath(...segments);
|
||||
}
|
||||
|
||||
return path.join(resolvePackagedDataDirectory(), ...segments);
|
||||
}
|
||||
|
||||
export function resolveProjectRootPath(...segments: string[]): string {
|
||||
return path.resolve(__dirname, '..', '..', ...segments);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user