diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..2b7be8e
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,21 @@
+name: Publish to NPM
+on:
+ release:
+ types: [created]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Setup Node
+ uses: actions/setup-node@v2
+ with:
+ node-version: '18.x'
+ registry-url: 'https://registry.npmjs.org'
+ - name: Install dependencies and build 🔧
+ run: npm ci && npm run build
+ - name: Publish package on NPM 📦
+ run: npm publish
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/README.md b/README.md
index f9849f3..f4b3646 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,105 @@
### Cloudflare Domains Manager
-Inside the `configuration.yaml` file there is the mapping of every DNS record used in this repo.
+This tool is helpful to manage cloudflare domains using a versionate file.
-For use the script just run under the domains folder:
+## How to use?
+
+Create a folder for your new project:
```
-npm install
-npm run update
-```
-Note:
-This script works only with domains managed by cloudflare.
-You have to create a `.env` file with Cloudflare credentials inside `domains` folder.
-In the configuration, `ttl=1` is the automatic ttl provided by cloudflare.
-In the configuration, `proxied=true` means the record will be proxed by cloudflare system.
-In the configuration, `deleted=true` means the record will be delete from cloudflare. Use it only when you need to delete a dns record. You can remove the dns from the yaml after the deletion.
-Please use load balancers mapping instead of explicit value in DNS records.
-If you have created a new domain in cloudflare you can run this command to get the mandatory zone_id for the configuration file:
+mkdir test-domains
+cd test-domains
+```
+
+Create a new node app:
+```
+npm init
+```
+
+Install this package:
+```
+npm i cloudflare-domains-manager
+```
+
+Create a index.js with this content:
+```
+const CloudflareDomainsManager = require ('cloudflare-domains-manager')
+
+const cdm = new CloudflareDomainsManager();
+cdm.run();
+```
+
+Create a .env file with your clouflare data:
+```
+CF_EMAIL=(your cloudflare email)
+CF_KEY= (your cloudflare token)
```
-node index.js action=list
+you can generate your cloudflare token here: https://dash.cloudflare.com/profile/api-tokens
+
+
+Edit the package.json adding these lines:
+```
+ "main": "index.js",
+ "scripts": {
+ "update": "node index.js action=update",
+ "list": "node index.js action=list"
+ }
+```
+
+install the dependencies:
+```
+npm install
```
-if you want to update a single domain instead of all, just add it as a parameter:
+
+you can now get a list of your domains with:
```
-npm run update uala.it
+npm run list
```
-Dry run mode is supported, just add it as a parameter:
+
+## The configuration file
+
+if it works, you can now create your `configuration.yaml` file starting with something like:
```
-npm run update uala.it --dry-run
+load_balancers:
+ - example-lb: &example-lb example-lb.your-infrastructure.com
+ - example-lb-ip: &example-lb-ip 172.0.0.10
+domains:
+ - name: example.com
+ zone_id: Y0UR_Z0N3_1D
+ dns_records:
+ - name: example.com
+ type: CNAME
+ content: *example-lb
+ ttl: 1
+ proxied: true
+ - name: test.example.com
+ type: A
+ content: *example-lb-ip
+ ttl: 1
+ proxied: true
+ - name: www.example.com
+ type: CNAME
+ content: *example-lb
+ ttl: 1
+ proxied: true
+ - name: tobedeleted.example.com
+ type: CNAME
+ deleted: true
+ content: *example-lb
+ ttl: 1
+ proxied: true
```
+
+In the configuration:
+- `ttl=1` is the automatic ttl provided by cloudflare.
+- `proxied=true` means the record will be proxed by cloudflare system.
+- `deleted=true` means the record will be delete from cloudflare. Use it only when you need to delete a dns record. You can remove the dns from the yaml after the deletion.
+
+Please use load balancers mapping instead of explicit value in DNS records.
+
+
+## Commands list
+
+- `npm run list`: Get the list of all your domains in cloudflare
+- `npm run update`: Update all your cloudflare domains with the content of `configuration.yaml`. Domains that don't exist in the file will be ignored
+- `npm run update example.com`: Update only the domain `example.com`
+- `npm run update example.com --dry-run`: Update the domain `example.com` in dry-run mode (nothing will change in cloudflare)
diff --git a/configuration.yaml.example b/configuration.yaml.example
new file mode 100644
index 0000000..879c29a
--- /dev/null
+++ b/configuration.yaml.example
@@ -0,0 +1,28 @@
+load_balancers:
+ - example-lb: &example-lb example-lb.your-infrastructure.com
+ - example-lb-ip: &example-lb-ip 172.0.0.10
+domains:
+ - name: example.com
+ zone_id: Y0UR_Z0N3_1D
+ dns_records:
+ - name: example.com
+ type: CNAME
+ content: *example-lb
+ ttl: 1
+ proxied: true
+ - name: test.example.com
+ type: A
+ content: *example-lb-ip
+ ttl: 1
+ proxied: true
+ - name: www.example.com
+ type: CNAME
+ content: *example-lb
+ ttl: 1
+ proxied: true
+ - name: tobedeleted.example.com
+ type: CNAME
+ deleted: true
+ content: *example-lb
+ ttl: 1
+ proxied: true
diff --git a/package.json b/package.json
index 624b0e2..0b85d91 100644
--- a/package.json
+++ b/package.json
@@ -4,9 +4,7 @@
"description": "An easy to use tool to manage your cloudflare domains",
"main": "dist/index.js",
"scripts": {
- "build": "npx tsc",
- "update": "npx tsc && node dist/index.js action=update",
- "list": "npx tsc && node dist/index.js action=list"
+ "build": "npx tsc"
},
"author": "",
"license": "MIT",
diff --git a/src/index.ts b/src/index.ts
index 1b1479d..33a6d18 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,167 +1,175 @@
-import fs = require('fs');
-import yaml = require('js-yaml');
+import cloudflare = require('cloudflare');
import colors = require('colors/safe');
import dotenv = require('dotenv')
-import cloudflare = require('cloudflare');
+import fs = require('fs');
+import yaml = require('js-yaml');
dotenv.config();
-var cf = new cloudflare({
- email: process.env.CF_EMAIL,
- key: process.env.CF_KEY
-});
+type Zone = {
+ name: string;
+ action: { id: string };
+ jump_start?: boolean | undefined;
+ type?: "full" | "partial" | undefined;
+};
+
+type ZonesResponse = { result: Zone[] };
-function groupBy(xs: any, f) {
- return xs.reduce((r, v, i, a, k = f(v)) => ((r[k] || (r[k] = [])).push(v), r), {});
-}
+class CloudflareDomainsManager {
-async function getDnsRecords(zoneId: string) {
- return (await cf.dnsRecords.browse(zoneId,{
- page: 1,
- per_page: 5000
- })).result
-}
+ cf: cloudflare;
-async function updateDomains(domain: string, dryRun: boolean) {
- let fileContent = fs.readFileSync('./configuration.yaml', 'utf8');
- let conf: any = yaml.load(fileContent);
- //console.log(conf.domains[0].dns_records);
- //return;
- let domains = conf.domains;
- if (domain) {
- domains = conf.domains.filter(x => x.name == domain);
+ async run() {
+ try {
+ this.cf = new cloudflare({
+ email: process.env.CF_EMAIL,
+ key: process.env.CF_KEY
+ });
+
+ // check cf auth
+ await this.cf.user.read()
+
+ let action: string;
+ if (process.argv[2]) {
+ let split = process.argv[2].split("=");
+ if (split[0] == "action") {
+ action = split[1];
+ }
+ }
+
+ let domain: string;
+ if (action == "update" && process.argv[3]) {
+ domain = process.argv[3];
+ }
+
+ let dryRun = false;
+ if (process.env['npm_config_dry_run'] == 'true') {
+ dryRun = true;
+ }
+
+ switch (action) {
+ case 'update':
+ await this.updateDomains(domain, dryRun);
+ break;
+ case 'list':
+ await this.listDomains();
+ break;
+ default:
+ console.log(colors.red(`[ERROR] Sorry, you have to pass a valid action (update | list).
+ ex: node index.js action=list` ));
+ process.exit(1);
+ }
+ } catch (e) {
+ if (e.message.includes("Forbidden")) {
+ console.error(colors.red("[ERROR] Ops, are you sure to have good CF credentials?"))
+ }
+ else {
+ console.error(e);
+ }
+ }
}
- var dryRunPrefix = "";
- if (dryRun) {
- console.log(colors.yellow(`** DRY RUN MODE! **\n`))
- dryRunPrefix = colors.white("[DRY-RUN] ");
+ groupBy(xs: any, f) {
+ return xs.reduce((r, v, i, a, k = f(v)) => ((r[k] || (r[k] = [])).push(v), r), {});
}
- for (const domain of domains) {
- // console.log(domain)
- console.log(colors.green(`Check domain: ${colors.white(domain.name)}`))
+ async getDnsRecords(zoneId: string) {
+ return (await this.cf.dnsRecords.browse(zoneId,{
+ page: 1,
+ per_page: 5000
+ })).result
+ }
- // load the cloudflare zone dns records
- const CFdnsRecords: any = await getDnsRecords(domain.zone_id);
- // console.log(CFdnsRecords);
+ async updateDomains(domain: string, dryRun: boolean) {
+ let fileContent = fs.readFileSync('./configuration.yaml', 'utf8');
+ let conf: any = yaml.load(fileContent);
+ //console.log(conf.domains[0].dns_records);
+ //return;
+ let domains = conf.domains;
+ if (domain) {
+ domains = conf.domains.filter(x => x.name == domain);
+ }
- // check conf.domains with registered dns records
- console.group();
- for (const dnsRecord of domain.dns_records) {
- // console.log(dnsRecord);
- console.log("Check record: " + colors.blue(dnsRecord.name))
+ var dryRunPrefix = "";
+ if (dryRun) {
+ console.log(colors.yellow(`** DRY RUN MODE! **\n`))
+ dryRunPrefix = colors.white("[DRY-RUN] ");
+ }
+
+ for (const domain of domains) {
+ // console.log(domain)
+ console.log(colors.green(`Check domain: ${colors.white(domain.name)}`))
- // find record in CFdnsRecords
- const CFdnsRecord = CFdnsRecords.find(x => x.name == dnsRecord.name);
+ // load the cloudflare zone dns records
+ const CFdnsRecords: any = await this.getDnsRecords(domain.zone_id);
+ // console.log(CFdnsRecords);
+ // check conf.domains with registered dns records
console.group();
- if (CFdnsRecord) {
- // console.log(CFdnsRecord);
- if (dnsRecord.deleted) {
- if (!dryRun) {
- await cf.dnsRecords.del(domain.zone_id, CFdnsRecord.id);
+ for (const dnsRecord of domain.dns_records) {
+ // console.log(dnsRecord);
+ console.log("Check record: " + colors.blue(dnsRecord.name))
+
+ // find record in CFdnsRecords
+ const CFdnsRecord = CFdnsRecords.find(x => x.name == dnsRecord.name);
+
+ console.group();
+ if (CFdnsRecord) {
+ // console.log(CFdnsRecord);
+ if (dnsRecord.deleted) {
+ if (!dryRun) {
+ await this.cf.dnsRecords.del(domain.zone_id, CFdnsRecord.id);
+ }
+ console.log(dryRunPrefix + colors.bgYellow("Record *Deleted*."));
+ }
+ else if (
+ CFdnsRecord.type == dnsRecord.type &&
+ CFdnsRecord.content == dnsRecord.content &&
+ CFdnsRecord.ttl == dnsRecord.ttl &&
+ CFdnsRecord.proxied == dnsRecord.proxied
+ ) {
+ console.log(dryRunPrefix + colors.green("OK."));
+ }
+ else {
+ if (!dryRun) {
+ await this.cf.dnsRecords.edit(domain.zone_id, CFdnsRecord.id, dnsRecord);
+ }
+ console.log(dryRunPrefix + colors.yellow("Record Updated."));
}
- console.log(dryRunPrefix + colors.bgYellow("Record *Deleted*."));
}
- else if (
- CFdnsRecord.type == dnsRecord.type &&
- CFdnsRecord.content == dnsRecord.content &&
- CFdnsRecord.ttl == dnsRecord.ttl &&
- CFdnsRecord.proxied == dnsRecord.proxied
- ) {
- console.log(dryRunPrefix + colors.green("OK."));
+ else if (dnsRecord.deleted) {
+ console.log(dryRunPrefix + colors.green("Record already deleted."));
}
else {
if (!dryRun) {
- await cf.dnsRecords.edit(domain.zone_id, CFdnsRecord.id, dnsRecord);
+ await this.cf.dnsRecords.add(domain.zone_id, dnsRecord);
}
- console.log(dryRunPrefix + colors.yellow("Record Updated."));
- }
- }
- else if (dnsRecord.deleted) {
- console.log(dryRunPrefix + colors.green("Record already deleted."));
- }
- else {
- if (!dryRun) {
- await cf.dnsRecords.add(domain.zone_id, dnsRecord);
+ console.log(dryRunPrefix + colors.bgYellow("Record Created."));
}
- console.log(dryRunPrefix + colors.bgYellow("Record Created."));
+ console.groupEnd();
}
console.groupEnd();
}
- console.groupEnd();
}
-}
-type Zone = {
- name: string;
- action: { id: string };
- jump_start?: boolean | undefined;
- type?: "full" | "partial" | undefined;
-};
-type ZonesResponse = { result: Zone[] };
-
-async function listDomains() {
-// @ts-ignore
- const zones = ((await cf.zones.browse({
- page: 1,
- per_page: 100
- }) as unknown) as ZonesResponse).result;
- // console.log(zones);
- const accounts = groupBy(zones, zone => zone.account.name);
- // console.log(accounts)
-
- for (const accountName of Object.keys(accounts)) {
- console.log(`account: ${colors.green(accountName)}`)
- for (const zone of accounts[accountName]) {
- console.log(` Domain: ${colors.white(zone.name)}`)
- console.log(` zone_id: ${colors.blue(zone.id)}`)
+ async listDomains() {
+ // @ts-ignore
+ const zones = ((await this.cf.zones.browse({
+ page: 1,
+ per_page: 100
+ }) as unknown) as ZonesResponse).result;
+ // console.log(zones);
+ const accounts = this.groupBy(zones, zone => zone.account.name);
+ // console.log(accounts)
+
+ for (const accountName of Object.keys(accounts)) {
+ console.log(`account: ${colors.green(accountName)}`)
+ for (const zone of accounts[accountName]) {
+ console.log(` Domain: ${colors.white(zone.name)}`)
+ console.log(` zone_id: ${colors.blue(zone.id)}`)
+ }
}
}
}
-void async function main() {
- try {
- // check cf auth
- await cf.user.read()
-
- let action;
- if (process.argv[2]) {
- let split = process.argv[2].split("=");
- if (split[0] == "action") {
- action = split[1];
- }
- }
-
- let domain;
- if (action == "update" && process.argv[3]) {
- domain = process.argv[3];
- }
-
- let dryRun = false;
- if (process.env['npm_config_dry_run'] == 'true') {
- dryRun = true;
- }
-
- switch (action) {
- case 'update':
- await updateDomains(domain, dryRun);
- break;
- case 'list':
- await listDomains();
- break;
- default:
- console.log(colors.red(`[ERROR] Sorry, you have to pass a valid action (update | list).
- ex: node index.js action=list` ));
- process.exit(1);
- }
- } catch (e) {
- if (e.message.includes("Forbidden")) {
- console.error(colors.red("[ERROR] Ops, are you sure to have good CF credentials?"))
- }
- else {
- console.error(e);
- }
- }
-}()
+module.exports = CloudflareDomainsManager;