mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-23 09:16:54 +00:00
chore: Implement basics of hetzner installation logic
This commit is contained in:
parent
e739f7ab9d
commit
8da7341ccb
|
@ -429,6 +429,7 @@
|
||||||
"modals": {
|
"modals": {
|
||||||
"dns_removal_error": "Couldn't remove DNS records.",
|
"dns_removal_error": "Couldn't remove DNS records.",
|
||||||
"server_deletion_error": "Couldn't delete active server.",
|
"server_deletion_error": "Couldn't delete active server.",
|
||||||
|
"volume_creation_error": "Couldn't create volume.",
|
||||||
"server_validators_error": "Couldn't fetch available servers.",
|
"server_validators_error": "Couldn't fetch available servers.",
|
||||||
"already_exists": "Such server already exists.",
|
"already_exists": "Such server already exists.",
|
||||||
"unexpected_error": "Unexpected error during placement from the provider side.",
|
"unexpected_error": "Unexpected error during placement from the provider side.",
|
||||||
|
@ -479,4 +480,4 @@
|
||||||
"length_not_equal": "Length is [], should be {}",
|
"length_not_equal": "Length is [], should be {}",
|
||||||
"length_longer": "Length is [], should be shorter than or equal to {}"
|
"length_longer": "Length is [], should be shorter than or equal to {}"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -340,34 +340,72 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GenericResult<ServerHostingDetails?>> createServer({
|
Future<GenericResult> createServer({
|
||||||
required final String dnsApiToken,
|
required final String dnsApiToken,
|
||||||
|
required final String dnsProviderType,
|
||||||
|
required final String serverApiToken,
|
||||||
required final User rootUser,
|
required final User rootUser,
|
||||||
|
required final String base64Password,
|
||||||
|
required final String databasePassword,
|
||||||
required final String domainName,
|
required final String domainName,
|
||||||
|
required final String hostName,
|
||||||
|
required final int volumeId,
|
||||||
required final String serverType,
|
required final String serverType,
|
||||||
required final DnsProviderType dnsProvider,
|
|
||||||
}) async {
|
}) async {
|
||||||
final GenericResult<ServerVolume?> newVolumeResponse = await createVolume();
|
final String stagingAcme = StagingOptions.stagingAcme ? 'true' : 'false';
|
||||||
|
|
||||||
if (!newVolumeResponse.success || newVolumeResponse.data == null) {
|
Response? serverCreateResponse;
|
||||||
return GenericResult(
|
DioError? hetznerError;
|
||||||
data: null,
|
bool success = false;
|
||||||
success: false,
|
final Dio client = await getClient();
|
||||||
message: newVolumeResponse.message,
|
try {
|
||||||
code: newVolumeResponse.code,
|
final Map<String, Object> data = {
|
||||||
);
|
'name': hostName,
|
||||||
|
'server_type': serverType,
|
||||||
|
'start_after_create': false,
|
||||||
|
'image': 'ubuntu-20.04',
|
||||||
|
'volumes': [volumeId],
|
||||||
|
'networks': [],
|
||||||
|
'user_data': '#cloud-config\n'
|
||||||
|
'runcmd:\n'
|
||||||
|
'- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/providers/hetzner/nixos-infect | '
|
||||||
|
"STAGING_ACME='$stagingAcme' PROVIDER=$infectProviderName DNS_PROVIDER_TYPE=$dnsProviderType "
|
||||||
|
"NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' "
|
||||||
|
'CF_TOKEN=$dnsApiToken DB_PASSWORD=$databasePassword API_TOKEN=$serverApiToken HOSTNAME=$hostName bash 2>&1 | '
|
||||||
|
'tee /tmp/infect.log',
|
||||||
|
'labels': {},
|
||||||
|
'automount': true,
|
||||||
|
'location': region!,
|
||||||
|
};
|
||||||
|
print('Decoded data: $data');
|
||||||
|
|
||||||
|
serverCreateResponse = await client.post('/servers', data: data);
|
||||||
|
success = true;
|
||||||
|
} on DioError catch (e) {
|
||||||
|
print(e);
|
||||||
|
hetznerError = e;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
} finally {
|
||||||
|
close(client);
|
||||||
}
|
}
|
||||||
return createServerWithVolume(
|
|
||||||
dnsApiToken: dnsApiToken,
|
String? apiResultMessage = serverCreateResponse?.statusMessage;
|
||||||
rootUser: rootUser,
|
if (hetznerError != null &&
|
||||||
domainName: domainName,
|
hetznerError.response!.data['error']['code'] == 'uniqueness_error') {
|
||||||
volume: newVolumeResponse.data!,
|
apiResultMessage = 'uniqueness_error';
|
||||||
serverType: serverType,
|
}
|
||||||
dnsProvider: dnsProvider,
|
|
||||||
|
return GenericResult(
|
||||||
|
data: serverCreateResponse?.data,
|
||||||
|
success: success && hetznerError == null,
|
||||||
|
code: serverCreateResponse?.statusCode ??
|
||||||
|
hetznerError?.response?.statusCode,
|
||||||
|
message: apiResultMessage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GenericResult<ServerHostingDetails?>> createServerWithVolume({
|
Future<GenericResult<ServerHostingDetails?>> skldfjalkdsjflkasd({
|
||||||
required final String dnsApiToken,
|
required final String dnsApiToken,
|
||||||
required final User rootUser,
|
required final User rootUser,
|
||||||
required final String domainName,
|
required final String domainName,
|
||||||
|
@ -375,8 +413,6 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
required final String serverType,
|
required final String serverType,
|
||||||
required final DnsProviderType dnsProvider,
|
required final DnsProviderType dnsProvider,
|
||||||
}) async {
|
}) async {
|
||||||
final Dio client = await getClient();
|
|
||||||
|
|
||||||
final String dbPassword = StringGenerators.dbPassword();
|
final String dbPassword = StringGenerators.dbPassword();
|
||||||
final int volumeId = volume.id;
|
final int volumeId = volume.id;
|
||||||
|
|
||||||
|
@ -388,14 +424,11 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
base64.encode(utf8.encode(rootUser.password ?? 'PASS'));
|
base64.encode(utf8.encode(rootUser.password ?? 'PASS'));
|
||||||
final String dnsProviderType = dnsProviderToInfectName(dnsProvider);
|
final String dnsProviderType = dnsProviderToInfectName(dnsProvider);
|
||||||
|
|
||||||
final String userdataString =
|
|
||||||
"#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | STAGING_ACME='$stagingAcme' PROVIDER=$infectProviderName DNS_PROVIDER_TYPE=$dnsProviderType NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$dnsApiToken DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$hostname bash 2>&1 | tee /tmp/infect.log";
|
|
||||||
|
|
||||||
Response? serverCreateResponse;
|
Response? serverCreateResponse;
|
||||||
ServerHostingDetails? serverDetails;
|
ServerHostingDetails? serverDetails;
|
||||||
DioError? hetznerError;
|
DioError? hetznerError;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
final Dio client = await getClient();
|
||||||
try {
|
try {
|
||||||
final Map<String, Object> data = {
|
final Map<String, Object> data = {
|
||||||
'name': hostname,
|
'name': hostname,
|
||||||
|
@ -404,7 +437,13 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
|
||||||
'image': 'ubuntu-20.04',
|
'image': 'ubuntu-20.04',
|
||||||
'volumes': [volumeId],
|
'volumes': [volumeId],
|
||||||
'networks': [],
|
'networks': [],
|
||||||
'user_data': userdataString,
|
'user_data': '#cloud-config\n'
|
||||||
|
'runcmd:\n'
|
||||||
|
'- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | '
|
||||||
|
"STAGING_ACME='$stagingAcme' PROVIDER=$infectProviderName DNS_PROVIDER_TYPE=$dnsProviderType "
|
||||||
|
"NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' "
|
||||||
|
'CF_TOKEN=$dnsApiToken DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$hostname bash 2>&1 | '
|
||||||
|
'tee /tmp/infect.log',
|
||||||
'labels': {},
|
'labels': {},
|
||||||
'automount': true,
|
'automount': true,
|
||||||
'location': region!,
|
'location': region!,
|
||||||
|
|
|
@ -9,8 +9,6 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/api_controller.dart';
|
||||||
import 'package:selfprivacy/logic/providers/provider_settings.dart';
|
import 'package:selfprivacy/logic/providers/provider_settings.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
|
||||||
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
import 'package:selfprivacy/logic/providers/providers_controller.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
|
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
|
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
@ -174,7 +172,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
await repository.saveServerType(serverType);
|
await repository.saveServerType(serverType);
|
||||||
|
|
||||||
await ProvidersController.currentServerProvider!
|
await ProvidersController.currentServerProvider!
|
||||||
.trySetServerType(serverType);
|
.trySetServerLocation(serverType);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
(state as ServerInstallationNotFinished).copyWith(
|
(state as ServerInstallationNotFinished).copyWith(
|
||||||
|
|
21
lib/logic/models/callback_dialogue_branching.dart
Normal file
21
lib/logic/models/callback_dialogue_branching.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
||||||
|
|
||||||
|
class CallbackDialogueBranching {
|
||||||
|
CallbackDialogueBranching({
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
required this.choices,
|
||||||
|
});
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final List<CallbackDialogueChoice> choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CallbackDialogueChoice {
|
||||||
|
CallbackDialogueChoice({
|
||||||
|
required this.title,
|
||||||
|
required this.callback,
|
||||||
|
});
|
||||||
|
final String title;
|
||||||
|
final Future<GenericResult<CallbackDialogueBranching?>> Function()? callback;
|
||||||
|
}
|
19
lib/logic/models/launch_installation_data.dart
Normal file
19
lib/logic/models/launch_installation_data.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
|
|
||||||
|
class LaunchInstallationData {
|
||||||
|
LaunchInstallationData({
|
||||||
|
required this.rootUser,
|
||||||
|
required this.dnsApiToken,
|
||||||
|
required this.dnsProviderType,
|
||||||
|
required this.domainName,
|
||||||
|
required this.serverType,
|
||||||
|
});
|
||||||
|
|
||||||
|
final User rootUser;
|
||||||
|
final String dnsApiToken;
|
||||||
|
final String domainName;
|
||||||
|
final DnsProviderType dnsProviderType;
|
||||||
|
final ServerType serverType;
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
export 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
export 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
||||||
|
|
||||||
abstract class ServerProvider {
|
abstract class ServerProvider {
|
||||||
Future<GenericResult<bool>> trySetServerType(final ServerType type);
|
Future<GenericResult<bool>> trySetServerLocation(final String location);
|
||||||
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
Future<GenericResult<bool>> tryInitApiByToken(final String token);
|
||||||
Future<GenericResult<List<ServerProviderLocation>>> getAvailableLocations();
|
Future<GenericResult<List<ServerProviderLocation>>> getAvailableLocations();
|
||||||
Future<GenericResult<List<ServerType>>> getServerTypes({
|
Future<GenericResult<List<ServerType>>> getServerTypes({
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_api.dart';
|
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_api.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/callback_dialogue_branching.dart';
|
||||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/launch_installation_data.dart';
|
||||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
import 'package:selfprivacy/logic/models/price.dart';
|
import 'package:selfprivacy/logic/models/price.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
||||||
|
@ -11,6 +17,8 @@ import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_type.dart';
|
import 'package:selfprivacy/logic/models/server_type.dart';
|
||||||
import 'package:selfprivacy/logic/providers/server_provider.dart';
|
import 'package:selfprivacy/logic/providers/server_provider.dart';
|
||||||
import 'package:selfprivacy/utils/extensions/string_extensions.dart';
|
import 'package:selfprivacy/utils/extensions/string_extensions.dart';
|
||||||
|
import 'package:selfprivacy/utils/network_utils.dart';
|
||||||
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
class ApiAdapter {
|
class ApiAdapter {
|
||||||
ApiAdapter({final String? region, final bool isWithToken = true})
|
ApiAdapter({final String? region, final bool isWithToken = true})
|
||||||
|
@ -42,7 +50,9 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
ApiAdapter _adapter;
|
ApiAdapter _adapter;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<bool>> trySetServerType(final ServerType type) async {
|
Future<GenericResult<bool>> trySetServerLocation(
|
||||||
|
final String location,
|
||||||
|
) async {
|
||||||
final bool apiInitialized = _adapter.api().isWithToken;
|
final bool apiInitialized = _adapter.api().isWithToken;
|
||||||
if (!apiInitialized) {
|
if (!apiInitialized) {
|
||||||
return GenericResult(
|
return GenericResult(
|
||||||
|
@ -54,7 +64,7 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
|
|
||||||
_adapter = ApiAdapter(
|
_adapter = ApiAdapter(
|
||||||
isWithToken: true,
|
isWithToken: true,
|
||||||
region: type.location.identifier,
|
region: location,
|
||||||
);
|
);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
@ -302,6 +312,7 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
end,
|
end,
|
||||||
'cpu',
|
'cpu',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (cpuResult.data.isEmpty || !cpuResult.success) {
|
if (cpuResult.data.isEmpty || !cpuResult.success) {
|
||||||
return GenericResult(
|
return GenericResult(
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -387,4 +398,170 @@ class HetznerServerProvider extends ServerProvider {
|
||||||
data: timestamp,
|
data: timestamp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String dnsProviderToInfectName(final DnsProviderType dnsProvider) {
|
||||||
|
String dnsProviderType;
|
||||||
|
switch (dnsProvider) {
|
||||||
|
case DnsProviderType.digitalOcean:
|
||||||
|
dnsProviderType = 'DIGITALOCEAN';
|
||||||
|
break;
|
||||||
|
case DnsProviderType.cloudflare:
|
||||||
|
default:
|
||||||
|
dnsProviderType = 'CLOUDFLARE';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return dnsProviderType;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<GenericResult<CallbackDialogueBranching?>> launchInstallation(
|
||||||
|
final LaunchInstallationData installationData,
|
||||||
|
) async {
|
||||||
|
final volumeResult = await _adapter.api().createVolume();
|
||||||
|
|
||||||
|
if (!volumeResult.success || volumeResult.data == null) {
|
||||||
|
return GenericResult(
|
||||||
|
data: CallbackDialogueBranching(
|
||||||
|
choices: [
|
||||||
|
CallbackDialogueChoice(
|
||||||
|
title: 'basis.cancel'.tr(),
|
||||||
|
callback: null,
|
||||||
|
),
|
||||||
|
CallbackDialogueChoice(
|
||||||
|
title: 'basis.try_again'.tr(),
|
||||||
|
callback: () async => launchInstallation(installationData),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
description:
|
||||||
|
volumeResult.message ?? 'modals.volume_creation_error'.tr(),
|
||||||
|
title: 'modals.unexpected_error'.tr(),
|
||||||
|
),
|
||||||
|
success: false,
|
||||||
|
message: volumeResult.message,
|
||||||
|
code: volumeResult.code,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final volume = volumeResult.data!;
|
||||||
|
final serverApiToken = StringGenerators.apiToken();
|
||||||
|
final hostname = getHostnameFromDomain(installationData.domainName);
|
||||||
|
|
||||||
|
final serverResult = await _adapter.api().createServer(
|
||||||
|
dnsApiToken: installationData.dnsApiToken,
|
||||||
|
rootUser: installationData.rootUser,
|
||||||
|
domainName: installationData.domainName,
|
||||||
|
serverType: installationData.serverType.identifier,
|
||||||
|
dnsProviderType:
|
||||||
|
dnsProviderToInfectName(installationData.dnsProviderType),
|
||||||
|
hostName: hostname,
|
||||||
|
volumeId: volume.id,
|
||||||
|
base64Password: base64.encode(
|
||||||
|
utf8.encode(installationData.rootUser.password ?? 'PASS'),
|
||||||
|
),
|
||||||
|
databasePassword: StringGenerators.dbPassword(),
|
||||||
|
serverApiToken: serverApiToken,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!serverResult.success || serverResult.data == null) {
|
||||||
|
await _adapter.api().deleteVolume(volume);
|
||||||
|
await Future.delayed(const Duration(seconds: 5));
|
||||||
|
if (serverResult.message != null &&
|
||||||
|
serverResult.message == 'uniqueness_error') {
|
||||||
|
return GenericResult(
|
||||||
|
data: CallbackDialogueBranching(
|
||||||
|
choices: [
|
||||||
|
CallbackDialogueChoice(
|
||||||
|
title: 'basis.cancel'.tr(),
|
||||||
|
callback: null,
|
||||||
|
),
|
||||||
|
CallbackDialogueChoice(
|
||||||
|
title: 'basis.yes'.tr(),
|
||||||
|
callback: () async {
|
||||||
|
final deleting = await deleteServer(hostname);
|
||||||
|
if (deleting.success) {
|
||||||
|
return launchInstallation(installationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleting;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
description: volumeResult.message ?? 'modals.destroy_server'.tr(),
|
||||||
|
title: 'modals.already_exists'.tr(),
|
||||||
|
),
|
||||||
|
success: false,
|
||||||
|
message: volumeResult.message,
|
||||||
|
code: volumeResult.code,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return GenericResult(
|
||||||
|
data: CallbackDialogueBranching(
|
||||||
|
choices: [
|
||||||
|
CallbackDialogueChoice(
|
||||||
|
title: 'basis.cancel'.tr(),
|
||||||
|
callback: null,
|
||||||
|
),
|
||||||
|
CallbackDialogueChoice(
|
||||||
|
title: 'basis.try_again'.tr(),
|
||||||
|
callback: () async => launchInstallation(installationData),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
description:
|
||||||
|
volumeResult.message ?? 'recovering.generic_error'.tr(),
|
||||||
|
title: 'modals.unexpected_error'.tr(),
|
||||||
|
),
|
||||||
|
success: false,
|
||||||
|
message: volumeResult.message,
|
||||||
|
code: volumeResult.code,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final serverDetails = ServerHostingDetails(
|
||||||
|
id: serverResult.data['server']['id'],
|
||||||
|
ip4: serverResult.data['server']['public_net']['ipv4']['ip'],
|
||||||
|
createTime: DateTime.now(),
|
||||||
|
volume: volume,
|
||||||
|
apiToken: serverApiToken,
|
||||||
|
provider: ServerProviderType.hetzner,
|
||||||
|
);
|
||||||
|
|
||||||
|
final createDnsResult = await _adapter.api().createReverseDns(
|
||||||
|
serverId: serverDetails.id,
|
||||||
|
ip4: serverDetails.ip4,
|
||||||
|
dnsPtr: installationData.domainName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!createDnsResult.success) {
|
||||||
|
return GenericResult(
|
||||||
|
data: CallbackDialogueBranching(
|
||||||
|
choices: [
|
||||||
|
CallbackDialogueChoice(
|
||||||
|
title: 'basis.cancel'.tr(),
|
||||||
|
callback: null,
|
||||||
|
),
|
||||||
|
CallbackDialogueChoice(
|
||||||
|
title: 'basis.try_again'.tr(),
|
||||||
|
callback: () async {
|
||||||
|
final deletion = await deleteServer(hostname);
|
||||||
|
if (deletion.success) {
|
||||||
|
return launchInstallation(installationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deletion;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
description: volumeResult.message ?? 'recovering.generic_error'.tr(),
|
||||||
|
title: 'modals.unexpected_error'.tr(),
|
||||||
|
),
|
||||||
|
success: false,
|
||||||
|
message: volumeResult.message,
|
||||||
|
code: volumeResult.code,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<GenericResult<CallbackDialogueBranching?>> deleteServer(
|
||||||
|
final String hostname,
|
||||||
|
) async {}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue