# 구성

> 셀프 호스팅 Nodaro의 모든 환경 변수를 용도별로 정리했습니다. 기본값, Compose 스택에서 .env로 설정할 수 있는지, 무엇이 바뀌는지 확인하세요.

Source: https://nodaro.ai/ko/docs/self-hosting/configuration

이 페이지는 셀프 호스팅 Nodaro의 **구성 레퍼런스**입니다. 운영자가 설정하는 모든 환경 변수를 용도별로 묶어 기본값과 함께 정리했습니다. 번들된 Compose 스택에서는 모든 변수에 작동하는 기본값이 있으므로, 바꾸고 싶은 것만 설정하면 됩니다. Nodaro Cloud에만 적용되는 변수는 싣지 않았습니다.

## 변수 설정 방법
Compose 스택에서는 두 곳 중 한 곳에서 변수를 설정합니다.

- **`.env`에서.** `docker-compose.community.yml` 옆에 있는 파일입니다. Compose 파일이 앱에 전달하는 변수에만 적용됩니다. 아래 표의 **.env** 열에 **예**로 표시된 변수입니다.
- **Compose 파일에서.** 그 밖의 모든 변수가 해당합니다. `nodaro` 서비스의 `environment:` 아래에 추가하세요.

```yaml
nodaro:
environment:
# ...the variables already listed...
MCP_ENABLED: "true"
MCP_PUBLIC_URL: https://mcp.nodaro.example.com
```

그다음 변경 사항을 적용하세요.

```bash
docker compose -f docker-compose.community.yml up -d
```

Nodaro는 부팅할 때 환경을 읽습니다. 유일한 예외는 `/setup`에 붙여 넣은 공급자 키로, 즉시 적용됩니다. Compose 스택 밖에서는 플랫폼의 환경 설정에서 변수를 설정하세요.

**Compose 스택 밖에서 필수:** `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `INTERNAL_ORCHESTRATOR_SECRET`. 이 변수가 없으면 API가 시작을 거부하고, 없거나 잘못된 변수를 나열합니다.

## 인스턴스
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `EDITION` | `community` | 아니요 | `community`, `business`, `cloud` 중 하나입니다. Compose 파일은 `community`로 고정합니다. [에디션과 서피스 프로필](https://nodaro.ai/docs/self-hosting/editions-and-profiles)을 참고하세요. |
| `PUBLIC_URL` | `http://localhost:3000` | 예 | 설치의 공개 오리진입니다. 로그인과 OAuth 콜백, 미디어 URL, CORS 검사에 쓰입니다. |
| `PUBLIC_URL_SAME_ORIGIN` | 설정 안 됨 | 아니요 | `true`로 설정하면 브라우저에 `PUBLIC_URL` 대신 API 주소 `/`를 알려 주므로, 스트림이 방문자가 연 호스트 이름에 머뭅니다. 여러 호스트 이름으로 응답하는 설치용입니다. 정확히 `true`라는 값만 이 설정을 켭니다. |
| `CORS_ORIGIN` | 비어 있음 | 예 | 추가로 허용할 브라우저 오리진을 쉼표로 구분해 적습니다. `PUBLIC_URL`은 항상 허용됩니다. 이 호스트에서는 SSO 착륙 리디렉션이 상대 경로가 됩니다. |
| `DEFAULT_LOCALE` | 비어 있음(브라우저 언어) | 예 | 새 방문자가 시작하는 언어입니다. [기본 언어](#default-language)를 참고하세요. |
| `NODARO_SURFACE_PROFILE` | 비어 있음(전체 인터페이스) | 예 | Business 설치의 인터페이스 범위를 좁힙니다. [서피스 프로필](https://nodaro.ai/docs/self-hosting/editions-and-profiles#surface-profiles)을 참고하세요. |
| `NODARO_TUTORIAL_PACKS` | 비어 있음(기본 제공 튜토리얼만) | 예 | 추가 튜토리얼 팩의 디렉터리를 쉼표로 구분해 적습니다. [튜토리얼 팩](#tutorial-packs)을 참고하세요. |
| `RUNTIME_ENV` | `local` | 아니요 | 두 설치가 데이터베이스 하나는 공유하지만 Redis는 공유하지 않을 때 설치를 구분하는 이름입니다. [확장](https://nodaro.ai/docs/self-hosting/scaling#two-installs-one-database)을 참고하세요. Railway에서는 `RAILWAY_ENVIRONMENT_NAME`이 이 값을 설정합니다. [Webhook 엔드포인트](https://nodaro.ai/docs/developers/api/webhooks)는 그것을 만든 설치에 속하므로, `RUNTIME_ENV`의 이름을 바꾸면 이전 이름으로 만든 엔드포인트가 보이지 않게 됩니다. |
| `PORT`, `HOST` | `8000`, `0.0.0.0` | 아니요 | API가 수신 대기하는 곳입니다. 이미지에서는 API가 `3000`번 포트의 웹 서버 뒤에서 `9000`번 포트로 수신 대기합니다. |
| `NODE_ENV` | `development` | 아니요 | 모든 이미지에서 `production`입니다. |

## 시크릿
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `INTERNAL_ORCHESTRATOR_SECRET` | Compose 스택에서는 부팅 때 생성 | 예 | 오케스트레이터를 API에 인증합니다. 32자 이상이어야 합니다. Compose 스택 밖에서는 필수입니다. |
| `NODARO_ENCRYPTION_KEY` | Compose 스택에서는 첫 부팅 때 생성되어 `app-data` 볼륨에 보관 | 예 | 저장된 자격 증명을 암호화하는 64자 16진수 키입니다. 붙여 넣은 공급자 키, 소셜 로그인 토큰, Webhook 출력용으로 저장한 HTTP 자격 증명, [Webhook 엔드포인트](https://nodaro.ai/docs/developers/api/webhooks)의 서명 시크릿이 대상입니다. 이 키가 없으면 Webhook 엔드포인트 등록이 `503 encryption_key_missing`으로 응답합니다. |
| `SOCIAL_ENCRYPTION_KEY` | 비어 있음 | 예 | `NODARO_ENCRYPTION_KEY`의 이전 이름입니다. 둘 다 작동합니다. |

시크릿은 `openssl rand -hex 32`로 생성하세요. 암호화 키는 데이터베이스와 함께 백업하세요. [백업과 복원](https://nodaro.ai/docs/self-hosting/backups)을 참고하세요.

## 데이터베이스와 로그인
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `SUPABASE_URL` | Compose 스택에서는 `http://localhost:3000/supabase` | 예 | API가 쓰는 Supabase URL입니다. Compose 스택 밖에서는 필수입니다. |
| `SUPABASE_SERVICE_ROLE_KEY` | Compose 스택에서는 공개된 기본 키 | 예 | API가 쓰는 service-role 키입니다. Compose 스택 밖에서는 필수입니다. |
| `SUPABASE_ANON_KEY` | Compose 스택에서는 공개된 기본 키 | 예 | 브라우저와 로그인 서비스에 전달되는 anon 키입니다. |
| `FRONTEND_SUPABASE_URL` | 번들된 스택에서는 `PUBLIC_URL/supabase` | 예 | 브라우저가 쓰는 Supabase URL로, 부팅 때 `/config.js`에 기록됩니다. 관리형 Supabase 프로젝트를 쓸 때 설정하세요. |
| `SUPABASE_JWT_SECRET` | 공개된 기본 시크릿 | 예 | 번들된 스택에서 anon 키와 service-role 키에 서명하는 시크릿입니다. 세 값 모두 `node tools/generate-selfhost-keys.mjs`로 발급하세요. |
| `POSTGRES_PASSWORD` | `nodaro-postgres-pw` | 예 | 번들된 데이터베이스의 비밀번호입니다. 내부 역할의 비밀번호는 데이터베이스를 처음 시작할 때만 이 값을 따릅니다. |
| `DATABASE_URL` | `postgres://postgres:nodaro-postgres-pw@db:5432/postgres` | 예 | 부팅 때 마이그레이션을 적용하는 데만 쓰는 Postgres 직접 연결 URL입니다. 비밀번호를 `POSTGRES_PASSWORD`와 맞춰 두세요. |
| `RUN_MIGRATIONS_ON_BOOT` | `false`, Compose 스택에서는 `true` | 예 | API가 시작되기 전에 `supabase/migrations/`를 적용합니다. 관리형 Supabase 프로젝트를 쓸 때는 `false`로 설정하세요. |
| `SUPABASE_MANAGED_PROXY` | 설정 안 됨 | 아니요 | `true`로 설정하면 관리형 Supabase 프로젝트를 앱 자체의 오리진을 통해 제공합니다. [데이터베이스](https://nodaro.ai/docs/self-hosting/database#serve-a-managed-project-through-your-own-origin)를 참고하세요. |
| `SUPABASE_PROXY_UPSTREAM` | 설정 안 됨 | 아니요 | 위 프록시가 쓰는, 경로를 뺀 관리형 프로젝트의 오리진입니다. |

## 큐
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `REDIS_URL` | `redis://localhost:6379`, Compose 스택에서는 `redis://redis:6379` | 아니요 | 큐와 공유 캐시에 쓰는 Redis입니다. 클러스터나 Sentinel URL도 작동합니다. |

## 스토리지
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `R2_ENDPOINT` | Compose 스택에서는 `http://minio:9000` | 예 | 스토리지의 S3 API URL입니다. Compose 스택에서 값이 비어 있으면 MinIO로 대체됩니다. |
| `R2_FORCE_PATH_STYLE` | Compose 스택에서는 `true` | 예 | 대부분의 셀프 호스팅 스토리지에 필요한 경로 방식 주소 지정입니다. |
| `R2_ACCOUNT_ID` | 비어 있음 | 예 | Cloudflare 계정 ID입니다. Compose 스택 밖에서 `R2_ENDPOINT`가 설정되지 않으면 이 값으로 R2 엔드포인트를 만듭니다. |
| `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY` | 번들된 MinIO의 자격 증명 | 예 | 스토리지 자격 증명입니다. Compose 스택에서는 MinIO 루트 자격 증명이기도 합니다. |
| `R2_BUCKET_NAME` | Compose 스택에서는 `nodaro-assets` | 예 | 버킷입니다. Compose 스택 밖에서는 기본값이 `scenenode-assets`이므로 항상 설정하세요. |
| `R2_PUBLIC_URL` | Compose 스택에서는 `http://localhost:3000/storage/nodaro-assets` | 예 | 버킷의 공개 URL입니다. |
| `R2_REGION` | `auto` | 예 | S3 리전입니다. AWS, DigitalOcean Spaces(`nyc3` 등), 로컬 Supabase(`local`)에서는 실제 리전을 설정하세요. |
| `STORAGE_OBJECT_ACL` | 비어 있음(ACL 헤더 없음) | 예 | 버킷 정책을 거부하는 스토리지를 위해, 업로드하는 모든 객체에 붙이는 미리 정의된 ACL입니다. [설치](https://nodaro.ai/docs/self-hosting/install#make-the-media-publicly-readable)를 참고하세요. |
| `R2_PUBLIC_FALLBACK_DOMAIN` | 비어 있음 | 아니요 | 미디어용 두 번째 공개 호스트입니다. 예를 들어 CDN 도메인 옆의 원래 `r2.dev` 주소입니다. |
| `R2_SHARED_WITH_RELAY_TARGET` | `false` | 아니요 | `R2_PUBLIC_URL`이 내 Nodaro Cloud 호스트가 쓰는 버킷과 같은 버킷을 가리킬 때만 `true`로 설정합니다. [Nodaro Cloud에 연결](https://nodaro.ai/docs/self-hosting/cloud-connect#configuration-reference)을 참고하세요. |

## 모델 공급자
이 키는 모두 `/setup`에 붙여 넣을 수도 있습니다. 환경에 설정한 키가 우선합니다. [공급자 키](https://nodaro.ai/docs/self-hosting/provider-keys)를 참고하세요.

| 변수 | 기본값 | .env | 실행할 수 있는 것 |
| --- | --- | --- | --- |
| `NODARO_API_KEY` | 비어 있음 | 예 | 개인 API 토큰으로 Nodaro Cloud를 공급자로 사용합니다. |
| `KIE_API_KEY` | 비어 있음 | 예 | 가장 폭넓은 미디어 및 텍스트 모델을 지원합니다. |
| `KIE_API_BASE_URL` | 공급자 자체의 API 호스트 | 예 | 이 공급자의 트래픽을 내 프록시로 보냅니다. `KIE_API_KEY`로 실행되는 Claude와 Gemini 텍스트 트래픽도 함께 옮겨 갑니다. |
| `REPLICATE_API_TOKEN` | 비어 있음 | 예 | Flux 2 모델을 포함한 대체 카탈로그입니다. |
| `ANTHROPIC_API_KEY` | 비어 있음 | 예 | 텍스트 노드에서 Claude를 직접 호출합니다. |
| `GEMINI_API_KEY` | 비어 있음 | 예 | Gemini를 직접 호출합니다. [Gemini 모델](https://nodaro.ai/docs/self-hosting/provider-keys#gemini-models)을 참고하세요. |
| `ELEVENLABS_API_KEY` | 비어 있음 | 예 | 음성, 보이스, 더빙입니다. |
| `ELEVENLABS_BASE_URL` | `https://api.elevenlabs.io` | 예 | ElevenLabs 트래픽을 내 프록시로 보냅니다. |
| `FAL_KEY` | 비어 있음 | 예 | Sync Lipsync v3 같은 모델입니다. |
| `HEYGEN_API_KEY` | 비어 있음 | 예 | AI 아바타와 시네마틱 아바타입니다. |
| `BEEBLE_API_KEY` | 비어 있음 | 예 | 조명 변경 및 교체입니다. |
| `APIFY_API_TOKEN` | 비어 있음 | 예 | 웹 스크래핑과 사이트 캡처입니다. |
| `SITE_CAPTURE_ENABLED` | 켜짐 | 아니요 | `false`로 설정하면 이 설치에서 사이트 캡처 노드, `POST /v1/site-capture`, `capture_site` MCP 도구가 꺼집니다. |
| `HEYGEN_CATALOG_REFRESH_HOURS` | `24` | 아니요 | HeyGen 아바타와 보이스의 공유 목록을 새로 고치는 주기입니다. |

## Nodaro Cloud 연결
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `NODARO_CLOUD_URL` | `https://app.nodaro.ai` | 예 | **nodaro.ai 연결**이 등록하는 Nodaro Cloud 호스트이자, `NODARO_API_KEY`로 하는 호출이 가는 곳입니다. 내 설치가 전용 Nodaro 배포에 속할 때만 바꾸세요. 부팅할 때 읽습니다. |

`NODARO_API_KEY`와 `R2_SHARED_WITH_RELAY_TARGET`도 연결에 속합니다. [Nodaro Cloud에 연결](https://nodaro.ai/docs/self-hosting/cloud-connect)을 참고하세요.

## 워커와 성능
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `MAX_CONCURRENT_NODES_PER_EXECUTION` | `6`, 최대 `20` | 아니요 | 워크플로 실행 하나가 동시에 실행할 수 있는 노드 수입니다. |
| `VIDEO_WORKER_CONCURRENCY` | `50` | 아니요 | 주로 공급자를 기다리는 미디어 워커의 병렬 작업 수입니다. |
| `ORCHESTRATOR_CONCURRENCY` | `20` | 아니요 | 오케스트레이터의 병렬 작업 수입니다. |
| `RENDER_WORKER_CONCURRENCY` | `2`, 최대 `10` | 아니요 | 병렬 렌더링 수입니다. 렌더링마다 헤드리스 Chrome 하나를 실행합니다. |
| `REMOTION_CONCURRENCY` | 3D 장면은 `2`, 다른 렌더링은 CPU 코어 수의 절반 | 아니요 | 렌더링당 브라우저 탭 수입니다. 직접 설정한 값은 두 경우 모두에 적용됩니다. 3D 작업을 여러 개 실행할 때는 낮게 유지하세요. WebGL 탭마다 스레드가 늘고 컨테이너의 프로세스 한도에 포함됩니다. |
| `FFMPEG_CONCURRENCY` | `4`, 최대 `32` | 아니요 | 모든 동영상 및 오디오 편집 노드를 통틀어 동시에 실행하는 ffmpeg 프로세스 수입니다. |
| `RAILWAY_DEPLOYMENT_DRAINING_SECONDS` | 설정 안 됨 | 아니요 | Railway 전용: 교체되는 컨테이너가 중지 신호를 받은 뒤 강제 중지되기까지의 시간입니다. 미디어 워커는 이 시간에서 5초를 뺀 만큼 남은 작업을 정리합니다. 설정하지 않으면 25초 동안 정리합니다. |

이 값을 올리기 전에 [확장](https://nodaro.ai/docs/self-hosting/scaling)을 읽어 보세요.

## 편집기
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `FREECUT_URL` | 비어 있음(호스팅된 편집기) | 예 | 캔버스의 **NodarCut에서 편집**과 앱의 동영상 결과에 있는 **편집**이 여는 동영상 편집기입니다. `off`로 설정하면 편집기가 꺼집니다. 버튼은 그대로 남지만 편집기는 열리지 않습니다. |
| `AUDIOMASS_URL` | 비어 있음(오디오 편집기 없음) | 예 | 앱의 오디오 결과에 있는 **편집**이 여는 오디오 편집기입니다. `off`는 비어 있을 때와 같이 동작합니다. |

[동영상 및 오디오 편집기](#video-and-audio-editors)를 참고하세요.

## 업데이트
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `NODARO_IMAGE` | `ghcr.io/nodaroai/nodaro-community:latest` | 예 | Compose 파일이 실행하는 앱 이미지입니다. 여기에서 릴리스 태그를 고정하세요. |
| `NODARO_UPDATE_CHECK` | 켜짐 | 아니요 | `off`로 설정하면 매일 하는 업데이트 확인을 멈춰, 설치 밖으로 요청이 나가지 않습니다. 외부와 차단된 설치용입니다. |
| `NODARO_UPDATE_CHECK_TOKEN` | 비어 있음(익명) | 아니요 | 업데이트 확인에 쓰는, 범위가 없는 GitHub 토큰입니다. |

[업데이트](https://nodaro.ai/docs/self-hosting/updating#which-version-you-run)를 참고하세요.

## MCP
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `MCP_ENABLED` | 꺼짐 | 아니요 | `true` 또는 `1`로 설정하면 MCP 엔드포인트를 제공합니다. |
| `MCP_PUBLIC_URL` | 비어 있음(Nodaro Cloud MCP 호스트) | 아니요 | 내 MCP 호스트의 공개 주소입니다. |
| `MCP_DYNAMIC_REGISTRATION` | `allowlist` | 아니요 | MCP 클라이언트의 동적 클라이언트 등록 방식입니다: `allowlist`, `open`, `off`. |
| `MCP_DCR_ALLOWLIST` | 알려진 클라이언트 14개 | 아니요 | `allowlist` 모드에서 등록할 수 있는 클라이언트 이름입니다. |

[MCP](https://nodaro.ai/docs/self-hosting/mcp)를 참고하세요.

## 로그인과 역할
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `EXTERNAL_SSO_PROVIDERS` | 비어 있음(SSO 꺼짐) | 아니요 | 신뢰하는 ID 공급자를 인라인 JSON이나 `@/path/to/file.json`으로 지정합니다. 형식이 잘못된 값은 부팅을 멈춥니다. |
| `EXTERNAL_SSO_LINK_EXISTING` | `false` | 아니요 | 싱글 사인온이 기존 로컬 계정에 연결될 수 있는지 정합니다. |
| `PLATFORM_OWNER_EMAIL` | 비어 있음 | 아니요 | Business: 다른 관리자가 역할을 바꿀 수 없는 소유자 계정입니다. |

[싱글 사인온](https://nodaro.ai/docs/self-hosting/sso)과 [첫 사용자와 관리자](https://nodaro.ai/docs/self-hosting/first-admin)를 참고하세요.

## 연동
| 변수 | 기본값 | .env | 역할 |
| --- | --- | --- | --- |
| `FIGMA_PLUGIN_OAUTH_CLIENT_ID` | 비어 있음(플러그인 연결 꺼짐) | 아니요 | Figma 플러그인이 연결에 쓰는 개발자 앱의 클라이언트 ID입니다. |
| 소셜 네트워크 변수 | 비어 있음 | 아니요 | [소셜 네트워크 앱](#social-network-apps)을 참고하세요. |

Figma 플러그인을 쓰려면 내 설치에 개발자 앱을 등록하세요. 리디렉션 URI는 `<PUBLIC_URL>/v1/oauth/plugin/callback`, 범위는 `jobs:read`, `assets:read`, `assets:write`, `credits:read`입니다. 그다음 그 앱의 클라이언트 ID를 여기에 설정하세요. 변수가 비어 있으면 플러그인 연결 경로가 `503`으로 응답하고, 다른 것은 바뀌지 않습니다. [OAuth 앱](https://nodaro.ai/docs/developers/oauth)을 참고하세요.

## 빌드 시점 설정
이 값은 이미지를 빌드할 때 고정됩니다. Compose 파일의 `build: args:`에서 바꾼 뒤 다시 빌드하세요.

| 빌드 인수 | 기본값 | 역할 |
| --- | --- | --- |
| `VITE_EDITION` | Compose 파일에서는 `community` | 편집기가 보여 주는 에디션입니다. 빌드는 비어 있거나 알 수 없는 값을 거부합니다. |
| `VITE_STUDIO_URL` | `https://studio.nodaro.ai` | **스튜디오에서 열기** 링크의 주소입니다. |
| `VITE_PERSON_URL` | `https://person.nodaro.ai` | 홈 화면의 **Person 열기** 카드가 여는 주소입니다. |

## 동영상 및 오디오 편집기
캔버스의 동영상 결과에 있는 **NodarCut에서 편집**과 [앱](https://nodaro.ai/docs/concepts/apps)의 동영상 결과에 있는 **편집**은 NodarCut 동영상 편집기를 엽니다. 앱의 오디오 결과에 있는 **편집**은 AudioMass 편집기를 엽니다. 내 미디어는 편집기의 서버로 가지 않습니다. 앱이 내 스토리지에서 파일을 읽어 브라우저 안에서 편집기에 넘깁니다. 편집과 내보내기도 브라우저에서 이루어집니다.

- **동영상.** `FREECUT_URL`이 비어 있으면 편집기를 `freecut.nodaro.ai`에서 불러오는데, 이 주소는 `http://localhost:3000`에서만 임베드를 허용합니다. LAN 주소나 도메인 같은 다른 오리진에서는 브라우저가 이를 거부하고, 패널에 그 이유가 표시됩니다. 내가 제공하는 URL을 허용받으려면 GitHub에 그 URL로 이슈를 열거나, 직접 [FreeCut](https://github.com/nodaroai/freecut)을 실행하세요. FreeCut은 MIT 라이선스로 공개되어 있으며 Dockerfile을 함께 제공합니다. 그다음 `FREECUT_URL=https://freecut.example.internal`로 설정하세요.
- **오디오.** 공개적으로 호스팅되는 AudioMass가 없으므로, 구성하기 전까지는 오디오 결과의 **편집**이 편집기를 열지 않습니다. 프레젠테이션 탭에서는 아무 동작도 하지 않고, 편집기 밖에서는 무엇을 설정해야 하는지 설명하는 패널을 보여 줍니다. 원래 AudioMass에 없는 브라우저 내 전달을 지원하는 Nodaro의 분기본 [AudioMass](https://github.com/nodaroai/audiomass)를 실행하세요. 그다음 `AUDIOMASS_URL=https://audiomass.example.internal`로 설정하세요.

변경한 뒤에는 스택을 재시작하세요. 다시 빌드할 필요는 없습니다.

## 모델 제작사 로고
`GET /v1/models`는 각 모델의 제작사를 알려 주며, 모델 선택기를 그리는 앱을 위해 제작사 로고를 `logoUrl`로 함께 보낼 수 있습니다. 회사의 로고는 그 회사의 상표이므로, Nodaro에는 로고가 하나도 포함되어 있지 않습니다. 설치는 표시할 권리가 있는 로고만 게시합니다. 나머지 제작사의 `logoUrl`은 `null`이며, 그러면 앱은 제작사 이름을 표시합니다. 로고를 게시하지 않는 설치는 설정할 것이 없습니다.

제작사 로고를 게시하려면:

1. **파일을 업로드하세요.** `R2_PUBLIC_URL` 뒤의 버킷에 `brand/makers/<maker id>.svg` 경로로, 콘텐츠 타입 `image/svg+xml`로 올립니다. 스크립트가 없는 SVG로 된 회사의 공식 로고를 쓰세요. 제작사 ID는 `GET /v1/models`가 반환하는 `maker.id` 값입니다.
2. **제작사 ID를 선언하세요.** 인스턴스 설정 `model_maker_logos`에 `["google", "openai"]` 같은 목록으로 적습니다. Business 설치에서는 플랫폼 운영자가 `PUT /v1/admin/settings/model_maker_logos`와 본문 `{"value": ["google", "openai"]}`로 설정합니다. 관리자 패널이 없는 Community Edition에서는 같은 인스턴스 설정을 데이터베이스에 직접 씁니다.

- **서버가 먼저 파일을 확인합니다.** 선언된 제작사는 파일이 SVG로 버킷에 있을 때만 `logoUrl`을 받으므로, 두 단계는 어느 순서로 해도 됩니다. 파일이 없거나 타입이 잘못되면 깨진 주소가 아니라 `null`이 됩니다.
- **업로드를 확인하려고 브라우저에서 주소를 열지 마세요.** CDN과, 번들 스택의 Caddy는 “찾을 수 없음” 응답을 1년 동안 보관합니다. 스토리지 콘솔이나 `curl -I` 같은 `HEAD` 요청으로 파일을 확인하세요.
- **반영 시간.** 새 로고는 약 10분 안에 나타납니다. 로고를 바꾸려면 새 파일을 이전 파일 위에 업로드하세요. 주소는 파일에 따라 바뀝니다. 로고를 내리려면 설정에서 ID를 빼거나 파일을 삭제하세요.
- **Nodaro는 이 파일을 절대 삭제하지 않습니다.** 로고를 지우려면 스토리지 콘솔에서 직접 삭제하세요.

## 기본 언어
`DEFAULT_LOCALE`은 새 방문자가 시작하는 언어를 정합니다. 값은 `he`, `ar`, `de`, `fr`, `es`, `hi`, `ja`, `ko`, `pt-BR`, `ru`, `zh-CN`, `en`입니다. 사용자가 직접 저장한 선택이 항상 우선합니다. 비어 있거나 공백이거나 알 수 없는 값이면 방문자의 브라우저 언어로 대체됩니다. 적용하려면 재시작하세요. [언어](https://nodaro.ai/docs/concepts/languages)를 참고하세요.

## 튜토리얼 팩
셀프 호스팅 설치는 다시 빌드하지 않고도 기본 제공 튜토리얼에 내 튜토리얼을 추가할 수 있습니다. 팩마다 디렉터리 하나로 구성됩니다.

- **`manifest.json`**: 팩의 `name`과 팩의 튜토리얼이 쓰는 `categories`입니다. 카테고리마다 `slug`, `name`이 있고, 선택적으로 `sortOrder`와 `description`이 있습니다. 선택 키는 `locale`, `version`, `forbiddenPromptTerms`, 그리고 팩의 모든 카드에 표시되는 작성자 이름인 `creatorDisplayName`입니다.
- **튜토리얼마다 `*.json` 파일 하나**: 노드, 연결, 이미 만들어진 결과가 담긴 워크플로 스냅숏이므로, 복제하면 완료된 실행이 표시된 상태로 열립니다. 선택적 카드 데이터로 실행 한 번의 크레딧인 `estimatedCredits`를 넣을 수 있습니다. 가격이 주어진 녹음의 길이에 따라 정해지는 튜토리얼은 `estimatedPerMinuteCredits`도 넣습니다. 이때 `estimatedCredits`는 고정 부분이고, `estimatedPerMinuteCredits`는 녹음 1분당 크레딧입니다. 그 밖의 선택적 데이터는 카드의 칩에 쓰는 `nodeTypesUsed`, `providersUsed`, 튜토리얼별 `creatorDisplayName`, 그리고 `listedIn`입니다. `listedIn`은 기본값인 튜토리얼 탭용 `["tutorial"]`이거나 템플릿 마켓플레이스용 `["marketplace"]`입니다. 이 값은 첫 시드 때만 적용됩니다.

튜토리얼이 쓰는 모든 미디어 파일은 공개 `https://` URL이어야 합니다. 각 튜토리얼의 카테고리는 매니페스트에 선언되어 있어야 하며, 첫 부팅 때 만들어집니다.

팩 디렉터리를 컨테이너에 읽기 전용으로 마운트하고, 컨테이너 안의 경로를 나열하세요.

```yaml
nodaro:
volumes:
      - ./tutorials/acme:/data/tutorials/acme:ro
```

```bash
NODARO_TUTORIAL_PACKS=/data/tutorials/acme
```

팩은 튜토리얼을 추가하기만 하며, 기본 제공 튜토리얼을 바꾸지는 않습니다. 검증에 실패한 팩은 통째로 건너뛰고 로그에 기록합니다. 잘못된 JSON, 선언되지 않은 카테고리, 공개되지 않은 미디어 URL, 기존 튜토리얼과 겹치는 slug가 있으면 검증에 실패합니다. 바뀐 팩을 불러오려면 컨테이너를 재시작하세요.

## 소셜 네트워크 앱
소셜 네트워크 게시는 네트워크별로 작동합니다. 네트워크는 OAuth 앱 자격 증명을 설정하면 사용할 수 있게 됩니다. 자격 증명이 없어도 네트워크는 **설정 › 연동**에 사용할 수 없음으로 표시되며, 없는 변수의 이름이 함께 나옵니다.

| 네트워크 | 변수 |
| --- | --- |
| Instagram | `META_APP_ID`, `META_APP_SECRET`. 선택: Facebook Login for Business용 `META_INSTAGRAM_CONFIG_ID`. |
| Instagram, Facebook 페이지 없이 | `INSTAGRAM_APP_ID`, `INSTAGRAM_APP_SECRET`. Meta가 Facebook 앱과 별도로 발급합니다. 토큰은 약 60일마다 저절로 갱신됩니다. |
| Facebook | `META_APP_ID`, `META_APP_SECRET`. 선택: `META_FACEBOOK_CONFIG_ID`. |
| TikTok | `TIKTOK_CLIENT_KEY`, `TIKTOK_CLIENT_SECRET` |
| YouTube | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` |
| LinkedIn | `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET` |
| X | `X_CLIENT_ID`, `X_CLIENT_SECRET` |
| Reddit | `REDDIT_CLIENT_ID`, `REDDIT_CLIENT_SECRET` |
| Pinterest | `PINTEREST_CLIENT_ID`, `PINTEREST_CLIENT_SECRET` |
| Discord | `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET`, `DISCORD_BOT_TOKEN` |
| Twitch | `TWITCH_CLIENT_ID`, `TWITCH_CLIENT_SECRET` |
| Threads | `THREADS_APP_ID`, `THREADS_APP_SECRET` |
| Mastodon | `MASTODON_CLIENT_ID`, `MASTODON_CLIENT_SECRET`. 선택: `MASTODON_URL`, 기본값 `mastodon.social`. |
| Telegram | 없음: 사용자가 자신의 봇 토큰을 붙여 넣습니다. |
| Bluesky | 없음: 사용자가 핸들과 앱 비밀번호로 연결합니다. |
| Dev.to, Hashnode, Medium | 없음: 사용자가 자신의 API 키나 토큰으로 연결합니다. |
| WordPress | 없음: 사용자가 사이트 URL과 애플리케이션 비밀번호로 연결합니다. |
| Lemmy | 없음: 사용자가 인스턴스, 로그인, 커뮤니티로 연결합니다. |

모든 OAuth 앱은 리디렉션 URI `https://<your-domain>/v1/social/callback/<network>`를 허용해야 합니다. Facebook이나 Instagram 로그인 하나가 페이지나 계정을 두 개 이상 관리하면, 연결 창에서 사용자에게 어느 것을 연결할지 묻습니다. [소셜 네트워크에 게시하기](https://nodaro.ai/docs/guides/publishing-to-social)를 참고하세요.

## 자주 묻는 질문

### .env 파일의 변수가 적용되지 않는 이유는 무엇인가요?

Compose 파일은 nodaro 서비스의 environment 블록에 나열된 변수만 앱에 전달합니다. 다른 변수는 docker-compose.community.yml의 그 블록에 추가한 뒤 docker compose up -d를 실행하세요.

### 필수 환경 변수는 무엇인가요?

번들된 Compose 스택에서는 스택이 변수를 설정하거나 생성하므로 필수 변수가 없습니다. 스택 밖에서는 SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, 32자 이상의 INTERNAL_ORCHESTRATOR_SECRET이 없으면 API가 시작되지 않습니다.

### 변수를 바꾼 뒤 재시작해야 하나요?

네. Nodaro는 부팅할 때 환경을 읽으므로 docker compose up -d를 실행해 새 값으로 컨테이너를 다시 만드세요. /setup에 붙여 넣은 공급자 키는 예외로, 재시작 없이 적용됩니다.

### 내 설치의 기본 언어는 어떻게 바꾸나요?

DEFAULT_LOCALE을 설정하고(예: DEFAULT_LOCALE=de) 재시작하세요. 새 방문자는 그 언어로 시작합니다. 사용자가 직접 저장한 선택이 항상 우선하며, 알 수 없는 값이면 브라우저 언어로 대체됩니다.

### 셀프 호스팅 설치에 내 튜토리얼을 추가하려면 어떻게 하나요?

튜토리얼 팩마다 manifest.json과 튜토리얼당 JSON 파일 하나를 담은 디렉터리를 만들고, 컨테이너에 마운트한 뒤 NODARO_TUTORIAL_PACKS에 디렉터리를 나열하세요. 재시작하면 불러옵니다.
