mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-23 17:26:35 +00:00
Merge branch 'master' into add_complated_state_for_jobs_widget
This commit is contained in:
commit
c0889c056e
|
@ -10,7 +10,7 @@ AppDir:
|
||||||
id: org.selfprivacy.app
|
id: org.selfprivacy.app
|
||||||
name: SelfPrivacy
|
name: SelfPrivacy
|
||||||
icon: org.selfprivacy.app
|
icon: org.selfprivacy.app
|
||||||
version: 0.9.1
|
version: 0.10.0
|
||||||
exec: selfprivacy
|
exec: selfprivacy
|
||||||
exec_args: $@
|
exec_args: $@
|
||||||
apt:
|
apt:
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
наступнае: **SSH Keys, API Tokens, Certificates, Members.** Вы
|
наступнае: **SSH Keys, API Tokens, Certificates, Members.** Вы
|
||||||
патрэбныя **API Tokens**. Націсніце на яго.
|
патрэбныя **API Tokens**. Націсніце на яго.
|
||||||
5. У правай частцы інтэрфейсу павінна быць **Generate API token** button. Калі вы карыстаецеся мабільнай версіяй вэб-старонкі, у
|
5. У правай частцы інтэрфейсу павінна быць **Generate API token** button. Калі вы карыстаецеся мабільнай версіяй вэб-старонкі, у
|
||||||
у правым ніжнім куце вы ўбачыце **red cross**. Націсніце гэтую кнопку.
|
у правым ніжнім куце вы ўбачыце **чырвоны плюс**. Націсніце гэтую кнопку.
|
||||||
6. У полі **Description** дайце нашаму токену імя (гэта можа быць любое
|
6. У полі **Description** дайце нашаму токену імя (гэта можа быць любое
|
||||||
імя, якое вам падабаецца. На сутнасць гэта не ўплывае.
|
імя, якое вам падабаецца. На сутнасць гэта не ўплывае.
|
||||||
7. Пад полем **permissions** мы бачым магчымасць выбару
|
7. Пад полем **permissions** мы бачым магчымасць выбару
|
||||||
|
@ -18,4 +18,4 @@
|
||||||
9. Пасля гэтага будзе паказаны наш ключ. Захоўвайце яго ў надзейным месцы,
|
9. Пасля гэтага будзе паказаны наш ключ. Захоўвайце яго ў надзейным месцы,
|
||||||
або ў менеджэры пароляў, што лепш.
|
або ў менеджэры пароляў, што лепш.
|
||||||
|
|
||||||
![Наладжванне маркера Hetzner](рэсурс:assets/images/gifs/Hetzner.gif)
|
![Наладжванне маркера Hetzner](resource:assets/images/gifs/Hetzner.gif)
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
### Как получить Hetzner API Token
|
### Как получить токен API от Hetzner
|
||||||
1. Переходим по ссылке https://hetzner.com
|
1. Посетите следующую [ссылку](https://console.hetzner.cloud/) и войдите в свой новый аккаунт.
|
||||||
2. Заходим в созданный нами проект. Если такового - нет, значит создаём.
|
2. Войдите в ранее созданный проект. Если вы еще не создали его, пожалуйста, сделайте это.
|
||||||
3. Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).
|
3. Наведите курсор мыши на боковую панель. Панель должна расшириться и показать меню. Нас интересует последний пункт — **Security** (иконка ключа).
|
||||||
4. Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.
|
4. Далее, в верхней части интерфейса, видим примерно следующее: **SSH Keys, API Tokens, Certificates, Members.** Нужно нажать **API Tokens**.
|
||||||
5. В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же Вы используете мобильную версию сайта, в нижнем правом углу Вы увидите красный плюсик. Нажимаем на эту кнопку.
|
5. В правой части интерфейса должна быть кнопка **Generate API token**. Если вы используете мобильную версию веб-страницы, в нижнем правом углу вы увидите **красный плюс**. Нажмите на эту кнопку.
|
||||||
6. В поле Description, даём нашему токену название (это может быть любое название, которые Вам нравиться. Сути оно не меняет.
|
6. В поле **Description** дайте вашему токену имя (это может быть любое имя, которое вам нравится).
|
||||||
|
7. Под полем **Description** мы видим возможность выбрать **permissions**. Выберите **Read & Write**.
|
||||||
|
8. Нажмите **Generate API Token**.
|
||||||
|
9. После этого вам будет показан ключ. Храните его в надежном месте, или в менеджере паролей, что еще лучше.
|
||||||
|
|
||||||
![Hetzner token setup](resource:assets/images/gifs/Hetzner.gif)
|
![Получение токена Hetzner](resource:assets/images/gifs/Hetzner.gif)
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"apply": "Solicitar",
|
"apply": "Solicitar",
|
||||||
"done": "Hecho",
|
"done": "Hecho",
|
||||||
"connect_to_existing": "Conectarse a un servidor de SelfPrivacy existente",
|
"connect_to_existing": "¡Ya tengo un servidor de SelfPrivacy!",
|
||||||
"app_name": "SelfPrivacy",
|
"app_name": "SelfPrivacy",
|
||||||
"please_connect": "¡Conecta tu servidor y dominio para sumergirte!",
|
"please_connect": "¡Conecta tu servidor y dominio para sumergirte!",
|
||||||
"copied_to_clipboard": "¡Copiado al portapapeles!"
|
"copied_to_clipboard": "¡Copiado al portapapeles!"
|
||||||
|
@ -45,7 +45,10 @@
|
||||||
"delete_server_title": "Eliminar servidor",
|
"delete_server_title": "Eliminar servidor",
|
||||||
"delete_server_description": "Esto elimina su servidor. Ya no será accesible.",
|
"delete_server_description": "Esto elimina su servidor. Ya no será accesible.",
|
||||||
"title": "Ajustes de la aplicación",
|
"title": "Ajustes de la aplicación",
|
||||||
"dark_theme_title": "Tema oscuro"
|
"dark_theme_title": "Tema oscuro",
|
||||||
|
"system_dark_theme_title": "Tema del sistema",
|
||||||
|
"system_dark_theme_description": "Utiliza un tema claro u oscuro de la configuración del sistema",
|
||||||
|
"dangerous_settings": "Configuraciones peligrosas"
|
||||||
},
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"delete_confirm_question": "¿Está seguro de que desea eliminar la clave SSH?",
|
"delete_confirm_question": "¿Está seguro de que desea eliminar la clave SSH?",
|
||||||
|
@ -57,7 +60,7 @@
|
||||||
"subtitle_without_keys": "Sin llaves",
|
"subtitle_without_keys": "Sin llaves",
|
||||||
"no_key_name": "Clave sin nombre",
|
"no_key_name": "Clave sin nombre",
|
||||||
"root_title": "Estas son las claves de superusuario",
|
"root_title": "Estas son las claves de superusuario",
|
||||||
"input_label": "Clave pública ED25519 o RSA"
|
"input_label": "Clave pública ED25519, ECDSA o RSA"
|
||||||
},
|
},
|
||||||
"about_application_page": {
|
"about_application_page": {
|
||||||
"application_version_text": "Versión de la aplicación {}",
|
"application_version_text": "Versión de la aplicación {}",
|
||||||
|
@ -92,5 +95,26 @@
|
||||||
},
|
},
|
||||||
"about_us_page": {
|
"about_us_page": {
|
||||||
"title": "Sobre nosotros"
|
"title": "Sobre nosotros"
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"reboot_after_upgrade_hint": "Reinicio sin aviso después de aplicar cambios en el servidor",
|
||||||
|
"card_title": "Servidor",
|
||||||
|
"description": "Todos sus servicios funcionan aquí",
|
||||||
|
"general_information": "Información general",
|
||||||
|
"resource_usage": "Uso de recursos",
|
||||||
|
"server_timezone": "Zona horaria del servidor",
|
||||||
|
"select_timezone": "Selecciona zona horaria",
|
||||||
|
"reboot_after_upgrade": "Reinicia después de actualizar",
|
||||||
|
"allow_autoupgrade": "Permite la autoactualización",
|
||||||
|
"allow_autoupgrade_hint": "Ppermite actualizaciones automáticas de paquetes en el servidor"
|
||||||
|
},
|
||||||
|
"resource_chart": {
|
||||||
|
"month": "Mes",
|
||||||
|
"out": "Fuera",
|
||||||
|
"day": "Día",
|
||||||
|
"hour": "Hora",
|
||||||
|
"cpu_title": "Uso de CPU",
|
||||||
|
"network_title": "Uso de la red",
|
||||||
|
"in": "En"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
assets/translations/et.json
Normal file
1
assets/translations/et.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -34,7 +34,7 @@
|
||||||
"username": "שם משתמש",
|
"username": "שם משתמש",
|
||||||
"loading": "בטעינה…",
|
"loading": "בטעינה…",
|
||||||
"later": "דילוג כדי להגדיר אחר כך",
|
"later": "דילוג כדי להגדיר אחר כך",
|
||||||
"connect_to_existing": "התחברות לשרת SelfPrivacy קיים",
|
"connect_to_existing": "כבר יש לי שרת SelfPrivacy!",
|
||||||
"reset": "איפוס",
|
"reset": "איפוס",
|
||||||
"details": "פרטים",
|
"details": "פרטים",
|
||||||
"no_data": "אין נתונים",
|
"no_data": "אין נתונים",
|
||||||
|
@ -461,7 +461,8 @@
|
||||||
"enter_username_and_password": "נא למלא שם משתמש וסיסמה חזקה",
|
"enter_username_and_password": "נא למלא שם משתמש וסיסמה חזקה",
|
||||||
"finish": "הכול מאותחל",
|
"finish": "הכול מאותחל",
|
||||||
"create_master_account": "יצירת חשבון ראשי",
|
"create_master_account": "יצירת חשבון ראשי",
|
||||||
"checks": "בדיקות הושלמו \n{} מתוך {}"
|
"checks": "בדיקות הושלמו \n{} מתוך {}",
|
||||||
|
"domain_critical_error": "לא הצלחנו להגיע לשם התחום הזה! נגיעה למידע נוסף…"
|
||||||
},
|
},
|
||||||
"recovering": {
|
"recovering": {
|
||||||
"method_select_other_device": "יש לי גישה דרך מכשיר אחר",
|
"method_select_other_device": "יש לי גישה דרך מכשיר אחר",
|
||||||
|
@ -480,7 +481,7 @@
|
||||||
"modal_confirmation_ip_valid": "ה־IP זהה לזה שברשומת ה־DNS",
|
"modal_confirmation_ip_valid": "ה־IP זהה לזה שברשומת ה־DNS",
|
||||||
"modal_confirmation_ip_invalid": "ה־IP שונה מזה שברשומת ה־DNS",
|
"modal_confirmation_ip_invalid": "ה־IP שונה מזה שברשומת ה־DNS",
|
||||||
"generic_error": "הפעולה בוטלה, נא לנסות שוב.",
|
"generic_error": "הפעולה בוטלה, נא לנסות שוב.",
|
||||||
"recovery_main_header": "התחברות לשרת SelfPrivacy קיים",
|
"recovery_main_header": "התחברות לשרת קיים",
|
||||||
"domain_recover_placeholder": "שם התחום שלך",
|
"domain_recover_placeholder": "שם התחום שלך",
|
||||||
"domain_recover_error": "לא נמצא שרת עם שם תחום כזה",
|
"domain_recover_error": "לא נמצא שרת עם שם תחום כזה",
|
||||||
"method_select_description": "נא לבחור שיטת שחזור:",
|
"method_select_description": "נא לבחור שיטת שחזור:",
|
||||||
|
@ -610,7 +611,11 @@
|
||||||
"use_staging_acme_description": "חל על הקמת שרתים חדשים.",
|
"use_staging_acme_description": "חל על הקמת שרתים חדשים.",
|
||||||
"use_staging_acme": "להשתמש בשרת ACME לבדיקות",
|
"use_staging_acme": "להשתמש בשרת ACME לבדיקות",
|
||||||
"ignore_tls": "לא לאמת אישורי TLS",
|
"ignore_tls": "לא לאמת אישורי TLS",
|
||||||
"ignore_tls_description": "היישום לא יאמת אישורי RLS בעת התחברות לשרת."
|
"ignore_tls_description": "היישום לא יאמת אישורי RLS בעת התחברות לשרת.",
|
||||||
|
"add_root_ssh_key": "הוספת מפתח SSH למשתמש העל (root)",
|
||||||
|
"allow_ssh_key_at_setup": "לאפשר הגדרת מפתח SSH למשתמש העל (root) במהלך ההתקנה",
|
||||||
|
"allow_ssh_key_at_setup_description": "כפתור להוספת מפתח יופיע במסך האישור.",
|
||||||
|
"root_ssh_key_added": "מפתח SSH למשתמש העל (root) הוגדר והוחל"
|
||||||
},
|
},
|
||||||
"cloud": {
|
"cloud": {
|
||||||
"title": "אחסון בענן",
|
"title": "אחסון בענן",
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"username": "Имя пользователя",
|
"username": "Имя пользователя",
|
||||||
"loading": "Загрузка…",
|
"loading": "Загрузка…",
|
||||||
"later": "Пропустить и настроить потом",
|
"later": "Пропустить и настроить потом",
|
||||||
"connect_to_existing": "Подключиться к существующему серверу SelfPrivacy",
|
"connect_to_existing": "У меня уже есть SelfPrivacy сервер!",
|
||||||
"reset": "Сбросить",
|
"reset": "Сбросить",
|
||||||
"details": "Детальная информация",
|
"details": "Детальная информация",
|
||||||
"no_data": "Нет данных",
|
"no_data": "Нет данных",
|
||||||
|
@ -471,11 +471,12 @@
|
||||||
},
|
},
|
||||||
"server_provider_description": "Место, где будут находиться ваши данные и сервисы SelfPrivacy:",
|
"server_provider_description": "Место, где будут находиться ваши данные и сервисы SelfPrivacy:",
|
||||||
"multiple_domains_found": "Найдено несколько доменов",
|
"multiple_domains_found": "Найдено несколько доменов",
|
||||||
"multiple_domains_found_text": "Предоставленный токен дает доступ к следующим доменам. Пожалуйста, выберите тот, который вы хотите использовать. Для обеспечения безопасности других доменов следует ограничить доступ этого токена только тем доменом, который вы хотите использовать с SelfPrivacy."
|
"multiple_domains_found_text": "Предоставленный токен дает доступ к следующим доменам. Пожалуйста, выберите тот, который вы хотите использовать. Для обеспечения безопасности других доменов следует ограничить доступ этого токена только тем доменом, который вы хотите использовать с SelfPrivacy.",
|
||||||
|
"domain_critical_error": "Не получается подключиться к домену! Нажмите для подробностей…"
|
||||||
},
|
},
|
||||||
"recovering": {
|
"recovering": {
|
||||||
"generic_error": "Ошибка проведения операции, попробуйте ещё раз.",
|
"generic_error": "Ошибка проведения операции, попробуйте ещё раз.",
|
||||||
"recovery_main_header": "Подключение к существующему серверу SelfPrivacy",
|
"recovery_main_header": "Подключение к существующему серверу",
|
||||||
"domain_recovery_description": "Введите домен, по которому вы хотите получить доступ к серверу:",
|
"domain_recovery_description": "Введите домен, по которому вы хотите получить доступ к серверу:",
|
||||||
"domain_recover_placeholder": "Домен",
|
"domain_recover_placeholder": "Домен",
|
||||||
"domain_recover_error": "Не удалось найти сервер с таким доменом",
|
"domain_recover_error": "Не удалось найти сервер с таким доменом",
|
||||||
|
@ -626,6 +627,10 @@
|
||||||
"cubit_statuses": "Текущий статут кубитов загрузки",
|
"cubit_statuses": "Текущий статут кубитов загрузки",
|
||||||
"reset_onboarding_description": "Принудить показ приветственного экрана",
|
"reset_onboarding_description": "Принудить показ приветственного экрана",
|
||||||
"ignore_tls_description": "Приложение не будет проверять сертификаты TLS при подключении к серверу.",
|
"ignore_tls_description": "Приложение не будет проверять сертификаты TLS при подключении к серверу.",
|
||||||
"ignore_tls": "Не проверять сертификаты TLS"
|
"ignore_tls": "Не проверять сертификаты TLS",
|
||||||
|
"add_root_ssh_key": "Добавить ключ суперпользователя",
|
||||||
|
"root_ssh_key_added": "SSH ключ суперпользователя задан и будет применён",
|
||||||
|
"allow_ssh_key_at_setup": "Разрешить задавать SSH ключи суперпользователя во время установки",
|
||||||
|
"allow_ssh_key_at_setup_description": "Кнопка для добавления ключа появится на экране подтверждения."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
devtools_options.yaml
Normal file
1
devtools_options.yaml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
extensions:
|
47
fastlane/metadata/android/en-US/changelogs/0.10.0.txt
Normal file
47
fastlane/metadata/android/en-US/changelogs/0.10.0.txt
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **Server installation**: New NixOS version is used during server setup ([#415](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/415))
|
||||||
|
- It is also possible to set a root SSH key during server setup. This feature can be activated in developer settings.
|
||||||
|
- **DNS management**: DNS records creation dynamically gets desired records from the server now ([#424](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/424), resolves [#265](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/265))
|
||||||
|
- **UI**: Add the button to copy password on the new user creation screen ([#409](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/409), resolves [#299](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/299))
|
||||||
|
- **UI**: Add animation to the recovery key screen ([#410](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/410), resolves [#164](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/164))
|
||||||
|
- **Backups**: Bucket name now includes the date of creation ([#403](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/403), resolves [#263](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/263))
|
||||||
|
- **UI**: Snapshots List page now shows the button to open the Jobs sheet ([#396](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/396), resolves [#290](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/290))
|
||||||
|
- **Server installation**: Implement better domain ownership check during installation ([#394](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/394), resolves [#389](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/389))
|
||||||
|
- **UI**: Implement flexible precision formatting for prices ([#387](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/387))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **UI**: Domain name no longer overflows the screen ([#422](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/422), resolves [#408](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/408))
|
||||||
|
- **UI**: Fix overflow of the filled buttons
|
||||||
|
- **Hetzner**: Filter away ARM architecture from available servers ([#404](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/404), resolves [#402](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/402))
|
||||||
|
- **UI**: Add refresh indicator on the 'Devices' screen ([#398](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/398), resolves [#258](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/258) and [#163](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/163))
|
||||||
|
- **GraphQL API**: Force DateTime to UTC when timezone naive ([#386](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/386), resolves [#385](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/385))
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
- Updated the copyright year ([#417](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/417))
|
||||||
|
- Happy new year!
|
||||||
|
- Upgrade to Flutter 3.16.1
|
||||||
|
- Rename the Recovery flow button to prevent user confusion ([#399](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/399), resolves [#346](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/346))
|
||||||
|
- **GraphQL API**: Remove and replace deprecated mutations ([#423](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/423), resolves [#418](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/418))
|
||||||
|
|
||||||
|
### Translation contributions
|
||||||
|
|
||||||
|
* French
|
||||||
|
* smtg (12)
|
||||||
|
|
||||||
|
* Spanish
|
||||||
|
* NaiJi ✨ (35)
|
||||||
|
|
||||||
|
* German
|
||||||
|
* Marvin F (23)
|
||||||
|
* User 1234 (30)
|
||||||
|
|
||||||
|
* Hebrew
|
||||||
|
* Yaron (578)
|
||||||
|
|
||||||
|
* Russian
|
||||||
|
* def (4)
|
||||||
|
* NaiJi ✨ (9)
|
||||||
|
* Inex Code (10)
|
|
@ -168,25 +168,19 @@ class DnsRecordsCubit
|
||||||
|
|
||||||
Future<void> fix() async {
|
Future<void> fix() async {
|
||||||
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
|
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
|
||||||
|
final List<DnsRecord> records = await api.getDnsRecords();
|
||||||
|
|
||||||
|
/// TODO: Error handling?
|
||||||
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
|
||||||
final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4;
|
|
||||||
await ProvidersController.currentDnsProvider!.removeDomainRecords(
|
await ProvidersController.currentDnsProvider!.removeDomainRecords(
|
||||||
|
records: records,
|
||||||
domain: domain!,
|
domain: domain!,
|
||||||
);
|
);
|
||||||
await ProvidersController.currentDnsProvider!.createDomainRecords(
|
await ProvidersController.currentDnsProvider!.createDomainRecords(
|
||||||
|
records: records,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
ip4: ipAddress,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final List<DnsRecord> records = await api.getDnsRecords();
|
|
||||||
final DnsRecord? dkimRecord = extractDkimRecord(records);
|
|
||||||
if (dkimRecord != null) {
|
|
||||||
await ProvidersController.currentDnsProvider!.setDnsRecord(
|
|
||||||
dkimRecord,
|
|
||||||
domain,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await load();
|
await load();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -264,12 +264,22 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
||||||
final ServerHostingDetails serverDetails,
|
final ServerHostingDetails serverDetails,
|
||||||
) async {
|
) async {
|
||||||
await repository.saveServerDetails(serverDetails);
|
await repository.saveServerDetails(serverDetails);
|
||||||
|
|
||||||
|
/// TODO: Error handling?
|
||||||
await ProvidersController.currentDnsProvider!.removeDomainRecords(
|
await ProvidersController.currentDnsProvider!.removeDomainRecords(
|
||||||
ip4: serverDetails.ip4,
|
records: getProjectDnsRecords(
|
||||||
|
state.serverDomain!.domainName,
|
||||||
|
serverDetails.ip4,
|
||||||
|
false,
|
||||||
|
),
|
||||||
domain: state.serverDomain!,
|
domain: state.serverDomain!,
|
||||||
);
|
);
|
||||||
await ProvidersController.currentDnsProvider!.createDomainRecords(
|
await ProvidersController.currentDnsProvider!.createDomainRecords(
|
||||||
ip4: serverDetails.ip4,
|
records: getProjectDnsRecords(
|
||||||
|
state.serverDomain!.domainName,
|
||||||
|
serverDetails.ip4,
|
||||||
|
true,
|
||||||
|
),
|
||||||
domain: state.serverDomain!,
|
domain: state.serverDomain!,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -558,14 +558,30 @@ class ServerInstallationRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> deleteServer(final ServerDomain serverDomain) async {
|
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 =
|
final deletionResult =
|
||||||
await ProvidersController.currentServerProvider!.deleteServer(
|
await ProvidersController.currentServerProvider!.deleteServer(
|
||||||
serverDomain.domainName,
|
serverDomain.domainName,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!deletionResult.success) {
|
if (!deletionResult.success) {
|
||||||
getIt<NavigationService>()
|
getIt<NavigationService>().showSnackBar(
|
||||||
.showSnackBar('modals.server_validators_error'.tr());
|
'modals.server_validators_error'.tr(),
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,13 +592,6 @@ class ServerInstallationRepository {
|
||||||
await box.put(BNames.isLoading, false);
|
await box.put(BNames.isLoading, false);
|
||||||
await box.put(BNames.serverDetails, null);
|
await box.put(BNames.serverDetails, null);
|
||||||
|
|
||||||
final GenericResult<void> removalResult = await ProvidersController
|
|
||||||
.currentDnsProvider!
|
|
||||||
.removeDomainRecords(domain: serverDomain);
|
|
||||||
|
|
||||||
if (!removalResult.success) {
|
|
||||||
getIt<NavigationService>().showSnackBar('modals.dns_removal_error'.tr());
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,18 @@ CloudflareDnsRecord _fromDnsRecord(
|
||||||
final DnsRecord dnsRecord,
|
final DnsRecord dnsRecord,
|
||||||
final String rootDomain,
|
final String rootDomain,
|
||||||
) {
|
) {
|
||||||
|
final String type = dnsRecord.type;
|
||||||
String name = dnsRecord.name ?? '';
|
String name = dnsRecord.name ?? '';
|
||||||
if (name != rootDomain && name != '@') {
|
if (name != rootDomain && name != '@') {
|
||||||
name = '$name.$rootDomain';
|
name = '$name.$rootDomain';
|
||||||
}
|
}
|
||||||
|
if (type == 'MX' && name == '@') {
|
||||||
|
name = rootDomain;
|
||||||
|
}
|
||||||
return CloudflareDnsRecord(
|
return CloudflareDnsRecord(
|
||||||
content: dnsRecord.content,
|
content: dnsRecord.content,
|
||||||
name: name,
|
name: name,
|
||||||
type: dnsRecord.type,
|
type: type,
|
||||||
zoneName: rootDomain,
|
zoneName: rootDomain,
|
||||||
id: null,
|
id: null,
|
||||||
ttl: dnsRecord.ttl,
|
ttl: dnsRecord.ttl,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_providers/cloudflare_dns_info.dart';
|
import 'package:selfprivacy/logic/models/json/dns_providers/cloudflare_dns_info.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
||||||
import 'package:selfprivacy/utils/network_utils.dart';
|
|
||||||
|
|
||||||
class ApiAdapter {
|
class ApiAdapter {
|
||||||
ApiAdapter({
|
ApiAdapter({
|
||||||
|
@ -80,15 +79,14 @@ class CloudflareDnsProvider extends DnsProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<void>> createDomainRecords({
|
Future<GenericResult<void>> createDomainRecords({
|
||||||
|
required final List<DnsRecord> records,
|
||||||
required final ServerDomain domain,
|
required final ServerDomain domain,
|
||||||
final String? ip4,
|
|
||||||
}) async {
|
}) async {
|
||||||
final syncZoneIdResult = await syncZoneId(domain.domainName);
|
final syncZoneIdResult = await syncZoneId(domain.domainName);
|
||||||
if (!syncZoneIdResult.success) {
|
if (!syncZoneIdResult.success) {
|
||||||
return syncZoneIdResult;
|
return syncZoneIdResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
final records = getProjectDnsRecords(domain.domainName, ip4);
|
|
||||||
return _adapter.api().createMultipleDnsRecords(
|
return _adapter.api().createMultipleDnsRecords(
|
||||||
zoneId: _adapter.cachedZoneId,
|
zoneId: _adapter.cachedZoneId,
|
||||||
records: records
|
records: records
|
||||||
|
@ -102,16 +100,17 @@ class CloudflareDnsProvider extends DnsProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<void>> removeDomainRecords({
|
Future<GenericResult<void>> removeDomainRecords({
|
||||||
|
required final List<DnsRecord> records,
|
||||||
required final ServerDomain domain,
|
required final ServerDomain domain,
|
||||||
final String? ip4,
|
|
||||||
}) async {
|
}) async {
|
||||||
final syncZoneIdResult = await syncZoneId(domain.domainName);
|
final syncZoneIdResult = await syncZoneId(domain.domainName);
|
||||||
if (!syncZoneIdResult.success) {
|
if (!syncZoneIdResult.success) {
|
||||||
return syncZoneIdResult;
|
return syncZoneIdResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
final result =
|
final result = await _adapter.api().getDnsRecords(
|
||||||
await _adapter.api().getDnsRecords(zoneId: _adapter.cachedZoneId);
|
zoneId: _adapter.cachedZoneId,
|
||||||
|
);
|
||||||
if (result.data.isEmpty || !result.success) {
|
if (result.data.isEmpty || !result.success) {
|
||||||
return GenericResult(
|
return GenericResult(
|
||||||
success: result.success,
|
success: result.success,
|
||||||
|
@ -121,9 +120,29 @@ class CloudflareDnsProvider extends DnsProvider {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<CloudflareDnsRecord> selfprivacyRecords = records
|
||||||
|
.map(
|
||||||
|
(final record) => CloudflareDnsRecord.fromDnsRecord(
|
||||||
|
record,
|
||||||
|
domain.domainName,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final List<CloudflareDnsRecord> cloudflareRecords = result.data;
|
||||||
|
|
||||||
|
/// Remove all records that do not match with SelfPrivacy
|
||||||
|
cloudflareRecords.removeWhere(
|
||||||
|
(final cloudflareRecord) => !selfprivacyRecords.any(
|
||||||
|
(final selfprivacyRecord) =>
|
||||||
|
selfprivacyRecord.type == cloudflareRecord.type &&
|
||||||
|
selfprivacyRecord.name == cloudflareRecord.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return _adapter.api().removeSimilarRecords(
|
return _adapter.api().removeSimilarRecords(
|
||||||
zoneId: _adapter.cachedZoneId,
|
zoneId: _adapter.cachedZoneId,
|
||||||
records: result.data,
|
records: cloudflareRecords,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_providers/desec_dns_info.dart';
|
import 'package:selfprivacy/logic/models/json/dns_providers/desec_dns_info.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
||||||
import 'package:selfprivacy/utils/network_utils.dart';
|
|
||||||
|
|
||||||
class ApiAdapter {
|
class ApiAdapter {
|
||||||
ApiAdapter({final bool isWithToken = true})
|
ApiAdapter({final bool isWithToken = true})
|
||||||
|
@ -75,16 +74,11 @@ class DesecDnsProvider extends DnsProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<void>> createDomainRecords({
|
Future<GenericResult<void>> createDomainRecords({
|
||||||
|
required final List<DnsRecord> records,
|
||||||
required final ServerDomain domain,
|
required final ServerDomain domain,
|
||||||
final String? ip4,
|
|
||||||
}) async {
|
}) async {
|
||||||
final List<DnsRecord> listDnsRecords = getProjectDnsRecords(
|
|
||||||
domain.domainName,
|
|
||||||
ip4,
|
|
||||||
);
|
|
||||||
|
|
||||||
final List<DesecDnsRecord> bulkRecords = [];
|
final List<DesecDnsRecord> bulkRecords = [];
|
||||||
for (final DnsRecord record in listDnsRecords) {
|
for (final DnsRecord record in records) {
|
||||||
bulkRecords.add(DesecDnsRecord.fromDnsRecord(record, domain.domainName));
|
bulkRecords.add(DesecDnsRecord.fromDnsRecord(record, domain.domainName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,21 +90,19 @@ class DesecDnsProvider extends DnsProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<void>> removeDomainRecords({
|
Future<GenericResult<void>> removeDomainRecords({
|
||||||
|
required final List<DnsRecord> records,
|
||||||
required final ServerDomain domain,
|
required final ServerDomain domain,
|
||||||
final String? ip4,
|
|
||||||
}) async {
|
}) async {
|
||||||
final List<DnsRecord> listDnsRecords = getProjectDnsRecords(
|
|
||||||
domain.domainName,
|
|
||||||
ip4,
|
|
||||||
);
|
|
||||||
|
|
||||||
final List<DesecDnsRecord> bulkRecords = [];
|
final List<DesecDnsRecord> bulkRecords = [];
|
||||||
for (final DnsRecord record in listDnsRecords) {
|
for (final DnsRecord record in records) {
|
||||||
final desecRecord = DesecDnsRecord.fromDnsRecord(
|
final desecRecord = DesecDnsRecord.fromDnsRecord(
|
||||||
record,
|
record,
|
||||||
domain.domainName,
|
domain.domainName,
|
||||||
);
|
);
|
||||||
bulkRecords.add(
|
bulkRecords.add(
|
||||||
|
/// Yes, it looks weird, but exactly forcing 'records' field
|
||||||
|
/// to empty array signals deSEC to remove the DNS record completely
|
||||||
|
/// https://desec.readthedocs.io/en/latest/dns/rrsets.html#deleting-an-rrset
|
||||||
DesecDnsRecord(
|
DesecDnsRecord(
|
||||||
subname: desecRecord.subname,
|
subname: desecRecord.subname,
|
||||||
type: desecRecord.type,
|
type: desecRecord.type,
|
||||||
|
@ -119,14 +111,6 @@ class DesecDnsProvider extends DnsProvider {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
bulkRecords.add(
|
|
||||||
DesecDnsRecord(
|
|
||||||
subname: 'selector._domainkey',
|
|
||||||
type: 'TXT',
|
|
||||||
ttl: 18000,
|
|
||||||
records: [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return _adapter.api().removeSimilarRecords(
|
return _adapter.api().removeSimilarRecords(
|
||||||
domainName: domain.domainName,
|
domainName: domain.domainName,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_providers/digital_ocean_dns_info.dart';
|
import 'package:selfprivacy/logic/models/json/dns_providers/digital_ocean_dns_info.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
import 'package:selfprivacy/logic/models/json/dns_records.dart';
|
||||||
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
|
||||||
import 'package:selfprivacy/utils/network_utils.dart';
|
|
||||||
|
|
||||||
class ApiAdapter {
|
class ApiAdapter {
|
||||||
ApiAdapter({final bool isWithToken = true})
|
ApiAdapter({final bool isWithToken = true})
|
||||||
|
@ -75,15 +74,12 @@ class DigitalOceanDnsProvider extends DnsProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<void>> createDomainRecords({
|
Future<GenericResult<void>> createDomainRecords({
|
||||||
|
required final List<DnsRecord> records,
|
||||||
required final ServerDomain domain,
|
required final ServerDomain domain,
|
||||||
final String? ip4,
|
|
||||||
}) async =>
|
}) async =>
|
||||||
_adapter.api().createMultipleDnsRecords(
|
_adapter.api().createMultipleDnsRecords(
|
||||||
domainName: domain.domainName,
|
domainName: domain.domainName,
|
||||||
records: getProjectDnsRecords(
|
records: records
|
||||||
domain.domainName,
|
|
||||||
ip4,
|
|
||||||
)
|
|
||||||
.map<DigitalOceanDnsRecord>(
|
.map<DigitalOceanDnsRecord>(
|
||||||
(final e) =>
|
(final e) =>
|
||||||
DigitalOceanDnsRecord.fromDnsRecord(e, domain.domainName),
|
DigitalOceanDnsRecord.fromDnsRecord(e, domain.domainName),
|
||||||
|
@ -93,8 +89,8 @@ class DigitalOceanDnsProvider extends DnsProvider {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GenericResult<void>> removeDomainRecords({
|
Future<GenericResult<void>> removeDomainRecords({
|
||||||
|
required final List<DnsRecord> records,
|
||||||
required final ServerDomain domain,
|
required final ServerDomain domain,
|
||||||
final String? ip4,
|
|
||||||
}) async {
|
}) async {
|
||||||
final result = await _adapter.api().getDnsRecords(domain.domainName);
|
final result = await _adapter.api().getDnsRecords(domain.domainName);
|
||||||
if (result.data.isEmpty || !result.success) {
|
if (result.data.isEmpty || !result.success) {
|
||||||
|
@ -106,17 +102,29 @@ class DigitalOceanDnsProvider extends DnsProvider {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ignoreType = 'SOA';
|
final List<DigitalOceanDnsRecord> selfprivacyRecords = records
|
||||||
final List<DigitalOceanDnsRecord> filteredRecords = [];
|
.map(
|
||||||
for (final record in result.data) {
|
(final record) => DigitalOceanDnsRecord.fromDnsRecord(
|
||||||
if (record.type != ignoreType) {
|
record,
|
||||||
filteredRecords.add(record);
|
domain.domainName,
|
||||||
}
|
),
|
||||||
}
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final List<DigitalOceanDnsRecord> oceanRecords = result.data;
|
||||||
|
|
||||||
|
/// Remove all records that do not match with SelfPrivacy
|
||||||
|
oceanRecords.removeWhere(
|
||||||
|
(final oceanRecord) => !selfprivacyRecords.any(
|
||||||
|
(final selfprivacyRecord) =>
|
||||||
|
selfprivacyRecord.type == oceanRecord.type &&
|
||||||
|
selfprivacyRecord.name == oceanRecord.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return _adapter.api().removeSimilarRecords(
|
return _adapter.api().removeSimilarRecords(
|
||||||
domainName: domain.domainName,
|
domainName: domain.domainName,
|
||||||
records: filteredRecords,
|
records: oceanRecords,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,23 +23,21 @@ abstract class DnsProvider {
|
||||||
/// Returns list of all available domain entries assigned to the account.
|
/// Returns list of all available domain entries assigned to the account.
|
||||||
Future<GenericResult<List<ServerDomain>>> domainList();
|
Future<GenericResult<List<ServerDomain>>> domainList();
|
||||||
|
|
||||||
/// Tries to create all main domain records needed
|
/// Tries to create domain records
|
||||||
/// for SelfPrivacy to launch on requested domain by ip4.
|
/// by our records list.
|
||||||
///
|
///
|
||||||
/// Doesn't check for duplication, cleaning has
|
/// Doesn't check for duplication, cleaning has
|
||||||
/// to be done beforehand by [removeDomainRecords]
|
/// to be done beforehand by [removeDomainRecords]
|
||||||
Future<GenericResult<void>> createDomainRecords({
|
Future<GenericResult<void>> createDomainRecords({
|
||||||
|
required final List<DnsRecord> records,
|
||||||
required final ServerDomain domain,
|
required final ServerDomain domain,
|
||||||
final String? ip4,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Tries to remove all domain records of requested domain by ip4.
|
/// Tries to remove all records of requested
|
||||||
///
|
/// domain that match our records list.
|
||||||
/// Will remove all entries, including the ones
|
|
||||||
/// that weren't created by SelfPrivacy.
|
|
||||||
Future<GenericResult<void>> removeDomainRecords({
|
Future<GenericResult<void>> removeDomainRecords({
|
||||||
|
required final List<DnsRecord> records,
|
||||||
required final ServerDomain domain,
|
required final ServerDomain domain,
|
||||||
final String? ip4,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Returns list of all [DnsRecord] entries assigned to requested domain.
|
/// Returns list of all [DnsRecord] entries assigned to requested domain.
|
||||||
|
|
|
@ -87,9 +87,13 @@ class _DomainPickerState extends State<DomainPicker> {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Text(
|
Expanded(
|
||||||
domain,
|
child: Text(
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
domain,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -118,12 +122,16 @@ class _DomainPickerState extends State<DomainPicker> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Expanded(
|
||||||
state.domain,
|
child: Text(
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
state.domain,
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
),
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
textAlign: TextAlign.center,
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -93,6 +93,7 @@ void launchURL(final url) async {
|
||||||
List<DnsRecord> getProjectDnsRecords(
|
List<DnsRecord> getProjectDnsRecords(
|
||||||
final String? domainName,
|
final String? domainName,
|
||||||
final String? ip4,
|
final String? ip4,
|
||||||
|
final bool isCreating,
|
||||||
) {
|
) {
|
||||||
final DnsRecord domainA =
|
final DnsRecord domainA =
|
||||||
DnsRecord(type: 'A', name: domainName, content: ip4);
|
DnsRecord(type: 'A', name: domainName, content: ip4);
|
||||||
|
@ -121,6 +122,16 @@ List<DnsRecord> getProjectDnsRecords(
|
||||||
ttl: 18000,
|
ttl: 18000,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// We never create this record!
|
||||||
|
/// This declaration is only for removal
|
||||||
|
/// as we need to compare by 'type' and 'name'
|
||||||
|
final DnsRecord txt3 = DnsRecord(
|
||||||
|
type: 'TXT',
|
||||||
|
name: 'selector._domainkey',
|
||||||
|
content: 'v=DKIM1; k=rsa; p=none',
|
||||||
|
ttl: 18000,
|
||||||
|
);
|
||||||
|
|
||||||
return <DnsRecord>[
|
return <DnsRecord>[
|
||||||
domainA,
|
domainA,
|
||||||
apiA,
|
apiA,
|
||||||
|
@ -132,6 +143,7 @@ List<DnsRecord> getProjectDnsRecords(
|
||||||
mx,
|
mx,
|
||||||
txt1,
|
txt1,
|
||||||
txt2,
|
txt2,
|
||||||
|
if (!isCreating) txt3,
|
||||||
vpn,
|
vpn,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
name: selfprivacy
|
name: selfprivacy
|
||||||
description: selfprivacy.org
|
description: selfprivacy.org
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.9.1+19
|
version: 0.10.0+20
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.2.1 <4.0.0'
|
sdk: '>=3.2.1 <4.0.0'
|
||||||
|
|
Loading…
Reference in a new issue