fdroid
Kherel 2021-01-21 08:35:38 +01:00
parent 618e426333
commit 7de50dd237
11 changed files with 245 additions and 139 deletions

View File

@ -43,11 +43,11 @@ class BNames {
static String key = 'key'; static String key = 'key';
static String domain = 'domain'; static String cloudFlareDomain = 'cloudFlareDomain';
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 = 'server';
static String isDnsCheckedAndDkimSet = 'isDnsCheckedAndDkimSet'; static String isDnsChecked = 'isDnsChecked';
static String serverInitStart = 'serverInitStart'; static String serverInitStart = 'serverInitStart';
} }

View File

@ -61,7 +61,7 @@ class CloudflareApi extends ApiMap {
String ip4, String ip4,
CloudFlareDomain cloudFlareDomain, CloudFlareDomain cloudFlareDomain,
}) async { }) async {
var domainName = cloudFlareDomain.name; var domainName = cloudFlareDomain.domainName;
var domainZoneId = cloudFlareDomain.zoneId; var domainZoneId = cloudFlareDomain.zoneId;
var domainA = DnsRecords(type: 'A', name: domainName, content: ip4); var domainA = DnsRecords(type: 'A', name: domainName, content: ip4);
@ -82,7 +82,7 @@ class CloudflareApi extends ApiMap {
var txt2 = DnsRecords( var txt2 = DnsRecords(
type: 'TXT', type: 'TXT',
name: cloudFlareDomain.name, name: cloudFlareDomain.domainName,
content: 'v=spf1 a mx ip4:$ip4 -all', content: 'v=spf1 a mx ip4:$ip4 -all',
ttl: 18000, ttl: 18000,
); );

View File

@ -0,0 +1,12 @@
import 'package:dio/dio.dart';
import 'api_map.dart';
class ServerApi extends ApiMap {
ServerApi([String token]) {
if (token != null) {
loggedClient.options =
BaseOptions(headers: {'Authorization': 'Bearer $token'});
}
}
}

View File

@ -1,153 +1,97 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/cloud_flare.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/get_it/console.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart'; import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/message.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';
import 'package:basic_utils/basic_utils.dart';
import 'app_config_repository.dart';
part 'app_config_state.dart'; part 'app_config_state.dart';
class AppConfigCubit extends Cubit<AppConfigState> { class AppConfigCubit extends Cubit<AppConfigState> {
AppConfigCubit() : super(InitialAppConfigState()); AppConfigCubit() : super(InitialAppConfigState());
Box box = Hive.box(BNames.appConfig); final repository = AppConfigRepository();
void load() { void load() {
emit( var state = repository.load();
state.copyWith( emit(state);
hetznerKey: box.get(BNames.hetznerKey),
cloudFlareKey: box.get(BNames.cloudFlareKey),
domain: box.get(BNames.domain),
rootUser: box.get(BNames.rootUser),
hetznerServer: box.get(BNames.hetznerServer),
serverStarted: box.get(BNames.isDnsCheckedAndDkimSet),
),
);
} }
void reset() { void reset() {
box.clear(); repository.reset();
emit(InitialAppConfigState()); emit(InitialAppConfigState());
} }
void setHetznerKey(String key) { void setHetznerKey(String hetznerKey) {
box.put(BNames.hetznerKey, key); repository.saveHetznerKey(hetznerKey);
emit(state.copyWith(hetznerKey: key)); emit(state.copyWith(hetznerKey: hetznerKey));
} }
void setCloudFlare(String cloudFlareKey) { void setCloudFlare(String cloudFlareKey) {
box.put(BNames.cloudFlareKey, cloudFlareKey); repository.saveCloudFlare(cloudFlareKey);
emit(state.copyWith(cloudFlareKey: cloudFlareKey)); emit(state.copyWith(cloudFlareKey: cloudFlareKey));
} }
void setDomain(CloudFlareDomain domain) { void setDomain(CloudFlareDomain cloudFlareDomain) {
box.put(BNames.domain, domain); repository.saveDomain(cloudFlareDomain);
emit(state.copyWith(domain: domain)); emit(state.copyWith(cloudFlareDomain: cloudFlareDomain));
} }
void setRootUser(User rootUser) { void setRootUser(User rootUser) {
box.put(BNames.rootUser, rootUser); repository.saveRootUser(rootUser);
emit(state.copyWith(rootUser: rootUser)); emit(state.copyWith(rootUser: rootUser));
} }
Future<void> checkDns() async { Future<void> checkDns() async {
var ip4 = state.server.ip4; var ip4 = state.hetznerServer.ip4;
var domainName = state.cloudFlareDomain.name; var domainName = state.cloudFlareDomain.domainName;
var addresses = <String>[
'$domainName', var isMatch = await repository.isDnsAddressesMatch(domainName, ip4);
'api.$domainName',
'cloud.$domainName', if (isMatch) {
'meet.$domainName', var server = await repository.startServer(
'password.$domainName' state.hetznerKey,
]; state.hetznerServer,
var hasError = false;
for (var address in addresses) {
var res = await DnsUtils.lookupRecord(
address,
RRecordType.A,
provider: DnsApiProvider.CLOUDFLARE,
); );
getIt.get<ConsoleModel>().addMessage( repository.saveServerDetails(server);
Message(
text:
'DnsLookup: address: $address, $RRecordType, provider: CLOUDFLARE, ip4: $ip4',
),
);
getIt.get<ConsoleModel>().addMessage(
Message(
text:
'DnsLookup: ${res.isEmpty ? (res[0].data != ip4 ? 'wrong ip4' : 'right ip4') : 'empty'}',
),
);
if (res.isEmpty || res[0].data != ip4) {
hasError = true;
break;
}
}
if (hasError) {
emit(state.copyWith(lastDnsCheckTime: DateTime.now()));
} else {
var hetznerApi = HetznerApi(state.hetznerKey);
var serverDetails = await hetznerApi.startServer(server: state.server);
await box.put(BNames.hetznerServer, serverDetails);
hetznerApi.close();
emit( emit(
state.copyWith( state.copyWith(
serverStarted: true, isDnsCheckedAndServerStarted: true,
isLoading: false, isLoading: false,
hetznerServer: serverDetails, hetznerServer: server,
), ),
); );
} else {
emit(state.copyWith(lastDnsCheckTime: DateTime.now()));
} }
print('check complete: $hasError, time:' + DateTime.now().toString());
} }
void createServer() async { void createServer() async {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
var hetznerApi = HetznerApi(state.hetznerKey);
var cloudflareApi = CloudflareApi(state.cloudFlareKey);
HetznerServerDetails serverDetails;
try { try {
serverDetails = await hetznerApi.createServer( var serverDetails = await repository.createServer(
rootUser: state.rootUser, state.hetznerKey,
domainName: state.cloudFlareDomain.name, state.rootUser,
state.cloudFlareDomain.domainName,
); );
await repository.createDnsRecords(
state.cloudFlareKey,
serverDetails.ip4,
state.cloudFlareDomain,
);
emit(state.copyWith(
isLoading: false,
hetznerServer: serverDetails,
));
} catch (e) { } catch (e) {
addError(e); addError(e);
emit(state.copyWith(isLoading: false));
} }
try {
cloudflareApi
.createMultipleDnsRecords(
ip4: serverDetails.ip4,
cloudFlareDomain: state.cloudFlareDomain,
)
.then((_) => cloudflareApi.close());
} catch (e) {
addError(e);
}
await box.put(BNames.hetznerServer, serverDetails);
hetznerApi.close();
emit(state.copyWith(
isLoading: false,
hetznerServer: serverDetails,
));
} }
} }

View File

@ -0,0 +1,130 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/server_details.dart';
import 'package:selfprivacy/logic/models/user.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/get_it/console.dart';
import 'package:selfprivacy/logic/models/message.dart';
import 'package:basic_utils/basic_utils.dart';
import 'app_config_cubit.dart';
class AppConfigRepository {
Box box = Hive.box(BNames.appConfig);
AppConfigState load() {
return AppConfigState(
hetznerKey: box.get(BNames.hetznerKey),
cloudFlareKey: box.get(BNames.cloudFlareKey),
cloudFlareDomain: box.get(BNames.cloudFlareDomain),
rootUser: box.get(BNames.rootUser),
hetznerServer: box.get(BNames.hetznerServer),
isDnsCheckedAndServerStarted: box.get(BNames.isDnsChecked),
);
}
void reset() {
box.clear();
}
void saveHetznerKey(String key) {
box.put(BNames.hetznerKey, key);
}
void saveCloudFlare(String key) {
box.put(BNames.cloudFlareKey, key);
}
void saveDomain(CloudFlareDomain cloudFlareDomain) {
box.put(BNames.cloudFlareDomain, cloudFlareDomain);
}
void saveRootUser(User rootUser) {
box.put(BNames.rootUser, rootUser);
}
Future<HetznerServerDetails> startServer(
String hetznerKey,
HetznerServerDetails hetznerServer,
) async {
var hetznerApi = HetznerApi(hetznerKey);
var serverDetails = await hetznerApi.startServer(server: hetznerServer);
hetznerApi.close();
return serverDetails;
}
Future<void> saveServerDetails(HetznerServerDetails serverDetails) async {
await box.put(BNames.hetznerServer, serverDetails);
}
Future<bool> isDnsAddressesMatch(String domainName, String ip4) async {
var addresses = <String>[
'$domainName',
'api.$domainName',
'cloud.$domainName',
'meet.$domainName',
'password.$domainName'
];
for (var address in addresses) {
var lookupRecordRes = await DnsUtils.lookupRecord(
address,
RRecordType.A,
provider: DnsApiProvider.CLOUDFLARE,
);
getIt.get<ConsoleModel>().addMessage(
Message(
text:
'DnsLookup: address: $address, $RRecordType, provider: CLOUDFLARE, ip4: $ip4',
),
);
getIt.get<ConsoleModel>().addMessage(
Message(
text:
'DnsLookup: ${lookupRecordRes.isEmpty ? (lookupRecordRes[0].data != ip4 ? 'wrong ip4' : 'right ip4') : 'empty'}',
),
);
if (lookupRecordRes.isEmpty || lookupRecordRes[0].data != ip4) {
return false;
}
}
return true;
}
Future<HetznerServerDetails> createServer(
String hetznerKey,
User rootUser,
String domainName,
) async {
var hetznerApi = HetznerApi(hetznerKey);
var serverDetails = await hetznerApi.createServer(
rootUser: rootUser,
domainName: domainName,
);
await box.put(BNames.hetznerServer, serverDetails);
hetznerApi.close();
return serverDetails;
}
Future<void> createDnsRecords(
String cloudFlareKey,
String ip4,
CloudFlareDomain cloudFlareDomain,
) async {
var cloudflareApi = CloudflareApi(cloudFlareKey);
await cloudflareApi.createMultipleDnsRecords(
ip4: ip4,
cloudFlareDomain: cloudFlareDomain,
);
cloudflareApi.close();
}
}

View File

@ -6,11 +6,12 @@ class AppConfigState extends Equatable {
this.cloudFlareKey, this.cloudFlareKey,
this.cloudFlareDomain, this.cloudFlareDomain,
this.rootUser, this.rootUser,
this.server, this.hetznerServer,
this.isDnsChecked = false, this.isDnsCheckedAndServerStarted = false,
this.isLoading = false, this.isLoading = false,
this.error, this.error,
this.lastDnsCheckTime, this.lastDnsCheckTime,
this.isDkimSetted = false,
}); });
@override @override
@ -19,19 +20,21 @@ class AppConfigState extends Equatable {
cloudFlareKey, cloudFlareKey,
cloudFlareDomain, cloudFlareDomain,
rootUser, rootUser,
server, hetznerServer,
isDnsChecked, isDnsCheckedAndServerStarted,
isLoading, isLoading,
error, error,
lastDnsCheckTime lastDnsCheckTime,
isDkimSetted,
]; ];
final String hetznerKey; final String hetznerKey;
final String cloudFlareKey; final String cloudFlareKey;
final CloudFlareDomain cloudFlareDomain; final CloudFlareDomain cloudFlareDomain;
final User rootUser; final User rootUser;
final HetznerServerDetails server; final HetznerServerDetails hetznerServer;
final bool isDnsChecked; final bool isDnsCheckedAndServerStarted;
final bool isDkimSetted;
final DateTime lastDnsCheckTime; final DateTime lastDnsCheckTime;
final bool isLoading; final bool isLoading;
@ -40,31 +43,35 @@ class AppConfigState extends Equatable {
AppConfigState copyWith({ AppConfigState copyWith({
String hetznerKey, String hetznerKey,
String cloudFlareKey, String cloudFlareKey,
CloudFlareDomain domain, CloudFlareDomain cloudFlareDomain,
User rootUser, User rootUser,
HetznerServerDetails hetznerServer, HetznerServerDetails hetznerServer,
bool serverStarted, bool isDnsCheckedAndServerStarted,
bool isLoading, bool isLoading,
Exception error, Exception error,
DateTime lastDnsCheckTime, DateTime lastDnsCheckTime,
bool isDkimSetted,
}) => }) =>
AppConfigState( AppConfigState(
hetznerKey: hetznerKey ?? this.hetznerKey, hetznerKey: hetznerKey ?? this.hetznerKey,
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
cloudFlareDomain: domain ?? this.cloudFlareDomain, cloudFlareDomain: cloudFlareDomain ?? this.cloudFlareDomain,
rootUser: rootUser ?? this.rootUser, rootUser: rootUser ?? this.rootUser,
server: hetznerServer ?? this.server, hetznerServer: hetznerServer ?? this.hetznerServer,
isDnsChecked: serverStarted ?? this.isDnsChecked, isDnsCheckedAndServerStarted:
isDnsCheckedAndServerStarted ?? this.isDnsCheckedAndServerStarted,
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,
); );
bool get isHetznerFilled => hetznerKey != null; bool get isHetznerFilled => hetznerKey != null;
bool get isCloudFlareFilled => cloudFlareKey != null; bool get isCloudFlareFilled => cloudFlareKey != null;
bool get isDomainFilled => cloudFlareDomain != null; bool get isDomainFilled => cloudFlareDomain != null;
bool get isUserFilled => rootUser != null; bool get isUserFilled => rootUser != null;
bool get isServerFilled => server != null; bool get isServerFilled => hetznerServer != null;
bool get hasFinalChecked => isDnsCheckedAndServerStarted && isDkimSetted;
bool get isFullyInitilized => _fulfilementList.every((el) => el); bool get isFullyInitilized => _fulfilementList.every((el) => el);
@ -76,7 +83,7 @@ class AppConfigState extends Equatable {
isDomainFilled, isDomainFilled,
isUserFilled, isUserFilled,
isServerFilled, isServerFilled,
isDnsChecked, hasFinalChecked,
]; ];
} }

View File

@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/cloud_flare.dart'; import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';

View File

@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/cloud_flare.dart'; import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart'; import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
@ -16,7 +16,9 @@ class DomainFormCubit extends FormCubit {
validations: [ validations: [
RequiredStringValidation('required'), RequiredStringValidation('required'),
ValidationModel<String>( ValidationModel<String>(
(s) => !regExp.hasMatch(s), 'invalid domain format'), (s) => !regExp.hasMatch(s),
'invalid domain format',
),
], ],
); );
@ -26,7 +28,7 @@ class DomainFormCubit extends FormCubit {
@override @override
FutureOr<void> onSubmit() async { FutureOr<void> onSubmit() async {
var domain = CloudFlareDomain( var domain = CloudFlareDomain(
name: domainName.state.value, domainName: domainName.state.value,
zoneId: zoneId, zoneId: zoneId,
); );
initializingCubit.setDomain(domain); initializingCubit.setDomain(domain);

View File

@ -4,16 +4,16 @@ part 'cloudflare_domain.g.dart';
@HiveType(typeId: 3) @HiveType(typeId: 3)
class CloudFlareDomain { class CloudFlareDomain {
CloudFlareDomain({this.name, this.zoneId}); CloudFlareDomain({this.domainName, this.zoneId});
@HiveField(0) @HiveField(0)
final String name; final String domainName;
@HiveField(1) @HiveField(1)
final String zoneId; final String zoneId;
@override @override
String toString() { String toString() {
return '$name: $zoneId'; return '$domainName: $zoneId';
} }
} }

View File

@ -17,7 +17,7 @@ class CloudFlareDomainAdapter extends TypeAdapter<CloudFlareDomain> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return CloudFlareDomain( return CloudFlareDomain(
name: fields[0] as String, domainName: fields[0] as String,
zoneId: fields[1] as String, zoneId: fields[1] as String,
); );
} }
@ -27,7 +27,7 @@ class CloudFlareDomainAdapter extends TypeAdapter<CloudFlareDomain> {
writer writer
..writeByte(2) ..writeByte(2)
..writeByte(0) ..writeByte(0)
..write(obj.name) ..write(obj.domainName)
..writeByte(1) ..writeByte(1)
..write(obj.zoneId); ..write(obj.zoneId);
} }

View File

@ -181,7 +181,6 @@ class InitializingPage extends StatelessWidget {
create: (context) => DomainFormCubit(initializingCubit), create: (context) => DomainFormCubit(initializingCubit),
child: Builder(builder: (context) { child: Builder(builder: (context) {
var formCubit = context.watch<DomainFormCubit>(); var formCubit = context.watch<DomainFormCubit>();
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -286,6 +285,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;
return Builder(builder: (context) { return Builder(builder: (context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -293,20 +293,31 @@ class InitializingPage extends StatelessWidget {
Spacer(flex: 2), Spacer(flex: 2),
SizedBox(height: 10), SizedBox(height: 10),
BrandText.body2( BrandText.body2(
'Мы начали процесс инциализации сервера, раз в минуты мы будем проверять наличие DNS записей, как только они вступят в силу мы закончим инциализацию', isDnsChecked
? 'Dns сервера вступили в силу, мы стартанули сервер, как только он поднимиться, мы закончим инициализацию.'
: 'Мы начали процесс инциализации сервера, раз в минуты мы будем проверять наличие DNS записей, как только они вступят в силу мы продолжим инциализацию',
), ),
SizedBox(height: 10), SizedBox(height: 10),
Row( Row(
children: [ children: [
BrandText.body2('До следующей проверки: '), BrandText.body2('До следующей проверки: '),
BrandTimer( isDnsChecked
startDateTime: ? BrandTimer(
state.lastDnsCheckTime ?? state.server.createTime, startDateTime:
duration: Duration(minutes: 1), state.lastDnsCheckTime ?? state.hetznerServer.createTime,
callback: () { duration: Duration(minutes: 1),
appConfigCubit.checkDns(); callback: () {
}, appConfigCubit.checkDns();
) },
)
: BrandTimer(
startDateTime:
state.lastDnsCheckTime ?? state.hetznerServer.createTime,
duration: Duration(minutes: 1),
callback: () {
appConfigCubit.checkDns();
},
)
], ],
), ),
Spacer( Spacer(