import 'dart:io'; import 'package:podman/podman.dart'; Future main(List args) async { if (args.isEmpty || args.contains('--help') || args.contains('-h')) { _printUsage(); return; } final command = args.first; final remaining = args.sublist(1); switch (command) { case 'checkpoint': await _runCheckpoint(remaining); return; case 'export': await _runExport(remaining); return; case 'restore': await _runRestore(remaining); return; case 'restore-archive': await _runRestoreArchive(remaining); return; default: stderr.writeln('Unknown command: $command'); _printUsage(); exitCode = 64; return; } } Future _runCheckpoint(List args) async { final positional = args .where((arg) => !arg.startsWith('--')) .toList(growable: false); if (positional.length != 1) { stderr.writeln('checkpoint requires exactly one argument.'); _printUsage(); exitCode = 64; return; } final container = positional.first; final options = _checkpointOptionsFromArgs(args); final client = PodmanClient(); try { final report = await client.checkpointContainer( container, options: options, ); print('Checkpoint completed for "$container"'); print(' id: ${report.id}'); print(' runtime duration: ${report.runtimeDuration}'); print(' criu stats keys: ${report.criuStatistics.keys.toList()..sort()}'); } finally { await client.close(); } } Future _runExport(List args) async { final positional = args .where((arg) => !arg.startsWith('--')) .toList(growable: false); if (positional.length != 2) { stderr.writeln('export requires .'); _printUsage(); exitCode = 64; return; } final container = positional.first; final archivePath = positional[1]; final options = _checkpointOptionsFromArgs(args); final client = PodmanClient(); try { final archive = await client.exportContainerCheckpoint( container, options: options, ); await File(archivePath).writeAsBytes(archive); print( 'Exported checkpoint for "$container" to $archivePath (${archive.length} bytes).', ); } finally { await client.close(); } } Future _runRestore(List args) async { final positional = args .where((arg) => !arg.startsWith('--')) .toList(growable: false); if (positional.length != 1) { stderr.writeln('restore requires exactly one argument.'); _printUsage(); exitCode = 64; return; } final container = positional.first; final options = _restoreOptionsFromArgs(args); final client = PodmanClient(); try { final report = await client.restoreContainer(container, options: options); print('Restore completed for "$container"'); print(' id: ${report.id}'); print(' runtime duration: ${report.runtimeDuration}'); print(' criu stats keys: ${report.criuStatistics.keys.toList()..sort()}'); } finally { await client.close(); } } Future _runRestoreArchive(List args) async { final positional = args .where((arg) => !arg.startsWith('--')) .toList(growable: false); if (positional.length != 1) { stderr.writeln( 'restore-archive requires exactly one argument.', ); _printUsage(); exitCode = 64; return; } final archivePath = positional.first; final archiveFile = File(archivePath); if (!await archiveFile.exists()) { stderr.writeln('Archive file not found: $archivePath'); exitCode = 66; return; } final importName = _readOption(args, 'import-name') ?? 'import'; final options = _restoreOptionsFromArgs(args); final bytes = await archiveFile.readAsBytes(); final client = PodmanClient(); try { final report = await client.restoreContainerFromArchive( bytes, importName: importName, options: options, ); print('Archive restore completed from $archivePath'); print(' id: ${report.id}'); print(' runtime duration: ${report.runtimeDuration}'); print(' criu stats keys: ${report.criuStatistics.keys.toList()..sort()}'); } finally { await client.close(); } } ContainerCheckpointOptions _checkpointOptionsFromArgs(List args) { return ContainerCheckpointOptions( keep: args.contains('--keep'), leaveRunning: args.contains('--leave-running'), tcpEstablished: args.contains('--tcp-established'), ignoreRootFs: args.contains('--ignore-rootfs'), ignoreVolumes: args.contains('--ignore-volumes'), printStats: args.contains('--print-stats'), preCheckpoint: args.contains('--pre-checkpoint'), withPrevious: args.contains('--with-previous'), fileLocks: args.contains('--file-locks'), createImage: _readOption(args, 'create-image'), ); } ContainerRestoreOptions _restoreOptionsFromArgs(List args) { final publishPorts = _readMultiOptions(args, 'publish-port'); return ContainerRestoreOptions( name: _readOption(args, 'name'), keep: args.contains('--keep'), tcpEstablished: args.contains('--tcp-established'), tcpClose: args.contains('--tcp-close'), ignoreRootFs: args.contains('--ignore-rootfs'), ignoreVolumes: args.contains('--ignore-volumes'), ignoreStaticIp: args.contains('--ignore-static-ip'), ignoreStaticMac: args.contains('--ignore-static-mac'), printStats: args.contains('--print-stats'), fileLocks: args.contains('--file-locks'), publishPorts: publishPorts, pod: _readOption(args, 'pod'), ); } String? _readOption(List args, String name) { final prefix = '--$name='; for (final arg in args) { if (arg.startsWith(prefix)) { return arg.substring(prefix.length); } } return null; } List _readMultiOptions(List args, String name) { final prefix = '--$name='; final values = []; for (final arg in args) { if (arg.startsWith(prefix)) { final value = arg.substring(prefix.length); if (value.isNotEmpty) { values.add(value); } } } return values; } void _printUsage() { print(''' Checkpoint/restore example Usage: dart run example/checkpoint_restore_example.dart [args] [options] Commands: checkpoint Create an in-place checkpoint and return report metadata. export Export checkpoint archive bytes to the provided file path. restore Restore a container from an existing checkpoint state/image. restore-archive Restore from a checkpoint archive tarball. Shared options: --keep --tcp-established --print-stats --file-locks Checkpoint/export options: --leave-running --ignore-rootfs --ignore-volumes --pre-checkpoint --with-previous --create-image= Restore/restore-archive options: --name= --tcp-close --ignore-rootfs --ignore-volumes --ignore-static-ip --ignore-static-mac --pod= --publish-port= (repeatable) restore-archive-only options: --import-name= Defaults to "import" Examples: dart run example/checkpoint_restore_example.dart checkpoint gw-service --print-stats dart run example/checkpoint_restore_example.dart export gw-service ./gw-service-checkpoint.tar dart run example/checkpoint_restore_example.dart restore-archive ./gw-service-checkpoint.tar --name=gw-restored '''); }