run method

  1. @override
Future<int> run()
override

Runs this command.

The return value is wrapped in a Future if necessary and returned by CommandRunner.runCommand.

Implementation

@override
Future<int> run() async {
  final dataDir =
      globals.option('data') ?? environment['OMNYSTORE_DATA'] ?? '.omnystore';
  final verbose = globals.flag('verbose');
  final quiet = globals.flag('quiet');
  final logger = CliContext.loggerFor(verbose: verbose || !quiet);

  final port = int.tryParse(argResults!.option('port') ?? '8080');
  if (port == null || port < 0 || port > 65535) {
    throw CliException(
      'Invalid --port "${argResults!.option('port')}".',
      exitCode: 64,
    );
  }

  final providerId = argResults!.option('provider-id') ?? 'hub-local';
  final local = await CliContext.openLocalStore(
    dataDir,
    logger: logger,
    providerId: providerId,
  );

  // Always a hub, even with no nodes: it costs one indirection and means
  // attaching a node later needs no restart into a different mode.
  final hub = OmnyStoreHub(logger: logger)
    ..addProvider(
      LocalStoreProvider(
        local,
        descriptor: ProviderDescriptor(
          id: providerId,
          kind: ProviderKind.hub,
          // A catch-all, so the server works with no configuration and a
          // node added later takes over its own organizations without the
          // hub being reconfigured.
          servesAll: true,
          dataPlane: DataPlaneMode.relay,
          agentVersion: omnyStoreVersion,
          priority: -100,
        ),
      ),
    );

  final publishToken =
      argResults!.option('publish-token') ??
      environment['OMNYSTORE_PUBLISH_TOKEN'];
  final nodeToken =
      argResults!.option('node-token') ?? environment['OMNYSTORE_NODE_TOKEN'];
  final corsOption = argResults!.option('cors');

  final server = OmnyStoreServer(
    store: hub,
    logger: logger,
    allowAnyOrigin: corsOption == '*',
    allowedOrigins: corsOption == null || corsOption == '*'
        ? const []
        : corsOption
              .split(',')
              .map((o) => o.trim())
              .where((o) => o.isNotEmpty),
    requireAuthForWrites: publishToken != null,
    writeAuthenticator: publishToken == null
        ? null
        : BearerTokenAuthenticator({
            publishToken: Principal(
              id: 'publisher',
              roles: const {'publisher'},
            ),
          }),
    enableNodes: argResults!.flag('nodes'),
    nodeMount: argResults!.option('node-mount') ?? '/_node',
    nodeAuthenticator: nodeToken == null
        ? null
        : BearerTokenAuthenticator({
            nodeToken: Principal(id: 'node', roles: const {'node'}),
          }),
  );

  final tls = _tlsFrom(argResults!);
  await server.start(
    port: port,
    address: argResults!.option('address') ?? '0.0.0.0',
    tls: tls,
  );

  final scheme = tls == null ? 'http' : 'https';
  if (!quiet) {
    out.writeln('OmnyStore $omnyStoreVersion');
    out.writeln('  data:     $dataDir');
    out.writeln('  API:      $scheme://localhost:${server.port}/api/v1');
    out.writeln('  health:   $scheme://localhost:${server.port}/health');
    if (server.nodes != null) {
      final wsScheme = tls == null ? 'ws' : 'wss';
      out.writeln(
        '  nodes:    $wsScheme://localhost:${server.port}'
        '${argResults!.option('node-mount') ?? '/_node'}',
      );
    }
    if (publishToken == null) {
      out.writeln(
        '  writes:   OPEN — anyone who can reach this server can publish. '
        'Pass --publish-token to require authentication.',
      );
    }
    out.writeln('Press Ctrl-C to stop.');
  }

  await _awaitInterrupt();
  if (!quiet) out.writeln('\nShutting down…');
  await server.stop();
  await local.close();
  return 0;
}