setupDirectSsh function

Future<void> setupDirectSsh(
  1. VirtualMachine vm
)

Implementation

Future<void> setupDirectSsh(VirtualMachine vm) async {
  final keyFile = File(_sshKeyPath);
  if (!keyFile.existsSync()) {
    await Process.run('ssh-keygen', [
      '-t',
      'ed25519',
      '-f',
      _sshKeyPath,
      '-N',
      '',
      '-q',
    ]);
    _log.info('Generated SSH key at $_sshKeyPath');
  }
  final pubKey = File('$_sshKeyPath.pub').readAsStringSync().trim();
  final ip = vm.ipAddress;
  if (ip == null) {
    throw StateError('VM IP is null; cannot install SSH key.');
  }

  // Drive the system `ssh` binary's password auth via SSH_ASKPASS (no TTY
  // required). Using /usr/bin/ssh keeps this exempt from macOS Local Network
  // privacy when the worker runs as a LaunchAgent.
  final askpass = File(_askPassPath);
  askpass.writeAsStringSync("#!/bin/sh\nprintf '%s' '$sshPassword'\n");
  await Process.run('chmod', ['+x', _askPassPath]);

  const installCmd =
      'mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys';

  Future<int> runPasswordSsh(String command) async {
    final process = await Process.start(
      '/usr/bin/ssh',
      [
        ..._sshBaseOpts,
        '-o',
        'PubkeyAuthentication=no',
        '-o',
        'PreferredAuthentications=password,keyboard-interactive',
        '-o',
        'NumberOfPasswordPrompts=1',
        '$sshUser@$ip',
        command,
      ],
      environment: {
        'SSH_ASKPASS': _askPassPath,
        'SSH_ASKPASS_REQUIRE': 'force',
        'DISPLAY': ':0',
      },
    );
    await process.stdin.close();
    await process.stdout.drain<void>();
    await process.stderr.drain<void>();
    return process.exitCode;
  }

  // The append uses base64 of the public key to avoid any quoting issues over
  // the password-auth ssh channel.
  final pubKeyB64 = base64Encode(utf8.encode('$pubKey\n'));
  final appendCmd =
      'printf %s \'$pubKeyB64\' | base64 -D >> ~/.ssh/authorized_keys';

  var exitCode = -1;
  for (var attempt = 1; attempt <= 5; attempt++) {
    exitCode = await runPasswordSsh('$installCmd && $appendCmd');
    if (exitCode == 0) break;
    _log.warning(
      'SSH key install attempt $attempt failed (exit $exitCode); retrying...',
    );
    await Future<void>.delayed(const Duration(seconds: 5));
  }

  try {
    askpass.deleteSync();
  } catch (_) {}

  if (exitCode != 0) {
    throw Exception('Failed to install SSH key on VM. Exit code: $exitCode');
  }
  _log.info('SSH key installed on VM');
}