새로운 프로젝트를 위해 멋진 도메인을 구매했는데, support@my-domain.com 같은 메일 주소로 답장까지 보낼 수 있다면 얼마나 프로페셔널해 보일까요?
최근 Cloudflare에서 도메인을 구매하고 Gmail로 메일을 연동하는 과정에서 겪은 시행착오와 해결 방안을 공유합니다. 특히 "메일은 받아지는데 왜 발신은 안 되지?" 혹은 "왜 내 메일이 스팸함에 가지?"라는 고민을 가진 초보 개발자분들에게 이 글이 완벽한 가이드가 될 것입니다.
Cloudflare의 Email Routing은 기본적으로 '포워딩(전달)' 서비스입니다. 내 도메인으로 오는 메일을 내 Gmail로 던져주는 역할만 하죠. 하지만 Gmail에서 내 도메인 이름표를 달고 메일을 발송하려면, 메일을 대신 배달해줄 SMTP 서버(우체국)가 필요합니다.
우리는 별도의 유료 서비스 없이, 우리가 이미 가진 Gmail의 SMTP 서버를 활용하는 영리한 방법을 사용할 것입니다.
2. Gmail을 발신 서버로 설정하기
Step 1: Google 앱 비밀번호 생성
Gmail은 보안상 일반 비밀번호로 외부 연결을 허용하지 않습니다.
Google 계정 설정 > 보안 > 2단계 인증을 활성화하세요.
하단의 앱 비밀번호 메뉴에서 'Cloudflare SMTP' 등의 이름으로 16자리 비밀번호를 생성해 저장해둡니다.
Step 2: Gmail에 다른 주소 추가
Gmail 설정 > 계정 및 가져오기 > 다른 주소에서 메일 보내기를 클릭합니다.
내 도메인 메일(예: support@example.com)을 입력합니다. 이때 '별칭으로 간주'는 체크 해제하는 것을 추천합니다. 그래야 개인 메일과 업무용 메일이 명확히 분리됩니다.
SMTP 서버 정보 입력:
SMTP 서버:smtp.gmail.com
사용자 이름: 내 Gmail 주소 전체
비밀번호: 아까 만든 16자리 앱 비밀번호
3. "신뢰할 수 없는 발신자" 경고 해결하기 (DNS 설정)
설정을 마쳤는데 메일을 보내보니 "보낸 사람을 확인할 수 없다"는 경고가 뜨나요? 이건 수신 측 서버가 여러분의 메일을 '사칭 메일'로 의심하기 때문입니다. 이를 해결하기 위해 Cloudflare DNS에 세 가지 조치를 취해야 합니다.
1) SPF (누가 보낼 수 있는가?)
내 도메인을 대신해 메일을 보낼 수 있는 서버 목록을 등록합니다.
기존:v=spf1 include:_spf.mx.cloudflare.net ~all
수정:v=spf1 include:_spf.mx.cloudflare.net include:_spf.google.com ~all (Google 서버도 내 도메인을 쓸 수 있다고 허가해주는 것입니다.)
2) DKIM (메일이 변조되었는가?)
메일에 디지털 서명을 입히는 과정입니다. Cloudflare에서 제공하는 기본 DKIM 레코드를 유지하세요.
3) DMARC (인증 실패 시 어떻게 할 것인가?)
최근 보안 트렌드에서 가장 중요한 레코드입니다. 이 레코드가 없으면 스팸 처리될 확률이 높습니다.
타입: TXT
이름:_dmarc
값:v=DMARC1; p=none; (p=none은 초기 설정 시 메일 유실을 방지하면서 모니터링하기 가장 안전한 값입니다.)
4. 초보 개발자를 위한 디버깅 팁: PowerShell 활용
설정이 잘 되었는지 브라우저에서만 확인하지 말고, 터미널을 열어 직접 질의해 보세요.
# SPF 레코드 확인
Resolve-DnsName -Name 내도메인 -Type TXT
# DMARC 레코드 확인
Resolve-DnsName -Name _dmarc.내도메인 -Type TXT
결과 값에 내가 입력한 include:_spf.google.com이나 v=DMARC1이 보인다면 설정 성공입니다!
5. 마치며: 인프라의 이해가 성장을 만듭니다
의의: 단순히 클릭 몇 번으로 끝낼 수도 있지만, SMTP, SPF, DKIM, DMARC라는 개념을 이해하는 과정은 네트워크와 보안 인프라를 이해하는 훌륭한 밑거름이 됩니다.
처음 설정을 마친 뒤 경고 문구가 바로 사라지지 않더라도 당황하지 마세요. DNS 전파와 수신 서버의 신뢰도 업데이트에는 시간(최대 24시간)이 필요합니다.
Cloudflare에서 구매한 도메인을 Gmail과 연동하여 무료로 메일을 주고받는 방법은 크게 두 단계로 나뉩니다. 받는 메일은 Cloudflare의 이메일 라우팅(Email Routing) 기능을 사용하고, 보내는 메일은 Gmail의 SMTP 서버와 앱 비밀번호를 이용해 설정합니다.
1단계: 메일 받기 설정 (Cloudflare Email Routing)
Cloudflare 대시보드에서 내 도메인으로 오는 메일을 내 기존 Gmail 계정으로 전달하는 설정입니다.
Email Routing 활성화: Cloudflare 대시보드에서 해당 도메인을 선택한 후 왼쪽 메뉴의 Email > Email Routing으로 들어갑니다.
대상 주소 등록: Destination addresses 탭에서 메일을 실제 수신할 본인의 Gmail 주소를 입력하고 인증 메일을 확인합니다.
라우팅 규칙 생성: Routing rules에서 me@yourdomain.com과 같은 커스텀 주소를 만들고, 이를 방금 인증한 Gmail 주소로 전달(Forward to)하도록 설정합니다.
DNS 레코드 자동 추가: 설정 과정 중 'Add records and enable' 버튼을 눌러 필요한 MX 및 TXT(SPF) 레코드를 자동으로 DNS에 등록합니다.
2단계: 메일 보내기 설정 (Gmail SMTP 연동)
Gmail 인터페이스에서 내 커스텀 도메인 주소를 발신인으로 설정하는 과정입니다.
Google 앱 비밀번호 생성:
Google 계정 설정에서 2단계 인증이 활성화되어 있어야 합니다.
보안 메뉴에서 앱 비밀번호(App Passwords)를 검색하여 '메일'용 비밀번호를 생성하고 생성된 16자리 코드를 복사해둡니다.
로그를 뜯어보니, 로그인 버튼을 누르려는데 정해진 시간 내에 버튼이 나타나지 않거나, 투명한 다이얼로그(팝업) 레이어가 버튼 위를 덮고 있어 클릭 이벤트가 전달되지 않는 상태였습니다.
2. 원인 분석
핵심 원인 1: 공유 메모리(/dev/shm)의 부족
도커 컨테이너는 기본적으로 공유 메모리 공간을 64MB로 극단적으로 제한합니다. 하지만 Chromium 브라우저는 복잡한 페이지를 렌더링할 때 이보다 훨씬 많은 메모리를 사용합니다. 메모리가 부족하면 화면 렌더링이 꼬이거나, 닫혔어야 할 팝업이 그대로 남아 버튼을 가리게 됩니다.
핵심 원인 2: 샌드박스 보안 충돌
도커라는 격리 공간 안에서 브라우저가 또 자체 샌드박스를 만들려다 보니 렌더링 스레드가 비정상적으로 작동하는 경우가 많습니다.
핵심 원인 3: Headless 환경의 UI 변형
환경 차이로 인해 평소엔 안 뜨던 보안 안내 팝업이나 공지사항이 도커 환경에서만 튀어나올 수 있습니다. 이때 Playwright의 일반적인 click()은 가려진 요소를 누르지 못하고 타임아웃에 빠집니다.
3. 해결 방안
Step 1. docker-compose 설정 (메모리 해방)
도커 컨테이너가 브라우저 렌더링을 위해 충분한 공유 메모리를 쓸 수 있도록 shm_size를 늘려줍니다.
안녕하세요! 오늘은 윈도우와 리눅스(Xubuntu 등)를 함께 사용하는 분들이 가장 흔히 겪는 불편함 중 하나인 "부팅 시마다 나타나는 BitLocker(장치 암호화) 복구 키 입력 창" 문제를 해결하는 방법을 정리해 보겠습니다.
1. 왜 자꾸 복구 키를 입력하라고 하나요?
윈도우의 장치 암호화(BitLocker) 기능은 보안 칩(TPM)을 통해 시스템의 안전을 확인합니다. 리눅스 설치를 위해 BIOS에서 Secure Boot(보안 부팅)를 끄거나 부팅 순서를 바꾸면, TPM은 "시스템이 변조되었다"고 판단하여 데이터 보호를 위해 복구 키를 요구하게 됩니다.
2. 보안적으로 암호화를 해제해도 괜찮을까?
결론부터 말씀드리면, 사용 환경에 따라 다릅니다.
해제해도 괜찮은 경우: 노트북을 집에서만 사용하거나, 분실 위험이 낮은 데스크탑인 경우.
유지해야 하는 경우: 카페, 학교 등 외부 이동이 잦은 노트북. (분실 시 SSD를 떼어가도 데이터를 볼 수 없게 보호해 줍니다.)
불편함을 없애는 방법은 두 가지입니다. 보안을 유지하며 해결하거나, 아예 기능을 끄는 것입니다.
3. 해결 방법 A: 보안 부팅(Secure Boot) 켜고 암호화 유지하기 (추천)
보안과 편의성을 모두 잡는 방법입니다. 최신 리눅스(Xubuntu 25.10 등)는 보안 부팅을 지원합니다.
리눅스에서 MOK 등록 준비:
터미널에서 다음 명령어 입력 후 일회용 비밀번호 설정.
sudo update-secureboot-policy --enroll-key
BIOS 설정:
재부팅 시 BIOS에 진입하여 Secure Boot를 [Enabled]로 변경.
MOK 등록(파란 화면):
리눅스로 부팅될 때 파란 화면(Perform MOK management)이 뜨면 Enroll MOK -> Continue -> Yes를 선택하고 아까 정한 비밀번호 입력.
윈도우에서 암호화 갱신:
윈도우로 부팅(마지막 키 입력) 후, [설정] > [장치 암호화]에서 기능을 잠시 [끄기] 했다가 다시 [켜기]를 합니다.
이제 시스템이 "보안 부팅이 켜진 상태"를 정상으로 기억하여 더 이상 키를 묻지 않습니다.
4. 해결 방법 B: 장치 암호화 기능 완전히 끄기
설정이 복잡하고 보안보다는 편의성이 중요하다면 암호화 자체를 해제하면 됩니다.
윈도우 [설정] > [개인 정보 및 보안] > [장치 암호화]로 이동합니다.
[장치 암호화] 스위치를 [끄기]로 바꿉니다.
복호화 완료까지 대기: 드라이브 용량에 따라 시간이 걸릴 수 있습니다. 완료되면 자물쇠 아이콘이 사라지며, 어떤 환경에서 부팅해도 더 이상 키를 묻지 않습니다.
마치며
리눅스와 윈도우를 오가는 환경에서는 보안 부팅(Secure Boot) 설정이 BitLocker와 긴밀하게 연결되어 있습니다. 가장 권장하는 방법은 방법 A를 통해 보안을 유지하는 것이지만, 개인용 PC이고 매번 뜨는 파란 창이 너무 번거롭다면 방법 B로 해제하는 것도 합리적인 선택입니다.
vscode, android studio, git, curl, flutter 등등 플러터 개발에 필요한 필수 프로그램을 설치하고 개발에 들어갔습니다. 그런데, 일단 문제가 여럿 생겼습니다.
문제
vscode에서 keytools을 인식 못한다는 에러가 자꾸 뜸 - 일부 extension github 로그인이 유지가 안되는 문제
android studio 설치시 공식 홈페이지에 있는 라이브러리가 설치가 안되는 문제
android studio에서 생성한 에뮬레이터가 vscode에서 인식되지 않는 문제
gradle language server initialize가 끝나지 않는 문제
android studio 에뮬레이터가 불안정한 문제 - 2에서 설치를 제대로 하지 못한게 문제인지, 아니면 리눅스 환경에서 돌리는게 불안정해서인지를 몰라도, 에뮬레이터에서 앱을 돌리려 하면 제대로 돌아가지 않는다는 문제가 있었습니다.
bitlocker문제 - 리눅스 설치를 하던 중, safe booting을 꺼야 하는 일이 있었는데 이때 기존 윈도우 디스크에 bitlocker가 설정되어 있어서 윈도우 부팅시마다 키를 입력해줘야 했습니다.
하지만, 이 모든 문제를 겪고도 '속도가 빠르다' 와 '램 사용량이 적다'는 확실한 장점이 있었기에, 리눅스를 선택했습니다.
해결
무엇보다 위 5가지의 문제는 다음 방법으로 해결할 수 있었습니다.
vscode에서 keytools을 인식 못한다는 에러 -> 무시한다
android studio 설치시 공식 홈페이지에 있는 라이브러리가 설치가 안되는 문제 -> 이건 gemini한테 물어봐서 상위 버전 library을 설치함으로서 얼추 해결했습니다. (설치 안해도 돌아가긴 합니다)
android studio에서 생성한 에뮬레이터가 vscode에서 인식되지 않는 문제 -> android studio 켜서 에뮬레이터를 키거나, 5.의 해결책 사용
gradle language server initialize가 끝나지 않는 문제 -> 무시한다
android studio 에뮬레이터가 불안정한 문제 -> android studio에서 wifi 디버깅 기능을 사용해서 다른 기기에서 앱을 돌리니 잘 돌아감.
bitlocker문제 - xubuntu도 safe booting을 지원해서 설치가 완료된 이후, 관련 설정을 마무리했습니다. 다시 설정을 키고, 리눅스에서 mok 서명을 확인한 뒤, 윈도우에 들어가 bitlocker 설정을 껐다가 키면 완료됩니다.
다른 문제들은 개발에 있어 직접적인 문제는 없었지만, 5번 문제가 앱 개발에 있어 상당히 치명적이었습니다. 하지만 다행히 android studio에서 같은 wifi 내 안드로이드 기기를 무선으로 연결해 디버깅할 수 있게 하는 기능을 지원해서 해당 방법으로 다른 기기에서 앱을 돌리니 잘 돌아가는 모습을 볼 수 있었습니다.
추가 적용한 점들
Xubuntu(Xfce 데스크탑 환경)에서 해상도를 기본값으로 맞췄는데 아이콘이나 글씨가 너무 작게 보이는 문제
1. 폰트 DPI 조정을 통한 크기 조절 (가장 효과적)
배율을 1.0(기본값)으로 되돌린 후, DPI 값을 높여서 전체적인 UI 요소와 텍스트 크기를 키우는 방법입니다.
Google Play store에 app을 올리기 위해선 key로 서명을 해야 한다. 평소에는 android studio을 이용해 generate signed appbundle 기능으로 android studio에서 signed app bundle을 만들었다.
그런데 flutter에서 .env파일 사용 시 dotenv을 사용하면 apk에서 .env 값을 볼 수 있다는 글을 보았다. 따라서 flutter_dotenv패키지를 전면 교체 후 build시에 커멘드라인에서 주입하는 방식으로 교체했다.
--dart-define-fron-file=.env
이걸 build 명령시 인자에 추가해주면 더이상 .env을 asset으로 포함시키지 않아도 된다.
명령어는 다음과 같다.
# run flutter
flutter run --dart-define-from-file=.env
# run without debug
flutter run --release --dart-define-from-file=.env
# build appbundle
flutter build appbundle --release --dart-define-from-file=.env
vscode launch.json 설정
또한 vscode 단축키를 눌러 (ctrl + shift + f5) 빌드를 자주 하는데, 이때도 추가로 인자를 넣어줘야 한다. 다음과 같이 설정해서 .env 인자를 추가할 수 있다.
{
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"args": [
"--dart-define-from-file=.env"
]
},
{
"name": "Flutter (Run Without Debugging)", // 새 구성 추가
"request": "launch",
"type": "dart",
"noDebug": true, // 디버그 모드 비활성화
"args": [
"--dart-define-from-file=.env"
]
}
]
}
1. flutter build appbundle --release --dart-define-from-file=.env으로 빌드시 google play에 appbundle을 업로드하면 서명이 되지 않았다고 뜬다. 사진에서도 '업로드된 모든 번들에 서명해야 합니다' 라고 에러가 떠있는걸 볼 수 있다.
app/build.gradle.kts 파일도 잘 설정을 해줬었는데 이때는 뭐가 문제였는지 몰랐다.
2. 또한 기존에는 android studio로 appbundle을 빌드했는데, 이제는 --dart--define-from-file 커맨드가 적용되지 않아 .env파일 주입을 할 수 없었다. (빌드는 되지만, appbundle 업로드 후 test을 받으면 .env파일이 빠져있다.)
3. 또한 flutter 공식 홈페이지에서도 android studio을 이용하는 방법 말고는 제대로 appbundle 서명 및 빌드 방법을 알려주지 않는다.
...
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
...
android {
...
signingConfigs {
create("release") {
if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
}
buildTypes {
release{
if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
signingConfig = signingConfigs.getByName("release")
}
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
...
원인
build.gradle.kts에서 key.properties 파일 경로를 찾을 때, file() 메서드를 사용하는데 이게 android/ 폴더가 기본이라고 한다. 따라서 key.properties 파일을 이제까지 찾지 못하고 있던 거였고, 따라서 서명이 되지 않고 있는 거였다.
서명이 제대로 되는 로그는 이렇게 나온다. 로그는 println문을 따로 추가했다.
또한 key 파일이 들어가지 않고 있던걸 다음 로그를 통해 알 수 있었다. key.properties 파일을 불러오는 if문과 else에 각각 성공/실패시 출력을 추가했더니 해당 파일이 들어가지 않고 있단걸 확실히 알 수 있었다.
해결 방법
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("app/key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
println("key.properties file found and loaded successfully.")
}else{
println("key.properties file not found. Using default signing configuration.")
}
다음과 같이 build.gradle.kts에서 key.properties 파일 경로를 수정해줬다. key파일 출력문도 추가해준 것을 볼 수 있다.
key.properties 파일을 불러오는 if문과 else문을 추가해준것도 해당 부분이다.
appbundle이 빌드될 때 key 파일이 잘 들어가고, 문제없이 빌드되는걸 볼 수 있다.
참고로 key파일 password가 틀렸을 땐 다음과 같이 비밀번호가 틀렸다고 로그가 나온다.
해결 후 build.gradke.kts 파일
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.util.Properties
import java.io.FileInputStream
plugins {
id("com.android.application")
// START: FlutterFire Configuration
id("com.google.gms.google-services")
// END: FlutterFire Configuration
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("app/key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
println("key.properties file found and loaded successfully.")
}else{
println("key.properties file not found. Using default signing configuration.")
}
android {
namespace = "app_name"
compileSdk = 36
ndkVersion = "27.3.13750724"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
// AndroidManifest.xml에서 ${applicationName} 플레이스홀더를 대체
manifestPlaceholders += mapOf("applicationName" to "io.flutter.app.FlutterApplication")
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.yeoseyo.byeolme"
minSdk = 24
targetSdk = 36
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
versionCode = flutter.versionCode
versionName = flutter.versionName
}
signingConfigs {
create("release") {
if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}else{
println("WARNING: key.properties file not found or storeFile not set")
logger.warn("key.properties file not found or storeFile not set")
}
}
}
buildTypes {
release{
if (keystorePropertiesFile.exists() && keystoreProperties["storeFile"] != null) {
signingConfig = signingConfigs.getByName("release")
}
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
// buildTypes {
// release {
// // TODO: Add your own signing config for the release build.
// // Signing with the debug keys for now, so `flutter run --release` works.
// signingConfig = signingConfigs.getByName("debug")
// }
// }
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.fromTarget("17")
}
}
flutter {
source = "../.."
}
//dependencies {
// // Firebase BoM을 통한 버전 관리 - 모든 Firebase 라이브러리의 호환성을 보장
// implementation(platform("com.google.firebase:firebase-bom:34.1.0"))
//
// // Firebase Analytics - 앱 사용 통계 및 분석
// implementation("com.google.firebase:firebase-analytics")
//
// // Firebase Firestore - 실시간 NoSQL 데이터베이스
// implementation("com.google.firebase:firebase-firestore")
//
// // 추가 Firebase 서비스는 필요시 아래에 추가
// // Firebase Auth: implementation("com.google.firebase:firebase-auth")
// // Firebase Storage: implementation("com.google.firebase:firebase-storage")
// // Firebase Messaging: implementation("com.google.firebase:firebase-messaging")
//}
결과
이렇게 해서 문제를 해결할 수 있었다.
느낀 점
해당 문제를 찾으면서 영어로 자료를 찾아도 잘 나오지 않았었다. ai의 발전으로 stackoverflow 및 기타 커뮤니티에 글이 올라오지 않게 된 부작용이다.
다만 ai을 이용해서 문제의 원인을 추론하고, 해결할 수 있었다. gemini가 file()이 android/가 기본 폴더라는걸 알려주지 않았더라면 많이 어려웠을 것이다.
나중에 발견했는데 flutter 공식 홈페이지에선 key.properties 파일을 android 경로에 올리라고 나와 있었다... 항상 공식 문서를 잘 읽어보자.
안녕하세요! 저는 현재 메인 OS로 Windows 11을 사용하고 있는 개발자입니다. 물론 윈도우의 WSL2 기능도 훌륭하지만, OS 자체가 기본적으로 점유하는 메모리와 백그라운드 프로세스가 상당히 많다는 점은 늘 아쉬움으로 남았습니다. 특히 안드로이드 에뮬레이터와 브라우저를 동시에 띄우면 팬 소리가 거슬릴 정도로 커지곤 했죠.
저에게 필요한 건 화려한 애니메이션이나 투명 효과 같은 UI가 아니었습니다. **오직 내 코드가 0.1초라도 빨리 빌드되고, 에뮬레이터가 부드럽게 돌아가는 '쾌적한 성능'**이었습니다.
그래서 다음과 같은 이유로 새로운 환경을 구축하게 되었습니다.
Native Linux: 가상화의 오버헤드 없이 하드웨어 성능을 100% 온전히 사용하고 싶었습니다.
Xubuntu (Xfce): 예쁘지만 무거운 표준 우분투(GNOME) 대신, UI는 조금 투박해도 RAM을 적게 차지하는 가벼운 Xubuntu를 선택했습니다.
목표: 윈도우의 방해 없는 쾌적한 Flutter 개발 환경 구축입니다.
2. 준비하기: 안전한 설치를 위한 준비물
설치 전 가장 중요한 것은 '준비'입니다. 혹시 모를 상황에 대비해 윈도우의 중요 데이터는 꼭 백업해 주시길 바랍니다.