fdroid
Kherel 2021-01-21 22:01:42 +01:00
parent 7de50dd237
commit 80213abf9b
14 changed files with 262 additions and 57 deletions

View File

@ -10,6 +10,8 @@ PODS:
- Flutter - Flutter
- url_launcher (0.0.1): - url_launcher (0.0.1):
- Flutter - Flutter
- wakelock (0.0.1):
- Flutter
DEPENDENCIES: DEPENDENCIES:
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
@ -18,6 +20,7 @@ DEPENDENCIES:
- path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
EXTERNAL SOURCES: EXTERNAL SOURCES:
Flutter: Flutter:
@ -32,6 +35,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/shared_preferences/ios" :path: ".symlinks/plugins/shared_preferences/ios"
url_launcher: url_launcher:
:path: ".symlinks/plugins/url_launcher/ios" :path: ".symlinks/plugins/url_launcher/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
@ -40,6 +45,7 @@ SPEC CHECKSUMS:
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
wakelock: bfc7955c418d0db797614075aabbc58a39ab5107
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c

View File

@ -47,7 +47,9 @@ class BNames {
static String hetznerKey = 'hetznerKey'; static String hetznerKey = 'hetznerKey';
static String cloudFlareKey = 'cloudFlareKey'; static String cloudFlareKey = 'cloudFlareKey';
static String rootUser = 'rootUser'; static String rootUser = 'rootUser';
static String hetznerServer = 'server'; static String hetznerServer = 'hetznerServer';
static String isDkimSetted = 'isDkimSetted';
static String isDnsChecked = 'isDnsChecked'; static String isDnsChecked = 'isDnsChecked';
static String serverInitStart = 'serverInitStart';
static String isServerStarted = 'isServerStarted';
} }

View File

@ -1,14 +1,26 @@
import 'dart:developer';
import 'dart:io';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/get_it/console.dart'; import 'package:selfprivacy/logic/get_it/console.dart';
import 'package:selfprivacy/logic/models/message.dart'; import 'package:selfprivacy/logic/models/message.dart';
abstract class ApiMap { abstract class ApiMap {
ApiMap() {
var client = Dio()..interceptors.add(ConsoleInterceptor());
(client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(HttpClient client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
};
loggedClient = client;
}
String rootAddress; String rootAddress;
// Dio client = Dio(); Dio loggedClient;
Dio loggedClient = Dio()..interceptors.add(ConsoleInterceptor());
void close() { void close() {
loggedClient.close(); loggedClient.close();
@ -45,7 +57,7 @@ class ConsoleInterceptor extends InterceptorsWrapper {
@override @override
Future onError(DioError err) async { Future onError(DioError err) async {
var response = err.response; var response = err.response;
log(err.toString());
addMessage( addMessage(
Message.warn( Message.warn(
text: text:

View File

@ -7,7 +7,8 @@ import 'package:selfprivacy/logic/models/dns_records.dart';
class CloudflareApi extends ApiMap { class CloudflareApi extends ApiMap {
CloudflareApi([String token]) { CloudflareApi([String token]) {
if (token != null) { if (token != null) {
loggedClient.options = BaseOptions(headers: {'Authorization': 'Bearer $token'}); loggedClient.options =
BaseOptions(headers: {'Authorization': 'Bearer $token'});
} }
} }
@ -116,12 +117,18 @@ class CloudflareApi extends ApiMap {
await Future.wait(allFutures); await Future.wait(allFutures);
} }
// createSDKIM(String dkim) { setDkim(String dkimRecordString, String domainZoneId) {
// var txt3 = DnsRecords( var txt3 = DnsRecords(
// type: 'TXT', type: 'TXT',
// name: 'selector._domainkey', name: 'selector._domainkey',
// content: dkim, content: dkimRecordString,
// ttl: 18000, ttl: 18000,
// ); );
// }
var url = '$rootAddress/zones/$domainZoneId/dns_records';
loggedClient.post(
url,
data: txt3.toJson(),
);
}
} }

View File

@ -1,12 +1,47 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'api_map.dart'; import 'api_map.dart';
class ServerApi extends ApiMap { class ServerApi extends ApiMap {
ServerApi([String token]) { ServerApi(String domainName) {
if (token != null) { loggedClient.options = BaseOptions(
loggedClient.options = baseUrl: 'https://api.$domainName',
BaseOptions(headers: {'Authorization': 'Bearer $token'}); );
}
Future<bool> isHttpServerWorking() async {
bool res;
Response response;
try {
response = await loggedClient.get('/serviceStatus');
res = response.statusCode == HttpStatus.ok;
} catch (e) {
res = false;
} }
return res;
}
Future<String> getDkim(String domainName) async {
var response = await loggedClient.get(
'/getDKIM',
options: Options(responseType: ResponseType.plain),
);
return _decodeAndCutData(response.data, domainName);
} }
} }
String _decodeAndCutData(String text, String domainName) {
var decodedTextString = text.substring(1, text.length - 1);
var stringToBase64 = utf8.fuse(base64);
return stringToBase64
.decode(decodedTextString)
.replaceAll("selector._domainkey IN TXT ( ", "")
.replaceAll("\"\n \"", "")
.replaceAll(' ) ; ----- DKIM key selector for $domainName\n', '');
}

View File

@ -1,5 +1,8 @@
import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart'; import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/server_details.dart'; import 'package:selfprivacy/logic/models/server_details.dart';
@ -9,6 +12,18 @@ import 'app_config_repository.dart';
part 'app_config_state.dart'; part 'app_config_state.dart';
/// initializeing steps: |setHetznerKey
/// 1. Hetzner key |setCloudFlare
/// 2. Cloudflare key |setCloudflareKey
/// 3. Set Domain address |setDomain
/// 4. Set Root user name password |setRootUser
/// 5. Set Create server ans set DNS-Records |createServerAndSetDnsRecords
/// (without start)
/// 6. ChecksAndSets:
/// 6.1 checkDnsAndStartServer |checkDnsAndStartServer
/// 6.2 setDkim |setDkim
/// (checkServer + getDkim + Set DKIM)
class AppConfigCubit extends Cubit<AppConfigState> { class AppConfigCubit extends Cubit<AppConfigState> {
AppConfigCubit() : super(InitialAppConfigState()); AppConfigCubit() : super(InitialAppConfigState());
@ -29,7 +44,7 @@ class AppConfigCubit extends Cubit<AppConfigState> {
emit(state.copyWith(hetznerKey: hetznerKey)); emit(state.copyWith(hetznerKey: hetznerKey));
} }
void setCloudFlare(String cloudFlareKey) { void setCloudflareKey(String cloudFlareKey) {
repository.saveCloudFlare(cloudFlareKey); repository.saveCloudFlare(cloudFlareKey);
emit(state.copyWith(cloudFlareKey: cloudFlareKey)); emit(state.copyWith(cloudFlareKey: cloudFlareKey));
} }
@ -44,7 +59,30 @@ class AppConfigCubit extends Cubit<AppConfigState> {
emit(state.copyWith(rootUser: rootUser)); emit(state.copyWith(rootUser: rootUser));
} }
Future<void> checkDns() async { void setDkim() async {
var callBack = () async {
var isServerWorking = await repository.isHttpServerWorking(
state.cloudFlareDomain.domainName,
);
if (!isServerWorking) {
var last = DateTime.now();
print(last);
emit(state.copyWith(lastServerStatusCheckTime: last));
return;
}
await repository.setDkim(
state.cloudFlareDomain.domainName,
state.cloudFlareKey,
state.cloudFlareDomain.zoneId,
);
emit(state.copyWith(isDkimSetted: true));
};
_tryOrAddError(state, callBack);
}
void checkDnsAndStartServer() async {
var ip4 = state.hetznerServer.ip4; var ip4 = state.hetznerServer.ip4;
var domainName = state.cloudFlareDomain.domainName; var domainName = state.cloudFlareDomain.domainName;
@ -58,7 +96,8 @@ class AppConfigCubit extends Cubit<AppConfigState> {
repository.saveServerDetails(server); repository.saveServerDetails(server);
emit( emit(
state.copyWith( state.copyWith(
isDnsCheckedAndServerStarted: true, isDnsChecked: true,
isServerStarted: true,
isLoading: false, isLoading: false,
hetznerServer: server, hetznerServer: server,
), ),
@ -68,30 +107,39 @@ class AppConfigCubit extends Cubit<AppConfigState> {
} }
} }
void createServer() async { void createServerAndSetDnsRecords() async {
emit(state.copyWith(isLoading: true)); var callback = () async {
try {
var serverDetails = await repository.createServer( var serverDetails = await repository.createServer(
state.hetznerKey, state.hetznerKey,
state.rootUser, state.rootUser,
state.cloudFlareDomain.domainName, state.cloudFlareDomain.domainName,
); );
await repository.createDnsRecords( // await repository.createDnsRecords(
state.cloudFlareKey, // state.cloudFlareKey,
serverDetails.ip4, // serverDetails.ip4,
state.cloudFlareDomain, // state.cloudFlareDomain,
); // );
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
hetznerServer: serverDetails, hetznerServer: serverDetails,
)); ));
};
_tryOrAddError(state, callback);
}
FutureOr<void> _tryOrAddError(
AppConfigState state,
AsyncCallback callback,
) async {
emit(state.copyWith(isLoading: true));
try {
await callback();
} catch (e) { } catch (e) {
addError(e); addError(e);
emit(state);
emit(state.copyWith(isLoading: false));
} }
} }
} }

View File

@ -2,6 +2,7 @@ import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart'; import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart'; import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/api_maps/server.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart'; import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/server_details.dart'; import 'package:selfprivacy/logic/models/server_details.dart';
import 'package:selfprivacy/logic/models/user.dart'; import 'package:selfprivacy/logic/models/user.dart';
@ -21,7 +22,9 @@ class AppConfigRepository {
cloudFlareDomain: box.get(BNames.cloudFlareDomain), cloudFlareDomain: box.get(BNames.cloudFlareDomain),
rootUser: box.get(BNames.rootUser), rootUser: box.get(BNames.rootUser),
hetznerServer: box.get(BNames.hetznerServer), hetznerServer: box.get(BNames.hetznerServer),
isDnsCheckedAndServerStarted: box.get(BNames.isDnsChecked), isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
isDnsChecked: box.get(BNames.isDnsChecked, defaultValue: false),
isDkimSetted: box.get(BNames.isDkimSetted, defaultValue: false),
); );
} }
@ -52,6 +55,7 @@ class AppConfigRepository {
var hetznerApi = HetznerApi(hetznerKey); var hetznerApi = HetznerApi(hetznerKey);
var serverDetails = await hetznerApi.startServer(server: hetznerServer); var serverDetails = await hetznerApi.startServer(server: hetznerServer);
hetznerApi.close(); hetznerApi.close();
box.put(BNames.isServerStarted, true);
return serverDetails; return serverDetails;
} }
@ -92,6 +96,8 @@ class AppConfigRepository {
} }
} }
box.put(BNames.isDnsChecked, true);
return true; return true;
} }
@ -117,14 +123,36 @@ class AppConfigRepository {
String ip4, String ip4,
CloudFlareDomain cloudFlareDomain, CloudFlareDomain cloudFlareDomain,
) async { ) async {
var cloudflareApi = CloudflareApi(cloudFlareKey); var cloudflareApi = CloudflareApi(cloudFlareKey);
await cloudflareApi.createMultipleDnsRecords( await cloudflareApi.createMultipleDnsRecords(
ip4: ip4, ip4: ip4,
cloudFlareDomain: cloudFlareDomain, cloudFlareDomain: cloudFlareDomain,
); );
cloudflareApi.close();
}
Future<bool> isHttpServerWorking(String domainName) async {
var api = ServerApi(domainName);
var isHttpServerWorking = await api.isHttpServerWorking();
print('isHttpServerWorking: $isHttpServerWorking');
api.close();
return isHttpServerWorking;
}
Future<void> setDkim(
String domainName,
String cloudFlareKey,
String zoneId,
) async {
var api = ServerApi(domainName);
var dkimRecordString = await api.getDkim(domainName);
var cloudflareApi = CloudflareApi(cloudFlareKey);
await cloudflareApi.setDkim(dkimRecordString, zoneId);
box.put(BNames.isDkimSetted, true);
cloudflareApi.close(); cloudflareApi.close();
} }
} }

View File

@ -7,10 +7,12 @@ class AppConfigState extends Equatable {
this.cloudFlareDomain, this.cloudFlareDomain,
this.rootUser, this.rootUser,
this.hetznerServer, this.hetznerServer,
this.isDnsCheckedAndServerStarted = false,
this.isLoading = false, this.isLoading = false,
this.error, this.error,
this.lastDnsCheckTime, this.lastDnsCheckTime,
this.lastServerStatusCheckTime,
this.isDnsChecked = false,
this.isServerStarted = false,
this.isDkimSetted = false, this.isDkimSetted = false,
}); });
@ -25,6 +27,7 @@ class AppConfigState extends Equatable {
isLoading, isLoading,
error, error,
lastDnsCheckTime, lastDnsCheckTime,
lastServerStatusCheckTime,
isDkimSetted, isDkimSetted,
]; ];
@ -33,10 +36,11 @@ class AppConfigState extends Equatable {
final CloudFlareDomain cloudFlareDomain; final CloudFlareDomain cloudFlareDomain;
final User rootUser; final User rootUser;
final HetznerServerDetails hetznerServer; final HetznerServerDetails hetznerServer;
final bool isDnsCheckedAndServerStarted;
final bool isDkimSetted; final bool isDkimSetted;
final bool isServerStarted;
final bool isDnsChecked;
final DateTime lastDnsCheckTime; final DateTime lastDnsCheckTime;
final DateTime lastServerStatusCheckTime;
final bool isLoading; final bool isLoading;
final Exception error; final Exception error;
@ -46,11 +50,13 @@ class AppConfigState extends Equatable {
CloudFlareDomain cloudFlareDomain, CloudFlareDomain cloudFlareDomain,
User rootUser, User rootUser,
HetznerServerDetails hetznerServer, HetznerServerDetails hetznerServer,
bool isDnsCheckedAndServerStarted,
bool isLoading, bool isLoading,
Exception error, Exception error,
DateTime lastDnsCheckTime, DateTime lastDnsCheckTime,
DateTime lastServerStatusCheckTime,
bool isDkimSetted, bool isDkimSetted,
bool isServerStarted,
bool isDnsChecked,
}) => }) =>
AppConfigState( AppConfigState(
hetznerKey: hetznerKey ?? this.hetznerKey, hetznerKey: hetznerKey ?? this.hetznerKey,
@ -58,12 +64,14 @@ class AppConfigState extends Equatable {
cloudFlareDomain: cloudFlareDomain ?? this.cloudFlareDomain, cloudFlareDomain: cloudFlareDomain ?? this.cloudFlareDomain,
rootUser: rootUser ?? this.rootUser, rootUser: rootUser ?? this.rootUser,
hetznerServer: hetznerServer ?? this.hetznerServer, hetznerServer: hetznerServer ?? this.hetznerServer,
isDnsCheckedAndServerStarted: isServerStarted: isServerStarted ?? this.isServerStarted,
isDnsCheckedAndServerStarted ?? this.isDnsCheckedAndServerStarted, isDnsChecked: isDnsChecked ?? this.isDnsChecked,
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,
error: error ?? this.error, error: error ?? this.error,
lastDnsCheckTime: lastDnsCheckTime ?? this.lastDnsCheckTime, lastDnsCheckTime: lastDnsCheckTime ?? this.lastDnsCheckTime,
isDkimSetted: isDkimSetted, lastServerStatusCheckTime:
lastServerStatusCheckTime ?? this.lastServerStatusCheckTime,
isDkimSetted: isDkimSetted ?? this.isDkimSetted,
); );
bool get isHetznerFilled => hetznerKey != null; bool get isHetznerFilled => hetznerKey != null;
@ -73,6 +81,8 @@ class AppConfigState extends Equatable {
bool get isServerFilled => hetznerServer != null; bool get isServerFilled => hetznerServer != null;
bool get hasFinalChecked => isDnsCheckedAndServerStarted && isDkimSetted; bool get hasFinalChecked => isDnsCheckedAndServerStarted && isDkimSetted;
bool get isDnsCheckedAndServerStarted => isDnsChecked && isServerStarted;
bool get isFullyInitilized => _fulfilementList.every((el) => el); bool get isFullyInitilized => _fulfilementList.every((el) => el);
int get progress => _fulfilementList.where((el) => el).length; int get progress => _fulfilementList.where((el) => el).length;

View File

@ -25,7 +25,7 @@ class CloudFlareFormCubit extends FormCubit {
@override @override
FutureOr<void> onSubmit() async { FutureOr<void> onSubmit() async {
initializingCubit.setCloudFlare(apiKey.state.value); initializingCubit.setCloudflareKey(apiKey.state.value);
} }
final AppConfigCubit initializingCubit; final AppConfigCubit initializingCubit;

View File

@ -18,12 +18,12 @@ class HetznerServerDetails {
@HiveField(1) @HiveField(1)
final int id; final int id;
@HiveField(2)
final DateTime startTime;
@HiveField(3) @HiveField(3)
final DateTime createTime; final DateTime createTime;
@HiveField(2)
final DateTime startTime;
HetznerServerDetails copyWith({DateTime startTime}) { HetznerServerDetails copyWith({DateTime startTime}) {
return HetznerServerDetails( return HetznerServerDetails(
startTime: startTime ?? this.startTime, startTime: startTime ?? this.startTime,

View File

@ -19,8 +19,8 @@ class HetznerServerDetailsAdapter extends TypeAdapter<HetznerServerDetails> {
return HetznerServerDetails( return HetznerServerDetails(
ip4: fields[0] as String, ip4: fields[0] as String,
id: fields[1] as int, id: fields[1] as int,
createTime: fields[3] as DateTime,
startTime: fields[2] as DateTime, startTime: fields[2] as DateTime,
createTime: fields[3] as DateTime,
); );
} }

View File

@ -0,0 +1,55 @@
import 'package:flutter/foundation.dart';
class ServerStatus {
final StatusTypes http;
final StatusTypes imap;
final StatusTypes smtp;
ServerStatus({
@required this.http,
this.imap = StatusTypes.nodata,
this.smtp = StatusTypes.nodata,
});
ServerStatus fromJson(Map<String, dynamic> json) {
return ServerStatus(
http: statusTypeFromNumber(json['http']),
imap: statusTypeFromNumber(json['imap']),
smtp: statusTypeFromNumber(json['smtp']),
);
}
}
StatusTypes statusTypeFromNumber(int number) {
if (number == 0) {
return StatusTypes.ok;
} else if (number == 1) {
return StatusTypes.error;
} else if (number == 2) {
return StatusTypes.wrongArgument;
} else if (number == 3) {
return StatusTypes.wrongFunction;
} else if (number == 4) {
return StatusTypes.noRights;
} else if (number == 5) {
return StatusTypes.notInstalled;
} else if (number == 6) {
return StatusTypes.notConfigured;
} else if (number == 7) {
return StatusTypes.off;
} else {
throw Exception('wrong status');
}
}
enum StatusTypes {
ok,
error,
wrongArgument,
wrongFunction,
noRights,
notInstalled,
notConfigured,
off,
nodata,
}

View File

@ -73,7 +73,8 @@ class _BrandTimerState extends State<BrandTimer> {
String _durationToString(Duration duration) { String _durationToString(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0"); String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitSeconds = twoDigits(60 - duration.inSeconds.remainder(60)); String twoDigitSeconds =
twoDigits(widget.duration.inSeconds - duration.inSeconds.remainder(60));
return "$twoDigitSeconds cек"; return "$twoDigitSeconds cек";
} }

View File

@ -270,7 +270,8 @@ class InitializingPage extends StatelessWidget {
BrandText.body2('Создать сервер'), BrandText.body2('Создать сервер'),
Spacer(), Spacer(),
BrandButton.rised( BrandButton.rised(
onPressed: isLoading ? null : appConfigCubit.createServer, onPressed:
isLoading ? null : appConfigCubit.createServerAndSetDnsRecords,
title: isLoading ? 'loading' : 'Создать сервер', title: isLoading ? 'loading' : 'Создать сервер',
), ),
Spacer(flex: 2), Spacer(flex: 2),
@ -285,7 +286,7 @@ class InitializingPage extends StatelessWidget {
Widget _stepCheck(AppConfigCubit appConfigCubit) { Widget _stepCheck(AppConfigCubit appConfigCubit) {
var state = appConfigCubit.state; var state = appConfigCubit.state;
var isDnsChecked = appConfigCubit.state.isDnsCheckedAndServerStarted; var isDnsChecked = state.isDnsCheckedAndServerStarted;
return Builder(builder: (context) { return Builder(builder: (context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -303,19 +304,19 @@ class InitializingPage extends StatelessWidget {
BrandText.body2('До следующей проверки: '), BrandText.body2('До следующей проверки: '),
isDnsChecked isDnsChecked
? BrandTimer( ? BrandTimer(
startDateTime: startDateTime: state.lastServerStatusCheckTime ??
state.lastDnsCheckTime ?? state.hetznerServer.createTime, state.hetznerServer.startTime,
duration: Duration(minutes: 1), duration: Duration(minutes: 1),
callback: () { callback: () {
appConfigCubit.checkDns(); appConfigCubit.setDkim();
}, },
) )
: BrandTimer( : BrandTimer(
startDateTime: startDateTime: state.lastDnsCheckTime ??
state.lastDnsCheckTime ?? state.hetznerServer.createTime, state.hetznerServer.createTime,
duration: Duration(minutes: 1), duration: Duration(minutes: 1),
callback: () { callback: () {
appConfigCubit.checkDns(); appConfigCubit.checkDnsAndStartServer();
}, },
) )
], ],