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,36 @@
|
||||
'use strict';
|
||||
// ============================================================================
|
||||
// Porkbun DNS 공급자 (v1.5.36, 요구 5) — Porkbun 에 등록(또는 Porkbun 네임서버를 쓰는) 도메인의 레코드 관리
|
||||
//
|
||||
// 접속 정보: 보관소 종류 'porkbun' — 키(secret) = Secret API Key, 설정 API_KEY=… (공개 API 키). 도메인마다 Porkbun 화면에서 "API Access" 를 켜야 한다.
|
||||
// API: https://api.porkbun.com/api/json/v3/ (JSON, 모든 호출이 POST 이고 본문에 apikey·secretapikey 를 넣는다). 시험용 대체 주소: PORKBUN_API_BASE.
|
||||
// 영역은 "계정에 있는 도메인" 그 자체라 따로 만들지 않는다(createZone = 계정 보유 확인). 레코드마다 id 가 있어 지울 때 이름·종류·값으로 id 를 찾는다.
|
||||
// ============================================================================
|
||||
const httpError = (m, s = 400) => Object.assign(new Error(m), { status: s });
|
||||
const base = () => (process.env.PORKBUN_API_BASE || 'https://api.porkbun.com/api/json/v3').replace(/\/+$/, '');
|
||||
function creds() { let x = null; try { x = require('../vault').resolve('porkbun'); } catch (_) {} const key = x && x.settings && (x.settings.API_KEY || x.settings.PORKBUN_API_KEY); if (!x || !x.secret || !key) return null; return { apikey: key, secretapikey: x.secret }; }
|
||||
const ready = () => !!creds();
|
||||
/** 호출: 응답 { status: 'SUCCESS'|'ERROR', message, ... } */
|
||||
async function call(path, body) {
|
||||
const c = creds(); if (!c) throw httpError('Porkbun 접속 정보가 없습니다 (관리자 콘솔 › 설정 › 외부 연동 접속 정보 › Porkbun: API_KEY 설정과 Secret API Key)', 503);
|
||||
let r; try { r = await fetch(`${base()}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...c, ...(body || {}) }), signal: AbortSignal.timeout(25000) }); } catch (e) { throw httpError(`Porkbun 연결 실패: ${e.message}`, 502); }
|
||||
const j = await r.json().catch(() => ({}));
|
||||
if (!r.ok || j.status !== 'SUCCESS') throw httpError(`Porkbun 오류: ${j.message || `HTTP ${r.status}`}`, r.status === 403 || /invalid api|not authorized|api access/i.test(j.message || '') ? 403 : r.status >= 500 ? 502 : 400);
|
||||
return j;
|
||||
}
|
||||
const low = (d) => String(d).toLowerCase().replace(/\.$/, '');
|
||||
async function listZones() { const j = await call('/domain/listAll', { start: '0', includeLabels: 'no' }); return (j.domains || []).map((d) => ({ id: d.domain, name: low(d.domain), status: d.status, expires: d.expireDate || null, nameServers: [] })); }
|
||||
async function getZone(domain) { const z = (await listZones()).find((x) => x.name === low(domain)); if (!z) return null; try { const ns = await call(`/domain/getNs/${low(domain)}`); z.nameServers = ns.ns || []; } catch (_) {} return z; }
|
||||
async function createZone(domain) { const z = await getZone(domain); if (!z) throw httpError(`Porkbun 계정에 ${domain} 이(가) 없습니다 — Porkbun 에서 등록한(또는 이전한) 도메인만 관리할 수 있습니다`, 404); return z; }
|
||||
async function deleteZone(domain) { const recs = await rawRecords(domain); for (const r of recs) if (!(r.type === 'NS' && r.rel === '')) await call(`/dns/delete/${low(domain)}/${r.id}`); return { ok: true, existed: true, cleared: recs.length }; }
|
||||
async function rawRecords(domain) { const j = await call(`/dns/retrieve/${low(domain)}`); const d = low(domain); return (j.records || []).map((r) => { const n = low(r.name); return { id: r.id, rel: n === d ? '' : n.endsWith('.' + d) ? n.slice(0, -(d.length + 1)) : n, type: r.type, value: r.content, ttl: Number(r.ttl) || 600, prio: r.prio != null && r.prio !== '' ? Number(r.prio) : undefined }; }); }
|
||||
async function listRecords(domain) { return (await rawRecords(domain)).map(({ rel, type, value, ttl, prio }) => ({ name: rel, type, value, ttl, prio })); }
|
||||
async function updateRecords(domain, { add = [], remove = [] }) {
|
||||
let n = 0;
|
||||
if (remove.length) { const cur = await rawRecords(domain); for (const r of remove) { const hit = cur.find((x) => x.rel === String(r.name || '') && x.type === r.type && String(x.value) === String(r.value)); if (hit) { await call(`/dns/delete/${low(domain)}/${hit.id}`); n += 1; } } }
|
||||
for (const r of add) { await call(`/dns/create/${low(domain)}`, { name: r.name || '', type: r.type, content: r.value, ttl: String(Math.max(600, r.ttl || 600)), ...(r.prio != null ? { prio: String(r.prio) } : {}) }); n += 1; }
|
||||
return { ok: true, changes: n };
|
||||
}
|
||||
async function nameServers(domain) { const j = await call(`/domain/getNs/${low(domain)}`); return j.ns || []; }
|
||||
async function setNameServers(domain, ns) { await call(`/domain/updateNs/${low(domain)}`, { ns }); return { ok: true }; }
|
||||
module.exports = { id: 'porkbun', label: 'Porkbun', ready, creds, call, listZones, getZone, createZone, deleteZone, listRecords, updateRecords, nameServers, setNameServers };
|
||||
Reference in New Issue
Block a user