mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-09-08 13:06:57 +00:00
# Conflicts: # lib/config/bloc_config.dart # lib/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart # lib/logic/cubit/backups/backups_cubit.dart # lib/logic/cubit/dns_records/dns_records_cubit.dart # lib/logic/cubit/providers/providers_cubit.dart # lib/logic/models/service.dart # lib/ui/pages/backups/backup_details.dart # lib/ui/pages/backups/change_period_modal.dart # lib/ui/pages/backups/change_rotation_quotas_modal.dart # lib/ui/pages/backups/copy_encryption_key_modal.dart # lib/ui/pages/more/more.dart # lib/ui/pages/server_storage/binds_migration/migration_process_page.dart # lib/ui/pages/server_storage/server_storage.dart # lib/ui/pages/server_storage/storage_card.dart
610 lines
19 KiB
Dart
610 lines
19 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:hive/hive.dart';
|
|
import 'package:pub_semver/pub_semver.dart';
|
|
import 'package:selfprivacy/config/get_it_config.dart';
|
|
import 'package:selfprivacy/config/hive_config.dart';
|
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
|
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
|
import 'package:selfprivacy/logic/models/hive/backups_credential.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/user.dart';
|
|
import 'package:selfprivacy/logic/models/json/device_token.dart';
|
|
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
|
import 'package:selfprivacy/logic/models/server_basic_info.dart';
|
|
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/utils/network_utils.dart';
|
|
import 'package:selfprivacy/utils/platform_adapter.dart';
|
|
|
|
class IpNotFoundException implements Exception {
|
|
IpNotFoundException(this.message);
|
|
final String message;
|
|
}
|
|
|
|
class ServerAuthorizationException implements Exception {
|
|
ServerAuthorizationException(this.message);
|
|
final String message;
|
|
}
|
|
|
|
class ServerInstallationRepository {
|
|
Box box = Hive.box(BNames.serverInstallationBox);
|
|
Box<User> usersBox = Hive.box(BNames.usersBox);
|
|
|
|
Future<ServerInstallationState> load() async {
|
|
final String? providerApiToken = getIt<ApiConfigModel>().serverProviderKey;
|
|
final String? location = getIt<ApiConfigModel>().serverLocation;
|
|
final String? dnsApiToken = getIt<ApiConfigModel>().dnsProviderKey;
|
|
final String? serverTypeIdentificator = getIt<ApiConfigModel>().serverType;
|
|
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain;
|
|
final DnsProviderType? dnsProvider = getIt<ApiConfigModel>().dnsProvider;
|
|
final ServerProviderType? serverProvider =
|
|
getIt<ApiConfigModel>().serverProvider;
|
|
final BackupsCredential? backblazeCredential =
|
|
getIt<ApiConfigModel>().backblazeCredential;
|
|
final ServerHostingDetails? serverDetails =
|
|
getIt<ApiConfigModel>().serverDetails;
|
|
|
|
if (serverProvider != null ||
|
|
(serverDetails != null &&
|
|
serverDetails.provider != ServerProviderType.unknown)) {
|
|
ProvidersController.initServerProvider(
|
|
ServerProviderSettings(
|
|
provider: serverProvider ?? serverDetails!.provider,
|
|
isAuthorized: providerApiToken != null,
|
|
location: location,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (dnsProvider != null ||
|
|
(serverDomain != null &&
|
|
serverDomain.provider != DnsProviderType.unknown)) {
|
|
ProvidersController.initDnsProvider(
|
|
DnsProviderSettings(
|
|
isAuthorized: dnsApiToken != null,
|
|
provider: dnsProvider ?? serverDomain!.provider,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
|
|
TlsOptions.verifyCertificate = true;
|
|
if (serverTypeIdentificator == null && serverDetails != null) {
|
|
final finalServerType = await ProvidersController.currentServerProvider!
|
|
.getServerType(serverDetails.id);
|
|
await saveServerType(finalServerType.data!);
|
|
await ProvidersController.currentServerProvider!
|
|
.trySetServerLocation(finalServerType.data!.location.identifier);
|
|
return ServerInstallationFinished(
|
|
installationDialoguePopUp: null,
|
|
providerApiToken: providerApiToken!,
|
|
serverTypeIdentificator: finalServerType.data!.identifier,
|
|
dnsApiToken: dnsApiToken!,
|
|
serverDomain: serverDomain!,
|
|
backblazeCredential: backblazeCredential!,
|
|
serverDetails: serverDetails,
|
|
rootUser: box.get(BNames.rootUser),
|
|
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
|
|
isServerResetedFirstTime:
|
|
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
|
isServerResetedSecondTime:
|
|
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
|
);
|
|
} else {
|
|
return ServerInstallationFinished(
|
|
installationDialoguePopUp: null,
|
|
providerApiToken: providerApiToken!,
|
|
serverTypeIdentificator: serverTypeIdentificator!,
|
|
dnsApiToken: dnsApiToken!,
|
|
serverDomain: serverDomain!,
|
|
backblazeCredential: backblazeCredential!,
|
|
serverDetails: serverDetails!,
|
|
rootUser: box.get(BNames.rootUser),
|
|
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
|
|
isServerResetedFirstTime:
|
|
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
|
isServerResetedSecondTime:
|
|
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
|
);
|
|
}
|
|
}
|
|
|
|
if (box.get(BNames.isRecoveringServer, defaultValue: false) &&
|
|
serverDomain != null) {
|
|
return ServerInstallationRecovery(
|
|
providerApiToken: providerApiToken,
|
|
dnsApiToken: dnsApiToken,
|
|
serverDomain: serverDomain,
|
|
serverTypeIdentificator: serverTypeIdentificator,
|
|
backblazeCredential: backblazeCredential,
|
|
serverDetails: serverDetails,
|
|
rootUser: box.get(BNames.rootUser),
|
|
currentStep: _getCurrentRecoveryStep(
|
|
providerApiToken,
|
|
dnsApiToken,
|
|
serverDomain,
|
|
serverDetails,
|
|
),
|
|
recoveryCapabilities: await getRecoveryCapabilities(serverDomain),
|
|
);
|
|
}
|
|
|
|
return ServerInstallationNotFinished(
|
|
providerApiToken: providerApiToken,
|
|
dnsApiToken: dnsApiToken,
|
|
serverDomain: serverDomain,
|
|
serverTypeIdentificator: serverTypeIdentificator,
|
|
backblazeCredential: backblazeCredential,
|
|
serverDetails: serverDetails,
|
|
rootUser: box.get(BNames.rootUser),
|
|
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
|
|
isServerResetedFirstTime:
|
|
box.get(BNames.isServerResetedFirstTime, defaultValue: false),
|
|
isServerResetedSecondTime:
|
|
box.get(BNames.isServerResetedSecondTime, defaultValue: false),
|
|
isLoading: box.get(BNames.isLoading, defaultValue: false),
|
|
dnsMatches: null,
|
|
customSshKey: null,
|
|
);
|
|
}
|
|
|
|
RecoveryStep _getCurrentRecoveryStep(
|
|
final String? serverProviderToken,
|
|
final String? dnsProviderToken,
|
|
final ServerDomain serverDomain,
|
|
final ServerHostingDetails? serverDetails,
|
|
) {
|
|
if (serverDetails != null) {
|
|
if (serverProviderToken != null) {
|
|
if (serverDetails.provider != ServerProviderType.unknown) {
|
|
if (serverDomain.provider != DnsProviderType.unknown) {
|
|
return RecoveryStep.backblazeToken;
|
|
}
|
|
return RecoveryStep.dnsProviderToken;
|
|
}
|
|
return RecoveryStep.serverSelection;
|
|
}
|
|
return RecoveryStep.serverProviderToken;
|
|
}
|
|
return RecoveryStep.selecting;
|
|
}
|
|
|
|
void clearAppConfig() {
|
|
box.clear();
|
|
usersBox.clear();
|
|
}
|
|
|
|
Future<ServerHostingDetails> startServer(
|
|
final ServerHostingDetails server,
|
|
) async {
|
|
final result = await ProvidersController.currentServerProvider!.powerOn(
|
|
server.id,
|
|
);
|
|
|
|
if (result.success && result.data != null) {
|
|
server.copyWith(startTime: result.data);
|
|
}
|
|
|
|
return server;
|
|
}
|
|
|
|
Future<bool> validateDnsToken(
|
|
final String token,
|
|
final String domain,
|
|
) async {
|
|
final result =
|
|
await ProvidersController.currentDnsProvider!.tryInitApiByToken(token);
|
|
if (!result.success) {
|
|
return false;
|
|
}
|
|
await setDnsApiToken(token);
|
|
final domainResult =
|
|
await ProvidersController.currentDnsProvider!.domainList();
|
|
if (!domainResult.success || domainResult.data.isEmpty) {
|
|
return false;
|
|
}
|
|
|
|
return domainResult.data.any(
|
|
(final serverDomain) => serverDomain.domainName == domain,
|
|
);
|
|
}
|
|
|
|
Future<void> createDkimRecord(final ServerDomain domain) async {
|
|
final ServerApi api = ServerApi();
|
|
|
|
late DnsRecord record;
|
|
try {
|
|
record = extractDkimRecord(await api.getDnsRecords())!;
|
|
} catch (e) {
|
|
print(e);
|
|
rethrow;
|
|
}
|
|
|
|
await ProvidersController.currentDnsProvider!.setDnsRecord(
|
|
record,
|
|
domain,
|
|
);
|
|
}
|
|
|
|
Future<bool> isHttpServerWorking() async {
|
|
final ServerApi api = ServerApi();
|
|
return api.isHttpServerWorking();
|
|
}
|
|
|
|
Future<ServerHostingDetails> restart() async {
|
|
final server = getIt<ApiConfigModel>().serverDetails!;
|
|
|
|
final result = await ServerApi().reboot();
|
|
|
|
if (result.success && result.data != null) {
|
|
server.copyWith(startTime: result.data);
|
|
} else {
|
|
getIt<NavigationService>().showSnackBar('jobs.reboot_failed'.tr());
|
|
}
|
|
|
|
return server;
|
|
}
|
|
|
|
Future<ServerHostingDetails> powerOn() async {
|
|
final server = getIt<ApiConfigModel>().serverDetails!;
|
|
return startServer(server);
|
|
}
|
|
|
|
Future<ServerRecoveryCapabilities> getRecoveryCapabilities(
|
|
final ServerDomain serverDomain,
|
|
) async {
|
|
final ServerApi serverApi = ServerApi(
|
|
isWithToken: false,
|
|
overrideDomain: serverDomain.domainName,
|
|
);
|
|
final String? serverApiVersion = await serverApi.getApiVersion();
|
|
if (serverApiVersion == null) {
|
|
return ServerRecoveryCapabilities.none;
|
|
}
|
|
try {
|
|
final Version parsedVersion = Version.parse(serverApiVersion);
|
|
if (!VersionConstraint.parse('>=1.2.0').allows(parsedVersion)) {
|
|
return ServerRecoveryCapabilities.legacy;
|
|
}
|
|
return ServerRecoveryCapabilities.loginTokens;
|
|
} on FormatException {
|
|
return ServerRecoveryCapabilities.none;
|
|
}
|
|
}
|
|
|
|
Future<String> getServerIpFromDomain(final ServerDomain serverDomain) async {
|
|
String? domain;
|
|
await InternetAddress.lookup(serverDomain.domainName).then(
|
|
(final records) {
|
|
for (final record in records) {
|
|
domain = record.address;
|
|
}
|
|
},
|
|
);
|
|
if (domain == null || domain!.isEmpty) {
|
|
throw IpNotFoundException('No IP found for domain $serverDomain');
|
|
}
|
|
return domain!;
|
|
}
|
|
|
|
Future<ServerHostingDetails> authorizeByNewDeviceKey(
|
|
final ServerDomain serverDomain,
|
|
final String newDeviceKey,
|
|
final ServerRecoveryCapabilities recoveryCapabilities,
|
|
) async {
|
|
final ServerApi serverApi = ServerApi(
|
|
isWithToken: false,
|
|
overrideDomain: serverDomain.domainName,
|
|
);
|
|
final String serverIp = await getServerIpFromDomain(serverDomain);
|
|
final GenericResult<String> result = await serverApi.authorizeDevice(
|
|
DeviceToken(
|
|
device: await PlatformAdapter.deviceName,
|
|
token: newDeviceKey,
|
|
),
|
|
);
|
|
|
|
if (result.success) {
|
|
return ServerHostingDetails(
|
|
apiToken: result.data,
|
|
volume: ServerProviderVolume(
|
|
id: 0,
|
|
name: '',
|
|
sizeByte: 0,
|
|
serverId: 0,
|
|
linuxDevice: '',
|
|
),
|
|
provider: ServerProviderType.unknown,
|
|
id: 0,
|
|
ip4: serverIp,
|
|
startTime: null,
|
|
createTime: null,
|
|
);
|
|
}
|
|
|
|
throw ServerAuthorizationException(
|
|
result.message ?? result.data,
|
|
);
|
|
}
|
|
|
|
Future<ServerHostingDetails> authorizeByRecoveryKey(
|
|
final ServerDomain serverDomain,
|
|
final String recoveryKey,
|
|
final ServerRecoveryCapabilities recoveryCapabilities,
|
|
) async {
|
|
final ServerApi serverApi = ServerApi(
|
|
isWithToken: false,
|
|
overrideDomain: serverDomain.domainName,
|
|
);
|
|
final String serverIp = await getServerIpFromDomain(serverDomain);
|
|
final GenericResult<String> result = await serverApi.useRecoveryToken(
|
|
DeviceToken(device: await PlatformAdapter.deviceName, token: recoveryKey),
|
|
);
|
|
|
|
if (result.success) {
|
|
return ServerHostingDetails(
|
|
apiToken: result.data,
|
|
volume: ServerProviderVolume(
|
|
id: 0,
|
|
name: '',
|
|
sizeByte: 0,
|
|
serverId: 0,
|
|
linuxDevice: '',
|
|
),
|
|
provider: ServerProviderType.unknown,
|
|
id: 0,
|
|
ip4: serverIp,
|
|
startTime: null,
|
|
createTime: null,
|
|
);
|
|
}
|
|
|
|
throw ServerAuthorizationException(
|
|
result.message ?? result.data,
|
|
);
|
|
}
|
|
|
|
Future<ServerHostingDetails> authorizeByApiToken(
|
|
final ServerDomain serverDomain,
|
|
final String apiToken,
|
|
final ServerRecoveryCapabilities recoveryCapabilities,
|
|
) async {
|
|
final ServerApi serverApi = ServerApi(
|
|
isWithToken: true,
|
|
overrideDomain: serverDomain.domainName,
|
|
customToken: apiToken,
|
|
);
|
|
final String serverIp = await getServerIpFromDomain(serverDomain);
|
|
if (recoveryCapabilities == ServerRecoveryCapabilities.legacy) {
|
|
if (await serverApi.isHttpServerWorking()) {
|
|
return ServerHostingDetails(
|
|
apiToken: apiToken,
|
|
volume: ServerProviderVolume(
|
|
id: 0,
|
|
name: '',
|
|
serverId: 0,
|
|
sizeByte: 0,
|
|
linuxDevice: '',
|
|
),
|
|
provider: ServerProviderType.unknown,
|
|
id: 0,
|
|
ip4: serverIp,
|
|
startTime: null,
|
|
createTime: null,
|
|
);
|
|
} else {
|
|
throw ServerAuthorizationException(
|
|
"Couldn't connect to server with this token",
|
|
);
|
|
}
|
|
}
|
|
final GenericResult<String> deviceAuthKey =
|
|
await serverApi.createDeviceToken();
|
|
final GenericResult<String> result = await serverApi.authorizeDevice(
|
|
DeviceToken(
|
|
device: await PlatformAdapter.deviceName,
|
|
token: deviceAuthKey.data,
|
|
),
|
|
);
|
|
|
|
if (result.success) {
|
|
return ServerHostingDetails(
|
|
apiToken: result.data,
|
|
volume: ServerProviderVolume(
|
|
id: 0,
|
|
name: '',
|
|
sizeByte: 0,
|
|
serverId: 0,
|
|
linuxDevice: '',
|
|
),
|
|
provider: ServerProviderType.unknown,
|
|
id: 0,
|
|
ip4: serverIp,
|
|
startTime: null,
|
|
createTime: null,
|
|
);
|
|
}
|
|
|
|
throw ServerAuthorizationException(
|
|
result.message ?? result.data,
|
|
);
|
|
}
|
|
|
|
Future<User> getMainUser() async {
|
|
final ServerApi serverApi = ServerApi();
|
|
const User fallbackUser = User(
|
|
isFoundOnServer: false,
|
|
type: UserType.primary,
|
|
note: "Couldn't find main user on server, API is outdated",
|
|
login: 'UNKNOWN',
|
|
sshKeys: [],
|
|
);
|
|
|
|
final String? serverApiVersion = await serverApi.getApiVersion();
|
|
final users = await serverApi.getAllUsers();
|
|
if (serverApiVersion == null || users.isEmpty) {
|
|
return fallbackUser;
|
|
}
|
|
try {
|
|
final Version parsedVersion = Version.parse(serverApiVersion);
|
|
if (!VersionConstraint.parse('>=1.2.5').allows(parsedVersion)) {
|
|
return fallbackUser;
|
|
}
|
|
return users.firstWhere(
|
|
(final User user) => user.type == UserType.primary,
|
|
);
|
|
} on FormatException {
|
|
return fallbackUser;
|
|
}
|
|
}
|
|
|
|
Future<List<ServerBasicInfo>> getServersOnProviderAccount() async =>
|
|
(await ProvidersController.currentServerProvider!.getServers()).data;
|
|
|
|
Future<void> saveServerDetails(
|
|
final ServerHostingDetails serverDetails,
|
|
) async {
|
|
await getIt<ApiConfigModel>().setServerDetails(serverDetails);
|
|
}
|
|
|
|
Future<void> deleteServerDetails() async {
|
|
await box.delete(BNames.serverDetails);
|
|
getIt<ApiConfigModel>().init();
|
|
}
|
|
|
|
Future<void> saveServerProviderType(final ServerProviderType type) async {
|
|
await getIt<ApiConfigModel>().storeServerProviderType(type);
|
|
}
|
|
|
|
Future<void> saveDnsProviderType(final DnsProviderType type) async {
|
|
await getIt<ApiConfigModel>().setDnsProviderType(type);
|
|
}
|
|
|
|
Future<void> saveServerProviderKey(final String key) async {
|
|
await getIt<ApiConfigModel>().setServerProviderKey(key);
|
|
}
|
|
|
|
Future<void> saveServerType(final ServerType serverType) async {
|
|
await getIt<ApiConfigModel>().setServerTypeIdentifier(
|
|
serverType.identifier,
|
|
);
|
|
await getIt<ApiConfigModel>().setServerLocation(
|
|
serverType.location.identifier,
|
|
);
|
|
}
|
|
|
|
Future<void> deleteServerProviderKey() async {
|
|
await box.delete(BNames.hetznerKey);
|
|
getIt<ApiConfigModel>().init();
|
|
}
|
|
|
|
Future<void> saveBackblazeKey(
|
|
final BackupsCredential backblazeCredential,
|
|
) async {
|
|
await getIt<ApiConfigModel>().setBackblazeCredential(backblazeCredential);
|
|
}
|
|
|
|
Future<void> deleteBackblazeKey() async {
|
|
await box.delete(BNames.backblazeCredential);
|
|
getIt<ApiConfigModel>().init();
|
|
}
|
|
|
|
Future<void> setDnsApiToken(final String key) async {
|
|
await getIt<ApiConfigModel>().setDnsProviderKey(key);
|
|
}
|
|
|
|
Future<void> deleteDnsProviderKey() async {
|
|
await box.delete(BNames.cloudFlareKey);
|
|
getIt<ApiConfigModel>().init();
|
|
}
|
|
|
|
Future<void> saveDomain(final ServerDomain serverDomain) async {
|
|
await getIt<ApiConfigModel>().setServerDomain(serverDomain);
|
|
}
|
|
|
|
Future<void> deleteDomain() async {
|
|
await box.delete(BNames.serverDomain);
|
|
getIt<ApiConfigModel>().init();
|
|
}
|
|
|
|
Future<void> saveIsServerStarted(final bool value) async {
|
|
await box.put(BNames.isServerStarted, value);
|
|
}
|
|
|
|
Future<void> saveIsServerResetedFirstTime(final bool value) async {
|
|
await box.put(BNames.isServerResetedFirstTime, value);
|
|
}
|
|
|
|
Future<void> saveIsServerResetedSecondTime(final bool value) async {
|
|
await box.put(BNames.isServerResetedSecondTime, value);
|
|
}
|
|
|
|
Future<void> saveRootUser(final User rootUser) async {
|
|
await box.put(BNames.rootUser, rootUser);
|
|
}
|
|
|
|
Future<void> saveIsRecoveringServer(final bool value) async {
|
|
await box.put(BNames.isRecoveringServer, value);
|
|
}
|
|
|
|
Future<void> saveHasFinalChecked(final bool value) async {
|
|
await box.put(BNames.hasFinalChecked, value);
|
|
}
|
|
|
|
Future<bool> deleteServer(final ServerDomain serverDomain) async {
|
|
final ServerApi api = ServerApi();
|
|
final dnsRecords = await api.getDnsRecords();
|
|
final GenericResult<void> removalResult =
|
|
await ProvidersController.currentDnsProvider!.removeDomainRecords(
|
|
domain: serverDomain,
|
|
records: dnsRecords,
|
|
);
|
|
|
|
if (!removalResult.success) {
|
|
getIt<NavigationService>().showSnackBar(
|
|
'modals.dns_removal_error'.tr(),
|
|
);
|
|
return false;
|
|
}
|
|
|
|
final deletionResult =
|
|
await ProvidersController.currentServerProvider!.deleteServer(
|
|
serverDomain.domainName,
|
|
);
|
|
|
|
if (!deletionResult.success) {
|
|
getIt<NavigationService>().showSnackBar(
|
|
'modals.server_validators_error'.tr(),
|
|
);
|
|
return false;
|
|
}
|
|
|
|
await box.put(BNames.hasFinalChecked, false);
|
|
await box.put(BNames.isServerStarted, false);
|
|
await box.put(BNames.isServerResetedFirstTime, false);
|
|
await box.put(BNames.isServerResetedSecondTime, false);
|
|
await box.put(BNames.isLoading, false);
|
|
await box.put(BNames.serverDetails, null);
|
|
|
|
return true;
|
|
}
|
|
|
|
Future<void> deleteServerRelatedRecords() async {
|
|
await box.deleteAll([
|
|
BNames.serverDetails,
|
|
BNames.isServerStarted,
|
|
BNames.isServerResetedFirstTime,
|
|
BNames.isServerResetedSecondTime,
|
|
BNames.hasFinalChecked,
|
|
BNames.isLoading,
|
|
]);
|
|
getIt<ApiConfigModel>().init();
|
|
}
|
|
}
|