config: 사이트 도메인·서버 설정을 .env 로 중앙화 + 설치기 자동 종료 복구
- dotenv 도입, src/shared/env.ts 추가 - loadEnv() 가 프로젝트 루트 .env 를 로드 (override=false: 쉘 env 우선) - getSiteBaseUrl() / getManifestUrl() 헬퍼 - 서버/설치기/리소스팩설치기 진입점에서 loadEnv() 호출 - 설치기 두 종의 기본 MANIFEST_URL 을 SITE_BASE_URL 기반으로 변경 (운영 도메인을 한 곳에서만 바꾸면 됨) - .env.example 템플릿 + .gitignore 에 .env 추가 - README / docs/admin-site.md 에 환경변수 표·사용법 추가 - installer/renderer.js: 4단계 완료 후 자동 종료 다시 활성화
This commit is contained in:
33
.env.example
Normal file
33
.env.example
Normal file
@@ -0,0 +1,33 @@
|
||||
# =============================================================================
|
||||
# 음악퀴즈 통합 패키지 — 환경변수 템플릿
|
||||
# 이 파일을 복사해 `.env` 로 만든 뒤 값만 수정해 사용하세요.
|
||||
# `.env` 는 .gitignore 로 제외되어 있습니다.
|
||||
# =============================================================================
|
||||
|
||||
# ----- 관리 사이트(서버) -----
|
||||
|
||||
# 서버가 listen 할 포트
|
||||
PORT=3000
|
||||
|
||||
# 서버 바인드 주소. 127.0.0.1 이면 로컬 전용, 0.0.0.0 이면 외부 노출.
|
||||
HOST=127.0.0.1
|
||||
|
||||
# Express 세션 시크릿. 운영 환경에서는 반드시 추측 어려운 무작위 값으로.
|
||||
SESSION_SECRET=music-quiz-installer-dev-secret
|
||||
|
||||
# ----- 사이트 도메인(설치기가 manifest 를 받아갈 주소) -----
|
||||
|
||||
# 설치기 두 종(installer / installer-rp) 이 첫 화면에서 자동으로 채워 넣는
|
||||
# manifest 의 호스트. 프로토콜 + 호스트(+포트) 까지만 적고 슬래시는 끝에 붙이지 않음.
|
||||
# 예) 운영 도메인 : https://mq.example.com
|
||||
# 로컬 개발 : http://127.0.0.1:3000
|
||||
SITE_BASE_URL=http://127.0.0.1:3000
|
||||
|
||||
# 위 SITE_BASE_URL 로부터 자동으로 `${SITE_BASE_URL}/manifest.json` 이 생성됩니다.
|
||||
# 특별히 다른 경로를 쓰고 싶을 때만 아래를 풀어서 우선 적용시키세요.
|
||||
# MANIFEST_URL=http://127.0.0.1:3000/manifest.json
|
||||
|
||||
# ----- 리소스팩 설치기 -----
|
||||
|
||||
# yt-dlp 동시 다운로드 수(1~8). 비워두면 CPU 코어 수로 자동 결정.
|
||||
# MUSIC_CONCURRENCY=
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,3 +4,6 @@ release/
|
||||
logs/
|
||||
*.log
|
||||
conversations/
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
# 의존성 설치
|
||||
npm install
|
||||
|
||||
# 환경변수 템플릿 복사 (처음 한 번만)
|
||||
cp .env.example .env
|
||||
|
||||
# 1) 관리 사이트 개발 실행 (http://localhost:3000)
|
||||
npm start
|
||||
|
||||
|
||||
@@ -6,11 +6,25 @@
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm start # 기본 포트 3000. 환경변수로 PORT 조정 가능.
|
||||
cp .env.example .env # 처음 한 번만. 운영 도메인이면 SITE_BASE_URL 만 바꾸면 됩니다.
|
||||
npm start # 기본 포트 3000.
|
||||
```
|
||||
|
||||
배포 시에는 시스템 서비스(systemd 등) 로 등록해 두면 됩니다.
|
||||
|
||||
### 환경변수 (`.env`)
|
||||
|
||||
| 키 | 기본값 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| `PORT` | `3000` | Express 서버 listen 포트. |
|
||||
| `HOST` | `127.0.0.1` | 바인드 주소. 외부 노출하려면 `0.0.0.0`. |
|
||||
| `SESSION_SECRET` | dev secret | `/op` 세션 쿠키 서명 키. 운영에서는 반드시 임의값으로 교체. |
|
||||
| `SITE_BASE_URL` | `http://127.0.0.1:3000` | 설치기 두 종이 첫 화면에서 자동으로 채우는 manifest 호스트. 운영 도메인으로 바꿔두면 manifest URL 도 자동으로 따라갑니다. |
|
||||
| `MANIFEST_URL` | — | 특별히 다른 경로를 쓰고 싶을 때만 지정. 비우면 `${SITE_BASE_URL}/manifest.json`. |
|
||||
| `MUSIC_CONCURRENCY` | (자동) | 리소스팩 설치기 yt-dlp 동시 다운로드 수(1~8). |
|
||||
|
||||
`.env` 는 `.gitignore` 로 제외되어 있습니다. 새 환경을 셋업할 때 `.env.example` 을 복사해서 시작하세요. 쉘에서 직접 환경변수를 지정한 경우에는 `.env` 값을 덮어쓰지 않습니다.
|
||||
|
||||
## 도메인 / 경로 구성
|
||||
|
||||
| 경로 | 내용 |
|
||||
|
||||
@@ -695,8 +695,7 @@ function renderStep5() {
|
||||
// 마무리 액션 실패는 무시하고 종료 진행
|
||||
}
|
||||
finishBtn.textContent = '완료됨'
|
||||
// 자동 종료는 임시 비활성화 (런처 실행 오류 메시지 확인용). 필요 시 X 버튼으로 직접 닫는다.
|
||||
// if (installerApi.quitApp) installerApi.quitApp()
|
||||
if (installerApi.quitApp) installerApi.quitApp()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
22
package-lock.json
generated
22
package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@types/archiver": "^7.0.0",
|
||||
"archiver": "^7.0.1",
|
||||
"dotenv": "^17.4.2",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
@@ -2630,12 +2631,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
|
||||
"integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==",
|
||||
"dev": true,
|
||||
"version": "17.4.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv-expand": {
|
||||
@@ -4721,6 +4724,15 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-config-file/node_modules/dotenv": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
|
||||
"integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"dependencies": {
|
||||
"@types/archiver": "^7.0.0",
|
||||
"archiver": "^7.0.1",
|
||||
"dotenv": "^17.4.2",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { ChildProcess } from 'node:child_process'
|
||||
import type { Manifest, PackDefinition, PackList } from '../shared/types.js'
|
||||
import { normalizePackDefinition } from '../shared/store.js'
|
||||
import { getAppDataDir, getMcCustomDir } from '../shared/paths.js'
|
||||
import { loadEnv, getManifestUrl } from '../shared/env.js'
|
||||
import type { RpFetchedPack } from './types.js'
|
||||
import { ensureYtDlpExe } from './ytdlp.js'
|
||||
import { ensureFfmpegExe } from './ffmpeg.js'
|
||||
@@ -17,6 +18,8 @@ import { downloadMusicTrack } from './music.js'
|
||||
import { downloadImage, normalizeToCover, coverFileName } from './images.js'
|
||||
import { buildResourcepackZip } from './pack.js'
|
||||
|
||||
loadEnv()
|
||||
|
||||
interface RpInstallerState {
|
||||
manifestUrl: string
|
||||
baseUrl: string
|
||||
@@ -68,7 +71,7 @@ function acquireMusicStartSlot(): Promise<void> {
|
||||
return slot
|
||||
}
|
||||
|
||||
const DEFAULT_MANIFEST_URL = process.env.MANIFEST_URL ?? 'http://127.0.0.1:3000/manifest.json'
|
||||
const DEFAULT_MANIFEST_URL = getManifestUrl()
|
||||
|
||||
const state: RpInstallerState = {
|
||||
manifestUrl: DEFAULT_MANIFEST_URL,
|
||||
|
||||
@@ -20,6 +20,9 @@ import type {
|
||||
} from './types.js'
|
||||
import type { Manifest, PackDefinition } from '../shared/types.js'
|
||||
import { normalizePackDefinition } from '../shared/store.js'
|
||||
import { loadEnv, getManifestUrl } from '../shared/env.js'
|
||||
|
||||
loadEnv()
|
||||
|
||||
interface InstallerState {
|
||||
manifestUrl: string
|
||||
@@ -31,7 +34,7 @@ interface InstallerState {
|
||||
configEditorPort: number | null
|
||||
}
|
||||
|
||||
const DEFAULT_MANIFEST_URL = process.env.MANIFEST_URL ?? 'http://127.0.0.1:3000/manifest.json'
|
||||
const DEFAULT_MANIFEST_URL = getManifestUrl()
|
||||
|
||||
const state: InstallerState = {
|
||||
manifestUrl: DEFAULT_MANIFEST_URL,
|
||||
|
||||
@@ -3,9 +3,12 @@ import session from 'express-session'
|
||||
import path from 'node:path'
|
||||
import fsp from 'node:fs/promises'
|
||||
import { manifestRootPath, manifestDirPath, fileDirPath, viewsDirPath, publicDirPath } from '../shared/paths.js'
|
||||
import { loadEnv } from '../shared/env.js'
|
||||
import { indexRouter } from './routes/index.js'
|
||||
import { opRouter } from './routes/op.js'
|
||||
|
||||
loadEnv()
|
||||
|
||||
const PORT = Number(process.env.PORT ?? 3000)
|
||||
// 터미널에서 Ctrl+클릭으로 바로 열 수 있도록 기본값은 127.0.0.1.
|
||||
// 외부 노출이 필요할 때만 HOST=0.0.0.0 환경변수로 덮어씀.
|
||||
|
||||
33
src/shared/env.ts
Normal file
33
src/shared/env.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import dotenv from 'dotenv'
|
||||
import { projectRoot } from './paths.js'
|
||||
|
||||
/**
|
||||
* 프로젝트 루트의 `.env` 를 읽어 `process.env` 에 주입.
|
||||
*
|
||||
* - 이미 설정된 환경변수는 덮어쓰지 않음(쉘에서 넘긴 값이 우선).
|
||||
* - 파일이 없으면 조용히 통과 — 운영 환경에서는 시스템 env 만으로도 동작해야 함.
|
||||
* - 서버/설치기/리소스팩설치기 진입점에서 한 번씩 호출.
|
||||
*/
|
||||
export function loadEnv(): void {
|
||||
const envPath = path.join(projectRoot, '.env')
|
||||
if (!fs.existsSync(envPath)) return
|
||||
dotenv.config({ path: envPath, override: false, quiet: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 사이트 베이스 URL. 관리 사이트가 호스팅되는 외부 주소(설치기가 manifest 를
|
||||
* 받아가는 도메인). 기본값은 로컬 개발용 `http://127.0.0.1:3000`.
|
||||
*/
|
||||
export function getSiteBaseUrl(): string {
|
||||
const raw = (process.env.SITE_BASE_URL ?? 'http://127.0.0.1:3000').trim()
|
||||
return raw.replace(/\/+$/, '')
|
||||
}
|
||||
|
||||
/** 사이트 베이스 URL + `/manifest.json`. `MANIFEST_URL` 가 따로 지정되면 그 값을 우선. */
|
||||
export function getManifestUrl(): string {
|
||||
const explicit = process.env.MANIFEST_URL?.trim()
|
||||
if (explicit) return explicit
|
||||
return `${getSiteBaseUrl()}/manifest.json`
|
||||
}
|
||||
Reference in New Issue
Block a user