내가 macOS Tahoe에서 Sunshine Screen Sharing을 고친 방법 (Native Wrapper Method)
Source: Dev.to
(번역을 진행하려면 번역하고자 하는 전체 텍스트를 제공해 주세요.)
왜 Mac Mini인가?
- 성능: M4‑시리즈 Mac Mini는 MacBook Pro와 동일한 순수 성능을 제공합니다.
- 가격: 비교 가능한 노트북에 비해 약 $600 vs. $2 000+ 입니다.
- 디스플레이: 저는 이미 고해상도 태블릿을 가지고 있어 노트북 화면이 필요 없습니다.
미니를 태블릿에 연결하는 방법
나는 Sidecar나 VNC를 사용하지 않는다. 나는 Sunshine(Moonlight 클라이언트의 호스트)를 사용한다.
Sunshine는 게임용으로 설계되어 초저지연을 우선시한다. 코딩 및 UI 탐색에서는 거의 네이티브에 가까운 느낌이며, 일반 VNC나 AirPlay보다 훨씬 부드럽다.
문제: macOS 26 (Tahoe) 설정이 깨짐
After updating to macOS 26 “Tahoe”, the following changed:
| What used to work | What happens now |
|---|---|
| Run Sunshine as a background service / CLI tool → System Settings showed the binary and let you enable Screen Recording. | No prompt appears. The binary never shows up in System Settings → Privacy & Security → Screen Recording. You can’t add it manually. |
| Permissions were flaky but at least visible. | The system now requires a proper native app bundle (with an Info.plist and bundle identifier). Raw binaries are invisible to the permission system. |
Bottom line
Any process that wants to capture screen pixels must be a real macOS app bundle.
The Fix: A Tiny Wrapper App
I wrote a script that creates a legitimate .app bundle on‑the‑fly.
The wrapper:
- Packages the Sunshine binary inside a proper app bundle.
- Gives the bundle a stable identifier (
dev.lizardbyte.sunshine.wrapper). - Triggers the macOS screen‑recording permission prompt.
- Handles the Quit command so the Sunshine process is terminated cleanly (no zombie processes).
You only need Terminal and the Swift toolchain—no Xcode required.
단계별 스크립트
1️⃣ 스크립트 파일 만들기
touch sunshine_wrapper.sh
2️⃣ 다음 코드를 sunshine_wrapper.sh에 붙여넣기
#!/bin/bash
# -------------------------------------------------
# Configuration
# -------------------------------------------------
APP_NAME="Sunshine"
APP_DIR="${APP_NAME}.app"
BUNDLE_ID="dev.lizardbyte.sunshine.wrapper"
# -------------------------------------------------
# Locate the Sunshine binary
# -------------------------------------------------
SUNSHINE_BIN=$(which sunshine)
if [ -z "$SUNSHINE_BIN" ]; then
if [ -f "/opt/homebrew/bin/sunshine" ]; then
SUNSHINE_BIN="/opt/homebrew/bin/sunshine"
else
echo "❌ Error: 'sunshine' binary not found!"
exit 1
fi
fi
echo "✅ Targeting Sunshine binary at: $SUNSHINE_BIN"
# -------------------------------------------------
# 1. Clean & create the .app structure
# -------------------------------------------------
echo "📂 Creating App Structure..."
rm -rf "$APP_DIR"
mkdir -p "${APP_DIR}/Contents/MacOS"
mkdir -p "${APP_DIR}/Contents/Resources"
# -------------------------------------------------
# 2. Write the Swift launcher source
# -------------------------------------------------
SWIFT_SOURCE="Launcher.swift"
cat "$SWIFT_SOURCE"
import Cocoa
import Foundation
class AppDelegate: NSObject, NSApplicationDelegate {
var process: Process!
func applicationDidFinishLaunching(_ notification: Notification) {
// Path to the real Sunshine binary (filled in by the Bash script)
let sunshinePath = "$SUNSHINE_BIN"
process = Process()
process.executableURL = URL(fileURLWithPath: sunshinePath)
// Forward any arguments passed to the wrapper onto Sunshine
process.arguments = CommandLine.arguments.dropFirst().map { String($0) }
// Pipe output so you can debug via Console.app if needed
process.standardOutput = FileHandle.standardOutput
process.standardError = FileHandle.standardError
// If Sunshine exits on its own, quit the wrapper too
process.terminationHandler = { _ in
NSApp.terminate(nil)
}
do {
try process.run()
} catch {
print("Failed to launch sunshine: \(error)")
NSApp.terminate(nil)
}
}
// Called when the user quits the wrapper (e.g., Right‑Click → Quit)
func applicationWillTerminate(_ notification: Notification) {
if let proc = process, proc.isRunning {
// Gracefully stop Sunshine
proc.terminate()
// Wait a moment for it to clean up
proc.waitUntilExit()
}
}
}
// -------------------------------------------------
// Main entry point
// -------------------------------------------------
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
app.setActivationPolicy(.regular) // Shows in Dock, has a menu bar
app.run()
EOF
# -------------------------------------------------
# 3. Compile the Swift launcher
# -------------------------------------------------
echo "🔨 Compiling Native Wrapper (with AppKit)..."
swiftc "$SWIFT_SOURCE" -o "${APP_DIR}/Contents/MacOS/${APP_NAME}"
rm "$SWIFT_SOURCE"
# -------------------------------------------------
# 4. Create Info.plist (includes bundle identifier)
# -------------------------------------------------
cat "${APP_DIR}/Contents/Info.plist"
CFBundleExecutable
${APP_NAME}
CFBundleIdentifier
${BUNDLE_ID}
CFBundleName
${APP_NAME}
CFBundlePackageType
APPL
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSMinimumSystemVersion
13.0
EOF
# -------------------------------------------------
# 5. Make the app executable & clean up
# -------------------------------------------------
chmod +x "${APP_DIR}/Contents/MacOS/${APP_NAME}"
echo "✅ Wrapper app '${APP_DIR}' created successfully!"
echo "🚀 Run it via: open ./${APP_DIR}"
Note: 위의
Info.plist스니펫은 원본 게시물에서 잘려 나간 누락된CFBundleShortVersionString라인을 복원합니다.
3️⃣ 스크립트를 실행 가능하게 만들기
chmod +x sunshine_wrapper.sh
4️⃣ 스크립트를 실행하기
./sunshine_wrapper.sh
이제 Sunshine 앱이 Applications 폴더(또는 현재 디렉터리)에 나타나는 것을 볼 수 있습니다. 앱을 열면 macOS Screen Recording 권한 요청 프롬프트가 표시됩니다. 권한을 허용하면 Sunshine은 이전과 동일하게 작동하며, 이제 macOS 26의 더 엄격한 보안 모델에서도 살아남을 수 있는 적절한 번들 식별자를 갖게 됩니다.
TL;DR
- macOS 26에서는 화면 녹화 프라이버시 목록에 원시 바이너리가 더 이상 표시되지 않습니다.
- Sunshine 바이너리를 최소한의 네이티브
.app번들로 감싸세요. - 제공된
sunshine_wrapper.sh스크립트가 모든 작업을 수행합니다(번들을 생성하고, 작은 Swift 런처를 컴파일하며,Info.plist를 만들고, 실행 가능하게 만듭니다). - 래퍼를 실행한 후 화면 녹화 권한을 부여하면, 가성비 좋은 “Work From Cafe” 설정이 다시 정상 작동합니다.
Sunshine Wrapper – Step‑by‑Step Guide
아래는 원본 마크다운을 정리한 버전입니다. 모든 코드 블록은 올바르게 fenced 처리되었으며, 가독성을 위해 헤딩을 추가했고 원본 내용은 그대로 유지했습니다.
1️⃣ Wrapper 스크립트 만들기
#!/usr/bin/env bash
# sunshine_wrapper.sh – Wrap Sunshine for macOS 12+ (Monterey/Big Sur)
set -euo pipefail
# -------------------------------------------------
# 1. Variables
# -------------------------------------------------
APP_NAME="Sunshine"
APP_DIR="${HOME}/Applications/${APP_NAME}.app"
BUNDLE_ID="dev.lizardbyte.sunshine.wrapper"
EXECUTABLE="/usr/local/bin/sunshine"
ICON_PATH="/Applications/Sunshine.app/Contents/Resources/AppIcon.icns"
# -------------------------------------------------
# 2. Create the .app bundle structure
# -------------------------------------------------
echo "📁 Creating bundle at ${APP_DIR}..."
mkdir -p "${APP_DIR}/Contents/MacOS"
mkdir -p "${APP_DIR}/Contents/Resources"
# -------------------------------------------------
# 3. Write the Info.plist
# -------------------------------------------------
cat > "${APP_DIR}/Contents/Info.plist"
CFBundleName
Sunshine
CFBundleDisplayName
Sunshine
CFBundleIdentifier
dev.lizardbyte.sunshine.wrapper
CFBundleVersion
2.0
LSMinimumSystemVersion
12.0
NSHighResolutionCapable
CFBundleIconFile
AppIcon
EOF
# -------------------------------------------------
# 4. Copy the executable & icon
# -------------------------------------------------
echo "🔧 Copying executable and icon..."
cp "${EXECUTABLE}" "${APP_DIR}/Contents/MacOS/${APP_NAME}"
chmod +x "${APP_DIR}/Contents/MacOS/${APP_NAME}"
cp "${ICON_PATH}" "${APP_DIR}/Contents/Resources/AppIcon.icns"
# -------------------------------------------------
# 5. Sign the app
# -------------------------------------------------
echo "🔐 Signing the application..."
codesign --force --deep --sign - "${APP_DIR}"
echo "------------------------------------------------"
echo "✅ Success! '${APP_DIR}' created."
echo "------------------------------------------------"
2️⃣ .app 번들 만들기
위 스크립트가 이미 번들을 생성하고, 바이너리와 아이콘을 복사하며, 최소한의 Info.plist를 작성하고, 앱에 서명합니다. 스크립트를 실행하는 것 외에 추가 단계는 필요하지 않습니다.
3️⃣ 실행 및 설치
-
스크립트를 실행 파일로 만들기
chmod +x sunshine_wrapper.sh -
스크립트 실행
./sunshine_wrapper.sh -
생성된 앱을
/Applications로 이동mv "${HOME}/Applications/Sunshine.app" /Applications/ -
스크린 캡처 권한 초기화 (필요한 경우)
tccutil reset ScreenCapture dev.lizardbyte.sunshine.wrapper -
앱 실행
macOS에서 스크린 캡처 접근을 요청하면 Allow(허용)를 클릭합니다. 이제 권한이 지속됩니다.
🎉 결과
포터블 Mac Mini 설정이 정상적으로 복구되었습니다. 이제 Mac Mini를 Moonlight 또는 Artemis와 같은 태블릿으로 지연 없이 스트리밍할 수 있습니다. 최신 macOS 버전에서 화면 공유 도구가 제대로 동작하지 않을 때, 네이티브 .app 로 래핑하는 것이 큰 차이를 만들습니다.