generated from ttipo/project-base
docs: 휴대폰 앱 사내 배포를 계약 · 운영 · 앱 · 디자인 문서와 스킬에 적는다
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m41s
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 22m13s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m13s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m13s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m5s
CI (Flutter) / Flutter (analyze · test) (push) Successful in 21m49s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 13m13s
CI / Frontend (lint · type-check · test · build) (push) Successful in 12m54s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 1m47s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 10m26s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m41s
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 22m13s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m13s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m13s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m5s
CI (Flutter) / Flutter (analyze · test) (push) Successful in 21m49s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 13m13s
CI / Frontend (lint · type-check · test · build) (push) Successful in 12m54s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 1m47s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 10m26s
계약 1절 "휴대폰 앱 받기"와 6절 운영 도구 명령, 서버 B 에 APK 올리는 차례와 직원 안내(링크 없이), 앱의 서명 키 · release 빌드 · 올리기 전 확인, 웹 화면 절, 스킬 표, App1Shell 보드의 앱 정보 줄. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #22.
This commit is contained in:
+108
-15
@@ -1,9 +1,10 @@
|
||||
# ARA SYSTEM 앱 (Flutter)
|
||||
|
||||
ARA SYSTEM 그룹웨어의 모바일 앱이다. 식별자 `kr.it.arasystem.app` · 이름 "ARA SYSTEM", 안드로이드 먼저(Play 내부 테스트).
|
||||
ARA SYSTEM 그룹웨어의 모바일 앱이다. 식별자 `kr.it.arasystem.app` · 이름 "ARA SYSTEM", 안드로이드 먼저.
|
||||
스토어에 올리지 않고 우리 키로 서명한 APK 를 사내에만 나눠 준다(포털의 휴대폰 앱 받기 화면 — 아래 "사내 배포", 2026-11-08).
|
||||
|
||||
- 구조 · 구현 규칙: [`.claude/skills/flutter/SKILL.md`](../.claude/skills/flutter/SKILL.md)
|
||||
- 앱 로그인(앱 전용 토큰 경로 — 서버는 아직 없다): 계약 [`docs/mail/api-contract.md`](../docs/mail/api-contract.md) 1절 "앱 로그인"
|
||||
- 앱 로그인(앱 전용 토큰 경로): 계약 [`docs/mail/api-contract.md`](../docs/mail/api-contract.md) 1절 "앱 로그인"
|
||||
- 화면 기준: [`docs/design/app/README.md`](../docs/design/app/README.md) (보드 사본 `docs/design/webmail/App1*.dc.html`)
|
||||
|
||||
이 문서는 개발 PC · 배포에서 알아 둘 사실만 적는다.
|
||||
@@ -22,6 +23,9 @@ flutter run # 에뮬레이터 · USB 실기기
|
||||
- **API 주소는 `.env` 의 `API_URL`** 하나다(루트 env 를 따르지 않는다). `.env` 는 앱 안에 그대로 들어가므로 비밀은 넣지 않는다.
|
||||
- Android 에뮬레이터: `http://10.0.2.2:3000/api` — 에뮬레이터의 10.0.2.2 가 PC 의 127.0.0.1 이다.
|
||||
- USB 실기기: `adb reverse tcp:3000 tcp:3000` 뒤 `http://localhost:3000/api`. 개발 백엔드는 `127.0.0.1` 에만 열려 있어 같은 와이파이의 휴대폰은 닿지 않는다(docker 스킬 "호스트 포트").
|
||||
- **받기 화면까지 볼 때**(업데이트 화면 · 설정 › 앱 정보의 [새 버전 받기] — 아래 "사내 배포"): vite 개발 서버(5173 — `/api` 를 백엔드로 넘긴다)를 가리킨다.
|
||||
에뮬레이터 `http://10.0.2.2:5173/api`, USB 실기기 `adb reverse tcp:5173 tcp:5173` 뒤 `http://localhost:5173/api`.
|
||||
[새 버전 받기]는 `API_URL` 의 출처에 `/app` 을 붙여 열기 때문에, 백엔드(3000)를 바로 가리키면 백엔드의 404 가 열린다. 받기 화면은 휴대폰 브라우저에서 웹메일에 로그인해야 보인다.
|
||||
- 개발 PC 의 안드로이드 도구: Android Studio 의 JDK 21 · SDK 36 · NDK 28.2(Flutter 기본)로 빌드된다. SDK 라이선스 동의(`flutter doctor --android-licenses`)는 사람이 직접 한다.
|
||||
|
||||
## Flutter 판
|
||||
@@ -36,14 +40,22 @@ flutter run # 에뮬레이터 · USB 실기기
|
||||
## 빌드
|
||||
|
||||
```bash
|
||||
flutter build apk --debug # 2~3분 · 약 160MB (build/ 는 git 이 무시한다)
|
||||
flutter build appbundle --release # Play 에 올리는 꼴 — 아래 서명 키가 먼저다
|
||||
flutter build apk --debug # 2~3분 · 약 160MB (build/ 는 git 이 무시한다) — 개발 · 시험용, debug 키로 서명한다
|
||||
flutter build apk --release # 직원에게 나눠 주는 판 — 우리 서명 키 · google-services.json · 운영 API_URL(https://<포털>/api)이 먼저다(아래 "사내 배포")
|
||||
```
|
||||
|
||||
- release 매니페스트(`android/app/src/main/AndroidManifest.xml`)에 INTERNET 권한이 있어야 네트워크가 된다(flutter create 기본은 debug · profile 에만 둔다).
|
||||
- **release 빌드는 `android/app/google-services.json` 이 없으면 멈춘다**(아래 "새 메일 알림") — 배포 판이 새 메일 알림 없이 나가지 않게. debug 빌드는 없어도 된다.
|
||||
Gradle 이 실행할 작업 그래프를 본다 — `flutter build apk` · `appbundle` 뿐 아니라 `./gradlew assemble` · `bundle` · `build`(모든 변형) · 줄여 쓴 이름(`aR`)도 멈춘다.
|
||||
debug · release 를 다른 Firebase 프로젝트로 나누려면(`app/src/debug` · `app/src/release` 에 따로) `build.gradle.kts` 의 조건도 함께 바꾼다(지금은 `app/google-services.json` 하나만 본다).
|
||||
- **release 빌드는 아래 셋 가운데 하나라도 갖추지 못하면 멈추고, 빠진 것을 한 번에 알린다**(`android/app/build.gradle.kts` 맨 아래). debug · profile 빌드는 셋 없이도 된다.
|
||||
- `android/app/google-services.json`(아래 "새 메일 알림") — 배포 판이 새 메일 알림 없이 나가지 않게.
|
||||
- 우리 서명 키 — `android/key.properties` 와 그것이 가리키는 keystore(아래 "사내 배포"). 파일이 없거나 · 칸이 비었거나 · keystore 파일이 없으면 멈춘다.
|
||||
flutter create 기본처럼 debug 키로 조용히 서명하지 않는다 — debug 키로 서명한 판을 깐 휴대폰에는 우리 키의 다음 판이 덮어 설치되지 않는다. `flutter run --release` 도 키가 든다.
|
||||
- 운영 API 주소 — 앱에 들어가는 `flutter/.env` 의 `API_URL` 이 운영 포털의 API 주소(`https://<포털>/api`, 끝의 `/` 하나까지)여야 한다:
|
||||
`https://` · 개발 주소(localhost · `.localhost` · `.test` 로 끝나는 이름 · 127.0.0.0/8 · `::1` · 0.0.0.0 · 에뮬레이터의 10.0.2.2)가 아님 · 경로 `/api` ·
|
||||
사용자 정보 · 쿼리 · 조각 없음(APK 안의 `.env` 는 누구나 풀어 볼 수 있다) · 앞뒤 빈칸 없음(앱은 읽은 값을 그대로 쓴다 — 따옴표 안의 빈칸은 떼지 않는다).
|
||||
`.env` 가 없거나 · `API_URL` 이 없거나 비었거나 · 읽지 못해도 멈춘다(값이 없으면 앱은 개발 주소로 돈다). 앱과 같은 규칙(flutter_dotenv 5.2.1 의 Parser — 주석 · `export` · 따옴표 · `$이름`)으로 읽고, 알리는 글에는 주소 값을 싣지 않는다(무엇이 틀렸는지만).
|
||||
- Gradle 이 실행할 작업 그래프를 본다 — `flutter build apk --release` 뿐 아니라 `./gradlew assemble` · `bundle` · `build`(모든 변형) · 줄여 쓴 이름(`aR`)도 멈춘다.
|
||||
- debug · release 를 다른 Firebase 프로젝트로 나누려면(`app/src/debug` · `app/src/release` 에 따로) `build.gradle.kts` 의 조건도 함께 바꾼다(지금은 `app/google-services.json` 하나만 본다).
|
||||
- CI(`ci-flutter.yml`)는 analyze · test 만 돌고 안드로이드를 빌드하지 않는다 — 서명 키가 없어도 CI 는 그대로다.
|
||||
|
||||
## 새 메일 알림 (Firebase Cloud Messaging)
|
||||
|
||||
@@ -68,15 +80,96 @@ flutter build appbundle --release # Play 에 올리는 꼴 — 아래 서명
|
||||
봉투로 바꾸려면 이 다섯 장만 같은 이름으로 바꾼다 — 매니페스트의 기본 아이콘(`default_notification_icon`)이 이 이름 하나를 가리킨다.
|
||||
서버 메시지는 아이콘 · 색을 싣지 않는다 — 휴대폰이 매니페스트의 기본값(아이콘 · `default_notification_color` · 채널 `new_mail`)으로 그린다.
|
||||
|
||||
## 배포 준비
|
||||
## 사내 배포 (안드로이드 APK — 2026-11-08)
|
||||
|
||||
- **release 서명 키는 저장소 밖의 비밀이다.** 지금 `build.gradle.kts` 의 release 는 debug 키로 서명한다 — 배포 전에 업로드 키(keystore)를 만들고 `key.properties` 로 읽게 한다. keystore · `key.properties` 는 커밋하지 않고(`android/.gitignore` 가 무시한다) 비밀번호 관리자에 둔다(잃으면 같은 앱으로 업데이트를 올릴 수 없다 — Play 앱 서명을 켜 두면 업로드 키는 다시 받을 수 있다).
|
||||
- **Play 데이터 보안 신고**: 새 메일 알림 때문에 Firebase 설치 ID(FID)를 서버에 보낸다 — Play Console 의 데이터 보안 양식에 "기기 또는 기타 ID"(수집 · 앱 기능, 공유하지 않음 · 전송 중 암호화)로 적는다.
|
||||
메일 내용 · 주소는 알림에 싣지 않는다(글은 "새 메일 N통"뿐). 광고 ID 는 쓰지 않는다(매니페스트에서 뺐다 — `tools:node="remove"`).
|
||||
- **안드로이드: Play 내부 테스트** — Play Console 계정(1회 25달러, 법인 계정은 D-U-N-S 번호), 테스터 100명까지 메일 목록으로 · 심사 없이 · 업데이트가 저절로. 식별자(`kr.it.arasystem.app`)는 올린 뒤 바꿀 수 없다.
|
||||
APK 를 직접 나눠 주는 길도 있지만 Google 의 "인증된 개발자만 설치" 정책(2026-09 부터 나라별)이 한국에 언제 걸리는지 확인이 필요하다.
|
||||
- **iOS: 맥 · Apple 개발자 계정이 먼저다** — 빌드 · 서명 · 시뮬레이터가 모두 맥(Xcode)에서만 되고, 배포 · 푸시(APNs 키)는 Apple Developer Program(연 99달러, 법인은 D-U-N-S)이 든다. 내부 배포는 TestFlight 내부 테스터(빌드 90일 만료).
|
||||
맥에서 처음 빌드할 때 `ios/Runner/Info.plist` 에 `CFBundleLocalizations`(`ko`)가 있는지 확인한다 — 없으면 복사 · 붙여 넣기 같은 시스템 글이 한국어로 나오지 않는다. bundle id 는 `kr.it.arasystem.app` 으로 맞춰 두었다(맥에서 확인 전).
|
||||
앱은 스토어에 올리지 않는다. 우리 키로 서명한 release APK 를 서버에 올리면, 웹메일에 로그인한 직원이 포털의 휴대폰 앱 받기 화면(`<포털>/app`)에서 받아 설치한다
|
||||
(PC 의 웹 설정 › 계정 › [휴대폰 앱]의 QR 코드가 그 주소다). 앱의 업데이트 화면과 설정 › 앱 정보의 [새 버전 받기]도 같은 화면을 휴대폰 브라우저로 연다 —
|
||||
주소는 `.env` 의 `API_URL` 에서 `/api` 를 뗀 출처 + `/app` 이다(같은 주소 배포 — `lib/core/app_download/`, 짝: 웹 `MOBILE_APP_PATH`).
|
||||
|
||||
### 1. 서명 키 만들기 (처음 한 번 — 사람이 한다)
|
||||
|
||||
keytool 은 Android Studio 에 든 JDK 의 것을 쓴다(`jbr\bin`). 키는 **저장소 밖**에 만든다. keytool 이 keystore 비밀번호를 두 번 묻는다.
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force "$env:USERPROFILE\arasystem-signing" | Out-Null
|
||||
& "C:\Program Files\Android\Android Studio\jbr\bin\keytool.exe" -genkeypair -v -keystore "$env:USERPROFILE\arasystem-signing\arasystem-release.jks" -alias arasystem -keyalg RSA -keysize 4096 -validity 10000 -dname "CN=ARA SYSTEM, C=KR"
|
||||
```
|
||||
|
||||
- `-dname` 은 APK 의 인증서에 그대로 보이는 이름이다 — 회사 이름을 넣으려면 `O=<회사 이름>` 을 더한다. 빼면 keytool 이 이름 · 조직 같은 칸을 차례로 묻는다.
|
||||
- 지금 keytool 의 기본 형식(PKCS12)은 키 비밀번호를 따로 묻지 않는다 — 아래 `keyPassword` 에는 `storePassword` 와 같은 값을 적는다.
|
||||
비밀번호는 영문 · 숫자로 길게 만든다(`key.properties` 는 `\` 를 이스케이프로 읽는다).
|
||||
- 유효 기간은 10000일(27년 남짓)이다 — 이 키로 앱을 계속 올리므로 짧게 잡지 않는다.
|
||||
- **keystore 파일과 비밀번호는 비밀번호 관리자에 함께 두고**(파일은 첨부로) 오프라인 사본을 하나 더 둔다. 저장소 · 메일 · 메신저에는 올리지 않는다 — 이 키를 가진 사람은 우리 앱처럼 보이는 판을 만들 수 있다.
|
||||
- **이 키는 바꿀 수 없다.** 잃거나 새로 만들면 그 뒤의 판은 직원 휴대폰의 앱 위에 설치되지 않는다 — 직원 모두가 앱을 지우고 새로 설치해야 한다(로그인 · 새 메일 알림 설정도 처음부터).
|
||||
|
||||
### 2. key.properties
|
||||
|
||||
`flutter/android/key.properties` 를 이 꼴로 만든다 — 커밋하지 않는다(`android/.gitignore` 가 `key.properties` · `*.jks` · `*.keystore` 를 무시한다).
|
||||
|
||||
```properties
|
||||
storeFile=C:/Users/<이름>/arasystem-signing/arasystem-release.jks
|
||||
storePassword=<keystore 비밀번호>
|
||||
keyAlias=arasystem
|
||||
keyPassword=<storePassword 와 같은 값>
|
||||
```
|
||||
|
||||
- 경로는 절대 경로로, `\` 대신 `/` 로 적는다(상대 경로면 `android/` 기준).
|
||||
- 파일이 없거나 · 칸이 비었거나 · keystore 파일이 없으면 release 빌드가 그 까닭을 알리고 멈춘다(위 "빌드"). 비밀번호 · 별칭이 틀리면 Gradle 의 서명 단계가 실패한다.
|
||||
|
||||
### 3. 버전 올리기
|
||||
|
||||
- `pubspec.yaml` 의 `version: <이름>+<숫자>` 를 판마다 올린다 — 예 `1.0.0+1` → `1.0.1+2`.
|
||||
- 이름(`1.0.1` — versionName): 직원에게 보이는 판(설정 › 앱 정보 · 받기 화면) · User-Agent · 서버의 최소 판과 견주는 값이다. 숫자 셋까지 · 칸마다 4자리까지(서버의 판 칸과 같다).
|
||||
- 숫자(`+2` — versionCode): **판마다 1 이상 올린다.** 숫자가 낮은 판은 깔린 앱 위에 설치되지 않는다.
|
||||
- **꼭 필요한 업데이트**(옛 앱으로는 쓸 수 없게 서버를 바꾸는 배포)면 서버 `backend/src/modules/app-config/app-config.constants.ts` 의 `MIN_SUPPORTED_APP_VERSION.android` 를 새 판 이름으로 올린다 —
|
||||
그 서버가 배포되면 옛 앱은 어느 화면에서든 업데이트 화면이 된다. **새 APK 를 먼저 서버에 올린 뒤** 그 서버를 배포한다(업데이트 화면의 [새 버전 받기]가 받을 판이 있게).
|
||||
|
||||
### 4. release 빌드
|
||||
|
||||
- **`.env` 는 앱 안에 그대로 들어간다** — release 를 만들 때는 `flutter/.env` 의 `API_URL` 을 운영 주소(`https://portal.arasystem.it.kr/api` 꼴 — 포털 출처 + `/api`)로 둔 채 만들고, 다 만든 뒤 개발 주소로 되돌린다.
|
||||
`API_URL` 의 출처가 곧 [새 버전 받기]가 여는 받기 화면의 출처다. 개발 주소 · http 주소인 채로 release 를 부르면 Gradle 이 멈춘다(위 "빌드").
|
||||
- **release 확인(`android/app/build.gradle.kts` — 서명 키 · `google-services.json` · API 주소 판정)을 고쳤으면** key.properties 없이(또는 `.env` 가 개발 주소인 채로)
|
||||
`flutter build apk --release` 를 불러 본다 — 빠진 것이 그 글로 한 번에 나오며 멈추는지, 글에 주소 값이 없는지 본다(판정 함수는 Gradle 스크립트라 저장소의 시험이 없다).
|
||||
|
||||
```powershell
|
||||
cd flutter
|
||||
flutter build apk --release
|
||||
Copy-Item build\app\outputs\flutter-apk\app-release.apk "$env:USERPROFILE\Desktop\ARA-SYSTEM-1.0.1.apk" # 이름은 ARA-SYSTEM-<버전>.apk
|
||||
```
|
||||
|
||||
- 나오는 파일은 `build/app/outputs/flutter-apk/app-release.apk` 하나다(모든 ABI — arm64-v8a · armeabi-v7a · x86_64). 저장소 밖으로 옮기며 이름을 `ARA-SYSTEM-<버전>.apk` 로 바꾼다(받기 화면이 주는 파일 이름과 같은 꼴).
|
||||
- 올리기 전에 본다:
|
||||
- 우리 키로 서명했는지 — `& "$env:LOCALAPPDATA\Android\Sdk\build-tools\36.1.0\apksigner.bat" verify --print-certs <파일>` 의 `certificate DN` 이 keytool 에 넣은 이름이다(`CN=Android Debug` 면 debug 키다).
|
||||
- 파일 안의 판이 파일 이름과 같은지 — `& "$env:LOCALAPPDATA\Android\Sdk\build-tools\36.1.0\aapt2.exe" dump badging <파일> | Select-String "^package:"` 의
|
||||
`versionName` 이 파일 이름의 버전, `versionCode` 가 `pubspec.yaml` 의 `+` 뒤 숫자다(예 `versionCode='2' versionName='1.0.1'`).
|
||||
- 운영 서버를 가리키는지 · 판이 맞는지 — 휴대폰 하나에 설치해 로그인해 보고, **설정 › 앱 정보의 앱 버전이 파일 이름의 버전과 같은지** 본다.
|
||||
운영 도구는 APK 안의 versionName 을 보지 않고 파일 이름 · 운영자가 넣은 버전을 믿는다 — 판이 어긋나면 받기 화면에 틀린 버전이 보이고, 최소 판을 올린 뒤에도 업데이트 화면이 풀리지 않을 수 있다.
|
||||
- 파일 지문 — `Get-FileHash <파일> -Algorithm SHA256` 를 적어 두고, 서버에 올린 뒤 운영 도구가 보이는 값과 견준다.
|
||||
|
||||
### 5. 서버에 올리기
|
||||
|
||||
파일을 서버 B 로 옮긴 뒤 `sudo docker cp ARA-SYSTEM-<버전>.apk <PROJECT_NAME>-backend:/tmp/` → 운영 도구(`sudo docker exec -it <PROJECT_NAME>-backend node dist/cli/ops.js`)의 「휴대폰 앱 올리기」. 자세한 차례는 mail/README(운영).
|
||||
|
||||
### 6. 설치 · 업데이트
|
||||
|
||||
- 직원은 휴대폰 브라우저로 받기 화면에 들어가 받고 설치한다(설치 차례 글은 그 화면에 있다 — 웹 `frontend/src/shared/constants/mobile-app.ts`). 새 판도 같은 곳에서 받아 쓰던 앱 위에 설치하면 로그인 · 설정이 그대로다.
|
||||
- **서명이 다른 시험판이 깔린 휴대폰은 먼저 그 앱을 지우고 설치한다** — `flutter run` · debug APK(debug 키)나 다른 키로 만든 판 위에는 설치되지 않는다. 지우면 그 휴대폰의 로그인 · 알림 설정은 처음부터다.
|
||||
- 받기 · 설치 때 뜨는 창의 글(받기 경고 · 알 수 없는 앱 설치 허용 · Play 프로텍트 경고)은 브라우저 · 제조사마다 다르다 — 실기기로 한 번씩 거쳐 본다(웹 보드 `SettingsMobileApp` "구현 때 맞출 것").
|
||||
|
||||
### 7. Google 개발자 인증 (정할 것)
|
||||
|
||||
Google 이 인증된 개발자의 앱만 설치되게 바꾼다 — 스토어 밖에서 받은 APK 도 그렇다. 일정은 2026-09-30 첫 네 나라, 2027 년 전 세계다(한국은 전 세계로 넓힐 때).
|
||||
그 전에 개발자 인증(신원 확인 · 앱 등록)을 받을지 정한다 — 받지 않으면 그때부터 직원 휴대폰에 새로 설치되지 않을 수 있다.
|
||||
|
||||
### 개인정보 메모
|
||||
|
||||
새 메일 알림 때문에 Firebase 설치 ID(FID)를 서버에 보낸다(전송 중 암호화 · 다른 곳에 주지 않는다). 메일 내용 · 주소는 알림에 싣지 않는다(글은 "새 메일 N통"뿐).
|
||||
광고 ID 는 쓰지 않는다(매니페스트에서 뺐다 — `tools:node="remove"`).
|
||||
|
||||
### iOS
|
||||
|
||||
**맥 · Apple 개발자 계정이 먼저다** — 빌드 · 서명 · 시뮬레이터가 모두 맥(Xcode)에서만 되고, 배포 · 푸시(APNs 키)는 Apple Developer Program(연 99달러, 법인은 D-U-N-S)이 든다. 내부 배포는 TestFlight 내부 테스터(빌드 90일 만료) 또는 Ad Hoc.
|
||||
맥에서 처음 빌드할 때 `ios/Runner/Info.plist` 에 `CFBundleLocalizations`(`ko`)가 있는지 확인한다 — 없으면 복사 · 붙여 넣기 같은 시스템 글이 한국어로 나오지 않는다. bundle id 는 `kr.it.arasystem.app` 으로 맞춰 두었다(맥에서 확인 전).
|
||||
|
||||
## 보안 메모
|
||||
|
||||
|
||||
Reference in New Issue
Block a user