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 domain = 'domain';
static String cloudFlareDomain = 'cloudFlareDomain';
static String hetznerKey = 'hetznerKey';
static String cloudFlareKey = 'cloudFlareKey';
static String rootUser = 'rootUser';
static String hetznerServer = 'server';
static String isDnsCheckedAndDkimSet = 'isDnsCheckedAndDkimSet';
static String isDnsChecked = 'isDnsChecked';
static String serverInitStart = 'serverInitStart';
}

View File

@ -61,7 +61,7 @@ class CloudflareApi extends ApiMap {
String ip4,
CloudFlareDomain cloudFlareDomain,
}) async {
var domainName = cloudFlareDomain.name;
var domainName = cloudFlareDomain.domainName;
var domainZoneId = cloudFlareDomain.zoneId;
var domainA = DnsRecords(type: 'A', name: domainName, content: ip4);
@ -82,7 +82,7 @@ class CloudflareApi extends ApiMap {
var txt2 = DnsRecords(
type: 'TXT',
name: cloudFlareDomain.name,
name: cloudFlareDomain.domainName,
content: 'v=spf1 a mx ip4:$ip4 -all',
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: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/message.dart';
import 'package:selfprivacy/logic/models/server_details.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';
class AppConfigCubit extends Cubit<AppConfigState> {
AppConfigCubit() : super(InitialAppConfigState());
Box box = Hive.box(BNames.appConfig);
final repository = AppConfigRepository();
void load() {
emit(
state.copyWith(
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),
),
);
var state = repository.load();
emit(state);
}
void reset() {
box.clear();
repository.reset();
emit(InitialAppConfigState());
}
void setHetznerKey(String key) {
box.put(BNames.hetznerKey, key);
emit(state.copyWith(hetznerKey: key));
void setHetznerKey(String hetznerKey) {
repository.saveHetznerKey(hetznerKey);
emit(state.copyWith(hetznerKey: hetznerKey));
}
void setCloudFlare(String cloudFlareKey) {
box.put(BNames.cloudFlareKey, cloudFlareKey);
repository.saveCloudFlare(cloudFlareKey);
emit(state.copyWith(cloudFlareKey: cloudFlareKey));
}
void setDomain(CloudFlareDomain domain) {
box.put(BNames.domain, domain);
emit(state.copyWith(domain: domain));
void setDomain(CloudFlareDomain cloudFlareDomain) {
repository.saveDomain(cloudFlareDomain);
emit(state.copyWith(cloudFlareDomain: cloudFlareDomain));
}
void setRootUser(User rootUser) {
box.put(BNames.rootUser, rootUser);
repository.saveRootUser(rootUser);
emit(state.copyWith(rootUser: rootUser));
}
Future<void> checkDns() async {
var ip4 = state.server.ip4;
var domainName = state.cloudFlareDomain.name;
var addresses = <String>[
'$domainName',
'api.$domainName',
'cloud.$domainName',
'meet.$domainName',
'password.$domainName'
];
var hasError = false;
for (var address in addresses) {
var res = await DnsUtils.lookupRecord(
address,
RRecordType.A,
provider: DnsApiProvider.CLOUDFLARE,
var ip4 = state.hetznerServer.ip4;
var domainName = state.cloudFlareDomain.domainName;
var isMatch = await repository.isDnsAddressesMatch(domainName, ip4);
if (isMatch) {
var server = await repository.startServer(
state.hetznerKey,
state.hetznerServer,
);
getIt.get<ConsoleModel>().addMessage(
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();
repository.saveServerDetails(server);
emit(
state.copyWith(
serverStarted: true,
isDnsCheckedAndServerStarted: true,
isLoading: false,
hetznerServer: serverDetails,
hetznerServer: server,
),
);
} else {
emit(state.copyWith(lastDnsCheckTime: DateTime.now()));
}
print('check complete: $hasError, time:' + DateTime.now().toString());
}
void createServer() async {
emit(state.copyWith(isLoading: true));
var hetznerApi = HetznerApi(state.hetznerKey);
var cloudflareApi = CloudflareApi(state.cloudFlareKey);
HetznerServerDetails serverDetails;
try {
serverDetails = await hetznerApi.createServer(
rootUser: state.rootUser,
domainName: state.cloudFlareDomain.name,
var serverDetails = await repository.createServer(
state.hetznerKey,
state.rootUser,
state.cloudFlareDomain.domainName,
);
await repository.createDnsRecords(
state.cloudFlareKey,
serverDetails.ip4,
state.cloudFlareDomain,
);
emit(state.copyWith(
isLoading: false,
hetznerServer: serverDetails,
));
} catch (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.cloudFlareDomain,
this.rootUser,
this.server,
this.isDnsChecked = false,
this.hetznerServer,
this.isDnsCheckedAndServerStarted = false,
this.isLoading = false,
this.error,
this.lastDnsCheckTime,
this.isDkimSetted = false,
});
@override
@ -19,19 +20,21 @@ class AppConfigState extends Equatable {
cloudFlareKey,
cloudFlareDomain,
rootUser,
server,
isDnsChecked,
hetznerServer,
isDnsCheckedAndServerStarted,
isLoading,
error,
lastDnsCheckTime
lastDnsCheckTime,
isDkimSetted,
];
final String hetznerKey;
final String cloudFlareKey;
final CloudFlareDomain cloudFlareDomain;
final User rootUser;
final HetznerServerDetails server;
final bool isDnsChecked;
final HetznerServerDetails hetznerServer;
final bool isDnsCheckedAndServerStarted;
final bool isDkimSetted;
final DateTime lastDnsCheckTime;
final bool isLoading;
@ -40,31 +43,35 @@ class AppConfigState extends Equatable {
AppConfigState copyWith({
String hetznerKey,
String cloudFlareKey,
CloudFlareDomain domain,
CloudFlareDomain cloudFlareDomain,
User rootUser,
HetznerServerDetails hetznerServer,
bool serverStarted,
bool isDnsCheckedAndServerStarted,
bool isLoading,
Exception error,
DateTime lastDnsCheckTime,
bool isDkimSetted,
}) =>
AppConfigState(
hetznerKey: hetznerKey ?? this.hetznerKey,
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
cloudFlareDomain: domain ?? this.cloudFlareDomain,
cloudFlareDomain: cloudFlareDomain ?? this.cloudFlareDomain,
rootUser: rootUser ?? this.rootUser,
server: hetznerServer ?? this.server,
isDnsChecked: serverStarted ?? this.isDnsChecked,
hetznerServer: hetznerServer ?? this.hetznerServer,
isDnsCheckedAndServerStarted:
isDnsCheckedAndServerStarted ?? this.isDnsCheckedAndServerStarted,
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
lastDnsCheckTime: lastDnsCheckTime ?? this.lastDnsCheckTime,
isDkimSetted: isDkimSetted,
);
bool get isHetznerFilled => hetznerKey != null;
bool get isCloudFlareFilled => cloudFlareKey != null;
bool get isDomainFilled => cloudFlareDomain != 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);
@ -76,7 +83,7 @@ class AppConfigState extends Equatable {
isDomainFilled,
isUserFilled,
isServerFilled,
isDnsChecked,
hasFinalChecked,
];
}

View File

@ -1,7 +1,7 @@
import 'dart:async';
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/forms/validations/validations.dart';

View File

@ -1,7 +1,7 @@
import 'dart:async';
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/models/cloudflare_domain.dart';
@ -16,7 +16,9 @@ class DomainFormCubit extends FormCubit {
validations: [
RequiredStringValidation('required'),
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
FutureOr<void> onSubmit() async {
var domain = CloudFlareDomain(
name: domainName.state.value,
domainName: domainName.state.value,
zoneId: zoneId,
);
initializingCubit.setDomain(domain);

View File

@ -4,16 +4,16 @@ part 'cloudflare_domain.g.dart';
@HiveType(typeId: 3)
class CloudFlareDomain {
CloudFlareDomain({this.name, this.zoneId});
CloudFlareDomain({this.domainName, this.zoneId});
@HiveField(0)
final String name;
final String domainName;
@HiveField(1)
final String zoneId;
@override
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(),
};
return CloudFlareDomain(
name: fields[0] as String,
domainName: fields[0] as String,
zoneId: fields[1] as String,
);
}
@ -27,7 +27,7 @@ class CloudFlareDomainAdapter extends TypeAdapter<CloudFlareDomain> {
writer
..writeByte(2)
..writeByte(0)
..write(obj.name)
..write(obj.domainName)
..writeByte(1)
..write(obj.zoneId);
}

View File

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