mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-27 16:49:51 -07:00
be31f96f02
subminer sync <host> exchanges VACUUM INTO snapshots over ssh/scp and each side merges the other's data as an insert-only union keyed on session UUIDs, video keys, series title keys, and word/kanji identity. Lifetime totals and daily/monthly rollups are applied incrementally so pre-retention history survives, remote-only historical rollups are copied, and re-syncing is idempotent. sync --snapshot/--merge expose the underlying steps for manual transfers; a pid-file/mpv-socket guard refuses to run while SubMiner may be writing the database and schema-version mismatches abort the merge.
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import { spawnSync } from 'node:child_process';
|
|
|
|
export interface RemoteRunResult {
|
|
status: number;
|
|
stdout: string;
|
|
}
|
|
|
|
/**
|
|
* Run a command on the SSH host. stdin/stderr stay attached to the terminal
|
|
* so key passphrase / password prompts and remote progress output work;
|
|
* stdout is captured for the caller.
|
|
*/
|
|
export function runSsh(host: string, remoteCommand: string): RemoteRunResult {
|
|
const result = spawnSync('ssh', [host, remoteCommand], {
|
|
encoding: 'utf8',
|
|
stdio: ['inherit', 'pipe', 'inherit'],
|
|
});
|
|
if (result.error) {
|
|
throw new Error(`Failed to run ssh: ${(result.error as Error).message}`);
|
|
}
|
|
return { status: result.status ?? 1, stdout: result.stdout ?? '' };
|
|
}
|
|
|
|
export function runScp(from: string, to: string): void {
|
|
const result = spawnSync('scp', ['-q', from, to], {
|
|
encoding: 'utf8',
|
|
stdio: ['inherit', 'inherit', 'inherit'],
|
|
});
|
|
if (result.error) {
|
|
throw new Error(`Failed to run scp: ${(result.error as Error).message}`);
|
|
}
|
|
if ((result.status ?? 1) !== 0) {
|
|
throw new Error(`scp failed copying ${from} -> ${to}`);
|
|
}
|
|
}
|
|
|
|
export function shellQuote(value: string): string {
|
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
}
|
|
|
|
/**
|
|
* Non-interactive SSH shells often miss ~/.local/bin in PATH, so probe the
|
|
* configured command first and fall back to the default install location.
|
|
*/
|
|
export function resolveRemoteSubminerCommand(host: string, preferred: string | null): string {
|
|
const candidates = preferred ? [preferred] : ['subminer', '~/.local/bin/subminer'];
|
|
for (const candidate of candidates) {
|
|
const probe = runSsh(host, `command -v ${candidate} >/dev/null 2>&1`);
|
|
if (probe.status === 0) {
|
|
return candidate;
|
|
}
|
|
}
|
|
throw new Error(
|
|
preferred
|
|
? `Remote command not found on ${host}: ${preferred}`
|
|
: `subminer not found on ${host} (tried PATH and ~/.local/bin/subminer). Pass --remote-cmd <path>.`,
|
|
);
|
|
}
|