From 80247f774b6499b1bfbc0141b1d806cfe5e84ce3 Mon Sep 17 00:00:00 2001 From: Scaffolder Date: Thu, 25 Jun 2026 08:50:30 +0000 Subject: [PATCH] initial commit Change-Id: I5b7bb3df064cd550520c44c805fd011e4e2691c7 --- .gitignore | 9 + .project-env.example | 30 +++ CLAUDE.md | 45 +++++ MANUAL.md | 402 +++++++++++++++++++++++++++++++++++++++++ README.md | 35 ++++ catalog-info.yaml | 23 +++ index.js | 4 + package.json | 21 +++ scripts/check-db.js | 11 ++ scripts/check-minio.js | 10 + src/config.js | 26 +++ src/db.js | 15 ++ src/s3.js | 24 +++ src/server.js | 33 ++++ 14 files changed, 688 insertions(+) create mode 100644 .gitignore create mode 100644 .project-env.example create mode 100644 CLAUDE.md create mode 100644 MANUAL.md create mode 100644 README.md create mode 100644 catalog-info.yaml create mode 100644 index.js create mode 100644 package.json create mode 100644 scripts/check-db.js create mode 100644 scripts/check-minio.js create mode 100644 src/config.js create mode 100644 src/db.js create mode 100644 src/s3.js create mode 100644 src/server.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9785f64 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +# 프로젝트 설정(DB/S3 자격증명 포함) — 절대 커밋 금지 +.project-env +.env +.env.* +!.project-env.example +npm-debug.log* +dist/ +.DS_Store diff --git a/.project-env.example b/.project-env.example new file mode 100644 index 0000000..f531ffc --- /dev/null +++ b/.project-env.example @@ -0,0 +1,30 @@ +# .project-env.example — 프로젝트 설정 예시(참조용). +# +# 실제 사용: `new-project <이름>` 명령으로 sample을 복사하면 .project-env 가 +# DB(본인 schema 자동계산)+S3 값이 채워진 채로 자동 생성됩니다. +# 이 example 을 직접 복사해 쓸 일은 거의 없습니다(수동 생성 시 참고용). +# +# 동작: 프로젝트 폴더에 cd 하면 로그인 셸이 .project-env 를 자동 export → 앱은 +# process.env 로 바로 읽습니다(별도 dotenv 불필요). git에는 올리지 마세요(.gitignore). + +# --- DB (본인 전용 Postgres schema) --- +# host 는 클러스터 내부 OpenEverest(postgresql-6ox) pgbouncer. 워크스페이스와 Kubero 배포 앱은 +# 둘 다 클러스터 내부라 같은 k8s DNS 로 도달(그대로 복붙 가능). +DATABASE_URL=postgresql://emp_<사번>:@postgresql-6ox-pgbouncer.everest.svc.cluster.local:5432/appdb?options=-c%20search_path%3Demp_<사번> +PGHOST=postgresql-6ox-pgbouncer.everest.svc.cluster.local +PGPORT=5432 +PGDATABASE=appdb +PGUSER=emp_<사번> +PGPASSWORD= +PGOPTIONS=-c search_path=emp_<사번> + +# --- MinIO / S3 --- +# endpoint 는 콘솔(minioc)이 아니라 API 호스트(minio)입니다. +S3_ENDPOINT=https://minio.bokdev.in +S3_REGION=us-east-1 +S3_BUCKET=coolify-user-data +S3_ACCESS_KEY_ID=REPLACE_WITH_YOUR_MINIO_ACCESS_KEY +S3_SECRET_ACCESS_KEY=REPLACE_WITH_YOUR_MINIO_SECRET_KEY + +# --- 앱 --- +PORT=3000 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3c9b7ce --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,45 @@ +# CLAUDE.md — 이 프로젝트에서 Claude Code가 따라야 할 규약 + +너는 사내 개발 워크스페이스에서 동작하는 코딩 에이전트다. 이 프로젝트는 **Node(ESM) 앱**이고, +**로컬 개발/테스트는 워크스페이스에서 직접 `node` 실행**, **배포는 Kubero(Gitea push → buildpack 자동빌드)** 로 한다. + +## 절대 규칙 +- **비밀값을 코드/커밋에 넣지 마라.** DB·MinIO 자격증명은 항상 환경변수에서만 읽는다. + 값은 프로젝트의 `.project-env` 에 있고(프로젝트 폴더 cd 시 셸이 자동 export), 이 파일은 **절대 커밋 금지**(`.gitignore`에 있음). 참고용 예시는 `.project-env.example`. +- 운영 배포를 직접 하지 마라. 배포는 **git push → Kubero**가 담당한다. 너는 코드만 책임진다. +- **진입점은 루트의 `index.js` 다(Kubero buildpack 의 run = `node index.js`).** 서버 구현은 `src/server.js`, + `index.js` 는 그것을 로드하기만 한다. 진입 파일 구조를 바꾸지 마라(바꾸면 Kubero 배포가 안 뜬다). + +## 외부 연결 (프로젝트 `.project-env` 에서 env로 주입됨) +- **Postgres**: `DATABASE_URL` (직원 전용 schema로 격리, `search_path` 고정). 코드는 `src/db.js`를 통해 접근. + DB는 클러스터 내부 OpenEverest(`postgresql-6ox`)의 `appdb` 다. +- **MinIO(S3)**: API endpoint `https://minio.bokdev.in` (콘솔 `minioc`가 아님), 버킷 `coolify-user-data`. + 키는 `.project-env`의 `S3_ACCESS_KEY_ID`/`S3_SECRET_ACCESS_KEY`. 코드는 `src/s3.js`를 통해 접근. +- **LiteLLM**: AI 호출은 사내 gateway(`https://litellm.bok.or.kr`) 경유. virtual key는 워크스페이스 전역인 `~/.env`(`LITELLM_KEY`)에서 관리 — `update-litellm-key` 명령으로 입력. (프로젝트 설정 아님) + +## 로컬 개발 워크플로 (컨테이너 빌드 불필요) +```bash +# 이 폴더는 new-project 로 만들어졌고 .project-env(DB/S3)가 이미 채워져 있다. +# 폴더에 들어와 있으면 셸이 .project-env 를 자동 export 한 상태다($DATABASE_URL 등 사용 가능). +npm install +npm run dev # node --watch 로 핫리로드, http://localhost:3000 + +# 배포(Kubero)와 "동일한 방식"으로 한 번 돌려보기 — 이게 가장 정확한 사전 검증이다. +npm start # = node index.js (Kubero 의 run 커맨드와 동일) +``` +- Kubero buildpack 은 Dockerfile 을 쓰지 않는다. 로컬에서 **컨테이너 이미지를 빌드할 필요가 없다.** + Kubero 가 결국 `npm install` → `node index.js` 만 하므로, 위 `npm start` 가 통과하면 배포도 거의 그대로 된다. + +연결 확인: `npm run db:check`, `npm run minio:check`, 또는 `/db` `/s3` 엔드포인트. + +## 배포 워크플로 (Kubero) +1. Gitea repo에 push (`main` 등 배포 브랜치). +2. Kubero(https://kubero.bokdev.in)가 이 repo를 받아 **NodeJS buildpack**으로 자동 빌드/배포한다 + (build=`npm install`, run=`node index.js`, Node 22). Dockerfile 은 쓰지 않는다. +3. Kubero 앱의 **Environment Variables** 에 `DATABASE_URL`, `S3_*`, `PORT` 를 채운다(로컬 `.project-env`와 같은 키). +4. 포트는 `3000`(`PORT`). 앱 주소는 `https://<앱이름>.apps.bokdev.in`. + +## 코드 규약 +- ESM(`import`/`export`), Node 22+. 외부 연결정보는 반드시 `src/config.js` 한 곳을 거친다. +- 새 외부 의존성을 추가하면 `package.json`에 반영하고 `npm install`로 lock 갱신. +- 변경 후에는 `npm run db:check` / `npm run minio:check`로 연결이 살아있는지 스스로 확인하라. diff --git a/MANUAL.md b/MANUAL.md new file mode 100644 index 0000000..bfdb69c --- /dev/null +++ b/MANUAL.md @@ -0,0 +1,402 @@ +# 개발환경 사용 매뉴얼 (Coder) + +사내 개발은 **Coder**(웹 기반 개발 워크스페이스)에서 합니다. 브라우저만 있으면 됩니다. +DB·MinIO·AI(LiteLLM)·Git(Gitea)·배포(Kubero)가 미리 연결돼 있어, 로그인 후 바로 코딩할 수 있습니다. + +--- + +## 0. 준비물 +- 사내망에서 접속 가능한 브라우저 (Chrome/Edge 권장) +- 본인 사번 + +### 주소(URL) 한눈에 + +| 용도 | 주소 | 쓰는 곳 | +|---|---|---| +| **개발 포털(시작점)** | https://backstage.bokdev.in | 새 프로젝트 생성·배포 (아래 "신규 흐름") | +| **개발 워크스페이스** | https://coder.bokdev.in | 로그인·VS Code (1~3번) | +| **코드 저장소(Git)** | https://gitea.bokdev.in | repo 생성·push (10번) | +| **배포(Kubero)** | https://kubero.bokdev.in | 앱 빌드·배포·로그 (11번) | +| **파일저장소(MinIO 콘솔)** | https://minioc.bokdev.in | 업로드된 파일 눈으로 확인 (선택) | +| **개발 중 앱 미리보기** | `https://<자동생성>.coder.bokdev.in` | 로컬 실행 앱 브라우저 확인 (9번, VS Code PORTS가 자동 발급) | +| **배포된 앱 주소** | `https://<이름>.apps.bokdev.in` | 실제 서비스 확인 (11번, Kubero에서 앱 생성 시 지정) | + +> 코드에서 쓰는 주소(자동 주입, 직접 입력 불필요): **DB** `postgresql-6ox-pgbouncer.everest.svc.cluster.local:5432` · **MinIO API** `https://minio.bokdev.in` · **AI 게이트웨이** `https://litellm.bok.or.kr`. +> (콘솔 `minioc` 와 API `minio` 는 다릅니다 — 코드는 `minio`, 사람이 눈으로 볼 땐 `minioc`.) + +--- + +## ★ 신규 흐름 (Backstage 중심) — 권장 + +`new-project` 로 직접 복사하는 대신, **Backstage 개발 포털**에서 프로젝트를 만들고 배포까지 합니다. + +1. **프로젝트 생성** — https://backstage.bokdev.in 로그인(사번 SSO) → **Create → "AI DEV 앱 만들기"** 템플릿 → + 앱 이름(예: `to-do`) 입력 → 실행. Gitea `playground/to-do` repo 가 만들어지고 카탈로그에 등록됩니다. +2. **개발 (Coder)** — Coder(`aidev-k8s`) 워크스페이스 터미널에서: + ```bash + open-project to-do # repo clone + .project-env(본인 DB/S3) 자동 생성 + cd ~/projects/to-do && npm install && npm run dev + ``` + AI 바이브코딩으로 기능을 만들고 `git push` 합니다. +3. **배포 (Backstage)** — Backstage 에서 배포(또는 템플릿의 "생성 직후 배포" 옵션)로 Kubero 에 올립니다. + 본인 DB/S3 환경변수는 자동 주입됩니다. 앱 주소: `https://to-do.apps.bokdev.in`. + +> 아래 1~11번은 **기존(수동) 흐름**입니다. Backstage 를 쓰면 7번(`new-project`)은 `open-project` 로, +> 11번(Kubero 수동 배포)은 Backstage 배포로 대체됩니다. 개발(8~9번)은 동일합니다. + +--- + +## 1. Coder 로그인 + +1. 브라우저에서 **https://coder.bokdev.in** 접속 +2. 로그인 + - **Username**: 본인 사번 (예: `0310700`) + - **Password**: `bok1234!!` + 본인 사번 (예: `bok1234!!0310700`) + - > 최초 로그인 후 비밀번호를 바꾸는 것을 권장합니다. (우측 상단 계정 메뉴 → Account) + +--- + +## 2. 워크스페이스 만들기 (최초 1회) + +워크스페이스 = 본인 전용 개발용 컨테이너(VS Code + 각종 도구가 깔린 PC). + +1. 로그인하면 **Workspaces** 화면. **`Create Workspace`** 클릭 (또는 **Templates → `aidev-k8s`** 선택) +2. 설정값 입력 + - **Name**: 워크스페이스 이름 (예: `ws-aidev-<사번>` 또는 자유롭게) + - **CPU / Memory / Home disk size**: 기본값(2 Core / 4 GiB / 10 GiB)으로 두면 됩니다. 필요하면 나중에 늘릴 수 있습니다. +3. **`Create Workspace`** 클릭 +4. 워크스페이스가 빌드됩니다 (**처음엔 이미지 다운로드로 2~5분** 걸릴 수 있습니다. 기다리세요). + - 상태가 **Running** 이 되면 준비 완료. + +> 한 번 만들면 계속 재사용합니다. 다음부터는 만들 필요 없이 **Start** 만 누르면 됩니다. + +--- + +## 3. VS Code 열기 + +1. 워크스페이스 화면에서 **`VS Code Web`** 버튼 클릭 +2. 브라우저에 VS Code가 열리고, 자동으로 **`/home/coder/projects`** 폴더가 열립니다. +3. 그 안에 **`sample`** 폴더가 이미 있습니다 — 참조용 예제입니다(직접 고치지 말고 복사해 쓰세요, 7번 참고). + +> 작업 파일은 반드시 **`/home/coder/projects`** 아래에 두세요. 이 폴더만 영구 보존됩니다. +> (워크스페이스를 stop/재시작해도 유지. 그 밖의 위치는 사라질 수 있습니다.) + +--- + +## 4. 미리 연결된 것들 (별도 설정 불필요) + +새 워크스페이스에는 아래가 자동으로 준비돼 있습니다. + +| 항목 | 내용 | +|---|---| +| **개발 도구** | Java(JDK)/Maven, Node 22, Python 3.12, git, psql, `tree`, `net-tools`(netstat/ifconfig) | +| **DB** | 본인 전용 Postgres 스키마에 자동 연결 (`$DATABASE_URL`) — 클러스터 내부 OpenEverest `postgresql-6ox` 의 `appdb` | +| **VS Code 확장** | Claude Code, Codex (이미 설치됨) | +| **AI CLI** | `claude`, `codex`, `gemini` (LiteLLM 게이트웨이 연동, 아래 5번 참고) | + +--- + +## 5. AI 에이전트 사용 설정 (최초 1회) — LiteLLM 키 입력 + +Claude Code / Codex / Gemini 가 사내 AI 게이트웨이를 쓰려면 **본인 LiteLLM virtual key**가 필요합니다. +키는 **한 곳에만** 넣으면 모든 도구(CLI·확장)가 공유합니다. + +1. 터미널 열기: VS Code 상단 메뉴 **Terminal → New Terminal** +2. 아래 명령을 입력하고, 안내가 나오면 발급받은 본인 키(`sk-...`)를 붙여넣습니다: + ```bash + update-litellm-key + ``` + ``` + LiteLLM virtual key 입력 (sk-...): sk-여기에-본인-키-붙여넣기 + 키 갱신 완료 (len=25). 현재 터미널에 즉시 적용됨. + ``` + > `update-litellm-key` 는 키를 파일에 저장하고 **현재 터미널에 바로 적용**합니다. + > 터미널을 새로 열거나 `source` 할 필요가 없습니다. 키를 바꿀 때도 같은 명령을 다시 쓰면 됩니다. +3. 확인: + ```bash + echo $ANTHROPIC_BASE_URL # https://litellm.bok.or.kr 가 나오면 정상 + claude # Claude Code CLI 실행 + ``` + +각 도구의 **기본 모델**은 사내 게이트웨이에 맞춰 미리 설정돼 있습니다(바꾸려면 각 설정 파일 수정): + +| 도구 | 기본 모델 | 설정 파일 | +|---|---|---| +| Claude Code | `claude-opus-4-8` | `~/.claude/settings.json` | +| Codex | `gpt-5.5` | `~/.codex/config.toml` | +| Gemini | `gemini-3.1-pro-preview` | `~/.gemini/settings.json` | + +> 키는 본인 워크스페이스 안에만 저장되고, 코드/깃에는 올라가지 않습니다. +> 게이트웨이 주소(`https://litellm.bok.or.kr`)는 이미 설정돼 있으니 건드릴 필요 없습니다. + +--- + +## 6. Git(Gitea) 사용 — 최초 1회 승인 + +코드 저장소는 사내 **Gitea**(https://gitea.bokdev.in)입니다. 토큰 입력 없이 자동 인증됩니다. + +1. 터미널에서 처음 `git clone` / `git push` 등을 하면, **Gitea 승인 화면**으로 안내됩니다. + - 또는 Coder 화면의 **`Gitea`** external auth 항목에서 **Authorize** 를 미리 눌러도 됩니다. +2. 한 번 **Authorize(승인)** 하면, 이후로는 비밀번호 입력 없이 clone/push 가 됩니다. + +예: +```bash +cd ~/projects +git clone https://gitea.bokdev.in//.git +``` + +--- + +## 7. 새 프로젝트 시작하기 (sample 복사) + +`~/projects/sample` 은 바로 돌려볼 수 있는 Node 예제이며 **읽기 전용 참조**입니다. +직접 고치지 말고, **`new-project` 명령으로 복사**해서 본인 프로젝트를 시작하세요. + +**(1) 터미널 열기** — VS Code 상단 메뉴 **Terminal → New Terminal** (화면 아래쪽에 터미널 창이 뜹니다) + +**(2) 프로젝트 만들기** — 터미널에 입력: +```bash +new-project myapp # sample 을 ~/projects/myapp 으로 복사 + .project-env(DB/S3) 자동생성 +``` +> `myapp` 은 예시입니다. 원하는 이름으로 바꿔도 됩니다. + +**(3) VS Code 로 그 폴더 열기** — 만든 폴더를 편집기에 띄웁니다(왼쪽 파일 목록에 보이게): + - 상단 메뉴 **File → Open Folder…** + - 경로 입력칸에 **`/home/coder/projects/myapp`** 입력 → **OK** + - (또는 왼쪽 맨 위 📁 **Explorer** 아이콘 → **Open Folder** 버튼) + - 창이 새로고침되며 왼쪽에 `myapp` 의 파일들이 보이면 성공입니다. + +> 폴더를 열면 VS Code 가 그 폴더를 "작업 공간"으로 삼습니다. 이제 왼쪽 목록에서 파일을 눌러 편집하고, +> 터미널도 자동으로 그 폴더(`~/projects/myapp`)에서 시작됩니다. + +**(4) 라이브러리 설치** — 다시 터미널(Terminal → New Terminal)에서: +```bash +npm install # 처음 1회: 필요한 라이브러리 설치 (이걸 안 하면 실행이 실패합니다) +``` + +여기까지 하면 프로젝트 준비 완료입니다. **이제 8번부터 본격적으로 개발**하면 됩니다(코드 편집 → AI 활용 → 실행 → 배포). + +> **`.project-env` 가 그 프로젝트의 설정 파일입니다** (DB·S3). 값을 바꾸려면 이 파일 한 곳만 고치면 됩니다. +> 프로젝트 폴더에 들어가면(cd) 자동으로 환경변수에 반영됩니다. (git에는 올라가지 않습니다) +> LiteLLM 키만은 프로젝트가 아니라 워크스페이스 전체 공용이라 `~/.env`(5번 `update-litellm-key`)에서 관리합니다. + +--- + +## 📌 전체 개발 흐름 (한눈에) + +``` +7. 새 프로젝트 만들기 (new-project → 폴더 열기 → 설치) ← 위에서 완료 +8. 개발하기 (코드 편집 + AI + DB/S3 사용) +9. 로컬에서 실행·확인 (npm run dev / npm start → 브라우저 미리보기) +10. Gitea에 올리기 (git push) +11. Kubero로 배포·확인 (실제 서비스로 띄우기) +``` +아래는 각 단계를 순서대로 설명합니다. + +--- + +## 8. 개발하기 + +(7번에서 **File → Open Folder 로 본인 프로젝트 폴더를 열어 둔 상태**에서) + +- **코드 편집**: 왼쪽 파일 목록에서 파일(예: `src/server.js`)을 눌러 수정 → **Ctrl+S 로 저장**. +- **AI 도구 활용**: 터미널에서 `claude`(또는 `codex`/`gemini`) 실행. **반드시 본인 프로젝트 폴더 안에서** 실행해야 그 프로젝트를 봅니다(폴더를 Open Folder 해뒀으면 새 터미널은 이미 그 폴더에서 시작). 키 설정은 5번. +- **내 DB 직접 접속**(필요 시): + ```bash + cd ~/projects/<본인 프로젝트> # 프로젝트 폴더에서 (그래야 .project-env 가 로드됨) + psql "$DATABASE_URL" # 본인 전용 스키마로 바로 접속됨 + ``` +- **참고**: DB(`$DATABASE_URL`)·S3 접속정보는 `.project-env` 에 이미 들어 있고, 프로젝트 폴더에 들어가면 자동 적용됩니다. 코드에서는 그냥 `process.env.DATABASE_URL` 등으로 쓰면 됩니다. + +### 8-1. AI 도구를 잘 쓰는 법 (팁) + +- **항상 프로젝트 폴더 안에서 실행**하세요. AI 는 "지금 폴더"의 파일을 읽어 맥락을 잡습니다. +- **`CLAUDE.md` 를 활용**하세요. sample 에는 이미 `CLAUDE.md`(이 프로젝트 규칙: 로컬 node 개발, Kubero 배포, 비밀값 금지 등)가 들어 있고, Claude 가 자동으로 읽습니다. 프로젝트 규칙·주의사항을 여기에 적어두면 AI 가 그대로 따릅니다. +- **구체적으로 시키세요**. "로그인 API 만들어줘" 보다 "`src/` 에 POST /login 엔드포인트 추가, 입력 검증하고 실패 시 401 반환" 처럼. +- **확인은 직접**: AI 가 만든 코드도 9번(로컬 실행)으로 **반드시 본인이 동작 확인** 후 커밋하세요. +- 키가 안 먹으면(401) → 5번 `update-litellm-key`. 모델 오류(400)면 → 5번 표의 모델명. + +### 8-2. bkit — AI 개발 보조 플러그인 (선택, 권장) + +**bkit**(Vibecoding Kit, https://www.bkit.ai/ )은 Claude Code 에 **체계적 개발 절차(PDCA: 계획→설계→구현→검증)** 와 다수의 전문 스킬을 더해주는 플러그인입니다. "무엇을 만들지"만 설명하면 bkit 이 계획부터 구현·검증까지 단계적으로 진행해 줍니다. + +**설치 (최초 1회, Claude Code 안에서 입력)** +``` +/plugin marketplace add popup-studio-ai/bkit-claude-code +/plugin install bkit +``` + +**자주 쓰는 명령** (Claude Code 프롬프트에 입력) +- `/pdca pm <기능이름>` — 기능 하나를 계획→구현→검증까지 한 번에. **처음엔 이거 하나면 충분**합니다. +- `/sprint` — 여러 기능을 묶은 릴리스 단위 작업. +- `/control` — AI 가 얼마나 자동으로 진행할지(자율도) 조절. + +> bkit 의 세부 절차를 몰라도 됩니다 — **원하는 걸 자연어로 말하면** bkit 이 알맞은 흐름을 골라 줍니다. +> 더 알아보려면 공식 사이트(bkit.ai) / GitHub(`popup-studio-ai/bkit-claude-code`) 참고. + +--- + +## 9. 로컬에서 실행하고 브라우저로 확인하기 + +코드를 바로 실행해 동작을 확인하는 단계입니다(가장 빠름). + +> **빌드·테스트는 3단계로 점점 "실제 배포에 가깝게" 검증합니다.** +> | 단계 | 무엇으로 | 무엇을 확인 | 어디서 | +> |---|---|---|---| +> | **9. 소스 실행** | `npm run dev` / `npm start` | 코드 로직 + Kubero 와 동일한 실행(`node index.js`) | 워크스페이스 | +> | **10. Git push** | `git push` | 배포에 쓸 코드를 저장소에 올림 | Gitea | +> | **11. 배포 빌드** | Kubero | 실제 서비스로 buildpack 빌드/기동 | Kubero | +> 앞 단계가 통과해야 뒤 단계가 거의 그대로 됩니다(Kubero 는 결국 `npm install`+`node index.js` 만 합니다). 막히면 **앞 단계로 돌아가** 고치세요. +> **컨테이너 이미지를 직접 빌드할 필요는 없습니다** — Kubero 가 buildpack 으로 알아서 빌드합니다. + +**1) 실행** +```bash +cd ~/projects/<본인 프로젝트> +npm run dev # 개발 중: 코드를 고치면 자동으로 다시 시작됨(핫리로드) +# 배포 직전 점검: 배포(Kubero)와 똑같은 방식으로 한 번 띄워보기 +npm start # = node index.js (Kubero 의 run 커맨드와 동일) +``` +실행되면 터미널에 `sample listening on :3000` 같은 줄이 뜹니다. **에러 없이** 이 줄이 보이면 기동 성공입니다. +(빨간 에러가 뜨면 거의 ① `npm install` 안 함 ② 코드 문법 오류 ③ `.project-env` 미로딩(=폴더 밖에서 실행) 셋 중 하나입니다.) + +**2) 연결만 빠르게 점검**(앱 안 띄우고 DB/파일저장소 자격증명·연결 확인): +```bash +npm run db:check # → "DB OK: { now: 2026-... }" 이면 DB 연결 정상 +npm run minio:check # → "S3 OK: { bucket: 'coolify-user-data', sampleKeys: [...] }" 이면 정상 +``` +> `DB FAIL` / `S3 FAIL` 이 나오면 `.project-env` 값(host·키)을 확인하세요. **여기서 통과하면 배포 환경에서도 거의 됩니다.** + +**3) 브라우저로 미리보기** — 워크스페이스는 사내 클러스터 안이라 `localhost:3000` 이 PC 브라우저엔 바로 안 열립니다. VS Code 의 포트 기능을 씁니다: +1. VS Code 하단 **`PORTS`** 탭 클릭 → **`Forward a Port`** → 포트 번호(`3000`) 입력 + (앱이 뜨면 자동 감지해 알림이 뜨기도 합니다) +2. 포워딩된 포트 옆 **🌐 (Open in Browser)** 클릭 → 새 탭에 앱이 열립니다. + - 열리는 주소: `https://<자동생성>.coder.bokdev.in` (본인 전용 임시 URL, 사내 정식 TLS) + +**4) 엔드포인트로 동작 확인** — 브라우저 주소 뒤에 경로를 붙이거나, 터미널에서 `curl` 로 확인합니다. **각 응답의 `"ok": true` 와 아래 기대값을 확인**하세요: + +| 경로 | 의미 | 정상 응답(예) | +|---|---|---| +| `/healthz` | 앱이 살아있나 | `{"ok":true}` | +| `/db` | 내 Postgres 연결 | `{"ok":true,"now":"2026-..."}` | +| `/s3` | 파일저장소(MinIO) 연결 | `{"ok":true,"bucket":"coolify-user-data","sampleKeys":[...]}` | + +```bash +curl 127.0.0.1:3000/healthz # {"ok":true} +curl 127.0.0.1:3000/db # DB 연결 + 현재시각 +curl 127.0.0.1:3000/s3 # 버킷명 + 파일목록 일부 +``` +> `"ok": false` + `error` 가 보이면 그 메시지가 원인입니다(예: DB 비번 틀림, 버킷 없음). `/db 500` 은 보통 `.project-env` 의 `DATABASE_URL` 문제입니다. +> 이 임시 URL 은 본인만 접근 가능하고 워크스페이스를 끄면 사라집니다. **미리보기용**이며, 정식 배포는 11번입니다. + +--- + +## 10. Gitea(git)에 올리기 + +`new-project` 로 만든 폴더는 아직 git 저장소가 아닙니다(`.git` 없음). 아래처럼 올립니다. +(배포(11번)는 Gitea repo 를 받아서 빌드하므로, 배포 전에 반드시 올려야 합니다.) + +> 아래 명령의 `myapp` 은 **본인이 `new-project` 로 만든 프로젝트 이름**으로 바꿔 쓰세요. + +**1) Gitea에 빈 저장소 만들기** +- **https://gitea.bokdev.in** → 우측 상단 **`+` → New Repository** +- Repository Name 입력(예: `myapp`) +- **README/.gitignore/License 는 체크하지 마세요**(빈 저장소여야 충돌이 없습니다) → Create +- 생성되면 주소가 나옵니다: `https://gitea.bokdev.in/<본인사번>/myapp.git` + +**2) 워크스페이스 터미널에서 올리기** +```bash +cd ~/projects/myapp +git init +git add . +git commit -m "first commit" +git branch -M main +git remote add origin https://gitea.bokdev.in/<본인사번>/myapp.git +git push -u origin main +``` +- 인증은 자동입니다(6번 Gitea 승인을 한 번 했다면). 처음이면 승인 화면이 한 번 뜹니다. +- 이후 수정한 뒤에는 `git add . && git commit -m "..." && git push` 만 반복하면 됩니다. + +> **순서는 상관없습니다.** 코드를 먼저 만들고(권장) 나중에 저장소를 만들어도 됩니다. `git push` 시점에 Gitea 저장소만 있으면 됩니다. +> **`.project-env` 는 git에 올라가지 않습니다**(DB·S3 자격증명 보호 — 정상). `git status` 에 안 보여도 맞습니다. 배포 환경값은 Kubero에 따로 넣습니다(11번). + +--- + +## 11. Kubero로 배포하고 확인하기 + +운영(실제 서비스) 배포는 **Kubero**가 담당합니다. 흐름은 "**Gitea에 push → Kubero가 buildpack으로 자동 빌드·배포**" 입니다. (먼저 10번으로 Gitea에 올려두세요.) +Kubero 는 **NodeJS buildpack**으로 빌드합니다 — build=`npm install`, run=`node index.js`(Node 22). **Dockerfile 은 쓰지 않습니다.** + +**1) 파이프라인 + 앱 만들기** +1. **Kubero**(https://kubero.bokdev.in) 로그인. +2. **Pipeline 생성** → 본인 repo 연결: + - **Git repository**: `https://gitea.bokdev.in/<본인사번>/myapp.git` (연결 시 deploy key·webhook 이 자동 생성됨) + - **Buildpack**: **NodeJS** 선택 + - phase 는 기본(production) 하나면 충분합니다. +3. 그 파이프라인 안에 **App 생성**: + - **App name**: 공개 주소가 되므로 **남과 겹치지 않게**(예: `사번-앱이름`). 앱 주소는 `https://.apps.bokdev.in` 이 됩니다. + - **Branch**: `main` + - **Port(Container port)**: `3000` + - 반드시 **`.apps.bokdev.in` 로 끝나는** 주소여야 외부에서 열립니다(라우팅이 이 대역에만 준비돼 있음). 미리보기(9번)의 `*.coder.bokdev.in` 과는 별개 대역입니다. + +**2) 환경변수 입력** (앱마다 **최초 1회만**, 이후 배포엔 유지됨) + - Kubero 앱의 **Settings → Environment Variables** 에 입력: `DATABASE_URL`, `S3_ENDPOINT`, `S3_REGION`, `S3_BUCKET`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `PORT` + - **가장 쉬운 방법: 워크스페이스 `.project-env` 파일을 열어 그 안의 값을 한 줄씩 그대로 복사**해 넣으세요(DB host 포함 동일, 변형 금지). + - 파일 보기: 터미널에서 `cat ~/projects/myapp/.project-env` (또는 VS Code 에서 그 파일 열기) + - `DATABASE_URL` 은 아래처럼 생겼습니다. **`%20`·`%3D` 까지 그대로** 넣으세요(libpq 인코딩이라 빼면 DB 연결이 깨집니다). DB 는 클러스터 내부 주소라 배포 앱(클러스터 안)에서도 그대로 도달합니다: + ``` + DATABASE_URL=postgresql://emp_<사번>:@postgresql-6ox-pgbouncer.everest.svc.cluster.local:5432/appdb?options=-c%20search_path%3Demp_<사번> + ``` + +**3) 배포(빌드) 실행 + 로그 보기** +1. 앱 화면에서 **Deploy**(또는 Redeploy) 클릭. +2. **Activity/Logs** 에서 빌드 로그가 흐릅니다. 단계는 대략: + - `Fetching ...`(Gitea 에서 코드 받기) → `Building ...`(`npm install`) → `Starting ...`(`node index.js`) → 앱이 **Running/Healthy** 가 되면 **배포 성공**. +3. **실패하면 로그**를 보세요. 위치로 원인이 갈립니다: + - `Fetching` 에서 실패 → Git repository URL/연결(deploy key) 문제. + - `Building`(`npm install`) 에서 실패 → 의존성 문제(`package.json` 확인). **9번에서 `npm install`·`npm start` 가 됐다면 여기서도 거의 됩니다** → 안 되면 push 한 코드가 최신인지(10번) 확인. + - 시작은 했는데 앱이 `Unhealthy`/재시작 반복 → 보통 ① Env 누락(아래 4번) 또는 ② 진입점 문제(루트에 `index.js` 가 있어야 함, run=`node index.js`). + +**4) 배포된 앱 주소 확인 + 테스트** +1. 앱 주소는 **`https://.apps.bokdev.in`** 입니다(배포가 끝나면 그 주소로 외부에서 열립니다). +2. 그 주소로 **9번과 똑같이** 엔드포인트를 확인합니다(브라우저 또는 PC 터미널 `curl`): + ```bash + curl https://.apps.bokdev.in/healthz # {"ok":true} ← 앱 기동 OK + curl https://.apps.bokdev.in/db # {"ok":true,"now":...} ← DB Env OK + curl https://.apps.bokdev.in/s3 # {"ok":true,"bucket":...} ← S3 Env OK + ``` + - `/healthz` 만 되고 `/db`·`/s3` 가 500 이면 → **Kubero Env 문제**(2번). `.project-env` 값과 정확히 같은지(특히 `DATABASE_URL` 의 `%20`/`%3D`) 다시 확인하고 재배포. +3. 이후 코드를 고쳐 **`git push` 하면 webhook 으로 자동 재빌드·배포**됩니다(Activity 에서 새 빌드 로그 확인 → 같은 주소로 재확인). + +> 자주 막히는 것: ① repository 연결(URL/deploy key) ② `DATABASE_URL` 인코딩(`%20`/`%3D`) 그대로인지 ③ Env 를 앱에 저장했는지 ④ App name 을 고유하게 정했는지(`.apps.bokdev.in`) ⑤ 루트에 `index.js` 가 있는지. (FAQ 참고) + +--- + +## 12. 워크스페이스 켜고 끄기 + +- **그만 쓸 때**: Coder 워크스페이스 화면 → **Stop** (자원 절약. 파일은 보존됩니다.) +- **다시 쓸 때**: **Start** (수십 초 내 기동) +- **업데이트 안내가 뜨면**(`Update` 버튼): 눌러서 최신 환경으로 갱신하세요. `~/projects` 파일은 유지됩니다. + +--- + +## 자주 묻는 것 +- **Q. 파일이 사라졌어요** → `~/projects` 밖에 저장했을 가능성. 작업물은 항상 `~/projects` 아래에. +- **Q. AI 도구가 인증 오류(401)** → `update-litellm-key` 를 다시 실행해 본인 키를 입력하세요 (5번). 키가 `sk-` 로 시작하는지 확인. +- **Q. AI 도구가 `Invalid model` 오류(400)** → 인증은 됐지만 모델명이 게이트웨이에 없는 경우. 5번 표의 기본 모델명을 쓰세요. +- **Q. `$DATABASE_URL` 이 비어있어요** → 프로젝트 폴더 안에서 실행했는지 확인하세요. `.project-env` 는 그 폴더에 `cd` 해야 적용됩니다(7번). +- **Q. sample 을 고쳤는데 git pull 이 안 돼요** → sample 은 참조용(읽기 전용)입니다. `new-project <이름>` 으로 복사한 폴더에서 작업하세요(7번). +- **Q. git push가 인증을 물어봐요** → Gitea Authorize를 한 번도 안 했을 때. 6번 참고. +- **Q. Kubero 가 repo 를 못 받아와요(Fetching 실패)** → Pipeline 의 Git repository URL 을 전체 주소(`https://gitea.bokdev.in/...git`)로 넣었는지, repo 연결(connect) 시 deploy key·webhook 이 생성됐는지 확인하세요(11번). +- **Q. 배포한 앱에서 DB 연결이 안 돼요(`/db` 500)** → ① Kubero Env 의 `DATABASE_URL` host 가 `postgresql-6ox-pgbouncer.everest.svc.cluster.local` 인지, ② 끝의 `?options=-c%20search_path%3D...` 가 `%20`/`%3D` 까지 그대로인지 확인하세요. 직접 타이핑하다 인코딩을 빼면 깨집니다 — `.project-env` 값을 그대로 복사하는 게 가장 안전합니다(11번). +- **Q. `npm run dev` 가 `Cannot find package 'express'` 로 실패해요** → 처음 1회 `npm install` 을 안 한 경우입니다. 프로젝트 폴더에서 `npm install` 후 다시 실행하세요(7번). +- **Q. Kubero 빌드(`npm install`)가 실패해요** → 의존성 문제입니다. 먼저 워크스페이스에서 `npm install` 과 `npm start` 가 되는지 확인하고, `package.json` 에 빠진 패키지가 없는지 보세요(9번). +- **Q. Kubero 빌드는 성공했는데 앱이 안 떠요(Unhealthy/재시작 반복)** → 보통 ① Env 누락 또는 ② 진입점 문제입니다. Kubero 는 `node index.js` 로 실행하므로 **루트에 `index.js` 가 있어야** 합니다(sample 에 이미 있음). `/healthz` 만 확인해 보고, DB/S3 Env(2번)를 `.project-env` 그대로 넣었는지 확인 후 재배포하세요(11번). +- **Q. Kubero 가 옛날 코드로 빌드돼요** → `git push` 가 됐는지(10번), Kubero 앱의 Branch 가 `main` 인지 확인하세요. push 후 Deploy(또는 webhook 자동배포)를 다시 도세요. +- **Q. 배포된 앱 주소가 안 열려요(접속 안 됨 / 인증서 경고)** → App name 을 **`.apps.bokdev.in` 으로 끝나는** 주소가 되게 정했는지 확인하세요(11번). 그 대역만 외부 접속이 준비돼 있습니다. 다른 대역(예: `*.bokdev.in` 루트)은 안 열립니다. App name 이 남과 겹쳐도 충돌하니 `사번-앱이름` 처럼 고유하게 정하세요. +- **Q. 빌드/실행은 됐는데 브라우저로 안 열려요** → 워크스페이스 앱은 `localhost` 가 PC 에서 안 열립니다. VS Code **PORTS → Forward 3000 → Open in Browser**(`https://<자동생성>.coder.bokdev.in`)로 여세요(9번). 배포된 앱은 Kubero 가 준 공개 주소(`https://.apps.bokdev.in`)로 엽니다(11번). +- **Q. DB가 비어있어요** → 정상입니다. 본인 전용 빈 스키마가 제공됩니다. 테이블은 직접 만들면 됩니다. +- **Q. K8s(쿠버네티스)는 어떻게 봐요?** → 직원은 K8s에 직접 접근하지 않습니다. DB/스토리지/배포는 위 도구들로 충분합니다. + +--- + +문의: 인프라 담당자 diff --git a/README.md b/README.md new file mode 100644 index 0000000..4fd5ab7 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# sample — Node 앱 샘플 (로컬 node 실행 + Kubero 배포) + +사내 워크스페이스용 Node(ESM) 샘플. DB·MinIO 연결, 로컬 개발, Kubero 자동배포가 미리 잡혀 있다. +이 폴더(`~/projects/sample`)는 **읽기 전용 참조**다. 직접 고치지 말고, 새 작업은 아래처럼 복사해서 시작한다. + +## 새 프로젝트 시작 +```bash +new-project myapp # sample 을 ~/projects/myapp 으로 복사 + .project-env(DB/S3) 자동생성 +cd ~/projects/myapp # 들어오면 셸이 .project-env 를 자동 export ($DATABASE_URL 등 사용 가능) +npm install +npm run dev # http://localhost:3000 +``` +- `GET /healthz` — 헬스체크 +- `GET /db` — Postgres 연결 확인 (직원 전용 schema) +- `GET /s3` — MinIO 버킷 연결 확인 + +## 배포와 동일한 방식으로 로컬 검증 (컨테이너 불필요) +```bash +npm start # = node index.js (Kubero 의 run 커맨드와 동일) +``` +Kubero 는 buildpack 으로 빌드하므로 **로컬에서 컨테이너 이미지를 빌드할 필요가 없다.** +`npm start` 로 떠서 엔드포인트가 정상이면 Kubero 배포도 거의 그대로 된다. + +## 배포 (Kubero) +Gitea에 push → Kubero(https://kubero.bokdev.in)가 **NodeJS buildpack**으로 자동 빌드/배포 +(build=`npm install`, run=`node index.js`). Kubero 앱의 Env에 `DATABASE_URL`, `S3_*`, `PORT` 입력. +앱 주소는 `https://<앱이름>.apps.bokdev.in`. + +## 연결정보 +- **DB**: 프로젝트 `.project-env` 의 `DATABASE_URL` (본인 schema 격리, 폴더 cd 시 자동 export). + 클러스터 내부 OpenEverest(`postgresql-6ox`)의 `appdb`. +- **MinIO(S3)**: endpoint `https://minio.bokdev.in`, bucket `coolify-user-data`, 키는 `.project-env`에 있음 +- **AI**: `~/.env` 의 `LITELLM_KEY`(`update-litellm-key` 명령으로 입력) → Claude Code/Codex/Gemini 공용 + +자세한 작업 규약은 `CLAUDE.md` 참고. diff --git a/catalog-info.yaml b/catalog-info.yaml new file mode 100644 index 0000000..fee3cc1 --- /dev/null +++ b/catalog-info.yaml @@ -0,0 +1,23 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: calc + description: 계산기 + annotations: + # Gitea 소스 위치 — Backstage 가 repo 를 연결. + backstage.io/source-location: url:https://gitea.bokdev.in/playground/calc + # Kubero 배포 앱 — 미리보기/배포 URL. + bokdev.in/kubero-app: calc + bokdev.in/app-url: https://calc.playground.bokdev.in + tags: + - ai-dev + - nodejs +spec: + type: service + lifecycle: experimental + owner: group:default/playground + system: ai-dev-platform + # 이 앱이 사용하는 공용 리소스 — 개발은 .project-env, 배포는 kubero env 로 자동 주입. + dependsOn: + - resource:default/appdb + - resource:default/minio diff --git a/index.js b/index.js new file mode 100644 index 0000000..3496789 --- /dev/null +++ b/index.js @@ -0,0 +1,4 @@ +// Kubero NodeJS buildpack 의 run 커맨드(`node index.js`) 진입점. +// 실제 서버 구현은 src/server.js 에 있으며, 여기서 그대로 로드한다. +// (로컬 개발은 `npm run dev` = node --watch src/server.js 로도 동일하게 동작) +import "./src/server.js"; diff --git a/package.json b/package.json new file mode 100644 index 0000000..83d89a2 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "sample", + "version": "0.1.0", + "description": "사내 Node 샘플 — 로컬 node 실행 + Kubero(buildpack) 배포", + "type": "module", + "main": "index.js", + "scripts": { + "dev": "node --watch src/server.js", + "start": "node index.js", + "db:check": "node scripts/check-db.js", + "minio:check": "node scripts/check-minio.js" + }, + "engines": { + "node": ">=22" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.700.0", + "express": "^4.21.2", + "pg": "^8.13.1" + } +} diff --git a/scripts/check-db.js b/scripts/check-db.js new file mode 100644 index 0000000..1ad376d --- /dev/null +++ b/scripts/check-db.js @@ -0,0 +1,11 @@ +// DB 연결 빠른 확인: `npm run db:check` +import { pingDb, pool } from "../src/db.js"; + +try { + console.log("DB OK:", await pingDb()); +} catch (e) { + console.error("DB FAIL:", e.message); + process.exitCode = 1; +} finally { + await pool.end(); +} diff --git a/scripts/check-minio.js b/scripts/check-minio.js new file mode 100644 index 0000000..d809fb4 --- /dev/null +++ b/scripts/check-minio.js @@ -0,0 +1,10 @@ +// MinIO(S3) 연결 빠른 확인: `npm run minio:check` +// 먼저 ~/.config 또는 .env 에서 S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY 를 채워야 함. +import { pingS3 } from "../src/s3.js"; + +try { + console.log("S3 OK:", await pingS3()); +} catch (e) { + console.error("S3 FAIL:", e.message); + process.exitCode = 1; +} diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..752978d --- /dev/null +++ b/src/config.js @@ -0,0 +1,26 @@ +// 환경설정 — 모든 외부 연결정보는 환경변수에서만 읽는다(코드에 비밀값 금지). +// +// 값의 출처: 프로젝트 폴더의 .project-env (DB 본인 schema + S3). 프로젝트 폴더에 cd 하면 +// 로그인 셸이 자동 export → 여기 process.env 로 그대로 들어온다(별도 dotenv 불필요). +// MinIO(S3): 사내 오브젝트 스토리지. endpoint는 API 호스트(minio.bokdev.in), +// 콘솔(minioc.bokdev.in)이 아님에 주의. +// +// Kubero 배포 시에는 Kubero 앱의 Environment Variables 화면에서 같은 키들을 채운다. + +export const config = { + port: parseInt(process.env.PORT || "3000", 10), + + // Postgres — .project-env 의 DATABASE_URL. Kubero에선 본인 DB 접속정보를 넣을 것. + databaseUrl: process.env.DATABASE_URL || "", + + // MinIO / S3 호환 + s3: { + endpoint: process.env.S3_ENDPOINT || "https://minio.bokdev.in", + region: process.env.S3_REGION || "us-east-1", + bucket: process.env.S3_BUCKET || "coolify-user-data", + accessKeyId: process.env.S3_ACCESS_KEY_ID || "", + secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "", + // MinIO는 path-style 필요 + forcePathStyle: true, + }, +}; diff --git a/src/db.js b/src/db.js new file mode 100644 index 0000000..0c5aec4 --- /dev/null +++ b/src/db.js @@ -0,0 +1,15 @@ +// Postgres 연결 풀. DATABASE_URL 한 줄로 접속(직원 전용 schema가 search_path에 고정됨). +import pg from "pg"; +import { config } from "./config.js"; + +export const pool = new pg.Pool({ + connectionString: config.databaseUrl, + max: 5, +}); + +export async function pingDb() { + const { rows } = await pool.query( + "select current_user as user, current_schema as schema, now() as now" + ); + return rows[0]; +} diff --git a/src/s3.js b/src/s3.js new file mode 100644 index 0000000..6d99737 --- /dev/null +++ b/src/s3.js @@ -0,0 +1,24 @@ +// MinIO(S3 호환) 클라이언트. 자격증명은 env에서만 읽는다. +import { S3Client, ListObjectsV2Command, HeadBucketCommand } from "@aws-sdk/client-s3"; +import { config } from "./config.js"; + +export const s3 = new S3Client({ + endpoint: config.s3.endpoint, + region: config.s3.region, + forcePathStyle: config.s3.forcePathStyle, + credentials: { + accessKeyId: config.s3.accessKeyId, + secretAccessKey: config.s3.secretAccessKey, + }, +}); + +export async function pingS3() { + await s3.send(new HeadBucketCommand({ Bucket: config.s3.bucket })); + const out = await s3.send( + new ListObjectsV2Command({ Bucket: config.s3.bucket, MaxKeys: 5 }) + ); + return { + bucket: config.s3.bucket, + sampleKeys: (out.Contents || []).map((o) => o.Key), + }; +} diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..e94230a --- /dev/null +++ b/src/server.js @@ -0,0 +1,33 @@ +// 최소 Express 서버 — /healthz, /db, /s3 로 연결 확인. +import express from "express"; +import { config } from "./config.js"; +import { pingDb } from "./db.js"; +import { pingS3 } from "./s3.js"; + +const app = express(); + +app.get("/healthz", (_req, res) => res.json({ ok: true })); + +app.get("/db", async (_req, res) => { + try { + res.json({ ok: true, ...(await pingDb()) }); + } catch (e) { + res.status(500).json({ ok: false, error: String(e.message || e) }); + } +}); + +app.get("/s3", async (_req, res) => { + try { + res.json({ ok: true, ...(await pingS3()) }); + } catch (e) { + res.status(500).json({ ok: false, error: String(e.message || e) }); + } +}); + +app.get("/", (_req, res) => + res.json({ name: "sample", endpoints: ["/healthz", "/db", "/s3"] }) +); + +app.listen(config.port, "0.0.0.0", () => { + console.log(`sample listening on :${config.port}`); +});