# 설정 파일과 초기 설정 페이지 — v1.5.7 (v1.5.11 개정) AI Webmaker v1.5.11 · 2026-10-14 · 대상: 운영자·설치 담당자 > v1.5.11: 설정 페이지 기본 포트 **8795**(모든 인터페이스, 겹치면 다음 포트 자동), 서비스의 `/setup` 도 접속 토큰으로 열림, 맨 위에 **설치 점검표**·**관리자 계정**·**AI 엔진 인증** 카드. 처음 설치 절차는 [FIRST-INSTALL.md](FIRST-INSTALL.md). ## 1. 무엇이 바뀌었나 (요구 1·2·3) | | 1.4.18 까지 | 1.5.7 부터 | |---|---|---| | 서비스 공개 주소, 소스 저장소(Gitea) 주소, AI 실행 파일 경로 같은 환경 정보 | systemd 유닛의 `Environment=` 줄 (`PUBLIC_BASE_URL`, `SITES_GIT_REMOTE` …) — 바꾸려면 서버에서 유닛을 편집하고 `daemon-reload` | **설정 파일 `data/config.json`** (권한 0600) — 웹 초기 설정 페이지·관리자 콘솔·명령줄에서 바꾼다 | | 처음 설치할 때 모르는 값 | 설치 스크립트가 빈 값·임시 값으로 유닛을 써 두고 나중에 사람이 고침 | 설치가 끝나면 **초기 설정 페이지 주소(별도 포트, 기본 8795 — 1.5.7 은 3081)** 를 알려 주고 거기서 입력. 1.5.11: 설치 점검표가 끝나기 전에는 서비스 첫 화면도 `/setup` 으로 넘어간다 | | 기존 운영 서버 | — | 1.5.7 로 올린 첫 기동 때 유닛의 환경 변수를 **그대로 설정 파일에 옮겨 적는다**. 운영 값은 바뀌지 않는다 | | 비밀값(토큰·비밀번호) | 일부는 유닛, 일부는 보관소 | 모두 암호화 보관소(`data/credentials.json`, lib/vault.js). 설정 파일에는 쓰지 않는다 | 유닛에는 배포 때 정해지는 값만 남는다: `PORT`, `HOST`, `SITES_ROOT`, `DATA_ROOT`(선택), `PATH`, `HOME`, `NODE_ENV`. 이 값들은 설정 파일에서 바꿀 수 없다(읽기 전용으로 보임) — 포트·경로는 웹서버(nginx/Caddy) 프록시 설정과 함께 바뀌어야 하기 때문이다. ## 2. 설정 파일 (`lib/config.js`) - 경로: 환경 변수 `WEBMAKER_CONFIG` > `/config.json` (기본 앱 폴더의 `data/config.json`). - 형식: `{ "version": 1, "updatedAt", "updatedBy": "setup|cli|migrate:env", "values": { "<그룹>.<항목>": 값 } }` - 우선순위: **설정 파일 > 환경 변수(유닛) > 기본값**. 배포 때 정해지는 값(`runtime.*`)만 환경 변수가 우선. - 서비스는 기동 맨 처음 `config.apply()` 로 파일 값을 `process.env` 에 넣는다. 그래서 코드의 다른 부분(`process.env.PUBLIC_BASE_URL` 등)은 그대로다. 파일이 바뀌면(설정 페이지·CLI) 다음 호출부터 새 값을 읽지만, 기동 때 한 번만 읽는 모듈의 값(아래 표 "재시작")은 서비스를 재시작해야 적용된다. - 파일이 없으면 지금 환경 변수로 파일을 만든다(`migrateFromEnv`). 1.5.7 로 올린 기존 서버에서 이 일이 한 번 일어난다 (`[config] 설정 파일을 환경 변수에서 만들었습니다` 로그). ### 항목표 | 그룹 | 항목 (`key`) | 환경 변수 | 뜻 | 기본값 | 재시작 | |---|---|---|---|---|---| | 서비스 | `service.publicBaseUrl` | `PUBLIC_BASE_URL` | 사용자가 접속하는 주소. 생성 사이트의 위젯·통계·웹메일 안내 파일이 이 주소를 부른다 (**필수**) | `http://192.168.20.66` | ○ | | | `service.formBaseUrl` | `FORM_BASE_URL` | 폼 접수 서버 주소 (비우면 공개 주소) | | ○ | | | `service.adminUsers` | `ADMIN_USERS` | 관리자로 취급할 아이디(쉼표) | | ○ | | | `service.systemdUnit` | `WEBMAKER_SERVICE` | 설정 페이지 "서비스 재시작" 이 재시작할 유닛 (설치 스크립트가 넣음) | | | | | `service.setupPort` / `setupBind` | `WEBMAKER_SETUP_PORT` / `_BIND` | 초기 설정 페이지 포트·바인드 (`lan` = 첫 사설 IPv4, `0.0.0.0` = 전체, 또는 특정 IP) | 3081 / lan | (setup 서비스) | | 실행 환경 (읽기 전용) | `runtime.port` `host` `sitesRoot` `dataRoot` `statsLog` | `PORT` `HOST` `SITES_ROOT` `DATA_ROOT` `STATS_LOG` | 배포 때 정해지는 값 | 3080 / 127.0.0.1 / /var/www/sites / … | — | | AI 엔진 | `ai.claudeBin` `codexBin` | `CLAUDE_BIN` `CODEX_BIN` | 실행 파일 (비우면 자동 탐색) | | ○ | | | `ai.claudeModel` `claudeMaxTurns` `openaiMaxTurns` `allowInsecureTls` | `CLAUDE_MODEL` `CLAUDE_MAX_TURNS` `OPENAI_ENGINE_MAX_TURNS` `CLAUDE_ALLOW_INSECURE_TLS` | 설치 기본 모델·턴 상한·TLS 완화 | claude-opus-5-5 / 80 / 60 / 켬 | ○ | | | `ai.codexUnsafe` | `CODEX_UNSAFE` | v1.6.31: 샌드박스를 만들 수 없을 때 기본은 작업 거부(fail-closed). true 면 샌드박스 없이 실행(위험) | false | ○ | | | `ai.maxJobMinutes` `maxConcurrentJobs` | `WM_MAX_JOB_MINUTES` `WM_MAX_CONCURRENT_JOBS` | v1.6.31: 한 AI 작업 최대 시간(분, 15분 무출력도 중단) · 서버 전체 동시 AI 작업 수(넘으면 429) | 45 / 4 | | | | `ai.allowInsecureTls` | `CLAUDE_ALLOW_INSECURE_TLS` | v1.6.31 부터 **기본 false**(claude 자식의 TLS 검증 유지). 사내 프록시·자체 서명 인증서 환경에서만 true | false | ○ | | 서비스(보안) | `service.trustedProxies` | `WM_TRUSTED_PROXIES` | v1.6.31: 이 IP 에서 온 연결의 X-Forwarded-For 만 클라이언트 IP 로 믿음(속도 제한·기록). 비우면 루프백만 | | | | | `service.internalAllow` | `WM_INTERNAL_ALLOW` | v1.6.31: 사용자가 넣는 주소·제공자 URL 이 사설/내부 주소여도 허용할 host:port 목록(쉼표) — 사내 Gitea·Arimail·AI 서버. 그 밖의 내부 주소는 차단 | | | | | `service.siteOrigin` | `WM_SITE_ORIGIN` | v1.6.31: 생성 사이트 전용 origin(예약 — 사용자 결정 뒤 연결, 비우면 같은 origin) | | ○ | | | `ai.codexSandbox` | `CODEX_SANDBOX` | codex 샌드박스: `auto`(커널이 비특권 user namespace 를 허용하면 workspace-write, 아니면 없이 — Ubuntu 24.04 기본 `kernel.apparmor_restrict_unprivileged_userns=1` 에서는 샌드박스 쓰기가 모두 실패) / `workspace-write` / `none` | auto | (다음 작업부터) | | 소스 보관 (Gitea) | `source.provider` | `SOURCE_PROVIDER` | 연동 모듈 (`gitea` / `none`) | gitea | | | | `source.remote` `web` `prefix` | `SITES_GIT_REMOTE` `SITES_GIT_WEB` `SITES_GIT_PREFIX` | 푸시 주소·웹 주소·브랜치 접두어 | / / sites | (다음 커밋부터) | | | `source.apiUrl` `user` `repo` | `GITEA_API_URL` `GITEA_USER` `GITEA_REPO` | API 주소·계정·저장소 (선택) | / webmaker / webmaker/sites | | | | `source.token` | — (보관소 `gitea`) | API 토큰 (install-gitea.sh 가 만듦) | | | | 메일 연동 (Arimail) | `mail.provider` | `MAIL_PROVIDER` | 연동 모듈 (`none` / `arimail`) | none | | | | `mail.url` `apiUrl` `domain` `adminLogin` | `ARIMAIL_URL` `ARIMAIL_API_URL` `ARIMAIL_DOMAIN` `ARIMAIL_ADMIN_LOGIN` | 공개 주소·내부 API 주소·메일 도메인·관리자 ID | / / / admin | | | | `mail.adminPassword` | — (보관소 `arimail`) | 관리자 비밀번호 | | | | SSL | `ssl.acmeDir` `acmeWaitMin` | `WM_ACME_DIR` `WM_ACME_WAIT_MIN` | 와일드카드 인증서 작업 폴더·수동 DNS 대기(분) | /var/tmp/webmaker-acme / 40 | ○ | | 한도 | `limits.copyMaxPages` `copyMaxAssets` | `COPY_MAX_PAGES` `COPY_MAX_ASSETS` | 사이트 복사 상한 | 300 / 5000 | ○ | | 외부 API (고급) | `endpoints.*` | `RESEND_API_BASE` `OPENPROVIDER_API_BASE` `GOOGLE_API_BASE` `GOOGLE_TOKEN_URL` `INDEXNOW_ENDPOINT` | 시험용 대체 주소 | 실제 서비스 주소 | | 관리자 콘솔 › 설정의 값(모델 등급·환율·마진·메일 발송 키·도메인 요율·DNS 공급자·등록 업체(v1.5.36) …)은 그대로 `data/settings.json` 이다. 설정 파일은 "서비스가 돌아가는 환경" 만 다룬다. ## 3. 초기 설정 페이지 (`setup-server.js`, 요구 2) - 별도 systemd 서비스 **webmaker-setup** 이 설정 파일의 `service.setupPort`(기본 **8795**, v1.5.11; 겹치면 8796… 을 차례로 잡고 설정 파일에 기록)·`setupBind`(기본 **`0.0.0.0`** — 공인 IP 만 있는 서버에서도 `http://:8795/` 로 접속; 사설망만 열려면 `lan` = 첫 사설 IPv4, 도커 브리지 등 가상 인터페이스 제외) 로 전용 페이지를 연다. 설치 스크립트(`deploy/install.sh`, `deploy/remote-install.sh`, `deploy/remote-update.sh`)가 설치·기동하고, 끝나면 접속 주소를 알려 준다: `http://<서버 IP>:8795/?token=<접속 토큰>` (`node scripts/config.js setup-url` 로 언제든 다시 본다) - 인증: 접속 토큰(`data/setup.token`, 0600 — 처음 기동 때 생성) 또는 웹메이커 관리자 계정 로그인. 세션은 12시간 쿠키. 로그인 시도는 IP 당 15분 10회. - 같은 화면이 서비스 안에도 있다: `https://<서비스>/setup` (v1.5.11: 로그인 없이 열리고 화면 안에서 접속 토큰 또는 관리자 계정으로 들어온다 — 인증 코드 `lib/setupauth.js` 를 두 서버가 공용) 과 관리자 콘솔 › **시스템 설정** 탭. 화면 코드는 `public/setup.js` 하나, API 는 `lib/setupapi.js` 하나다 (`/api/setup/*` ↔ `/api/admin/config/*`). - 화면 (v1.5.11 맨 위): **설치 점검표**(관리자 비밀번호·AI 엔진 인증·서비스 공개 주소 — `lib/setupstate.js`; 필수 항목이 남아 있으면 서비스 첫 화면 `/`·`/login` 이 `/setup` 으로 302, 끄기 `service.setupRedirect=off`, 우회 `/login?direct=1`), **관리자 계정**(목록·마지막 로그인, 기본 관리자 `admin/admin` 만들기, 비밀번호 재설정, 새 관리자), **AI 엔진 인증**(Claude 서버 로그인/OAuth 토큰/API 키 저장·확인, OpenAI API 키/codex 로그인). 이어서 상태 카드(웹메이커 서비스·설정 페이지 서비스·Gitea·Arimail·Claude(실행 파일+인증)·Codex·Resend, "연결 확인"), 그룹별 설정 카드(출처 배지: 설정 파일/환경 변수/기본값, 재시작 표시, 비밀값은 힌트만), "이 묶음 저장"·"바뀐 값 모두 저장", **서비스 재시작**(실행 중 AI 작업이 끝날 때까지 기다린 뒤 `sudo systemctl restart`), "환경 변수에서 옮겨 적기", 연동 서비스 설치·등록 명령 안내. - 재시작 권한: 설치 스크립트가 `/etc/sudoers.d/webmaker-setup` 에 `systemctl restart <유닛>` 만 허용하는 줄을 둔다. 없으면 화면이 수동 명령을 알려 준다. - 방화벽: v1.5.11 부터 설치·갱신 스크립트가 ufw 에 설정 페이지 포트(8795)를 허용하고 1.5.7 의 3081 사설망 규칙은 지운다(FQDN 을 모르는 서버에서도 `http://:8795/` 가 기본 접속 위치). 접속은 토큰·관리자 로그인·시도 제한(IP 당 15분 10회)으로 보호되지만 관리 화면이므로, 점검이 끝나면 `sudo ufw delete allow 8795/tcp` 로 닫거나 `service.setupBind=lan` 으로 바꿔도 된다(서비스 안 `/setup`·관리자 콘솔 탭은 그대로). - 새 설정 항목(v1.5.11): `service.setupRedirect`(auto/off), `service.allowSignup`(기본 켬 — 끄면 로그인 화면의 회원가입 탭이 사라지고 API 는 403 "회원가입이 닫혀 있습니다"). - API(v1.5.11, `lib/setupapi.js`): `GET /ready` 점검표, `POST /admin/bootstrap|create|password`, `PUT /ai/claude|openai`. 명령줄: `node scripts/admin.js bootstrap`. ## 4. 명령줄 (`scripts/config.js`) ```bash node scripts/config.js list # 전체 값과 출처 node scripts/config.js get service.publicBaseUrl node scripts/config.js set service.publicBaseUrl https://webmaker.example.com # 여러 쌍 가능 node scripts/config.js set mail.adminPassword '비밀번호' # 비밀값 → 보관소 node scripts/config.js unset source.apiUrl node scripts/config.js import-env --unit /etc/systemd/system/webmaker.service # 유닛 → 파일 (없는 항목만) node scripts/config.js check # 필수 값 점검 (없으면 종료 코드 2) node scripts/config.js setup-url # 초기 설정 페이지 주소(토큰 포함) ``` 환경 변수 `DATA_ROOT`·`WEBMAKER_CONFIG` 를 서비스와 같게 준다(기본 앱 폴더의 `data/`). ## 5. 기존 서버 올리기 (요구 3) 1. `deploy/remote-update.sh <앱 폴더> <유닛>` — 코드 갱신·재시작 뒤 (a) 유닛의 환경 변수를 설정 파일로 옮기고 (b) webmaker-setup 서비스를 설치·기동하고 (c) `deploy/install-services.sh --check` 로 이미 설치된 Gitea·Arimail 을 등록한다. 서비스 기동 로그에 `[config] 설정 파일을 환경 변수에서 만들었습니다` 가 한 번 찍힌다. 2. 이후 유닛의 `Environment=PUBLIC_BASE_URL=…`, `SITES_GIT_*` 줄은 **참고용**이다 — 설정 파일이 우선한다. `install-gitea.sh` 를 다시 실행하면 `SITES_GIT_*` 줄을 유닛에서 지운다. 3. 다른 서버로 DB 를 복사할 때(`remote-install.sh`) 설정 파일과 토큰은 복사하지 않는다(서버마다 다른 값). ## 6. 자주 나는 메시지 | 메시지 | 원인 | 해결 | |---|---|---| | `[config] 설정 파일: …` 로그만 있고 값이 바뀌지 않음 | 바꾼 항목이 "재시작 후 적용" 항목 | 설정 페이지의 "서비스 재시작" 또는 `sudo systemctl restart <유닛>` | | 설정 페이지 "서비스 재시작" → `재시작 실패: sudo: a password is required` | sudoers 규칙 없음 | `deploy/install.sh`/`remote-update.sh` 다시 실행(규칙 생성) 또는 서버에서 직접 재시작 | | `접속 토큰이 맞지 않습니다` | 토큰이 바뀜(data/setup.token 삭제·재생성) | `node scripts/config.js setup-url` 로 새 주소, 또는 관리자 계정으로 로그인 | | `<항목>은(는) 배포 때 정해지는 값이라 여기서 바꿀 수 없습니다` | `runtime.*` 항목 저장 시도 | 유닛과 웹서버 설정을 함께 고친 뒤 재시작 | | `<항목>은(는) http:// 또는 https:// 로 시작하는 주소여야 합니다` 등 | 형식 오류 | 안내대로 수정. 저장은 전체가 검증을 통과했을 때만 된다 | ## 7. 백업 · 복구 (v1.6.31, 요구 29) - `deploy/remote-update.sh`·`deploy/install.sh` 가 재시작 전에 `node scripts/backup-data.js --tag update` 를 실행해 DATA_ROOT 전체를 `DATA_ROOT/backups/-update.tar.gz`(0600) 로 묶는다(최근 10개 보관, `--keep N`, `--list`). `sites/` 의 git 저장소와 통계 로그는 크기 때문에 제외하지 않고 그대로 들어간다 — 용량이 크면 `--keep` 을 줄인다. - 복구: 서비스를 멈추고(`sudo systemctl stop <유닛>`) `tar -xzf DATA_ROOT/backups/<파일> -C DATA_ROOT` 뒤 재시작. 특정 파일만 되돌릴 때는 `DATA_ROOT/<파일>.bak`(직전 저장본) 을 먼저 본다. 손상 파일 목록은 서버 기동 로그 `[jsonstore]` 와 관리자 콘솔 › 운영 복구에 보인다. - 두 운영 서버는 DATA_ROOT 가 다르므로(bear: 앱 폴더 `data/`, hostinger: `/data/webmaker`) 백업도 각자 서버에만 남는다 — 서로 복사하지 않는다.