Backups push automatically; the token is only for restore
Matches the master's tokenless-push change: pushBackup no longer waits for master_backup_token — every instance with a uuid backs up on its own, and the 5-minute tick force-pushes so a poisoned master copy self-heals. The token now only gates pulling a backup back (restore), so setup-linux.sh asks for it only when restoring and the Settings help text says so. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
8154f25bb9
commit
ba8bd50da0
@@ -347,8 +347,8 @@ export default function SettingsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-slate-800">Backup token</p>
|
<p className="text-sm font-medium text-slate-800">Backup token</p>
|
||||||
<p className="text-xs text-slate-400 mt-0.5">
|
<p className="text-xs text-slate-400 mt-0.5">
|
||||||
Enables encrypted settings + credentials backups to the master
|
Backups push automatically — this token (the master's
|
||||||
(its INSTANCE_BACKUP_TOKEN). Blank = backups off.
|
INSTANCE_BACKUP_TOKEN) is only needed to restore one.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-slate-400 mt-0.5">
|
<p className="text-xs text-slate-400 mt-0.5">
|
||||||
{backupStatus.at
|
{backupStatus.at
|
||||||
|
|||||||
+17
-14
@@ -5,9 +5,11 @@
|
|||||||
* instance's uuid.
|
* instance's uuid.
|
||||||
*
|
*
|
||||||
* This is the deliberate exception to "credentials never leave the machine":
|
* This is the deliberate exception to "credentials never leave the machine":
|
||||||
* the payload goes over HTTPS to the bearer-gated /api/instances/backup and is
|
* the payload goes over HTTPS to /api/instances/backup and is AES-encrypted
|
||||||
* AES-encrypted at rest on the master. It never rides the unauthenticated
|
* at rest on the master. It never rides the control socket. Pushing needs no
|
||||||
* control socket.
|
* token — every instance backs up automatically once it has its uuid; only
|
||||||
|
* PULLING a backup back out (lib/backup-restore.ts) is token-gated, because
|
||||||
|
* that is the direction credentials come out in the clear.
|
||||||
*
|
*
|
||||||
* Push policy: on bootstrap, then every POLL_INTERVAL, but only when the
|
* Push policy: on bootstrap, then every POLL_INTERVAL, but only when the
|
||||||
* content actually changed (hash comparison) — plus an immediate debounced
|
* content actually changed (hash comparison) — plus an immediate debounced
|
||||||
@@ -85,19 +87,20 @@ function contentHash(payload: BackupPayload): string {
|
|||||||
.digest('hex');
|
.digest('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
function preconditions(): { masterUrl: string; id: string; token: string } | null {
|
function preconditions(): { masterUrl: string; id: string } | null {
|
||||||
const masterUrl = getSetting('master_dashboard_url');
|
const masterUrl = getSetting('master_dashboard_url');
|
||||||
const token = getSetting('master_backup_token');
|
|
||||||
// No uuid yet = the master has never said welcome; there is nothing stable
|
// No uuid yet = the master has never said welcome; there is nothing stable
|
||||||
// to key the backup by, so wait for the first hello to complete.
|
// to key the backup by, so wait for the first hello to complete. No token
|
||||||
|
// needed: pushes are open on the master (only PULLING a backup is
|
||||||
|
// token-gated), so every instance backs up automatically.
|
||||||
const id = getSetting('instance_id');
|
const id = getSetting('instance_id');
|
||||||
if (!masterUrl || !token || !id) return null;
|
if (!masterUrl || !id) return null;
|
||||||
return { masterUrl, id, token };
|
return { masterUrl, id };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pushBackup(force = false): Promise<{ ok: boolean; skipped?: boolean; error?: string }> {
|
export async function pushBackup(force = false): Promise<{ ok: boolean; skipped?: boolean; error?: string }> {
|
||||||
const pre = preconditions();
|
const pre = preconditions();
|
||||||
if (!pre) return { ok: false, skipped: true, error: 'master url, backup token or instance uuid not set' };
|
if (!pre) return { ok: false, skipped: true, error: 'master url or instance uuid not set' };
|
||||||
|
|
||||||
const payload = collectBackup();
|
const payload = collectBackup();
|
||||||
const hash = contentHash(payload);
|
const hash = contentHash(payload);
|
||||||
@@ -113,10 +116,7 @@ export async function pushBackup(force = false): Promise<{ ok: boolean; skipped?
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: { 'content-type': 'application/json' },
|
||||||
'content-type': 'application/json',
|
|
||||||
authorization: `Bearer ${pre.token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
id: pre.id,
|
id: pre.id,
|
||||||
name: getSetting('instance_name') ?? '',
|
name: getSetting('instance_name') ?? '',
|
||||||
@@ -151,7 +151,10 @@ export function scheduleBackupPush(): void {
|
|||||||
export function startBackupSync(): void {
|
export function startBackupSync(): void {
|
||||||
stopBackupSync();
|
stopBackupSync();
|
||||||
const tick = async () => {
|
const tick = async () => {
|
||||||
await pushBackup();
|
// Forced: pushes need no token, so anyone who reaches the master and
|
||||||
|
// knows this uuid could overwrite our backup there. Re-asserting the
|
||||||
|
// real state every tick bounds that poisoning window to 5 minutes.
|
||||||
|
await pushBackup(true);
|
||||||
// Jitter so a fleet restarting together doesn't push in lockstep.
|
// Jitter so a fleet restarting together doesn't push in lockstep.
|
||||||
g.__backupSyncTimer = setTimeout(tick, POLL_INTERVAL_MS + Math.floor(Math.random() * 30_000));
|
g.__backupSyncTimer = setTimeout(tick, POLL_INTERVAL_MS + Math.floor(Math.random() * 30_000));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -197,8 +197,9 @@ seedSetting.run('twocaptcha_api_key', '');
|
|||||||
// Blank falls back to the proxy hardcoded in app/api/autobuyer/captcha; `none`
|
// Blank falls back to the proxy hardcoded in app/api/autobuyer/captcha; `none`
|
||||||
// solves without one.
|
// solves without one.
|
||||||
seedSetting.run('twocaptcha_proxy', '');
|
seedSetting.run('twocaptcha_proxy', '');
|
||||||
// Bearer for pushing/pulling the encrypted settings+credentials backup on the
|
// Bearer for PULLING an encrypted settings+credentials backup back from the
|
||||||
// master (its INSTANCE_BACKUP_TOKEN). Blank = backups off.
|
// master (its INSTANCE_BACKUP_TOKEN) — pushing needs no token and happens
|
||||||
|
// automatically. Blank = restore unavailable from this machine's UI.
|
||||||
seedSetting.run('master_backup_token', '');
|
seedSetting.run('master_backup_token', '');
|
||||||
// Routes broker traffic (TopstepX, Tradovate, Yahoo quotes) through a proxy:
|
// Routes broker traffic (TopstepX, Tradovate, Yahoo quotes) through a proxy:
|
||||||
// http(s):// or socks5(h)://, user:pass ok. The master link stays direct on
|
// http(s):// or socks5(h)://, user:pass ok. The master link stays direct on
|
||||||
|
|||||||
+5
-4
@@ -86,10 +86,11 @@ read -r -p " Master dashboard URL [$DEFAULT_MASTER]: " MASTER_URL
|
|||||||
MASTER_URL="${MASTER_URL:-$DEFAULT_MASTER}"
|
MASTER_URL="${MASTER_URL:-$DEFAULT_MASTER}"
|
||||||
read -r -p " Instance name [$(hostname)]: " INSTANCE
|
read -r -p " Instance name [$(hostname)]: " INSTANCE
|
||||||
INSTANCE="${INSTANCE:-$(hostname)}"
|
INSTANCE="${INSTANCE:-$(hostname)}"
|
||||||
read -r -p " Backup token (blank = no master backups): " BACKUP_TOKEN
|
# Backups push automatically; the token is only needed to pull one back.
|
||||||
RESTORE_FROM=""
|
read -r -p " Restore a dead instance? Its uuid (blank = fresh install): " RESTORE_FROM
|
||||||
if [ -n "$BACKUP_TOKEN" ]; then
|
BACKUP_TOKEN=""
|
||||||
read -r -p " Restore a dead instance? Its uuid (blank = fresh install): " RESTORE_FROM
|
if [ -n "$RESTORE_FROM" ]; then
|
||||||
|
read -r -p " Backup token (the master's INSTANCE_BACKUP_TOKEN): " BACKUP_TOKEN
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
info "installing to: $TARGET"
|
info "installing to: $TARGET"
|
||||||
|
|||||||
Reference in New Issue
Block a user