Ariweb Suite 0.1.1 기반: 공통 라이브러리(db·http·세션·작업 엔진·할당/예약·알림·감사·보관소), 포털 뼈대, Webmaker/AriMail 도입 소스와 manifest
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
9ca4cee341
commit
c7aea8d419
@@ -0,0 +1,99 @@
|
||||
# 외부 서비스 연동 모듈 — Arimail(메일)·Gitea(소스 보관) — v1.5.7
|
||||
|
||||
AI Webmaker v1.5.7 · 2026-10-14 · 대상: 운영자·개발자
|
||||
|
||||
## 1. 연동 모듈 (요구 5·6)
|
||||
|
||||
웹메이커가 다른 제품으로 바꿔 쓸 수 있는 외부 서비스는 종류(kind)마다 **연동 모듈** 하나를 고른다. 모듈은 `lib/connectors/` 에 있고 등록소 `lib/connectors/index.js` 가 설정 파일(`source.provider`, `mail.provider`)에 따라 고른다.
|
||||
|
||||
| 종류 | 모듈 | 하는 일 | 쓰는 쪽 |
|
||||
|---|---|---|---|
|
||||
| `source` 소스 보관 | `gitea.js` | 푸시 주소·웹 주소·브랜치 접두어, git/ssh 오류 해석, 연결 확인(`git ls-remote` + API), API 토큰이 있으면 저장소 만들기·공개키 등록 | `lib/gitstore.js` (git 명령은 그대로, 접속 정보만 모듈에서) |
|
||||
| `mail` 메일 서버 | `arimail.js` | 관리자 API 로그인, 계정 목록·생성·비밀번호 재설정·삭제, 상태(/api/health), 웹메일 주소·메일 프로그램 설정값 | `lib/mailsys.js` (받는 메일 백엔드: 사이트 관리자 계정의 메일 사용 켜기/끄기, 웹메일 연결) |
|
||||
|
||||
모듈의 공통 모양: `id, label, kind, settings(), configured(), status()`. 다른 제품(GitLab, 다른 메일 서버)으로 바꾸려면 같은 모양의 모듈을 만들고 `index.js` 의 `MODULES` 와 `lib/config.js` 의 provider 선택지에 넣는다. 접속 정보(주소·계정)는 설정 파일, 비밀값은 보관소(`lib/vault.js` 종류 `gitea`, `arimail`)에 둔다.
|
||||
|
||||
## 2. Arimail 이란
|
||||
|
||||
OZ-Group 의 메일 게이트웨이 **mail-gateway** 가 0.9.x 에서 **AriMail(Arimail)** 로 이름이 바뀐다. Resend 로 받은 메일을 보관하고 웹메일(`/`), 관리자 페이지(`/admin`), IMAP(993/143)·POP3(995/110)·SMTP 제출(465/587) 서버를 연다. 한 설치는 한 메일 도메인(환경 파일 `MAILBOX=admin@도메인`)의 계정 `로그인ID@도메인` 을 다룬다. 설치·운영 문서는 그 저장소의 `docs/SETUP.md`.
|
||||
|
||||
웹메이커와의 관계
|
||||
- **보내기**는 지금처럼 웹메이커의 Resend 키(관리자 콘솔 › 설정 › 메일 발송)로 한다. Arimail 도 같은 Resend 로 보내므로 발신 인증(DKIM) 레코드는 공통이다.
|
||||
- **받기·웹메일·메일 계정**은 Arimail 이 맡는다. 사이트 관리자 계정의 "메일 사용" 을 켜면 웹메이커가 Arimail 관리자 API 로 같은 로그인 ID 의 계정을 만들고, 끄면 지운다. 웹메이커는 사이트 관리자 비밀번호의 원문을 갖고 있지 않으므로(scrypt 해시만) **켤 때 메일 비밀번호를 정한다** — 웹메일·IMAP·POP·SMTP 가 그 비밀번호를 쓴다.
|
||||
- 사이트의 `/webmail` 진입 파일 → 웹메이커 `/webmail/<사이트ID>/` → 메일 서버가 연결된 사이트면 Arimail 웹메일로 302. 사이트 관리자 페이지 › 메일의 "웹메일 열기" 도 그 주소.
|
||||
- **도메인 일치 조건**: 사이트의 메일 도메인(관리자 콘솔 › 메일 시스템에서 켠 도메인)이 `mail.domain` 과 같을 때만 받는 메일이 연결된다. 다른 도메인 사이트는 "메일 서버는 X 도메인만 다룬다" 안내와 함께 보내기만 가능하다. 도메인마다 Arimail 을 하나씩 설치한다(여러 설치를 한 설정에 담는 것은 다음 단계).
|
||||
|
||||
## 3. 설치·등록 (`deploy/install-arimail.sh`, 요구 4)
|
||||
|
||||
서비스 서버에서 웹메이커 실행 계정으로(비밀번호 없는 sudo 필요):
|
||||
|
||||
```bash
|
||||
# 새로 설치 + 등록 (소스: ~/work/mail-gateway 체크아웃을 번들로 묶거나 --repo 로 복제)
|
||||
deploy/install-arimail.sh --host mail.example.com --mailbox admin@example.com [--source ~/work/mail-gateway | --bundle <번들> | --repo <URL>]
|
||||
[--user arimail] [--app-name arimail] [--port 8790] [--node <node 22.13+>] [--from-name "이름"] [--limit-accounts 50 --limit-mailbox 500M …]
|
||||
# 이미 설치된(또는 다른 서버의) Arimail 등록만
|
||||
deploy/install-arimail.sh --register-only --url https://mail.example.com --domain example.com --admin-login webmaker-admin --admin-password '…' [--api-url http://127.0.0.1:8790]
|
||||
# 이 서버에 설치된 Arimail 을 찾아 등록 (install-services.sh --check / remote-update.sh 가 부른다)
|
||||
deploy/install-arimail.sh --check
|
||||
```
|
||||
|
||||
하는 일: ① 소스 확보(git bundle — 서버에 저장소 자격 증명을 두지 않음) ② 실행 계정(시스템 계정)과 Node 22.13+(시스템 node 가 맞으면 그것, 아니면 `/opt/<app>-node`) ③ Arimail 표준 절차 `deploy/new-site.sh`(standalone) → `setup.sh`(root: 디렉터리·`/etc/<app>/gateway.env`·systemd·sudoers·nginx vhost·certbot 훅) → `deploy.sh`(실행 계정: 테스트·릴리스·DB 마이그레이션·재시작). nginx 가 없는 서버(Caddy 등)는 nginx 를 뺀 같은 준비를 하고 프록시 설정 예시를 알려 준다 ④ `gateway.env` 의 `RESEND_API_KEY` 가 비어 있으면 웹메이커 보관소의 메일 발송 키를 넣는다 ⑤ 웹메이커 전용 Arimail 관리자 계정 `webmaker-admin`(무작위 비밀번호)을 만들고 설정 파일에 등록: `mail.provider=arimail`, `mail.url=https://<host>`, `mail.apiUrl=http://127.0.0.1:<port>`, `mail.domain`, `mail.adminLogin`, `mail.adminPassword`(보관소) ⑥ 남은 수동 작업 안내.
|
||||
|
||||
남는 수동 작업
|
||||
1. DNS: `mail.<도메인>` A 레코드 → 서버 공인 IP(프록시 없이). 바뀌면 Arimail 의 `<app>-tls.timer` 가 certbot 으로 인증서를 받아 443·메일 포트 TLS 를 켠다.
|
||||
2. Resend: 도메인을 수신·발신 도메인으로 등록하고 Webhooks → `https://mail.<도메인>/api/webhooks/resend`(email.received) 의 Signing Secret 을 `/etc/<app>/gateway.env` 의 `RESEND_WEBHOOK_SECRET` 에 넣고 서비스 재시작. 받는 메일 MX 는 Resend 안내값(관리자 콘솔 › 메일 시스템 › 기본값의 "메일 서버 주소" 에 그 값을 넣으면 DNS 안내 표에 나온다).
|
||||
3. 초기 관리자 `admin / adminpasswd` 로 `/admin` 에 들어가 비밀번호를 바꾼다(웹메이커는 별도 계정 `webmaker-admin` 을 쓴다).
|
||||
|
||||
`deploy/install-services.sh --gitea | --arimail … | --check` 가 Gitea 와 Arimail 을 한 번에 다루며, `deploy/install.sh`·`remote-install.sh`(끝에 Gitea 설치, `ARIMAIL_HOST`·`ARIMAIL_MAILBOX` 환경 변수가 있으면 Arimail 도)·`remote-update.sh`(`--check`)가 부른다.
|
||||
|
||||
## 4. 동작 확인
|
||||
|
||||
- 초기 설정 페이지 / 관리자 콘솔 › 시스템 설정 › "메일 서버 — Arimail" 카드 → 연결 확인: `연결 성공 — Arimail 0.9.9 · 도메인 example.com · 계정 N개`.
|
||||
- 관리자 콘솔 › 메일 시스템 상단: "받는 메일·웹메일: Arimail … 연결됨 — 메일 도메인이 example.com 인 사이트에 적용".
|
||||
- 사이트 관리자 페이지 › 메일 › 메일 계정 스위치 켜기 → 메일 비밀번호 입력 → `메일 서버에 계정을 만들었습니다: id@example.com …` 안내. Arimail 관리자 페이지 계정 목록에 같은 계정이 보인다. 끄면 계정이 지워진다(메일은 Arimail 저장소에 남아 관리자 페이지에서 정리).
|
||||
- 보관소 항목 `arimail`·`gitea` 의 "연결 확인" 도 같은 모듈 status() 를 쓴다.
|
||||
|
||||
## 5. 자주 나는 메시지
|
||||
|
||||
| 메시지 | 원인 | 해결 |
|
||||
|---|---|---|
|
||||
| `메일 서버(Arimail) 연동이 꺼져 있어 받을 수 없습니다` | `mail.provider=none` | 설정 페이지 › 메일 연동에서 Arimail 로 바꾸고 주소·도메인·관리자 계정 입력, 또는 `install-arimail.sh` |
|
||||
| `Arimail 주소는 있지만 도메인 또는 관리자 비밀번호가 없습니다` | 설정 미완 | `mail.domain`, `mail.adminPassword` 입력 |
|
||||
| `Arimail 응답 없음 — 연결 실패: ECONNREFUSED` | 서비스가 꺼짐·내부 API 주소 틀림 | `systemctl status <app>`, `mail.apiUrl` 확인 |
|
||||
| `Arimail 관리자 로그인에 실패했습니다` | 비밀번호 틀림·계정이 관리자 역할 아님 | Arimail `/admin` 에서 계정 확인, `install-arimail.sh --check` 로 webmaker-admin 비밀번호 재설정·재등록 |
|
||||
| `메일 비밀번호를 입력하세요` (메일 사용 켜기) | Arimail 이 연결된 사이트는 비밀번호가 필요 | 사이트 관리자 페이지의 입력 창에서 정함 |
|
||||
| `메일 비밀번호가 Arimail 의 비밀번호 정책에 맞지 않습니다` | Arimail 관리자 페이지 › 비밀번호 정책 | 정책에 맞는 비밀번호 |
|
||||
| `Arimail 의 계정 수 제한(LIMIT_ACCOUNTS)에 이르렀습니다` | 설치 때 정한 시스템 제한 | `/etc/<app>/gateway.env` 의 `LIMIT_ACCOUNTS` 조정 후 재시작 |
|
||||
| `메일 서버(Arimail)는 X 도메인만 다루는데 이 사이트의 메일 도메인은 Y 입니다` | 도메인 불일치 | 같은 도메인으로 메일 시스템을 켜거나 그 도메인용 Arimail 설치 |
|
||||
| `Arimail 로그인 시도가 너무 많아 잠시 막혔습니다 (15분 뒤 다시)` / 표의 "Arimail 로그인 잠금 · HH:MM 까지" | 틀린 비밀번호로 로그인을 반복 (Arimail 15분 10회 제한) | 1.6.39: 해제 시각까지 "연결 확인" 이 비활성. 그 사이 "수정"/"이전 비밀번호로" 로 맞는 값을 저장해 두고 해제 뒤 한 번만 확인 |
|
||||
| `Arimail 관리자 계정 "…" 은(는) 웹메이커에서 지울 수 없습니다 — … 보호됩니다` | 연동 관리자·관리자 역할 계정 삭제·역할 하향 시도 | 1.6.39: 설계상 막음. 필요하면 Arimail /admin 에서 직접 |
|
||||
| `사이트 관리자 비밀번호와 다릅니다 — 메일 비밀번호는 이 계정의 비밀번호와 같게 유지되므로…` (메일 사용 켜기) | 본인 계정에 다른 메일 비밀번호를 넣음 | 1.6.39: 지금 로그인한 비밀번호를 입력 |
|
||||
| 사이트 관리자 페이지 메일 계정 표 "비밀번호 동기화: 실패 — … 비밀번호 정책" | Arimail 비밀번호 정책이 웹메이커 규칙보다 엄격 | 관리자 비밀번호를 정책에 맞게 바꾸면 다음 로그인 때 다시 맞춰진다 |
|
||||
| `Arimail 로그인 실패 — … (저장된 비밀번호가 틀리면 "다른 관리자 계정으로 재설정" 을 쓰세요)` (비밀번호 초기화) | 보관소의 연동 계정 비밀번호가 틀림 | 열리는 폼에 Arimail 의 다른 관리자(초기 admin 등) 아이디·비밀번호 입력 → 연동 계정 비밀번호 재생성 |
|
||||
|
||||
## 6. 여러 메일 서버 — 다른 서버에 설치된 Arimail (v1.6.38, 요구 2)
|
||||
|
||||
웹메이커와 Arimail 을 다른 서버에 나눠 설치할 수 있고(예: webmaker.ibit.kr 121.152.149.45 ↔ webmail.ibit.kr 115.68.194.192), 메일 도메인마다 다른 Arimail 을 붙일 수 있다.
|
||||
|
||||
- **등록부** `lib/mailservers.js` → `data/mail-servers.json`(id·닉네임·IP·공개 주소·API 주소·메일 도메인·관리자 ID·연결 확인). 관리자 비밀번호는 보관소(`lib/vault.js`) 종류 `arimail`, 식별자 `arimail-<id>`.
|
||||
- **기본 서버(설정 파일)**: `mail.url / mail.apiUrl / mail.domain / mail.adminLogin / mail.adminPassword` 의 서버는 id `primary` 로 등록부에 함께 보인다. `install-arimail.sh --register-only` 는 여기에 쓴다. 닉네임·IP 는 `mail.nickname`·`mail.ip`.
|
||||
- **관리자 콘솔 › 메일 시스템 › 메일 서버 (Arimail) 연동**: 추가(닉네임·서버 IP·Arimail 주소·API 주소·메일 도메인·관리자 ID·비밀번호) · 연결 확인(health → 관리자 로그인 → 계정 수) · 수정 · 기본으로 · 삭제. 비밀번호는 화면·작업 기록에 나오지 않는다.
|
||||
- **사이트가 쓰는 서버**: 사이트 메일 설정의 "메일 서버" 에서 고른 서버 → 없으면 사이트 메일 도메인과 같은 도메인을 다루는 서버 → 없으면 기본 서버. 도메인이 맞지 않으면 사이트 메일 상태가 그 이유를 말한다.
|
||||
- **MX 기본값**: 사이트·전역 MX 호스트가 비어 있으면 고른 메일 서버의 호스트(예: webmail.ibit.kr)를 MX 로 안내한다.
|
||||
- **API**: `GET/POST /api/admin/mail-servers`, `PUT/DELETE /api/admin/mail-servers/<id>`, `POST /api/admin/mail-servers/<id>/test`, `POST /api/admin/mail-servers/<id>/default`.
|
||||
- 연동 모듈: `require('./lib/connectors/arimail').forServer(server)` 가 서버에 묶인 동작(로그인·계정·상태)을 돌려준다. 최상위 함수는 기본 서버에 묶여 있다.
|
||||
|
||||
## 7. 비밀번호 — 보관·되돌리기·잠금·보기·초기화·동기화 (v1.6.39, 요구 2·3·4)
|
||||
|
||||
2026-10-21 webmaker.ibit.kr 에서 기본 메일 서버를 수정하다 비밀번호 칸에 값을 넣어 동작하던 Arimail 관리자 비밀번호를 덮어쓰고, "연결 확인" 을 반복해 15분 잠금까지 걸린 사고에서 나온 설계다.
|
||||
|
||||
- **연동 관리자 비밀번호의 자리**: 기본 서버는 설정 파일 `mail.adminPassword`(보관소 `arimail`), 추가 서버는 보관소 `arimail-<id>`. **이전 값**은 `prev-arimail` / `prev-arimail-<id>`(비밀번호를 바꾸기 직전 자동 보관). 모두 `data/credentials.json`(0600, AES-256-GCM) 이고 1.6.39 부터 `.bak` 이 함께 남는다(`config.json`·`mail-servers.json` 도).
|
||||
- **관리자 콘솔 › 메일 시스템 › 메일 서버 연동 표**
|
||||
- 관리자 ID 칸 **[보기]/[숨기기]·[복사]** — `GET /api/admin/mail-servers/<id>/password`(관리자 전용). 60초 뒤 자동 숨김. 작업 기록 `mailserver.reveal`(비밀번호 없음). 초기 설정 뒤 Arimail `/admin` 로그인에 쓴다.
|
||||
- **수정** 때 비밀번호 칸에 값이 있으면 확인 창 — 그대로 두려면 칸을 비운다.
|
||||
- **이전 비밀번호로** — `POST …/revert-password`. 이전 값 ↔ 현재 값을 맞바꾼다(다시 되돌릴 수 있음).
|
||||
- **비밀번호 초기화** — `POST …/reset-password`. 16자 무작위(대·소문자·숫자·기호) 비밀번호를 Arimail 계정에 설정하고 보관. 저장된 비밀번호로 로그인이 안 되면 "다른 관리자 계정으로 재설정" 폼(`{via:{login,password}}`, 저장하지 않음; 연동 계정이 없으면 관리자 역할로 생성).
|
||||
- **잠금** — Arimail 이 `rate_limited` 를 돌려주면 등록부에 `lockedUntil`(15분). 그동안 "연결 확인" 비활성 + 남은 시간 표시, 서버의 `mailservers.test()` 도 Arimail 을 부르지 않는다. 해제 뒤 성공하면 기록이 지워진다.
|
||||
- **관리자 계정 보호**(`protectAdmin`): 연동 관리자 계정과 Arimail 관리자 역할 계정은 웹메이커의 DELETE·역할 하향·비활성화가 403. 비밀번호·이름 변경과 admin 으로 올리기는 허용.
|
||||
- **사이트 관리자 비밀번호 ↔ 메일 비밀번호 동기화**(`mailsys.syncPassword`): 웹메이커는 관리자 비밀번호를 해시로만 가지므로, 원문을 아는 순간(사이트 관리자 페이지·웹메일 로그인 성공)마다 등록부 `data/mail-accounts.json` 의 `pwSyncHash` 와 지금 해시를 비교해 다르면 Arimail 계정 비밀번호를 PATCH 로 맞춘다. 결과는 `pwSyncAt / pwSyncError` 와 작업 기록 `mail.account.sync`(원문 없음). "메일 사용 켜기" 는 본인 계정이면 관리자 비밀번호 그대로(다르면 400), 소유자·운영자가 대신 켤 때는 임시 비밀번호 → 다음 로그인 때 자동으로 맞춰진다. 사이트 관리자 페이지 메일 계정 표의 "비밀번호 동기화" 열에서 상태를 본다.
|
||||
- API 정리: `GET …/<id>/password` · `POST …/<id>/revert-password` · `POST …/<id>/reset-password` · 연결 확인 응답 `locked / lockedUntil / code` · 서버 객체 `hasPrevPassword / prevPasswordAt / lockedUntil / adminUrl`.
|
||||
@@ -0,0 +1,50 @@
|
||||
# 일정관리 모듈 · 만세력 — v1.4.18
|
||||
|
||||
사이트 기본 제공 모듈 `calendar`(일정관리). 월간·주간·일간 캘린더에 예정된 일정을 보여 주고, 음력 날짜·만세력 간지·24절기 표시와
|
||||
반복 일정·음력 기준 일정 등록을 지원한다.
|
||||
|
||||
## 구성
|
||||
| 파일 | 역할 |
|
||||
|---|---|
|
||||
| `lib/lunar.js` | 만세력 계산: 양력 ↔ 음력, 사주식 간지(연주·월주·일주), 24절기. 자료표 없이 천문 계산(아래) |
|
||||
| `lib/calendar.js` | 일정 데이터 규칙(`normalizeEvent`), 반복·음력 일정의 발생 전개(`expand`), 캘린더 조회(`range`), 다음 발생일 |
|
||||
| `lib/modules.js` | 카탈로그 `calendar`(설정 스키마·`events` 컬렉션), 공개 조회 `listCalendar`, 관리 CRUD(`/c/events`) |
|
||||
| `modules/calendar/widget.html` | 사이트에 들어가는 표준 위젯 마크업 (`data-view`·`data-lunar`·`data-ganji`·`data-category`) |
|
||||
| `public/wm-modules.js` `R.calendar` | 브라우저 런타임: 월간 격자·주간/일간 시간표·일정 상세 팝업 |
|
||||
| `public/site-admin.js` | 사이트 관리자 페이지 › 일정관리: 날짜 조회, 일정 표(다음 발생), 일정 편집기 |
|
||||
|
||||
## 공개 API (`/api/m/<사이트ID>/calendar/…`, 모듈이 켜져 있을 때)
|
||||
- `GET range?from=YYYY-MM-DD&to=YYYY-MM-DD[&cat=분류]` — 최대 70일. `days[]`(날짜마다 음력·간지·절기), `events[]`(공개 일정의 발생), `settings`
|
||||
- `GET settings` — 기본 보기, 음력·간지·절기 표시, 주 시작 요일, 주간·일간 시간 범위, 분류
|
||||
- `GET day?date=YYYY-MM-DD` — 한 날의 음력·간지(한글·한자·띠)·절기
|
||||
- `GET lunar?year&month&day&leap=1` — 음력 → 양력, 그 해 윤달·달 길이
|
||||
|
||||
## 일정 형식
|
||||
| 항목 | 뜻 |
|
||||
|---|---|
|
||||
| `unit` | `time` 시·분 단위(한 날의 시작~종료 시각) / `day` 일 단위(`length` 일) / `week` 주 단위(`length` 주) |
|
||||
| `cal` | `solar` 양력 / `lunar` 음력 — 음력이면 `lunar:{year,month,day,leap}` 가 시작일, `date` 에 변환한 양력 시작일을 함께 둔다 |
|
||||
| `repeat` | `freq` none·daily·weekly·monthly·yearly, `interval`(1~99), `until`(종료일), `weekdays`(매주 반복 요일) |
|
||||
|
||||
규칙
|
||||
- 양력 매월·매년 반복에서 없는 날(31일, 2월 29일)은 그 달 마지막 날.
|
||||
- 음력 매년 반복은 해마다 같은 음력 달·날. 윤달에 시작한 일정은 그 해에 같은 윤달이 있을 때만 윤달, 없으면 평달(윤달 생일을 평달에 지내는 관행). 30일이 없는 달은 29일.
|
||||
- 음력 매월 반복은 음력 달마다 같은 날(윤달도 한 달로 센다). 음력 일정의 매일·매주 반복은 변환한 양력 시작일부터 이어진다.
|
||||
- 발생(occurrence) id 는 `<일정ID>@<시작일>`.
|
||||
|
||||
## 만세력 계산 방법 (`lib/lunar.js`)
|
||||
한국천문연구원 만세력과 같은 규칙을 한국 표준시(KST) 기준으로 적용한다.
|
||||
- **합삭(새달)**: Meeus 『Astronomical Algorithms』 49장 급수. 합삭 순간이 든 KST 날짜가 음력 1일.
|
||||
- **태양 황경**: VSOP87 지구 급수 축약(Meeus 부록 III, 약 1″)에 장동·광행차를 더한 시황경. 24절기는 황경이 15° 배수가 되는 순간(뉴턴 반복).
|
||||
역표시 ↔ 세계시 차(ΔT)는 Espenak-Meeus 다항식.
|
||||
- **달 번호·윤달**: 동지가 든 달이 11월. 동지에서 다음 동지 사이(歲)에 달이 13개면 중기(황경 30° 배수)가 없는 첫 달이 윤달.
|
||||
- **간지**: 일주는 60일 주기(2000-01-01 = 무오일), 연주는 입춘(315°)에 바뀌고, 월주는 절기(15°+30°k)에 바뀌며 월간은 오호둔. 캘린더는 날짜 단위라 절입 당일부터 새 달·새 해로 표시한다.
|
||||
- 검증(scratch 테스트): 설날 1990~2033, 추석 2000~2027, 윤달 2001~2047(2033 윤11월 포함), 절기 시각(2025 동지 12-22 00:03, 2026 춘분 03-20 23:46), 일진(2024-01-01 갑자, 2026-10-14 병오년 무술월 신유일) 모두 일치.
|
||||
KST 기준이라 중국 음력과 다른 해(예: 2027 설날 2/7, 2028 설날 1/27 — 합삭이 KST 자정 직후)도 한국 기준으로 나온다.
|
||||
- 한계: 합삭·절기가 KST 자정 수십 초 안에 드는 극히 드문 날은 하루 차이가 날 수 있다. 유효 범위 1900~2100년.
|
||||
|
||||
## 위젯 표시
|
||||
- 월간: 7×6 격자, 날짜 칸에 `음 9.4 · 신유`(설정에 따라 음력·일진) 와 절기 배지, 일정 3개 + "+n개 더". 칸을 누르면 그 날의 일간 보기.
|
||||
- 주간·일간: 종일 줄(일·주 단위 일정) + 시간표(관리자 설정 시작~끝 시각, 겹치는 일정은 열로 나눔). 일간 보기 위에는 전체 간지(병오년 무술월 신유일)와 절기.
|
||||
- 일정 클릭 → 상세 팝업(날짜·시간, 단위, 분류, 음력 기준 여부, 장소, 설명).
|
||||
- 한국어가 아닌 페이지에서는 간지·절기를 한자(丙午年 戊戌月 辛酉日, 寒露)로 보인다. 문구는 `locales/site/ko.json` 의 `calendar.*`.
|
||||
@@ -0,0 +1,103 @@
|
||||
# 설정 파일과 초기 설정 페이지 — 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` > `<DATA_ROOT>/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://<IP>: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://<IP>: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 <base>/ready` 점검표, `POST <base>/admin/bootstrap|create|password`, `PUT <base>/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 <ssh> <앱 폴더> <유닛>` — 코드 갱신·재시작 뒤 (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/<UTC시각>-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`) 백업도 각자 서버에만 남는다 — 서로 복사하지 않는다.
|
||||
@@ -0,0 +1,94 @@
|
||||
# 도메인 구매·DNS 관리 자동화 — 공급자 모듈 (v1.5.36, v1.6.32 Route 53 Domains 추가, v1.6.35 사용자 프로세스)
|
||||
|
||||
AI Webmaker v1.5.36 · 2026-10-16 · 대상: 운영자
|
||||
|
||||
## 1. 구성
|
||||
|
||||
| 역할 | 공급자 | 모듈 | 접속 정보(보관소 종류) |
|
||||
|---|---|---|---|
|
||||
| 도메인 등록 업체(검색·구매·갱신·네임서버) | Openprovider | `lib/registrars/openprovider.js` | `openprovider` (USERNAME 설정 + 비밀번호) |
|
||||
| | Porkbun | `lib/registrars/porkbun.js` | `porkbun` (API_KEY 설정 + Secret API Key) |
|
||||
| | Amazon Route 53 Domains (v1.6.32) | `lib/registrars/route53.js` (+ `lib/awsv4.js`) | `route53` (DNS 와 같은 키 — IAM 에 `route53domains:*` 권한 추가 필요, 아래 2-1) |
|
||||
| DNS 공급자(영역·레코드·MX·ACME TXT·이전 영역) | Openprovider DNS | `lib/dns/openprovider.js` | `openprovider` |
|
||||
| | Porkbun DNS | `lib/dns/porkbun.js` | `porkbun` |
|
||||
| | Amazon Route 53 | `lib/dns/route53.js` (+ `lib/awsv4.js` 서명) | `route53` (ACCESS_KEY_ID 설정 + Secret Access Key, 선택 SESSION_TOKEN·REGION) |
|
||||
| | Google Cloud DNS | `lib/dns/gcloud.js` (+ `lib/google.js` 토큰) | `gcloud-dns` (서비스 계정 키 JSON, roles/dns.admin; PROJECT 설정은 키의 project_id 와 다를 때만) |
|
||||
|
||||
공통 창구: `lib/registrar.js`(등록 업체), `lib/dnszone.js`(DNS). 기존 코드(`lib/domains.js` 구매·레코드·메일 MX, `lib/ssl.js`·`scripts/acme-dns-hook.js` 와일드카드 인증서 TXT)는 모두 창구를 거친다. API 키 등 접속 정보는 **나중에** 관리자 콘솔 › 설정 › 외부 연동 접속 정보에 넣으면 되고, 넣기 전에는 "준비되지 않음" 으로 보인다.
|
||||
|
||||
## 2. 설정
|
||||
|
||||
- 관리자 콘솔 › 설정 › **도메인 서비스**: 플랫폼 도메인 · **DNS 공급자**(수동 / Openprovider / Porkbun / Route 53 / Google Cloud DNS) · **등록 업체**(Openprovider / Porkbun / Amazon Route 53 — v1.6.32). 저장값 `settings.domains.dnsProvider`, `settings.domains.registrar` (1.5.31 까지의 `platformDns` 는 호환용으로 함께 유지).
|
||||
- 접속 정보: 설정 › 외부 연동 접속 정보에서 종류를 고르고 저장 → "연결 확인" 이 실제 목록 호출(영역·도메인 목록)로 검사한다.
|
||||
- 명령줄: `node scripts/config.js` 는 설정 파일용이며 도메인 설정은 관리자 설정(`data/settings.json`)이다.
|
||||
|
||||
### 2-1. Amazon Route 53 로 도메인 등록 (v1.6.32)
|
||||
|
||||
- 접속 정보는 DNS 와 같은 보관소 항목 `route53` 을 쓴다(ACCESS_KEY_ID 설정 + Secret Access Key). 등록 업체로 쓰려면 그 IAM 사용자에 Route 53 **Domains** 권한을 더한다:
|
||||
`route53domains:CheckDomainAvailability` · `ListPrices` · `RegisterDomain` · `GetOperationDetail` · `GetDomainDetail` · `ListDomains` · `RenewDomain` · `UpdateDomainNameservers`.
|
||||
보관소의 "연결 확인" 이 호스팅 영역 수와 함께 "등록 도메인 N개(Route 53 Domains 사용 가능)" 또는 "도메인 등록은 불가: …(권한 오류)" 를 보여 준다.
|
||||
- 관리자 콘솔 › 설정 › 도메인 서비스 › **등록 업체** 에서 "Amazon Route 53" 을 고른다. 가격은 USD(ListPrices) × 요율. 등록자 정보는 사용자가 도메인 탭에 저장한 것을 RegisterDomain 에 그대로 넣는다(AWS 에는 고객 핸들이 없음; WHOIS 개인정보 보호 켬; 전화는 `+82.1012345678` 형식으로 바꿈).
|
||||
- **비동기 등록**: AWS 는 등록 요청을 접수(OperationId)하고 뒤에서 처리한다(보통 몇 분, 등록자 이메일 확인이 필요하면 더 오래). 구매는 최대 60초 완료를 기다린 뒤 끝나지 않으면 도메인 상태 **PENDING** 으로 기록하고 차감한다(요청은 AWS 에 접수된 상태). 이후 사용자가 도메인 탭을 열 때(`propagation`)와 관리자 운영 복구 "등록 상태 확인" 이 `finalizeRegistration` 으로 완료를 확인해 만료일을 채우고 DNS 영역(@·www A)·네임서버를 맞춘다. AWS 가 ERROR/FAILED 로 끝내면 상태 FAILED 로 운영 복구에 "관리자 확인 필요" 로 남는다(비용 환불은 회원·청구에서).
|
||||
- Route 53 Domains 는 등록 완료 때 같은 이름의 호스팅 영역을 **스스로 만든다**. DNS 공급자도 Route 53 이면 `createZone` 이 같은 이름의 영역을 재사용하므로 영역이 둘 생기지 않는다. DNS 공급자가 다른 업체면 그 영역의 네임서버를 `UpdateDomainNameservers` 로 지정한다(AWS 가 만든 영역은 비어 있는 채 남으며 월 요금이 붙으니 AWS 콘솔에서 지워도 된다).
|
||||
- 지원하지 않는 확장자(.kr 등)는 ListPrices 가 비어 "Route 53 는 .kr 도메인을 등록하지 않습니다" 로 안내한다. 프리미엄·예약 이름은 등록 불가로 표시된다.
|
||||
- 엔드포인트는 `route53domains.us-east-1.amazonaws.com` 하나(글로벌). 시험용 대체 주소 `ROUTE53DOMAINS_API_BASE`(모의 서버 검증 `test/registrar-route53.test.js`).
|
||||
|
||||
## 3. 동작
|
||||
|
||||
| 기능 | 흐름 |
|
||||
|---|---|
|
||||
| 검색·견적 | 등록 업체 `check/price` → 요율(기본 250%) 적용 원화 견적 |
|
||||
| 구매 | 등록 업체 `createDomain` → 캐시 차감 → DNS 공급자에 영역(@·www A 레코드) 생성 → 공급자가 등록 업체와 다르면 등록 업체에 **네임서버 지정**(예: Porkbun 등록 + Route 53 DNS). 도메인 기록에 `registrar`·`dnsProvider`·`nameServers` 저장 |
|
||||
| 레코드 | 구입 도메인: 그 도메인의 DNS 공급자 영역에서만, 영역 기준 상대 이름으로만. 서브 도메인: 플랫폼 영역 공급자가 있을 때 자기 이름 아래만 |
|
||||
| 메일(MX·SPF·DMARC·Resend) | `setupMail` 이 공급자 창구로 레코드 추가 (수동이면 안내) |
|
||||
| 와일드카드 SSL(DNS-01) | `lib/ssl.js` 가 플랫폼 도메인의 공급자 id 를 훅에 넘기고, 훅이 `_acme-challenge` TXT 를 넣고 지운다 |
|
||||
| 영역 관리(관리자) | (v1.6.33: 1.5.36~1.6.32 는 API 경로 누락으로 이 화면의 모든 호출이 "not found" 였음 — 수정됨. "이전된 영역 받기" 는 NS 조회로 영역 상태를 먼저 확인해 미등록/위임 없음/네임서버 무응답은 원인과 함께 실패, 레코드가 없으면 "zone 파일 구성을 찾을 수 없습니다" + 빈 영역) 설정 › 도메인 서비스 › **DNS 영역 관리**: 목록·만들기·**이전된 영역 받기**(공용 DNS 에 보이는 A·AAAA·CNAME·MX·TXT 를 읽어 새 영역에 복사, 네임서버 안내)·레코드 보기/추가/삭제·지우기 |
|
||||
|
||||
## 4. 보안 (여러 도메인·여러 사용자)
|
||||
|
||||
- 접속 정보는 관리자만 넣고 보관소에 암호화된다. 사용자 API 응답에는 키·영역 id 가 나가지 않는다.
|
||||
- 사용자는 `getOwned`(자기 소유 도메인)와 계정 종류(자체 도메인 계정) 검사를 거쳐서만 레코드를 만지고, 레코드 이름은 영역 기준 상대 이름이라 다른 영역·상위 이름을 건드릴 수 없다. 서브 도메인 계정은 DNS 를 직접 만지지 못한다.
|
||||
- 도메인 이름은 서비스 전체에서 하나(`byName`)라 다른 사용자가 등록한 이름을 다시 등록하거나 가로챌 수 없다.
|
||||
- 관리자 영역 API(`/api/admin/dns/*`)는 관리자 세션만, 회원이 쓰는 도메인의 영역 삭제는 `force=1` 확인이 있어야 한다. 모든 변경은 `data/dns-audit.json`(누가·언제·공급자·영역·레코드)에 남고 화면의 "DNS 감사 기록" 에 보인다.
|
||||
|
||||
## 5. 관리자 API
|
||||
|
||||
```
|
||||
GET /api/admin/dns 공급자 목록·준비 여부, 감사 기록, 전체 도메인
|
||||
GET /api/admin/dns/zones?provider=route53 영역 목록
|
||||
POST /api/admin/dns/zones {domain, provider, import?} 영역 만들기 / 이전된 영역 받기
|
||||
DELETE /api/admin/dns/zones/<domain>?provider=&force=1
|
||||
GET /api/admin/dns/records/<domain>?provider=
|
||||
POST /api/admin/dns/records/<domain> {records:[{name,type,value,ttl,prio}]}
|
||||
DELETE /api/admin/dns/records/<domain> {records:[…]}
|
||||
POST /api/admin/dns/domains/<id> {dnsProvider} 도메인별 DNS 공급자 지정
|
||||
```
|
||||
|
||||
## 6. 검증과 한계
|
||||
|
||||
- 공급자 클라이언트는 모의 API 서버(Route 53 XML · Cloud DNS JSON · Porkbun JSON)로 영역 생성·레코드 추가/삭제/교체·영역 삭제·네임서버·등록 흐름을 검사했고, SigV4 서명은 AWS 공식 예제 서명값과 일치한다. 실제 계정으로는 접속 정보를 넣은 뒤 "연결 확인" 과 작은 영역으로 먼저 시험한다.
|
||||
- Porkbun 의 도메인 등록·갱신 API(`domain/create`·`domain/renew`)는 계정에 API 등록 권한이 켜져 있어야 하며, 응답이 다르면 그 메시지를 그대로 안내한다. 등록자 정보는 Porkbun 계정의 기본 연락처를 쓴다.
|
||||
- 시험용 대체 주소: `ROUTE53DOMAINS_API_BASE`(v1.6.32), `ROUTE53_API_BASE`, `GCLOUD_DNS_API_BASE`, `GOOGLE_TOKEN_URL`, `PORKBUN_API_BASE`, `OPENPROVIDER_API_BASE`.
|
||||
|
||||
## 7. 사용자 도메인 준비 프로세스 (v1.6.35)
|
||||
관리자 콘솔은 접속 정보·플랫폼 도메인·서브 도메인 등록 허용 여부·가격 요율만 정한다(설정 › 도메인 서비스). 검색·구매·영역·레코드는 사용자 "도메인" 탭에서 사용자가 한다(`lib/domains.js` "도메인 준비 프로세스" 절).
|
||||
|
||||
| 단계 | 사용자가 하는 일 | 서버가 하는 일 | API |
|
||||
|---|---|---|---|
|
||||
| ① 구매 | 검색 → 장바구니에 담기 → 장바구니에서 구매 | 가격(요율 적용)·등록 가능 여부를 담을 때와 열 때 다시 확인, 구매 시 금액 재확인(409) | `POST /api/domains/search`, `GET/POST/DELETE /api/domains/cart`, `POST /api/domains/purchase` |
|
||||
| ② 네임서버 이전 | 도메인·쓰는 하위 이름 입력, "이미 구매했습니다" 확인 → 네임서버 변경 → "네임서버 변경했습니다" | NS/SOA 로 생존 확인 → 공용 DNS 레코드를 읽어 공급자에 영역 생성 → 네임서버 안내 → NS 집합 비교로 완료 판정. 완료 전 `pending.kind='transfer'`(레코드 수정 409) | `POST /api/domains/external {mode:'ns'}`, `POST /api/domains/<id>/transfer-check {confirmed}` |
|
||||
| ③ 직접 DNS 관리 | 도메인 입력, "이미 구매했습니다" 확인 → 안내된 레코드를 자기 DNS 에 넣음 → "설정 완료 — 확인" | 필요한 레코드(A @·www, 선택 시 MX·SPF·DMARC·DKIM) 계산 → 공용 DNS 조회로 레코드별 확인 → `manual.siteOk/mailOk` 기록 | `GET /api/domains/<id>/required`, `POST /api/domains/<id>/verify` |
|
||||
| ④ 자동 설정 | 사이트에 연결 → 발행 → 메일(MX) 자동 설정 → "도메인 상태" | ns 도메인은 영역의 @·www A 를 사이트 IP 로 교체(`setRecords`), 발행 때 vhost·SSL, 상태는 소유→DNS→A→배포 단계→메일 로 판정 | `POST /api/domains/<id>/connect`, `POST /api/domains/<id>/mail`, `GET /api/domains/<id>/status` |
|
||||
|
||||
- 도메인 기록 type: `subdomain` · `registered`(구매) · `external`(mode `ns` | `manual`). `external/ns` 는 `registered` 와 같은 영역 관리(레코드 추가·삭제·메일 자동)를 받는다.
|
||||
- 등록 업체 이전(registrar transfer)은 지원하지 않는다 — 화면에 명시. `quote(operation:'transfer')` 는 가격 조회만 남아 있다.
|
||||
- 영역을 먼저 만드는 이유: 공급자(Route 53 등)는 영역을 만든 뒤에야 그 영역의 네임서버를 알려 준다. 네임서버가 바뀌기 전까지 새 영역은 공용 DNS 에 보이지 않으므로, 옮긴 레코드는 전파 뒤 그대로 살아난다.
|
||||
- 관리자 "DNS 영역 현황" 은 조회·정리(쓰지 않는 영역 삭제)만 한다. 관리자 API `/api/admin/dns/zones POST`(만들기·import)는 호환을 위해 남아 있지만 화면에서는 쓰지 않는다.
|
||||
|
||||
## 8. 네임서버 정보 · DNS 레코드 정보 팝업 (v1.6.38, 요구 3)
|
||||
|
||||
직접 산 도메인(네임서버 이전·직접 DNS 관리)은 사용자가 등록 업체에서 네임서버를 바꾸거나 그 업체의 DNS 레코드를 직접 넣어야 한다. 도메인 탭 "내 도메인" 항목의 안내 줄이 어느 버튼을 누르는지 먼저 말하고, 상세는 팝업으로 보인다.
|
||||
|
||||
- **네임서버 정보**: 등록 업체에서 "네임서버 설정"(DNS 레코드 관리가 아님)을 열어 사용자 네임서버로 바꾸는 단계, 네임서버 표(값 클릭 전체 선택·복사 버튼·전체 복사), 지금 네임서버, DNS 공급자 이름. 직접 DNS 관리 도메인에는 네임서버 이전으로 바꾸는 순서(해제 → 네임서버 이전 등록)를 안내한다 — Route 53 등은 영역을 만들 때 네임서버가 정해지기 때문.
|
||||
- **DNS 레코드 정보**: "DNS 레코드 관리" 를 여는 단계, 사이트용(A @·www) · 메일용 MX(우선순위 안내) · 메일용 TXT(SPF·DMARC) · 보내기 인증(DKIM 등) 표. 이름·값·우선순위를 클릭하면 전체 선택(Ctrl-C), "복사" 버튼, 묶음별·전체 복사(탭 구분). 데이터는 `GET /api/domains/<id>/required?siteId=&mail=1`.
|
||||
- 값 복사는 https 에서 클립보드 API 로 동작하고, 막히면 "직접 선택해 Ctrl-C" 로 바뀐다.
|
||||
@@ -0,0 +1,244 @@
|
||||
# 도메인 서비스 준비 목록과 만드는 방법
|
||||
|
||||
AI Webmaker v1.4.3 · 2026-10-09 작성 · 대상: 운영자(관리자 콘솔 사용자)
|
||||
|
||||
빌더의 **도메인** 탭(v1.4.0)이 실제로 동작하려면 서비스 밖에서 준비할 것(계정·DNS)과 관리자 콘솔에서 넣을 값이 있다.
|
||||
이 문서는 그 목록, 각 항목을 만드는 방법, 2026-10-09 기준 두 운영 서버의 상태를 정리한 것이다.
|
||||
기능 설명은 [README.md](../README.md) "도메인 · 계정 종류" 절, 오류 메시지와 원인은 [ERRORS.md](../ERRORS.md) "설정·운영 안내 (v1.4.0 도메인·메일)" 표에 있다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 한눈에 보기
|
||||
|
||||
| # | 준비할 것 | 어디서 | 필요한 기능 | webmaker.ibit.kr (bear) | 192.168.20.66 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Openprovider 리셀러 계정 + 선불 잔액 | openprovider.com | 도메인 검색·구매·갱신·DNS | ❌ 없음 | ❌ 없음 |
|
||||
| 2 | Openprovider 접속 정보 등록 | 관리자 콘솔 › 설정 › 외부 연동 접속 정보 | 1 과 같음 | ❌ 미등록 | ❌ 미등록 |
|
||||
| 3 | 도메인 서비스 설정 (요율·플랫폼 도메인 등) | 관리자 콘솔 › 설정 › 도메인 서비스 | 가격·서브 도메인 | ✅ 기본값 (ibit.kr, 요율 250%) | ⚠️ 플랫폼 도메인 없음 (주소가 IP) |
|
||||
| 4 | 서브 도메인용 DNS (`*.ibit.kr`) | hosting.co.kr DNS 관리 | 서브 도메인 계정의 `이름.ibit.kr` | ❌ 와일드카드 없음 | — |
|
||||
| 5 | 웹서버·SSL 자동 설정 조건 (80/443, sudo, certbot) | 서비스 서버 | 연결한 도메인으로 사이트 열기·HTTPS | ✅ 준비됨 | — |
|
||||
| 6 | Let's Encrypt 알림 이메일 | 관리자 콘솔 › 설정 › SSL | 인증서 만료 알림 | ⚠️ 비어 있음 (발급은 됨) | — |
|
||||
| 7 | 메일 발송 키 (Resend) | 관리자 콘솔 › 설정 › 외부 연동 접속 정보 | 메일(MX) 자동 설정, 보내는 메일 | ✅ 등록됨 | — |
|
||||
| 8 | 메일 서버 주소 (MX 호스트) | 관리자 콘솔 › 메일 시스템 (기본값) | 메일(MX) 자동 설정 | ❌ 비어 있음 | — |
|
||||
| 9 | 받는 메일 서버 (mail-gateway 연동) | mail-gateway + 이 서비스 | 받는 메일 | ❌ 연동 전 (보내기만 가능) | — |
|
||||
| 10 | 회원 계정 종류·캐시 | 관리자 콘솔 › 회원·청구 | 누가 무엇을 쓸 수 있는지, 구매 대금 | ✅ 기존 회원은 자체 도메인 | ✅ |
|
||||
| 11 | 바깥 인터넷 접속 (Openprovider API, 환율) | 서비스 서버 | 가격·구매 | ✅ 확인함 | — |
|
||||
|
||||
✅ 준비됨 · ⚠️ 동작은 하지만 손볼 것 · ❌ 없으면 그 기능이 동작하지 않음 · — 이 서버에서는 해당 없음
|
||||
|
||||
**최소 구성**
|
||||
- 자체 도메인 판매만: 1·2·10.
|
||||
- 서브 도메인까지: 3·4 추가.
|
||||
- 메일(MX)까지: 7·8 추가. 받는 메일은 9.
|
||||
|
||||
---
|
||||
|
||||
## 2. Openprovider 리셀러 계정 (항목 1)
|
||||
|
||||
도메인 등록 기관(레지스트라) 역할을 하는 공급사다. 이 서비스는 Openprovider API v1beta(`https://api.openprovider.eu/v1beta`)로 등록 가능 여부·가격 조회, 등록·갱신, DNS 영역 관리를 한다.
|
||||
|
||||
**만드는 방법**
|
||||
1. https://www.openprovider.com 에서 리셀러(Reseller) 계정으로 가입한다.
|
||||
- 회사 정보·담당자·주소를 넣는다.
|
||||
- 가입 후 본인·회사 확인 절차가 있을 수 있다.
|
||||
2. 요금제(멤버십)를 고른다.
|
||||
- 무료 등급으로 시작할 수 있다.
|
||||
- 유료 등급은 도메인 원가가 낮아진다.
|
||||
- 사용자 가격은 원가 × 요율이므로 원가가 낮을수록 사용자 가격도 내려간다.
|
||||
3. **선불 잔액(예치금)을 충전한다.**
|
||||
- 도메인을 등록·갱신할 때마다 Openprovider 잔액에서 원가가 빠져나간다.
|
||||
- 잔액이 부족하면 구매가 `도메인 서비스 오류: …` 로 실패한다. 이때 사용자 캐시는 차감되지 않는다(서비스가 등록 성공 뒤에만 차감).
|
||||
4. API 로 쓸 로그인 계정을 정한다.
|
||||
- 이 서비스는 제어판 로그인과 같은 **사용자 이름·비밀번호** 로 `/auth/login` 토큰을 받는다.
|
||||
- 운영용으로 API 전용 하위 사용자(sub-user)를 따로 만들어 쓰는 것을 권장한다.
|
||||
- 그러면 사람이 비밀번호를 바꿔도 서비스가 멈추지 않는다.
|
||||
5. API 접근 허용 IP 를 확인한다.
|
||||
- Openprovider 제어판의 API 설정에 허용 IP 목록이 있으면 서비스 서버의 공인 IP 를 넣는다.
|
||||
- webmaker.ibit.kr(bear)의 바깥 주소는 **121.152.149.45**.
|
||||
- 허용되지 않으면 로그인 단계에서 `Openprovider 로그인 실패 (…)` 가 난다.
|
||||
6. (선택) 시험 환경.
|
||||
- 실제 구매 전에 시험하려면 Openprovider 에 시험(sandbox) 계정을 요청한다.
|
||||
- 시험 주소는 2번 단계의 "주소" 칸에 넣는다.
|
||||
- 시험 주소를 받기 전에는 아래 9번 점검 순서에서 **가장 싼 도메인 1개로만** 실제 구매를 시험한다.
|
||||
|
||||
> 확인함 (2026-10-09): bear 에서 `api.openprovider.eu` 로 접속이 된다(가짜 계정으로 `Authentication/Authorization Failed` 응답 = 네트워크는 정상).
|
||||
|
||||
---
|
||||
|
||||
## 3. 접속 정보 등록 (항목 2)
|
||||
|
||||
**관리자 콘솔 › 설정 › 외부 연동 접속 정보**
|
||||
|
||||
| 칸 | 넣을 값 |
|
||||
|---|---|
|
||||
| 종류 | **Openprovider** |
|
||||
| 주소 | 비워 두면 `https://api.openprovider.eu/v1beta` (시험 계정이면 그 주소) |
|
||||
| API 키 또는 토큰 | Openprovider **비밀번호** |
|
||||
| 설정 (한 줄에 이름=값) | `USERNAME=<Openprovider 사용자 이름>` (필수)<br>`NS_GROUP=dns-openprovider` (선택, 기본값과 같음) |
|
||||
|
||||
1. **암호화해 저장** 을 누른다.
|
||||
- 비밀번호는 `data/credentials.json` 에 AES-256-GCM 으로 암호화된다.
|
||||
- 화면에는 앞뒤 몇 글자만 보이고 다시 볼 수 없다.
|
||||
2. 목록의 **연결 확인** 을 누른다. 서비스가 로그인한 뒤 확장자 목록(`/tlds`)을 받아 보고 결과를 표시한다.
|
||||
3. 바꿀 때는 같은 종류로 새 값을 넣고 다시 저장한다. 비밀번호 칸을 비우고 저장하면 기존 비밀번호가 유지된다.
|
||||
|
||||
등록 전에는 사용자 도메인 탭에 **"도메인 서비스가 아직 준비되지 않았습니다. 관리자에게 문의하세요"** 가 보인다. 이때 검색·구매는 안 되고, 서브 도메인 등록은 된다.
|
||||
|
||||
**NS_GROUP (네임서버 그룹)**
|
||||
- 구매한 도메인은 Openprovider 네임서버 그룹으로 등록되어, 이 서비스가 그 도메인의 DNS 레코드를 직접 관리한다.
|
||||
- 기본 그룹 이름은 `dns-openprovider` 이다.
|
||||
- 제어판에서 자기 네임서버 그룹(화이트라벨 네임서버 등)을 만들었다면 그 이름을 넣는다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 도메인 서비스 설정 (항목 3)
|
||||
|
||||
**관리자 콘솔 › 설정 › 도메인 서비스** — 값마다 옆의 버튼을 눌러야 저장된다.
|
||||
|
||||
| 값 | 의미 | 권장 (webmaker) |
|
||||
|---|---|---|
|
||||
| 도메인 요율 % | 사용자 가격 = Openprovider 원가 × 요율 ÷ 100 을 그날 환율로 원화로 바꿔 100원 단위로 올림. 구매·갱신 전에 사용자에게 먼저 보인다 | 250 (요구 사항) |
|
||||
| 플랫폼 도메인 | 서브 도메인 계정이 쓰는 도메인 (`이름.ibit.kr`) | `ibit.kr` (기본값으로 들어 있음) |
|
||||
| DNS 방식 | **수동**: 운영자가 와일드카드 레코드를 한 번 넣음 (5장)<br>**자동(Openprovider)**: 플랫폼 도메인 DNS 가 Openprovider 에 있을 때 이름별 레코드를 서비스가 만듦 | 수동 (지금 ibit.kr DNS 는 hosting.co.kr) |
|
||||
| 사이트 IP | 연결한 도메인이 가리킬 서버 IP. 비우면 서비스 주소의 IP | 비워 둠 (= 121.152.149.45) |
|
||||
| 판매 확장자 | 검색에 쓰는 확장자 목록 | com, net, org, co, io, shop |
|
||||
| 등록 금지 이름 | 서브 도메인으로 못 쓰는 이름 (www·mail·admin 등은 기본 금지) | `board, file, matrix, office, source` 추가 권장 — 이미 쓰는 ibit.kr 이름 (webmaker 는 기본 금지) |
|
||||
| 새 가입자 계정 종류 | 서브 도메인 전용 / 자체 도메인 | 서브 도메인 전용 (기본) |
|
||||
| 메일 시스템도 켜기 | 메일(MX) 자동 설정 때 그 사이트의 메일 시스템을 켬 | 켬 |
|
||||
|
||||
**192.168.20.66**
|
||||
- 서비스 주소가 IP 라 플랫폼 도메인이 비어 있다.
|
||||
- 그래서 새 가입자는 자동으로 자체 도메인 계정이 되고, 서브 도메인 등록은 `플랫폼 도메인이 설정되지 않았습니다` 로 막힌다.
|
||||
- 이 서버에서 서브 도메인을 쓰려면 이 서버를 가리키는 도메인을 플랫폼 도메인으로 넣는다.
|
||||
|
||||
> 확인함: bear 에서 환율 조회가 된다(9.5 EUR → 14,419원 → 요율 250% 적용 시 36,100원).
|
||||
|
||||
---
|
||||
|
||||
## 5. 서브 도메인용 DNS — `*.ibit.kr` (항목 4)
|
||||
|
||||
서브 도메인 계정은 `mycompany.ibit.kr` 같은 이름을 등록한다. 이 이름이 실제로 열리려면 DNS 가 그 이름을 서비스 서버(121.152.149.45)로 보내야 한다.
|
||||
|
||||
**지금 상태 (2026-10-09)**
|
||||
- `ibit.kr` 의 네임서버는 **hosting.co.kr** (ns1~ns4.hosting.co.kr) 이다.
|
||||
- 이름마다 A 레코드가 따로 있다: board / file / matrix / office / source / webmaker → 모두 121.152.149.45.
|
||||
- **와일드카드가 없다.**
|
||||
- 그래서 지금 서브 도메인을 등록하면 서비스 안에서는 등록되지만 주소가 열리지 않는다.
|
||||
|
||||
### 방법 A (권장, 5분) — hosting.co.kr 에 와일드카드 1줄 추가
|
||||
1. hosting.co.kr 에 로그인 → 도메인 관리 → `ibit.kr` → DNS(네임서버) 레코드 관리.
|
||||
2. 레코드를 추가한다.
|
||||
|
||||
| 호스트(이름) | 종류 | 값 | TTL |
|
||||
|---|---|---|---|
|
||||
| `*` | A | `121.152.149.45` | 기본값(3600 등) |
|
||||
|
||||
3. 기존 레코드(board·file·matrix·office·source·webmaker)는 그대로 둔다. 이름이 정확히 있는 레코드가 와일드카드보다 먼저 적용된다.
|
||||
4. 확인: 몇 분~몇 시간 뒤 `dig +short 아무이름.ibit.kr` 이 `121.152.149.45` 를 돌려주면 된다.
|
||||
5. 관리자 콘솔 › 설정 › 도메인 서비스 의 DNS 방식은 **수동** 그대로 둔다.
|
||||
|
||||
- 이 방법은 서브 도메인마다 DNS 를 바꿀 필요가 없다.
|
||||
- 서브 도메인의 메일(MX) 레코드는 자동으로 만들 수 없다. 도메인 탭이 넣을 레코드를 표로 보여 주므로, 메일을 쓰는 서브 도메인만 hosting.co.kr 에 직접 넣는다.
|
||||
|
||||
### 방법 B — ibit.kr DNS 를 Openprovider 로 옮기기 (서브 도메인 메일까지 자동)
|
||||
1. Openprovider 제어판에서 `ibit.kr` DNS 영역(zone)을 만들고, **지금 있는 레코드를 모두 먼저 옮겨 적는다.** 빠뜨리면 그 서비스가 멈춘다.
|
||||
- board / file / matrix / office / source / webmaker A 레코드
|
||||
- 그 밖에 hosting.co.kr 에 있는 TXT·MX 등
|
||||
2. 도메인 등록 기관(hosting.co.kr)에서 `ibit.kr` 의 네임서버를 Openprovider 네임서버로 바꾼다. 전파에 최대 48시간이 걸린다.
|
||||
3. 전파가 끝나면 관리자 콘솔 › 설정 › 도메인 서비스 의 DNS 방식을 **자동(Openprovider)** 으로 바꾸고 저장한다.
|
||||
4. 이후 서브 도메인을 연결하면 A 레코드를, 메일(MX) 자동 설정을 누르면 메일 레코드를 서비스가 만든다.
|
||||
|
||||
위험이 큰 작업(사내 서비스 전체 DNS 이전)이므로 방법 A 로 먼저 운영하고, 서브 도메인 메일 자동화가 꼭 필요할 때 B 로 옮기는 것을 권장한다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 웹서버·SSL (항목 5·6)
|
||||
|
||||
사용자가 도메인을 사이트에 **연결** 한 뒤 **발행(또는 재배포)** 하면 서비스가 자동으로 처리한다.
|
||||
- 배포 서버에 그 도메인용 nginx 설정(`/etc/nginx/sites-available/wh-<사이트ID>.conf`)을 만든다.
|
||||
- DNS 가 이 서버를 가리키는 도메인에 Let's Encrypt 인증서를 발급하고 HTTP→HTTPS 로 돌린다.
|
||||
|
||||
| 조건 | 상태 (bear) |
|
||||
|---|---|
|
||||
| 바깥에서 80·443 포트가 서비스 서버로 들어옴 | ✅ (121.152.149.45 → bear nginx) |
|
||||
| 서비스 계정 `master` 가 비밀번호 없는 sudo | ✅ |
|
||||
| certbot 설치 | ✅ `/usr/bin/certbot` |
|
||||
| 배포 서버 "서비스 서버 (bear, webmaker.ibit.kr)" 의 도메인 vhost 관리 | ✅ (연결 확인: "도메인 vhost 설정 가능") |
|
||||
| Let's Encrypt 알림 이메일 | ⚠️ 비어 있음 → 관리자 콘솔 › 설정 › **SSL (Let's Encrypt)** 에 운영자 메일을 넣고 저장 (만료·문제 알림을 받기 위해) |
|
||||
|
||||
**주의**
|
||||
- 인증서는 DNS 가 이 서버를 가리켜야 발급된다.
|
||||
- 새로 산 도메인은 위임 전파(최대 48시간)가 끝난 뒤 발행해야 HTTPS 가 붙는다.
|
||||
- 전파 중에는 서비스가 발행을 막고 "DNS 연결이 진행 중입니다 … 약 N분 후에 다시 이용해 주세요" 를 보인다.
|
||||
- Let's Encrypt 는 같은 등록 도메인(ibit.kr)당 주간 인증서 발급 수에 한도가 있다.
|
||||
- 서브 도메인마다 인증서를 따로 받으므로, 하루에 수십 개를 한꺼번에 연결하면 한도에 걸릴 수 있다.
|
||||
- 많아지면 `*.ibit.kr` 와일드카드 인증서(DNS 인증 필요 — 방법 B 이후 가능)를 검토한다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 메일 (항목 7·8·9)
|
||||
|
||||
도메인 탭의 **메일(MX) 자동 설정** 은 그 도메인 DNS 에 다음을 넣는다.
|
||||
- 받는 메일 MX
|
||||
- SPF·DMARC
|
||||
- Resend(보내는 메일) 서명 레코드(DKIM·반송 MX·SPF)
|
||||
|
||||
| 준비할 것 | 만드는 방법 | 상태 (bear) |
|
||||
|---|---|---|
|
||||
| 메일 발송 키 (Resend) | resend.com 가입 → API Keys 에서 키 발급 → 관리자 콘솔 › 설정 › 외부 연동 접속 정보 › 종류 "메일 발송 (Resend)" | ✅ 등록됨 |
|
||||
| 메일 서버 주소 (MX 호스트) | 관리자 콘솔 › **메일 시스템** 탭의 기본값에 받는 메일 서버 이름을 넣는다 (예: mail-gateway 가 설치된 `mail.arihakdang.com`). 비어 있으면 메일(MX) 자동 설정이 `메일 서버 주소(MX 호스트)가 설정되지 않았습니다…` 로 멈춘다 | ❌ 비어 있음 |
|
||||
| 받는 메일 처리 (mail-gateway 연동) | mail-gateway 서버가 MX 로 받은 메일을 이 서비스의 웹메일로 넘기는 연동. 소스 연동이 아직 끝나지 않아 지금은 **보내기만** 된다(웹메일 상태 "사용 가능 (보내기)") | ❌ 연동 전 |
|
||||
|
||||
**서브 도메인 사용자의 메일**
|
||||
- 주소는 `admin@mycompany.ibit.kr`, 웹메일은 `https://mycompany.ibit.kr/mail` 이다.
|
||||
- DNS 방식이 수동이면 넣을 레코드가 표로 나오므로, 운영자가 hosting.co.kr 에 직접 넣는다(5장).
|
||||
|
||||
---
|
||||
|
||||
## 8. 회원 계정 종류·캐시 (항목 10)
|
||||
|
||||
- **계정 종류**: 관리자 콘솔 › 회원·청구 › 회원 행의 선택 상자에서 고르고 **변경** 을 누른다.
|
||||
- **서브 도메인 전용**: `이름.ibit.kr` 검색·등록·연결·해제만 할 수 있다.
|
||||
- **자체 도메인**: 구매·갱신·DNS 관리를 할 수 있다.
|
||||
- 1.4.0 이전 회원과 관리자는 자체 도메인 계정이다.
|
||||
- **캐시**:
|
||||
- 구매·갱신 금액은 사용자 캐시에서 차감된다.
|
||||
- 잔액이 모자라면 `캐시가 부족합니다 (필요 …, 잔액 …)` 로 구매 전에 막힌다. 회원·청구 탭에서 충전한다.
|
||||
- 표시 단위는 설정 › 캐시 표시 단위(v1.4.1). 1 단위 = 1원.
|
||||
- **등록자 정보**:
|
||||
- 사용자가 처음 구매할 때 도메인 탭 › 등록자 정보(영문 이름·주소·전화·이메일)를 한 번 저장해야 한다.
|
||||
- 저장할 때 Openprovider 에 고객(handle)이 만들어지므로, 항목 1·2 가 준비된 뒤에만 보인다.
|
||||
|
||||
---
|
||||
|
||||
## 9. 준비 후 점검 순서
|
||||
|
||||
1. 외부 연동 접속 정보 › Openprovider › **연결 확인** → 성공.
|
||||
2. 자체 도메인 계정으로 빌더 › 도메인 탭 › 아무 이름 **검색** → 확장자별 "구매 가능 / 사용 중" 과 원화 가격이 보이는지.
|
||||
3. 가격 확인: 표시 가격 ≈ Openprovider 제어판의 원가 × 환율 × 요율(2.5). 다르면 요율·환율 설정 확인.
|
||||
4. 등록자 정보 저장 → "저장했습니다".
|
||||
5. **실제 구매 1회**: 가장 싼 확장자로 시험용 도메인 1개(예: 1년 `.com`).
|
||||
- 확인 창의 금액·잔액을 보고 구매한다.
|
||||
- 사용자 캐시 차감, Openprovider 잔액 차감, 내 도메인 목록에 "DNS 연결이 진행 중" 이 보이는지 확인한다.
|
||||
6. DNS 레코드 열기 → `@`·`www` A 레코드(121.152.149.45)가 있는지. TXT 레코드 하나를 추가·삭제해 본다.
|
||||
7. 사이트에 연결 → 관리 탭에서 발행. 전파 전에는 발행이 막히고 남은 시간이 보인다. 전파 후 발행하면 `https://도메인/` 이 열리는지(SSL 포함).
|
||||
8. (항목 8 준비 후) 메일(MX) 자동 설정 → 레코드 생성 → 웹메일 상태가 "메일시스템 설정중" → "사용 가능 (보내기)" 로 바뀌는지.
|
||||
9. 서브 도메인 계정(새로 가입한 계정)으로 `시험이름.ibit.kr` 중복 확인·등록·사이트 연결 → 발행 → `https://시험이름.ibit.kr/` 이 열리는지(항목 4 필요).
|
||||
|
||||
---
|
||||
|
||||
## 10. 자주 나는 메시지
|
||||
|
||||
| 메시지 | 원인 | 해결 |
|
||||
|---|---|---|
|
||||
| 도메인 서비스가 아직 준비되지 않았습니다 | Openprovider 접속 정보 없음 | 3장 |
|
||||
| Openprovider 로그인 실패 (…) | 사용자 이름·비밀번호 틀림, 허용 IP 아님 | 2장 4·5, 3장 |
|
||||
| 도메인 서비스 오류: … (구매·갱신 때) | Openprovider 잔액 부족, 그 확장자의 추가 요구 정보 등 | Openprovider 제어판에서 잔액·주문 기록 확인 |
|
||||
| 먼저 등록자 정보(…)를 저장하세요 | 사용자가 등록자 정보 미입력 | 도메인 탭 › 등록자 정보 |
|
||||
| 캐시가 부족합니다 (…) | 사용자 캐시 < 가격 | 회원·청구에서 충전 |
|
||||
| 가격이 바뀌었습니다 (… → …) | 확인 창을 연 사이 원가·환율 변동 | 새 금액 확인 후 다시 구매 |
|
||||
| 플랫폼 도메인이 설정되지 않았습니다 | 플랫폼 도메인 비어 있음 (66 은 기본) | 4장 |
|
||||
| 서브 도메인을 등록했는데 주소가 안 열림 | `*.ibit.kr` 와일드카드 없음 | 5장 방법 A |
|
||||
| 메일 서버 주소(MX 호스트)가 설정되지 않았습니다 | 메일 시스템 기본 MX 호스트 비어 있음 | 7장 |
|
||||
| 메일 발송(Resend) 도메인을 준비하지 못했습니다 | Resend 키 없음·틀림 | 7장 |
|
||||
| DNS 연결이 진행 중입니다 … 약 N분 후에 다시 이용해 주세요 | 정상 — 위임(최대 48시간)·레코드 전파 중 | 기다리면 자동으로 풀림 |
|
||||
@@ -0,0 +1,59 @@
|
||||
# 처음 설치하기 — 설치는 짧게, 설정은 웹에서 (v1.5.11)
|
||||
|
||||
AI Webmaker v1.5.11 · 2026-10-14 · 대상: 설치 담당자·운영자
|
||||
|
||||
1.5.11 부터 새 서버 설치는 "스크립트 한 번 → 웹에서 나머지" 로 끝난다. 설치 중에 사람이 넣을 값은 없다(FQDN 조차 몰라도 된다).
|
||||
설치가 끝나면 **`http://<서버 IP>:8795/?token=…`** 가 기본 접속 위치이고, 서비스 첫 화면(`http://<서버 IP>/`, `https://<도메인>/`)도
|
||||
설치 점검표가 끝나기 전에는 같은 설정 페이지(`/setup`)로 넘어간다.
|
||||
|
||||
## 1. 설치 (이 PC 에서 한 줄)
|
||||
|
||||
```bash
|
||||
# 도메인을 아직 모를 때 — 서버 IP 로 설치 (처음 설치 모드: DB 를 복사하지 않음. v1.6.31: 관리자 admin 은 1회용 무작위 비밀번호로 만들어 설치 출력에 한 번 보인다)
|
||||
deploy/remote-install.sh <ssh-host>
|
||||
|
||||
# 도메인이 정해져 있을 때 (처음 설치 모드로 하려면 FRESH=1)
|
||||
FRESH=1 deploy/remote-install.sh <ssh-host> webmaker.example.com
|
||||
```
|
||||
|
||||
- 전제: `<ssh-host>` 에 비밀번호 없는 SSH 와 `sudo -n` 이 된다(Ubuntu 22.04/24.04). node 22, claude/codex CLI, nginx 사이트 블록, systemd 유닛 `webmaker`·`webmaker-setup`, sudoers 규칙, Gitea 컨테이너(docker 가 있으면)를 스크립트가 설치한다.
|
||||
- FQDN 을 생략하면 서버의 대표 IP(공인 IP 가 있으면 그것)로 공개 주소 `http://<IP>` 와 nginx `server_name <IP>` 를 만든다. 나중에 도메인이 정해지면 설정 페이지에서 공개 주소를 바꾸고 nginx 의 `server_name` 을 고친 뒤 `certbot --nginx -d <도메인>` 을 실행한다.
|
||||
- 끝나면 이렇게 알려 준다:
|
||||
|
||||
```
|
||||
== 완료. 설치 중 더 입력할 것은 없습니다 — 나머지는 웹에서 이어집니다:
|
||||
0) 초기 설정 페이지(설치 점검표: 관리자 비밀번호 · AI 엔진 인증 · 공개 주소 → 연동 정보)에서 설정하고 서비스를 재시작:
|
||||
http://203.0.113.10:8795/?token=xxxxxxxx
|
||||
서비스 첫 화면 http://203.0.113.10/ 도 점검표가 끝나기 전에는 같은 설정 페이지(/setup)로 넘어갑니다. 최초 관리자는 설정 페이지에서 만든다(고정 비밀번호 없음)
|
||||
```
|
||||
|
||||
## 2. 웹에서 끝내기 — 설치 점검표
|
||||
|
||||
설정 페이지(별도 포트 8795 또는 서비스의 `/setup`)는 **접속 토큰**(설치 스크립트가 알려 준 주소에 포함, 서버의 `data/setup.token`) 또는 **관리자 계정**으로 들어간다.
|
||||
맨 위 **설치 점검표** 카드에 필수 세 항목과 해결 방법이 보인다. 셋이 모두 "완료" 가 되면 서비스 첫 화면은 더 이상 설정 페이지로 넘어오지 않는다.
|
||||
|
||||
| 항목 | 완료 조건 | 하는 법 |
|
||||
|---|---|---|
|
||||
| 관리자 계정 | 비밀번호를 정한(변경 강제가 아닌) 관리자가 있다 | 설정 페이지 "관리자 계정" 카드에서 최초 관리자를 만들거나, 설치 출력의 1회용 비밀번호로 로그인해 비밀번호를 바꾼다 |
|
||||
| AI 엔진 인증 | Claude Code(서버 로그인·OAuth 토큰·API 키 중 하나) 또는 OpenAI(API 키·codex 로그인)가 쓸 수 있다 | "AI 엔진 인증" 카드. 서버 로그인은 서비스 실행 계정으로 서버에서 `claude` 를 한 번 실행해 브라우저 로그인; 서버에 들어가기 어려우면 다른 PC 에서 `claude setup-token` 으로 만든 OAuth 토큰이나 API 키를 넣는다 |
|
||||
| 서비스 공개 주소 | 설치값(`http://192.168.20.66`)이 아닌 이 서버의 주소 | 설치 스크립트가 넣어 둔다. 도메인이 생기면 "서비스" 묶음에서 `https://<도메인>` 으로 바꾸고 서비스 재시작 |
|
||||
|
||||
참고 항목(운영은 되지만 안내): 메일 발송 키(Resend), 소스 보관(Gitea) 원격, 메일 서버(Arimail). 각 항목의 설치 명령은 같은 화면 아래 "연동 서비스 설치·등록 명령" 에 있다.
|
||||
|
||||
## 3. 최초 관리자 (v1.6.31)
|
||||
|
||||
- 고정 비밀번호 `admin / admin` 은 더 이상 만들지 않는다. 처음 설치(FRESH)는 `node scripts/admin.js bootstrap` 이 관리자 `admin` 을 **1회용 무작위 비밀번호**로 만들어 설치 출력에 한 번 보인다(첫 로그인 뒤 변경 강제). 다른 절차로 설치해 사용자가 없는 서버는 설정 페이지(접속 토큰) › 관리자 계정 › "새 관리자 만들기" 로 아이디·비밀번호를 정한다.
|
||||
- 접속 토큰은 **최초 설정 전용**이다. 비밀번호를 바꾼 관리자가 생기면(설치 점검표 완료) 토큰 로그인은 403 으로 거절되고 관리자 로그인만 받는다 — 누군가 나중에 토큰 주소를 알아내도 쓸 수 없다. 토큰 로그인·관리자 로그인은 IP 당 15분 20회, 계정당 15분 10회 실패로 제한된다.
|
||||
- IP·포트(8795)로 시작하는 설정은 그대로 가능하지만, 공인망 서버는 설치 직후 바로 최초 관리자를 만들어 점검표를 끝내는 것이 안전하다(끝나면 토큰이 무효).
|
||||
|
||||
## 4. 포트·방화벽·포워딩
|
||||
|
||||
- 설정 페이지 포트: 설정 `service.setupPort`(기본 **8795**), 바인드 `service.setupBind`(기본 `0.0.0.0`; 사설망만 열려면 `lan`). 포트가 겹치면 `webmaker-setup` 이 8796, 8797 … 을 차례로 잡고 설정 파일에 기록한다. 접속 주소는 언제든 `node scripts/config.js setup-url`.
|
||||
- 방화벽(ufw)이 켜져 있으면 설치·갱신 스크립트가 그 포트를 허용한다. 접속은 토큰·관리자 로그인·시도 제한(IP 당 15분 10회)으로 보호되지만 관리 화면이므로, 점검이 끝난 뒤에는 `sudo ufw delete allow 8795/tcp` 로 닫거나 바인드를 `lan` 으로 바꿔도 된다(서비스 안 `/setup` 과 관리자 콘솔 탭은 그대로 쓸 수 있다).
|
||||
- 첫 화면 포워딩: 점검표 미완 동안 `/`·`/login` → `/setup`. 끄려면 `service.setupRedirect=off`(`node scripts/config.js set service.setupRedirect off`), 점검 전에 로그인 화면을 열려면 `/login?direct=1`.
|
||||
- 회원가입: `service.allowSignup`(기본 켬). 끄면 로그인 화면의 회원가입 탭이 사라지고 API 는 403 + "회원가입이 닫혀 있습니다". 웹서버(nginx) 규칙으로 `/api/auth/signup` 을 막으면 이유 없는 403 이 되므로 이 설정을 쓴다.
|
||||
|
||||
## 5. 이미 설치된 서버에 1.5.11 올리기
|
||||
|
||||
`deploy/remote-update.sh <ssh-host> [app-dir] [service]` — 코드 갱신과 함께: 설정 페이지 유닛에 본 서비스의 `DATA_ROOT` 를 맞추고, 포트를 8795 로(설정 파일에 `service.setupPort` 가 없으면 기본값이 바뀌므로 자동), ufw 의 3081 규칙을 지우고 8795 를 허용한다.
|
||||
운영 중인 서버는 사용자가 있으므로 기본 관리자가 만들어지지 않고, 점검표도 보통 완료 상태라 포워딩이 생기지 않는다(관리자 콘솔 › 시스템 설정 탭에서 점검표를 볼 수 있다).
|
||||
@@ -0,0 +1,72 @@
|
||||
# 사이트 소스 저장소 — 서비스 서버의 로컬 Gitea
|
||||
|
||||
AI Webmaker v1.4.14 · 2026-10-10 · 대상: 운영자
|
||||
|
||||
## 1. 무엇이 바뀌었나
|
||||
|
||||
| | 1.4.5 까지 | 1.4.14 부터 |
|
||||
|---|---|---|
|
||||
| 사이트 버전(생성·AI 수정·직접 편집·배포본)을 올리는 곳 | 회사 소스 저장소 source.ibit.kr 의 `OZ-Group/WebHosting_AI_agent` (이 도구의 코드 저장소에 `sites/<ID>/…` 브랜치로 섞여 있었음) | 서비스 서버 안의 **전용 Gitea** — 계정 `webmaker`, 비공개 저장소 `webmaker/sites` |
|
||||
| 저장소 접근 | 두 운영 서버(webmaker.ibit.kr, 192.168.20.66)가 같은 저장소에 같은 사이트 ID 로 푸시 → 이력이 서로 엇갈릴 수 있었음 | 서버마다 자기 Gitea 에만 푸시 (서버 밖으로 나가지 않음) |
|
||||
| 설치 | 사람이 Gitea 에 배포 키를 등록 | `deploy/install-gitea.sh` 한 번 (컨테이너·계정·저장소·키·서비스 설정) |
|
||||
|
||||
브랜치 구조(`sites/<ID>/main`, `sites/<ID>/changes/NNNN-종류`)와 빌더의 "이전 버전으로 되돌리기" 동작은 그대로다.
|
||||
|
||||
## 2. 구성
|
||||
|
||||
- Docker 컨테이너 `webmaker-gitea` (`gitea/gitea:1.27`, SQLite). 데이터 `/srv/webmaker-gitea` (서비스 사용자 소유).
|
||||
- HTTP: `<LAN IP>:3030` — 운영자가 브라우저로 버전을 본다. 바깥(공인망)에는 열지 않는다. 가입 금지, 로그인해야 열람.
|
||||
- SSH: `127.0.0.1:2230` — 서비스가 같은 서버에서만 푸시한다.
|
||||
- 계정
|
||||
- `webmaker-admin` — Gitea 관리자(콘솔용)
|
||||
- `webmaker` — 서비스 전용. 저장소 `webmaker/sites` 소유자. 서비스 서버의 키 `~/.ssh/id_ed25519_webmaker_gitea` 가 이 계정의 SSH 키로 등록된다.
|
||||
- 비밀번호는 `/srv/webmaker-gitea/credentials.env` (0600). 화면에 다시 보이지 않는다.
|
||||
- `~/.ssh/config` 에 `Host webmaker-gitea` (127.0.0.1:2230, 위 키) 가 추가된다.
|
||||
- **v1.5.7 부터** 접속 정보는 systemd 유닛이 아니라 설정 파일 `data/config.json` 의 `source.*` 에 들어간다(`source.remote=git@webmaker-gitea:webmaker/sites.git`, `source.web=http://<LAN IP>:3030/webmaker/sites`, `source.apiUrl=http://<LAN IP>:3030`, `source.user`, `source.repo`, API 토큰은 보관소 `gitea`). 1.4.14~1.4.18 의 `SITES_GIT_REMOTE/WEB` 유닛 줄은 1.5.7 첫 기동 때 설정 파일로 옮겨지고, `install-gitea.sh` 를 다시 실행하면 유닛에서 지워진다. 연동 모듈은 `lib/connectors/gitea.js` ([docs/ARIMAIL.md](ARIMAIL.md) 1절) — 초기 설정 페이지·관리자 콘솔 › 시스템 설정에서 값을 보고 바꾸며, API 토큰이 있으면 "저장소 만들기"·"공개키 등록" 버튼도 쓸 수 있다.
|
||||
|
||||
## 3. 설치·마이그레이션 (한 번)
|
||||
|
||||
서비스 서버에서, 서비스 사용자로:
|
||||
|
||||
```bash
|
||||
# webmaker.ibit.kr (bear)
|
||||
/home/master/webmaker/deploy/install-gitea.sh --bind 192.168.20.23 --service webmaker \
|
||||
--migrate-from git@source-webmaker:OZ-Group/WebHosting_AI_agent.git
|
||||
# 192.168.20.66
|
||||
~/work/WebHoting_AI_Agent/deploy/install-gitea.sh --bind 192.168.20.66 --service webhosting-agent \
|
||||
--migrate-from git@source-webhosting:OZ-Group/WebHosting_AI_agent.git
|
||||
```
|
||||
|
||||
하는 일 (다시 실행해도 안전, 있는 것은 건너뜀):
|
||||
1. 컨테이너를 만들고 준비될 때까지 기다린다.
|
||||
2. 계정 2개와 저장소 `webmaker/sites` 를 만든다.
|
||||
3. SSH 키를 만들고(없으면) `webmaker` 계정에 등록, `~/.ssh/config` 에 별칭을 넣고 `git ls-remote` 로 확인한다.
|
||||
4. `--migrate-from` 가 있으면 `scripts/migrate-gitstore.sh` 로 옛 저장소의 `sites/*` 브랜치를 모두 받아 새 저장소에 올리고, 이 서버의 `data/git/<ID>` 저장소들의 `origin` 을 새 주소로 바꾼 뒤 그 최신 상태(main 강제, 변경 브랜치)를 올린다. 마지막에 옛·새 저장소의 브랜치 목록을 비교해 빠진 것을 알린다.
|
||||
5. (v1.5.7) 설정 파일 `data/config.json` 에 `source.remote`/`source.web`/`source.apiUrl`/`source.user`/`source.repo` 를 쓰고, `webmaker` 계정의 API 토큰을 만들어 보관소(`gitea`)에 넣는다. 유닛에 남은 옛 `SITES_GIT_*` 줄은 지운다. 서비스는 호출 때마다 설정을 읽어 푸시 주소가 바로 적용되며, 실행 중인 AI 작업이 없을 때 서비스를 재시작한다(`--no-restart` 로 생략).
|
||||
|
||||
옛 저장소의 브랜치는 **지우지 않는다**. 새 저장소를 확인한 뒤 정리하려면:
|
||||
```bash
|
||||
git ls-remote --heads git@source-webhosting:OZ-Group/WebHosting_AI_agent.git 'refs/heads/sites/*' | awk '{print $2}' | sed 's#refs/heads/##' | xargs git push git@source-webhosting:OZ-Group/WebHosting_AI_agent.git --delete
|
||||
```
|
||||
|
||||
두 서버가 같은 사이트 ID 로 옛 저장소에 서로 다른 커밋을 올린 변경 브랜치가 있으면(2026-10-10 기준 1개), 새 저장소에는 옛 저장소의 커밋이 남고 그 서버의 로컬 커밋은 올라가지 않는다(빠른 진행만). 로컬 `main` 은 강제로 올리므로 "되돌리기" 와 최신 상태에는 영향이 없다.
|
||||
|
||||
## 4. 확인
|
||||
|
||||
- 관리자 콘솔 › 설정 › **소스 저장소 (로컬 Gitea)** › 연결 확인 → "연결 성공 — 저장소에 사이트 브랜치 N개", 아래에 이 서버의 공개키.
|
||||
- 브라우저: `http://<LAN IP>:3030/webmaker/sites` (webmaker-admin 로그인) → 브랜치 목록.
|
||||
- 빌더에서 수정 지시를 한 번 보내면 `sites/<ID>/changes/NNNN-ai-update` 브랜치가 새로 생긴다.
|
||||
|
||||
## 5. 자주 나는 메시지
|
||||
|
||||
| 메시지 | 원인 | 해결 |
|
||||
|---|---|---|
|
||||
| `소스 저장소 서버에 연결할 수 없습니다 — 로컬 Gitea 컨테이너(webmaker-gitea)가 실행 중인지…` | 컨테이너가 멈춤·docker 재시작 중 | `docker start webmaker-gitea` (restart=unless-stopped 라 부팅 때 자동 시작) |
|
||||
| `소스 저장소(Gitea) 접근 거부 — 이 서버의 키가 저장소 계정(webmaker)에 등록되지 않았거나…` | 키 파일이 바뀜·계정 키 삭제 | `deploy/install-gitea.sh` 다시 실행(키를 다시 등록) |
|
||||
| `푸시 주소(source.remote)가 없어 소스를 원격에 보관하지 않습니다` (1.4.x: `SITES_GIT_REMOTE 가 설정되지 않아…`) | 설정 파일에 값이 없음(새 서버) | `deploy/install-gitea.sh --service <유닛 이름>` 또는 초기 설정 페이지 › 소스 보관 연동에 푸시 주소 입력 |
|
||||
| `Gitea API 토큰이 올바르지 않습니다 (401)` | 토큰 폐기·계정 변경 | `install-gitea.sh` 다시 실행(토큰 재생성) 또는 Gitea › 설정 › 애플리케이션에서 토큰을 만들어 설정 페이지 `source.token` 에 입력 |
|
||||
| 되돌리기 목록은 보이는데 Gitea 에 브랜치가 없음 | 푸시 실패분(컨테이너 정지 중 등) | 다음 변경 때 자동 재시도. 서둘러 맞추려면 `scripts/migrate-gitstore.sh <옛> <새>` 를 다시 실행(로컬 저장소를 다시 올림) |
|
||||
|
||||
## 6. 백업
|
||||
|
||||
`/srv/webmaker-gitea` 전체(SQLite DB + 저장소)를 그대로 복사하면 된다. 사이트 파일의 원본은 여전히 서비스 서버의 `data/git/<ID>` + `/var/www/…/<ID>` 이므로, Gitea 는 "볼 수 있는 사본·이력" 역할이다.
|
||||
@@ -0,0 +1,76 @@
|
||||
# 플랫폼 도메인 와일드카드 인증서 (Let's Encrypt)
|
||||
|
||||
AI Webmaker v1.4.14 · 2026-10-10 · 대상: 운영자
|
||||
|
||||
## 1. 두 종류의 인증서
|
||||
|
||||
| 대상 | 인증서 | 발급 방법 | 누가·언제 |
|
||||
|---|---|---|---|
|
||||
| 서브 도메인 사이트 `이름.ibit.kr` (플랫폼 도메인 아래 한 단계) | **와일드카드** `*.ibit.kr` — 이름 `wh-wild-ibit.kr`, 하나로 전체 | DNS 인증(TXT `_acme-challenge.ibit.kr`) | 운영자가 관리자 콘솔에서 한 번 (갱신 60~90일마다) |
|
||||
| 고객 도메인 `example.com`, `www.example.com` | **사이트별** `wh-<사이트ID>` | HTTP 인증(webroot) | 발행(배포) 때 자동 (DNS 가 이 서버를 가리키는 도메인만) |
|
||||
|
||||
- 분리하는 이유: 서브 도메인은 수가 늘어도 인증서 하나(발급 한도 문제 없음), 고객 도메인은 사이트 폐기 때 그 인증서만 지운다.
|
||||
- nginx 설정은 도메인 묶음마다 server 블록을 만든다. 와일드카드가 없으면 서브 도메인은 HTTP 로 열리고, 와일드카드를 발급하면 배포된 서브 도메인 사이트들의 설정을 **재배포 없이** 다시 써 HTTPS 가 된다.
|
||||
- `a.b.ibit.kr` 처럼 두 단계 아래 이름과 `ibit.kr` 자신은 와일드카드로 덮이지 않는다(서브 도메인 등록은 한 단계만 허용하므로 해당 없음).
|
||||
|
||||
## 2. 조건
|
||||
|
||||
| 조건 | 확인 |
|
||||
|---|---|
|
||||
| 플랫폼 도메인 | 관리자 콘솔 › 설정 › 도메인 서비스 › 플랫폼 도메인 (webmaker 는 `ibit.kr`) |
|
||||
| 기본 배포 서버가 서비스 서버 자신(종류 local)·sudo 허용·certbot 설치 | webmaker: "서비스 서버 (bear, webmaker.ibit.kr)" ✅ |
|
||||
| 원격(ssh) 배포 서버 | 지원하지 않음 — 훅이 그 서버에서 실행되어 이 서비스의 DNS 접속 정보를 쓸 수 없다. 그 서버의 서브 도메인은 사이트별 인증서로 발급된다 |
|
||||
| 와일드카드 DNS `*.ibit.kr A 121.152.149.45` | 인증서와 별개로 사이트가 열리려면 필요 ([DOMAIN-SETUP.md](DOMAIN-SETUP.md) 5장). 인증서 발급 자체에는 필요 없다 |
|
||||
| Let's Encrypt 알림 이메일 | 설정 › SSL (비워도 발급됨) |
|
||||
|
||||
## 3. 발급 절차
|
||||
|
||||
관리자 콘솔 › 설정 › **SSL (Let's Encrypt)** › 플랫폼 도메인 와일드카드 인증서
|
||||
|
||||
### DNS 방식이 수동 (ibit.kr — hosting.co.kr DNS, 지금 상태)
|
||||
1. **와일드카드 인증서 발급·갱신** 을 누른다.
|
||||
2. 몇 초 뒤 화면에 **DNS 에 넣을 TXT 레코드** 가 뜬다.
|
||||
| 이름 | 종류 | 값 |
|
||||
|---|---|---|
|
||||
| `_acme-challenge.ibit.kr` (hosting.co.kr 화면에서는 호스트 `_acme-challenge`) | TXT | 화면의 값 (43자) |
|
||||
3. hosting.co.kr 도메인 관리 › ibit.kr › DNS 레코드에 위 TXT 를 추가한다.
|
||||
4. 서비스가 공용 DNS(1.1.1.1·8.8.8.8)에서 그 값을 확인하면 자동으로 인증이 이어진다(최대 40분 대기, 보통 수 분). 끝나면 상태 줄에 "인증서 있음 — *.ibit.kr, 만료 …" 가 보이고, 서브 도메인 사이트들의 nginx 설정이 HTTPS 로 바뀐다.
|
||||
5. TXT 는 지워도 된다. **갱신 때(만료 30일 전부터 certbot 이 시도, 화면은 20일 전 경고)** 새 값이 다시 필요하다 — 같은 절차를 반복한다. 갱신 전에는 기존 인증서가 그대로 쓰인다.
|
||||
|
||||
### DNS 방식이 자동 (플랫폼 도메인 DNS 를 Openprovider 로 옮긴 뒤)
|
||||
- 발급·갱신을 누르면 훅이 Openprovider API 로 TXT 를 넣고 인증 뒤 지운다. certbot 타이머의 자동 갱신도 같은 훅으로 사람 손 없이 된다.
|
||||
- 조건: 설정 › 도메인 서비스 › DNS 방식 "자동(Openprovider)", 외부 연동 접속 정보에 Openprovider 등록.
|
||||
|
||||
## 4. 동작 원리 (코드)
|
||||
|
||||
- `lib/ssl.js`: 상태(`GET /api/admin/ssl`)·발급 시작(`POST /api/admin/ssl/wildcard`, 비동기)·취소. certbot 을 `--manual --preferred-challenges dns` 와 훅으로 실행한다:
|
||||
`certbot certonly --manual --manual-auth-hook "node scripts/acme-dns-hook.js auth" --manual-cleanup-hook "… cleanup" --cert-name wh-wild-<도메인> -d "*.<도메인>" --keep-until-expiring --deploy-hook "systemctl reload nginx"`
|
||||
- `scripts/acme-dns-hook.js`: certbot 이 넘기는 `CERTBOT_DOMAIN`·`CERTBOT_VALIDATION` 으로 TXT 를 넣거나(Openprovider) 대기 파일(`/var/tmp/webmaker-acme/<도메인>.json`)에 적고 공용 DNS 에 보일 때까지 기다린다. 서비스는 그 파일을 읽어 화면에 보인다.
|
||||
- 발급이 끝나면 `lib/ssl.js reconfigureSites` 가 그 플랫폼의 서브 도메인이 연결된 배포 사이트들의 vhost 를 다시 쓴다(`deploy.configureVhost` — 묶음별 인증서).
|
||||
- 결과는 `data/ssl-wildcard.json` 에 남고, certbot 로그는 `/var/log/letsencrypt/letsencrypt.log`.
|
||||
|
||||
## 5. 자주 나는 메시지
|
||||
|
||||
| 메시지 | 원인 | 해결 |
|
||||
|---|---|---|
|
||||
| 플랫폼 도메인이 없습니다 | 설정 › 도메인 서비스 › 플랫폼 도메인 비어 있음 | 도메인 입력 (예: ibit.kr) |
|
||||
| 와일드카드 인증서는 서비스 서버가 배포 서버인 경우(종류 local)에만 발급합니다 | 기본 배포 서버가 원격(ssh) | 서비스 서버를 기본 배포 서버로, 또는 그대로 사이트별 인증서 사용 |
|
||||
| 40분 안에 TXT 가 보이지 않아 중단합니다 (마지막 발급: 실패) | DNS 에 TXT 를 넣지 않았거나 값이 다름·전파 지연 | TXT 를 확인하고 다시 "발급·갱신" (새 값이 뜬다) |
|
||||
| too many certificates / rate limit | Let's Encrypt 발급 한도 (같은 이름 주 5회) | 1주일 뒤 다시. 인증서가 이미 있으면 `--keep-until-expiring` 이라 보통 발생하지 않음 |
|
||||
| 서브 도메인 사이트가 발행 뒤 HTTP 로만 열림 — "운영자가 와일드카드 인증서를 발급하면 HTTPS 로 열립니다" | 와일드카드 인증서 없음 | 위 3장 절차. 발급되면 재배포 없이 HTTPS |
|
||||
| 인증서 있음인데 특정 서브 도메인 사이트가 HTTP | 그 사이트의 nginx 설정이 와일드카드 전 것 | 그 사이트를 재배포하거나, 관리자 콘솔에서 "발급·갱신"(끝나면 설정을 다시 씀) |
|
||||
|
||||
## 6. 서버에 미리 발급해 둔 와일드카드 인증서 (v1.5.17)
|
||||
|
||||
서버를 설치할 때 운영자가 이미 `sudo certbot certonly … -d '*.example.com'` 으로 인증서를 받아 두었다면(이름은 무엇이든: `live/example.com`, `live/wild-example` …) 서비스가 그것을 **인지해 서브 도메인 사이트에 자동으로 쓴다**. 별도 설정은 없다.
|
||||
|
||||
- 감지: 배포 서버의 `/etc/letsencrypt/live/*/fullchain.pem` 을 sudo 로 훑어 SAN 에 `*.<플랫폼 도메인>` 이 있는 인증서를 고른다(`lib/deploy.js findWildcardCert`). 서비스가 발급한 `wh-wild-<도메인>` 이 있으면 그것, 없으면 만료가 가장 먼 것. 플랫폼 도메인은 설정이 없으면 서비스 공개 주소에서 유도된다(webmaker.example.com → example.com).
|
||||
- 적용: 새로 발행하는 사이트는 vhost 구성 때 바로, 이미 HTTP 로 배포된 사이트는 **서비스 기동 20초 뒤** `ssl.autoApply` 가 nginx 설정을 다시 써서 HTTPS 가 된다(재배포 없음). 직접 실행: `node scripts/ssl-wildcard.js --apply`.
|
||||
- 확인: 설치 점검표(초기 설정 페이지·시스템 설정 탭)의 "와일드카드 인증서 (*.도메인)" 항목, `node scripts/ssl-wildcard.js`(설치·갱신 스크립트가 마지막에 출력), 관리자 콘솔 › 설정 › SSL 상태 줄("서버에 미리 발급된 인증서 <이름> 를 씁니다").
|
||||
- 갱신: 그 인증서를 받은 방법(certbot 타이머·DNS 플러그인 등)대로 이어진다. 관리자 콘솔의 "발급·갱신" 은 미리 발급된 인증서가 유효하면 409 로 거절하고(만료 20일 전부터 허용), 강제 갱신이면 서비스 이름(`wh-wild-<도메인>`)으로 따로 발급한다 — 그 뒤에는 서비스 이름이 우선한다.
|
||||
- 조건: 배포 서버 종류 local 또는 ssh 모두 감지한다(ssh 는 그 서버에서 sudo 가 되어야 함). 발급(3장)은 local 만.
|
||||
|
||||
## 7. letsencrypt 밖의 와일드카드 인증서 (v1.5.27)
|
||||
|
||||
감지는 `/etc/letsencrypt/live` 외에 **nginx 설정이 참조하는 `ssl_certificate` 파일**과 **`/etc/ssl/*/*.pem|.crt`**(예: Cloudflare 원본 인증서 `/etc/ssl/cloudflare/example.pem` + `example.key`)의 SAN 도 본다. 키 파일은 같은 폴더의 `<이름>.key` / `privkey.pem` / `<이름>-key.pem` 중 있는 것이며, 키를 못 찾으면 후보에서 뺀다. 사이트 vhost 에는 그 경로가 그대로 들어간다(`ssl_certificate /etc/ssl/cloudflare/example.pem;`).
|
||||
플랫폼 도메인에 인증서가 하나도 없으면(예: webmaker.ariweb.net 의 `*.ariweb.net`) 서브 도메인은 HTTP 로 열린다 — 3장 절차나 위 방법으로 받아 두면 다음 기동 때 자동 적용된다.
|
||||
Reference in New Issue
Block a user