"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // packages/lib/src/types/shared.ts var IPFamily; var init_shared = __esm({ "packages/lib/src/types/shared.ts"() { "use strict"; IPFamily = /* @__PURE__ */ ((IPFamily2) => { IPFamily2["V4"] = "v4"; IPFamily2["V6"] = "v6"; return IPFamily2; })(IPFamily || {}); } }); // packages/lib/src/types/index.ts var init_types = __esm({ "packages/lib/src/types/index.ts"() { "use strict"; init_shared(); } }); // packages/lib/src/dns-provider/dns-provider.ts var init_dns_provider = __esm({ "packages/lib/src/dns-provider/dns-provider.ts"() { "use strict"; } }); // packages/lib/src/dns-provider/errors.ts var import_error_lib, AuthenticationFailedError; var init_errors = __esm({ "packages/lib/src/dns-provider/errors.ts"() { "use strict"; import_error_lib = require("error-lib"); AuthenticationFailedError = class extends import_error_lib.ApplicationError { constructor(message, opts) { super(message ?? "Unauthorized", { cause: opts?.cause, code: opts?.code ?? "E_UNAUTHORIZED" }); Error.captureStackTrace(this, AuthenticationFailedError); Object.setPrototypeOf(this, AuthenticationFailedError.prototype); } }; } }); // packages/lib/src/dns-provider/adapter-digital-ocean.ts var import_axios, import_error_lib2, DigitalOceanAdapter; var init_adapter_digital_ocean = __esm({ "packages/lib/src/dns-provider/adapter-digital-ocean.ts"() { "use strict"; import_axios = __toESM(require("axios")); import_error_lib2 = require("error-lib"); init_errors(); init_types(); DigitalOceanAdapter = class { constructor(opts) { this.opts = opts; } ensureIPFamilyIsCorrect(ipFamily) { if (Object.values(IPFamily).indexOf(ipFamily) === -1) { throw new import_error_lib2.BadRequestError(`Unknown IP address family: '${ipFamily}'`); } } getBaseURL() { return "https://api.digitalocean.com/v2"; } createHTTPClient(baseURL, apiKey) { return import_axios.default.create({ baseURL, headers: { Authorization: `Bearer ${apiKey}` }, timeout: 10 * 1e3, withCredentials: false, validateStatus: () => true }); } ensureAuthenticationWasSuccessful(response) { if (response.status === 401) { throw new AuthenticationFailedError( `${response.data.message} (request id: ${response.data.request_id})`, { code: response.data.id } ); } } ensureRateLimitNotExceeded(response) { if (response.status === 429) { throw new import_error_lib2.ForbiddenError( `${response.data.message} (request id: ${response.data.request_id})`, { code: response.data.id } ); } } ensureRequestWasSuccessful(response, ...successfulStatusCodes) { if (successfulStatusCodes.includes(response.status) === true) { return; } const errorResponse = response; throw new import_error_lib2.ApplicationError( `${errorResponse.data.message} (request id: ${errorResponse.data.request_id}) (${response.status} - ${response.statusText})`, { code: errorResponse.data.id, cause: response } ); } ensureResourceWasFound(response) { if (response.status === 404) { throw new import_error_lib2.NotFoundError( `${response.data.message} (request id: ${response.data.request_id})`, { code: response.data.id } ); } } async fetchDomainByName({ httpClient, domainName }) { const response = await httpClient.get( `/domains/${domainName}` ); this.ensureAuthenticationWasSuccessful(response); this.ensureRateLimitNotExceeded(response); this.ensureResourceWasFound(response); this.ensureRequestWasSuccessful(response, 200); const { domain } = response.data; return { name: domain.name, ttl: domain.ttl, zoneFile: domain.zone_file }; } async fetchDomainRecords({ httpClient, filters }) { const response = await httpClient.get( `/domains/${filters.domainName}/records`, { params: { type: filters.recordType.toUpperCase(), per_page: filters.pageSize, page: filters.pageNumber } } ); this.ensureAuthenticationWasSuccessful(response); this.ensureRateLimitNotExceeded(response); this.ensureResourceWasFound(response); this.ensureRequestWasSuccessful(response, 200); const { domain_records } = response.data; return domain_records.map((_) => ({ ..._ })); } findDomainRecord({ domainRecords, filters }) { return domainRecords.find( (_) => _.name === filters.recordName && _.type.toUpperCase() === filters.recordType.toUpperCase() ); } async createDomainRecord({ httpClient, domainName, domainRecord }) { const response = await httpClient.post( `/domains/${domainName}/records`, { ...domainRecord, type: domainRecord.type.toUpperCase() } ); this.ensureAuthenticationWasSuccessful(response); this.ensureRateLimitNotExceeded(response); this.ensureResourceWasFound(response); this.ensureRequestWasSuccessful(response, 201); const { domain_record: createdDomainRecord } = response.data; return { ...createdDomainRecord }; } async updateDomainRecord({ httpClient, domainName, domainRecord }) { const response = await httpClient.put( `/domains/${domainName}/records/${domainRecord.id}`, { ...domainRecord, type: domainRecord.type.toUpperCase() } ); this.ensureAuthenticationWasSuccessful(response); this.ensureRateLimitNotExceeded(response); this.ensureResourceWasFound(response); this.ensureRequestWasSuccessful(response, 200); const { domain_record: updatedDomainRecord } = response.data; return { ...updatedDomainRecord }; } async updateRecord({ domainName, subdomain, recordType, data, ttl, createIfNotExists, forceUpdate }) { const baseURL = this.getBaseURL(); const httpClient = await this.createHTTPClient(baseURL, this.opts.apiKey); await this.fetchDomainByName({ httpClient, domainName }); const recordName = subdomain ?? "@"; const recordTTL = ttl ?? 300; const domainRecords = await this.fetchDomainRecords({ httpClient, filters: { domainName, recordType, pageNumber: 1, pageSize: 100 } }); let domainRecord = this.findDomainRecord({ domainRecords, filters: { recordName, recordType } }); if (typeof domainRecord === "undefined" && createIfNotExists === true) { domainRecord = await this.createDomainRecord({ httpClient, domainName, domainRecord: { type: recordType, name: recordName, data, ttl: recordTTL } }); return; } if (typeof domainRecord === "undefined") { throw new import_error_lib2.NotFoundError( `No record found on domain '${domainName}' called '${recordName}' with type '${recordType}'` ); } const needUpdate = forceUpdate === true || domainRecord.data !== data || domainRecord.ttl !== recordTTL; if (needUpdate === true) { domainRecord = await this.updateDomainRecord({ httpClient, domainName, domainRecord: { id: domainRecord.id, type: recordType, name: domainRecord.name, data, ttl: recordTTL } }); } } }; } }); // packages/lib/src/dns-provider/index.ts var init_dns_provider2 = __esm({ "packages/lib/src/dns-provider/index.ts"() { "use strict"; init_dns_provider(); init_adapter_digital_ocean(); } }); // packages/digital-ocean/src/cli.ts var require_cli = __commonJS({ "packages/digital-ocean/src/cli.ts"() { "use strict"; var import_yargs = __toESM(require("yargs")); var import_helpers = require("yargs/helpers"); init_types(); init_dns_provider2(); var import_error_lib3 = require("error-lib"); (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).scriptName("dyndns-do").epilog("for more information visit https://github.com/dmanavi/dyndns").option("h", { alias: "help" }).env("DYNDNS_DO").command( ["set"], "Set create/update DNS record", (yargs2) => yargs2.option("domain", { alias: "d", description: "Domain name (e.g. example.com)", demandOption: true, string: true }).option("subdomain", { alias: "s", description: "Subdomain that you want to bind the IP to", string: true }).option("ip-address", { alias: "a", demandOption: true, string: true }).option("ttl", { alias: "t", description: "TTL (in seconds)", default: 300, number: true }).option("ip-version", { describe: "IP address family/version", choices: ["v4" /* V4 */, "v6" /* V6 */], default: "v4" /* V4 */, string: true }).option("api-key", { alias: "k", description: "DO API key (https://cloud.digitalocean.com/account/api/tokens)", demandOption: true, string: true }).option("force-update", { alias: "f", description: "Forcefully update DNS record", boolean: true, default: false }).option("create", { alias: "c", description: "Create if record doesn't exist", boolean: true, default: true }), ({ apiKey, domain: domainName, subdomain, ipVersion, ipAddress, ttl, create: createIfNotExists, forceUpdate }) => { const dnsProvider = new DigitalOceanAdapter({ apiKey }); let recordType; switch (ipVersion) { case "v4" /* V4 */: { recordType = "a" /* A */; break; } case "v6" /* V6 */: { recordType = "aaaa" /* AAAA */; break; } default: { throw new import_error_lib3.ApplicationError( `IP version '${ipVersion}' has no corresponding DNS record type.` ); } } dnsProvider.updateRecord({ domainName, subdomain, recordType, data: ipAddress, ttl, createIfNotExists, forceUpdate }).catch((err) => { console.error("DYNDNS: Failed to create/update DNS record"); console.error(`Digital Ocean: `, err.message); process.exit(1); }); } ).demandCommand(1, "").help().strict().completion().parse(); } }); // packages/digital-ocean/src/index.ts var import_cli = __toESM(require_cli());