Merge branch 'master' into dynamic-dns

pull/392/head
NaiJi ✨ 2023-12-18 08:46:19 +04:00
commit f1b0b3fc9e
28 changed files with 181 additions and 42 deletions

View File

@ -0,0 +1,6 @@
### How to point Name Servers for Cloudflare DNS
1. Visit the following [link](https://dash.cloudflare.com) and sign
into your Cloudflare account.
2. Enter DNS settings of your domain.
3. Copy your NS records and paste them into a Nameservers section of your domain registar settings.
4. For more specific instructions for each of commonly used registars, follow the Cloudflare [guide](https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/).

View File

@ -0,0 +1,6 @@
### How to point Name Servers for deSEC DNS
1. Visit the following [link](https://desec.io/domains) and sign
into your deSEC account.
2. Click on the "Setup instructions" icon on the right side of your domain card, in the "Actions" section.
3. Copy your NS records and paste them into a Nameservers section of your domain registar settings.
4. For more specific instructions follow the official deSEC guide listed on the page.

View File

@ -0,0 +1,8 @@
### How to point Name Servers for Digital Ocean DNS
1. Visit the following [link](https://cloud.digitalocean.com/) and sign
into your Digital Ocean account.
2. Enter the [Networking](https://cloud.digitalocean.com/networking/domains) tab from the menu bar on the left.
3. Make sure you are on the [Domain](https://cloud.digitalocean.com/networking/domains) section of the tab, which is the very first one.
4. Click on your domain card, the one you have selected for SelfPrivacy.
5. Copy your NS records and paste them into a Nameservers section of your domain registar settings.
6. For more specific instructions for each of commonly used registars, follow the Digital Ocean [guide](https://docs.digitalocean.com/products/networking/dns/getting-started/dns-registrars/).

View File

@ -452,6 +452,7 @@
"server_rebooted": "Server rebooted. Waiting for the last verification…",
"server_started": "Server started. It will be validated and rebooted now…",
"server_created": "Server created. DNS checks and server boot in progress…",
"domain_critical_error": "We can't reach this domain! Tap to read more…",
"until_the_next_check": "Until the next check: ",
"check": "Check",
"one_more_restart": "One more restart to apply your security certificates.",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@ -63,8 +63,8 @@ class BackupsCubit extends ServerInstallationDependendCubit<BackupsState> {
.state.serverDomain!.domainName
.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
final int serverId = serverInstallationCubit.state.serverDetails!.id;
String bucketName = 'selfprivacy-$domain-$serverId';
// If bucket name is too long, shorten it
String bucketName =
'${DateTime.now().millisecondsSinceEpoch}-$serverId-$domain';
if (bucketName.length > 49) {
bucketName = bucketName.substring(0, 49);
}

View File

@ -21,7 +21,7 @@ class ApiDevicesCubit
}
Future<void> refresh() async {
emit(const ApiDevicesState([], LoadingStatus.refreshing));
emit(ApiDevicesState([state.thisDevice], LoadingStatus.refreshing));
_refetch();
}

View File

@ -23,6 +23,7 @@ import 'package:selfprivacy/logic/models/server_type.dart';
import 'package:selfprivacy/logic/providers/provider_settings.dart';
import 'package:selfprivacy/logic/providers/providers_controller.dart';
import 'package:selfprivacy/ui/helpers/modals.dart';
import 'package:selfprivacy/utils/network_utils.dart';
export 'package:provider/provider.dart';
@ -321,13 +322,16 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final String ip4 = dataState.serverDetails!.ip4;
final String domainName = dataState.serverDomain!.domainName;
final Map<String, bool> matches = await repository.isDnsAddressesMatch(
final Map<String, DnsRecordStatus> matches = await validateDnsMatch(
domainName,
['api'],
ip4,
dataState.dnsMatches ?? {},
);
if (matches.values.every((final bool value) => value)) {
if (matches.values.every(
(final DnsRecordStatus value) => value == DnsRecordStatus.ok,
) &&
matches.values.isNotEmpty) {
final ServerHostingDetails server = await repository.startServer(
dataState.serverDetails!,
);

View File

@ -212,33 +212,6 @@ class ServerInstallationRepository {
return domainResult.data.contains(domain);
}
Future<Map<String, bool>> isDnsAddressesMatch(
final String? domainName,
final String? ip4,
final Map<String, bool> skippedMatches,
) async {
final Map<String, bool> matches = <String, bool>{};
try {
await InternetAddress.lookup(domainName!).then(
(final records) {
for (final record in records) {
if (skippedMatches[record.host] ?? false) {
matches[record.host] = true;
continue;
}
if (record.address == ip4!) {
matches[record.host] = true;
}
}
},
);
} catch (e) {
print(e);
}
return matches;
}
Future<void> createDkimRecord(final ServerDomain domain) async {
final ServerApi api = ServerApi();

View File

@ -145,7 +145,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
super.installationDialoguePopUp,
});
final bool isLoading;
final Map<String, bool>? dnsMatches;
final Map<String, DnsRecordStatus>? dnsMatches;
@override
List<Object?> get props => [
@ -175,7 +175,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
final bool? isServerResetedFirstTime,
final bool? isServerResetedSecondTime,
final bool? isLoading,
final Map<String, bool>? dnsMatches,
final Map<String, DnsRecordStatus>? dnsMatches,
final CallbackDialogueBranching? installationDialoguePopUp,
}) =>
ServerInstallationNotFinished(

View File

@ -79,6 +79,7 @@ class HetznerServerTypeInfo {
this.prices,
this.name,
this.description,
this.architecture,
);
final int cores;
final num memory;
@ -86,6 +87,7 @@ class HetznerServerTypeInfo {
final String name;
final String description;
final String architecture;
final List<HetznerPriceInfo> prices;

View File

@ -83,6 +83,7 @@ HetznerServerTypeInfo _$HetznerServerTypeInfoFromJson(
.toList(),
json['name'] as String,
json['description'] as String,
json['architecture'] as String,
);
Map<String, dynamic> _$HetznerServerTypeInfoToJson(
@ -93,6 +94,7 @@ Map<String, dynamic> _$HetznerServerTypeInfoToJson(
'disk': instance.disk,
'name': instance.name,
'description': instance.description,
'architecture': instance.architecture,
'prices': instance.prices,
};

View File

@ -39,6 +39,9 @@ class CloudflareDnsProvider extends DnsProvider {
@override
DnsProviderType get type => DnsProviderType.cloudflare;
@override
String get howToRegistar => 'how_fix_domain_cloudflare';
@override
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
final api = _adapter.api(getInitialized: false);

View File

@ -34,6 +34,9 @@ class DesecDnsProvider extends DnsProvider {
@override
DnsProviderType get type => DnsProviderType.desec;
@override
String get howToRegistar => 'how_fix_domain_desec';
@override
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
final api = _adapter.api(getInitialized: false);

View File

@ -34,6 +34,9 @@ class DigitalOceanDnsProvider extends DnsProvider {
@override
DnsProviderType get type => DnsProviderType.digitalOcean;
@override
String get howToRegistar => 'how_fix_domain_digital_ocean';
@override
Future<GenericResult<bool>> tryInitApiByToken(final String token) async {
final api = _adapter.api(getInitialized: false);

View File

@ -9,6 +9,10 @@ abstract class DnsProvider {
/// provider implements [DnsProvider] interface.
DnsProviderType get type;
/// Returns a full url to a guide on how to setup
/// DNS provider nameservers
String get howToRegistar;
/// Tries to access an account linked to the provided token.
///
/// To generate a token for your account follow instructions of your

View File

@ -482,6 +482,9 @@ class HetznerServerProvider extends ServerProvider {
final rawTypes = result.data;
for (final rawType in rawTypes) {
if (rawType.architecture == 'arm') {
continue;
}
for (final rawPrice in rawType.prices) {
if (rawPrice.location == location.identifier) {
types.add(

View File

@ -91,6 +91,12 @@ class _DevicesInfo extends StatelessWidget {
color: Theme.of(context).colorScheme.secondary,
),
),
if (devicesStatus.status == LoadingStatus.refreshing) ...[
const Center(
heightFactor: 4,
child: CircularProgressIndicator(),
),
],
...devicesStatus.otherDevices
.map((final device) => _DeviceTile(device: device)),
],

View File

@ -44,10 +44,8 @@ class MorePage extends StatelessWidget {
title: 'storage.start_migration_button'.tr(),
iconData: Icons.drive_file_move_outline,
goTo: () => ServicesMigrationRoute(
diskStatus: context
.watch<ApiServerVolumeCubit>()
.state
.diskStatus,
diskStatus:
context.read<ApiServerVolumeCubit>().state.diskStatus,
services: context
.read<ServicesCubit>()
.state

View File

@ -0,0 +1,61 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
import 'package:selfprivacy/ui/components/cards/filled_card.dart';
class BrokenDomainOutlinedCard extends StatelessWidget {
const BrokenDomainOutlinedCard({
required this.domain,
required this.dnsProvider,
super.key,
});
final String domain;
final DnsProvider dnsProvider;
@override
Widget build(final BuildContext context) => SizedBox(
width: double.infinity,
child: FilledCard(
error: true,
child: InkResponse(
highlightShape: BoxShape.rectangle,
onTap: () => context.read<SupportSystemCubit>().showArticle(
article: dnsProvider.howToRegistar,
context: context,
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.error,
color: Theme.of(context).colorScheme.error,
size: 24.0,
),
const SizedBox(width: 12.0),
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
domain,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyLarge,
),
Text('initializing.domain_critical_error'.tr()),
],
),
),
],
),
),
),
),
);
}

View File

@ -10,6 +10,7 @@ import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_fo
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart';
import 'package:selfprivacy/logic/providers/providers_controller.dart';
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
import 'package:selfprivacy/ui/components/buttons/outlined_button.dart';
@ -17,6 +18,7 @@ import 'package:selfprivacy/ui/components/drawers/progress_drawer.dart';
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
import 'package:selfprivacy/ui/components/drawers/support_drawer.dart';
import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart';
import 'package:selfprivacy/ui/pages/setup/initializing/broken_domain_outlined_card.dart';
import 'package:selfprivacy/ui/pages/setup/initializing/dns_provider_picker.dart';
import 'package:selfprivacy/ui/pages/setup/initializing/domain_picker.dart';
import 'package:selfprivacy/ui/pages/setup/initializing/server_provider_picker.dart';
@ -24,6 +26,7 @@ import 'package:selfprivacy/ui/pages/setup/initializing/server_type_picker.dart'
import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart';
import 'package:selfprivacy/ui/router/router.dart';
import 'package:selfprivacy/utils/breakpoints.dart';
import 'package:selfprivacy/utils/network_utils.dart';
@RoutePage()
class InitializingPage extends StatelessWidget {
@ -493,12 +496,17 @@ class InitializingPage extends StatelessWidget {
Column(
children: state.dnsMatches!.entries.map((final entry) {
final String domain = entry.key;
final bool isCorrect = entry.value;
if (entry.value == DnsRecordStatus.nonexistent) {
return BrokenDomainOutlinedCard(
domain: domain,
dnsProvider: ProvidersController.currentDnsProvider!,
);
}
return Row(
children: [
if (isCorrect)
if (entry.value == DnsRecordStatus.ok)
const Icon(Icons.check, color: Colors.green),
if (!isCorrect)
if (entry.value == DnsRecordStatus.waiting)
const Icon(Icons.schedule, color: Colors.amber),
const SizedBox(width: 10),
Text(domain),

View File

@ -1,6 +1,54 @@
import 'dart:io';
import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:url_launcher/url_launcher.dart';
enum DnsRecordStatus { ok, waiting, nonexistent }
/// Check if DNS records were recognized.
///
/// Return pairs of full record name matched to its status.
///
/// If no record found, return just one pair of [domain] matched to critical non-existent status.
///
/// - [domain] - full domain delegated to SelfPrivacy (e.g. reimu.love)
/// - [subdomains] - list of all subdomains we want to validate recods of (e.g. api, cloud...)
/// - [ip4] - IP address of our server we want to validate DNS records by (e.g. 127.0.0.1)
Future<Map<String, DnsRecordStatus>> validateDnsMatch(
final String domain,
final List<String> subdomains,
final String ip4,
) async {
final Map<String, DnsRecordStatus> matches = <String, DnsRecordStatus>{};
Future<void> lookup(final String address) async {
await InternetAddress.lookup(address).then(
(final records) {
for (final record in records) {
final bool isIpCorrect = record.address == ip4;
matches[record.host] =
isIpCorrect ? DnsRecordStatus.ok : DnsRecordStatus.waiting;
}
},
);
}
try {
await lookup(domain);
for (final subdomain in subdomains) {
await lookup('$subdomain.$domain');
}
} catch (e) {
print(e);
}
if (matches.isEmpty) {
matches[domain] = DnsRecordStatus.nonexistent;
}
return matches;
}
DnsRecord? extractDkimRecord(final List<DnsRecord> records) {
DnsRecord? dkimRecord;