writeHubState function
Persists {masterSecret, clients} (0600 — the file carries secrets).
Best-effort: IO failures never take the hub down.
Temp + chmod + rename: the file must never be world-readable for
even a moment, and content-first/chmod-second on the live path
leaves exactly that window while hub.json carries the master
secret (issue #794 review).
Implementation
Future<void> writeHubState(
File file, {
required String? masterSecret,
required Map<String, String> clients,
}) async {
try {
if (!await file.parent.exists()) {
await file.parent.create(recursive: true);
}
// Unique temp name: two overlapping writes must not race one
// shared .tmp (A renames it away; B's rename then throws and its
// enrollment is silently lost — issue #794 review round 5). The
// write counter keeps same-microsecond writes apart; microseconds
// keep restarts apart.
final tmp = File(
'${file.path}.${DateTime.now().microsecondsSinceEpoch}'
'.${_hubStateWriteSeq++}.tmp',
);
await tmp.writeAsString(
jsonEncode({'masterSecret': masterSecret, 'clients': clients}),
flush: true,
);
if (!Platform.isWindows) {
await Process.run('chmod', ['600', tmp.path]);
}
await tmp.rename(file.path);
} on Object {
// Persistence is best-effort; the in-memory state still serves.
// (A leftover uniquely-named .tmp from a crashed write is never
// read — harmless.)
}
}