
2025.04.19 2nd FlutterSeoul Open-Stage
지난 토요일 플러터서울에서 주최한 오픈스테이지에 작년에 이어 연사자로 참가하여 "Flutter, Firebase로 macOS 프로그램 개발, 배포하기"라는 세션을 발표했습니다. 연사 경험이 차차 쌓이다 보니 이제 자연스럽게 말도 나오고 반응도 좋았기에 제가 준비한 세션에 대한 내용, 후기에 대한 글을 써보려고 합니다.
오픈 스테이지는 플러터 서울에서 주최한 행사로 평소에 지식을 나누고 싶었던 사람, 그리고 Flutter 개발자끼리 모이고 싶은 누구나 참여할 수 있는 자리입니다. Flutter에 관심 있는 누구나 연사로 참여하여 자기 경험과 노하우를 공유할 수 있습니다. 강연 경력, 발표 경력 등을 전혀 보지 않고 누구나 자기 경험을 이야기할 수 있는 기회를 주는 행사로 정말 좋은 취지의 행사입니다.
최근 플러터로 macOS 프로그램을 개발할 일이 있었는데 개발을 진행하다 보니 코드 밖에서 세팅해야 하는 것도 생각보다 많았고, 한국어로 되어있는 자료뿐만 아니라 아예 자료가 거의 잘 나오지 않았기에 개발부터 배포까지 진행하면서 그 과정을 기록하여 다른 분들이 같은 작업을 하실 때 조금이라도 편하게 하실 수 있도록 이번 세션을 준비하게 되었습니다.
해당 세션은 Flutter에서 macOS프로그램을 개발하면서 생기는 코드 밖의 세팅과 디버깅에 대해 다루고 있습니다. 상단 이미지의 QR코드를 통해 전체 내용을 확인하실 수 있습니다.
macOS에서 Firebase를 사용하면 세팅하는 과정에서 굉장히 많은 버그들을 발견하게 됩니다. Platform osx 최소버전 호환 문제, 네트워크 권한 문제, 키체인 접근 권한 문제 등 코드로 인한 버그가 아닌 세팅이 되어있지 않아서 생기는 버그들이 대부분입니다.
하지만 한국에는 이런 버그들을 다루는 글이 없더라구요. Firebase와 관련된 내용은 제가 한국어로 여기에 정리해두었으니 확인해보시면 좋을 것 같습니다.
프로그램을 모두 개발했다면 다른 사용자들이 사용할 수 있도록 배포해야 합니다. 기존 반드시 스토어를 통해 배포해야만 했던 iOS와는 다르게 macOS프로그램은 스토어 밖에서도 배포가 가능합니다. 단순히 실행파일을 압축해서 공유하는 것만으로도 다른 사람들이 실행할 순 있지만
다음처럼 한번에 깔끔하게 열리진 않습니다. 이는 macOS에 기본적으로 탑재되어 있는 Gatekeeper라는 프로그램이 내 프로그램에 악성코드가 있는지 아닌지 확인 할 수 없다는 이유로 막는 것입니다.
이 과정을 생략하기 위해서는 CodeSign과 Notarization(공증)이라는 과정을 거쳐야만 합니다. 지금부터 이 과정을 한번 살펴보겠습니다.
CodeSign은 내가 이 프로그램을 개발했다고 나의 이름을 프로그램에 새기는 과정입니다. 반드시 애플 개발자 프로그램(개발자 계정)이 필요하고 Developer ID Application이라고 하는 인증서 또한 필요합니다.
다들 개발자 계정은 있으실거라고 생각하고 인증서를 발급하는 방법을 한번 알아보겠습니다. 인증서 발급은 키체인 접근 앱에서 진행됩니다. 인증서에 정보를 입력하고 저장을 한 뒤


Apple Developer사이트에 접속하여 등록하면 됩니다.


등록한 인증서는 저장하신 뒤 신뢰 설정을 항상 신뢰로 설정하셔야 사용 가능합니다.
codesign --verbose --timestamp -s "인증서 이름" --option=runtime 앱이름
그 뒤 해당 명령어를 입력하시면 성공적으로 CodeSign과정이 마무리 됩니다.
그러면 이제 공증받는 과정을 한번 알아봅시다. 공증은 App Specific Password와 notarytool이라고하는 Xcode command line tool이 필요합니다.

AppleID에서 새 앱 암호를 발급받으시고 ( 다시 확인 불가하니 잘 적어두세요. )
xcrun notarytool store-credentials --apple-id "개발자계정 이메일" --team-id "개발자 계정 고유식별자" --password "앱 암호" "앱 암호 이름"
해당 명령어를 통해 Keychain profile을 발급 받으신 뒤에
xcrun notarytool submit "공증 보낼 경로" --keychain-profile "키체인 프로필 이름" --wait
해당 명령어를 통해 애플 공증 서버에 공증 요청을 보낼 수 있습니다. 공증 과정은 순전히 애플 공증 서버의 상태에 따라 소요시간이 달라지기에 다른 작업을 하시다가 공증 상태를 확인하시는 걸 추천드리고 있어요.
xcrun notarytool history --keychain-profile "키체인 프로필 이름"
공증 상태 확인은 해당 명령어를 입력하시면 됩니다. 만약 너무 오래걸리면 해당 사이트에서 애플 서버 상태를 확인 가능합니다.
공증을 성공적으로 마치게 된다면 애플 공증 서버에서 공증 티켓이라는것이 발급됩니다. 공증 티켓은 물리적으로 우리에게 존재하는 것은 아니지만 이를 앱에 첨부시키는 과정이 필요합니다. 이를 스테이플링 이라고 하는데요.
xcrun stapler staple "스테이플링 경로"
해당 명령어를 통해 스테이플링을 할 수 있습니다. 이제 우리의 프로그램은 시스템의 아무 간섭없이 깔끔하게 열리게 돼요.
하지만 해당 과정은 앱에 조금의 수정사항이라도 생기면 매번 반복해줘야한다는 명확한 단점이 있었습니다. 또한 처음 진행하는 과정에서 인증서가 로컬 환경에서만 신뢰된다거나 코드 서명이 모든 패키지에 되지 않는 문제또한 종종 발생했어요. 저는 이러한 문제점을 해결하고자 코드서명부터 공증 dmg 패키징까지 자동화 하는 쉘 스크립트 코드를 작성했습니다.
#!/bin/bash
set -e
APP_PATH="$1"
APP_NAME=$(basename "$APP_PATH")
APP_PARENT_DIR=$(dirname "$APP_PATH")
APP_BASENAME="${APP_NAME%.app}"
DMG_PATH="${APP_PARENT_DIR}/${APP_BASENAME}.dmg"
PROFILE_NAME="Keychain Profile"
CERT_NAME="Developer ID Application: Name (ID)"
if [ -z "$APP_PATH" ]; then
echo "❌ 앱 경로를 인자로 전달해주세요."
exit 1
fi
APP_REAL_PATH=$(realpath "$APP_PATH")
echo -e "\n🔍 Target app: $APP_PATH"
echo "🔐 Developer ID Profile: $PROFILE_NAME"
# Step 0: 인증서 추출 및 시스템 신뢰 등록
echo -e "\n🔐 Step 0: Export and trust Developer ID certificate..."
CERT_OUTPUT_PATH="${APP_PARENT_DIR}/developer_id.cer"
security find-certificate -c "$CERT_NAME" -p > "$CERT_OUTPUT_PATH"
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$CERT_OUTPUT_PATH"
echo "✅ 인증서 $CERT_NAME 를 시스템에 신뢰 등록했습니다."
# Step 1: 내부 바이너리 코드 서명
echo -e "\n🔏 Step 1: Signing all internal binaries..."
find "$APP_PATH/Contents" -type f -perm +111 -exec codesign --force --options runtime --sign "$CERT_NAME" --timestamp {} \;
# Step 2: .app 번들 전체 코드 서명
echo -e "\n🔏 Step 2: Signing .app bundle..."
codesign --force --options runtime --sign "$CERT_NAME" --timestamp "$APP_PATH"
# Step 3: 코드 서명 검증
echo -e "\n🧪 Step 3: 코드 서명 검증"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
spctl --assess --type execute --verbose=2 "$APP_PATH"
# Step 4: appdmg 구성 파일 생성
echo -e "\n🛠️ Step 4: Creating appdmg config..."
DMG_JSON="${APP_PARENT_DIR}/${APP_BASENAME}_dmg_config.json"
echo "{
\"title\": \"$APP_BASENAME\",
\"contents\": [
{ \"x\": 448, \"y\": 344, \"type\": \"link\", \"path\": \"/Applications\" },
{ \"x\": 192, \"y\": 344, \"type\": \"file\", \"path\": \"$APP_REAL_PATH\" }
]
}" > "$DMG_JSON"
# Step 5: .dmg 생성 (앱을 Applications로 드래그할 수 있도록)
echo -e "\n🗜️ Step 5: Creating DMG with appdmg..."
appdmg "$DMG_JSON" "$DMG_PATH"
echo "✅ $DMG_PATH 생성 완료"
# Step 6: .dmg 공증 요청
echo -e "\n📤 Step 6: Submitting $DMG_PATH for notarization..."
xcrun notarytool submit "$DMG_PATH" --keychain-profile "$PROFILE_NAME" --wait
echo "✅ 공증 완료"
# Step 7: 스테이플링
echo -e "\n📎 Step 7: Stapling notarization ticket..."
xcrun stapler staple "$APP_PATH"
xcrun stapler staple "$DMG_PATH" || echo "⚠️ DMG staple 실패. 잠시 후 다시 시도하거나 생략해도 됩니다."
echo -e "\n✅ 🎉 Done! App is now signed, notarized, and packed into a user-friendly DMG!"
해당 코드는 키체인 프로필과 인증서를 미리 입력해준뒤에 단순히
chmod +x 실행파일이름
./실행파일이름 대상경로
명령어를 입력하는 것으로 배포까지의 모든 과정을 자동으로 해결해줬어요.
코드의 흐름을 보면
인증서 추출 및 신뢰 등록
모든 패키지에 재귀적으로 코드 사인
dmg 패키징
공증
스테이플링
으로 구성되어 있습니다.
그래서 저는 실제로 macOS 프로그램을 개발부터 배포까지 진행을 해봤는데요. 확실히 익숙하지 않은 swift로 하는거보단 개발 속도가 빨랐고 mac 환경에서 개발 후 windows도 테스트를 진행하였는데 큰 문제 없이 잘 돌아갔어서 Windows 환경도 지원하기에 매우 용이했습니다. 하지만 오늘 다룬 내용과 같은 코드 밖의 설정들이 굉장히 많았지만 스토어 배포를 기다리지 않아도 된다는 큰 장점이 있기에 총점을 매기자면 4점 정도로 매우 좋은 것 같아요. 다들 한번씩 개발해보셔도 좋을 듯 합니다.
이번 연사는 1차 티켓도 매진 추가로 열린 티켓도 매진되고 현장도 꽉 찰 만큼 정말 많은 사람이 오셔서 무대에 서기전까지 정말 긴장을 많이 했었어요. 근데 연사를 시작하니 말이 술술나와서 정말 신기했던 기억이 있습니다. 연사 뒤 네트워킹 시간에 많은 분들과 얘기를 나누면서 다들 제 주제를 흥미롭고 또 재미있게 들어주셔서 정말 뿌듯했던 기억이 남습니다. 이제는 또 다른 재미있는 주제를 찾기 위해 열심히 달려야 할 것 같아요. 좋은 행사 만들어주신 플러터 서울 감사드리고 항상 행사 앞 뒤로 고생해주시는 플러터 서울 오거나이저분들 감사드립니다. 다음에 또 좋은 자리에서 제 경험을 나눌 수 있도록 노력하겠습니다.