Backups to master, replacement-VPS restore, broker proxy, multi-instance
Four features: - Backup: lib/backup-sync.ts pushes the full settings table and firm credentials (the deliberate exception to "credentials never leave the machine") to the master's bearer-gated /api/instances/backup — every 5 minutes when the content hash changed, within seconds of a settings or credential edit, and on the new backup-now command. Enabled by the master_backup_token setting. - Restore: a dead VPS's replacement pulls the backup and adopts its uuid/name, so roster, payouts and card links carry on. Pre-boot via seed-settings.js --restore-from (setup-linux.sh prompts for it) or the Settings page's restore picker (lib/backup-restore.ts). Stop the old machine first; the master 409s a live-name rename. - Proxy: the proxy_url setting routes BROKER traffic only — TopstepX REST + SignalR hub, Tradovate REST + websocket, Yahoo quotes — via lib/outbound-proxy.ts (axios agents, undici dispatcher, ws agent). The master link, reporter, catalog and backup push stay direct, so a dead proxy cannot take down the control channel. Restart to apply. - Multi-instance: AUTOFIRMER_DB moves the SQLite file so one checkout runs N instances; scripts/instances.json (per-device, gitignored) drives start-all/update-check, and add-instance.sh / remove-instance.sh manage the extra PM2 processes. Absent registry = single-instance behavior, unchanged. Instance 1 keeps port 3000 for the clicker and extension. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
97947bffde
commit
8154f25bb9
@@ -1,9 +1,22 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { mkdirSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const db = new Database(path.join(process.cwd(), 'autotrader.sqlite'));
|
||||
// AUTOFIRMER_DB lets several instances share one checkout, each with its own
|
||||
// database. It is read once, at module load — schema creation and seeding are
|
||||
// import side effects — so it must be in the environment at process start.
|
||||
const DB_PATH = process.env.AUTOFIRMER_DB
|
||||
? path.resolve(process.env.AUTOFIRMER_DB)
|
||||
: path.join(process.cwd(), 'autotrader.sqlite');
|
||||
|
||||
mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
||||
const db = new Database(DB_PATH);
|
||||
|
||||
db.pragma('foreign_keys = ON');
|
||||
// If two processes are ever misconfigured onto the same file, WAL + a busy
|
||||
// timeout turns SQLITE_BUSY crashes into brief contention.
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('busy_timeout = 5000');
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS instruments (
|
||||
@@ -123,6 +136,11 @@ export function listAssignments(firmId: string): AssignmentRow[] {
|
||||
return db.prepare('SELECT * FROM account_assignments WHERE firm_id = ?').all(firmId) as AssignmentRow[];
|
||||
}
|
||||
|
||||
/** Every assignment, for the backup pushed to the master. */
|
||||
export function listAllAssignments(): AssignmentRow[] {
|
||||
return db.prepare('SELECT * FROM account_assignments').all() as AssignmentRow[];
|
||||
}
|
||||
|
||||
export function setAssignment(
|
||||
accountId: number,
|
||||
firmId: string,
|
||||
@@ -179,12 +197,31 @@ seedSetting.run('twocaptcha_api_key', '');
|
||||
// Blank falls back to the proxy hardcoded in app/api/autobuyer/captcha; `none`
|
||||
// solves without one.
|
||||
seedSetting.run('twocaptcha_proxy', '');
|
||||
// Bearer for pushing/pulling the encrypted settings+credentials backup on the
|
||||
// master (its INSTANCE_BACKUP_TOKEN). Blank = backups off.
|
||||
seedSetting.run('master_backup_token', '');
|
||||
// Routes broker traffic (TopstepX, Tradovate, Yahoo quotes) through a proxy:
|
||||
// http(s):// or socks5(h)://, user:pass ok. The master link stays direct on
|
||||
// purpose. Blank = direct. Takes effect on restart.
|
||||
seedSetting.run('proxy_url', '');
|
||||
// Mirrors the in-memory scheduler state so a restart can resume it.
|
||||
seedSetting.run('scheduler_running', '0');
|
||||
seedSetting.run('scheduler_action', 'Buy');
|
||||
seedSetting.run('scheduler_symbol', 'NQ');
|
||||
seedSetting.run('scheduler_stop_after_all', '0');
|
||||
|
||||
/** Wraps several writes in one transaction (used by the backup restore, so a
|
||||
* half-applied backup cannot survive a crash). */
|
||||
export function runInTransaction<T>(fn: () => T): T {
|
||||
return db.transaction(fn)();
|
||||
}
|
||||
|
||||
/** The whole table, for the backup pushed to the master. */
|
||||
export function listSettings(): Record<string, string> {
|
||||
const rows = db.prepare('SELECT key, value FROM settings').all() as { key: string; value: string }[];
|
||||
return Object.fromEntries(rows.map((r) => [r.key, r.value]));
|
||||
}
|
||||
|
||||
export function getSetting(key: string): string | null {
|
||||
const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined;
|
||||
return row?.value ?? null;
|
||||
|
||||
Reference in New Issue
Block a user