Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
shaunoneill-r7 committed Jul 12, 2023
1 parent 5e09530 commit 2137860
Show file tree
Hide file tree
Showing 10 changed files with 528 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
npm-debug.log
.env
.github
28 changes: 28 additions & 0 deletions .github/workflows/docker-image-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Docker Image CI

on:
push:
branches: [ "master" ]

jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v4
with:
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/cloudflare-ddns:latest
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,6 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# testing docker compose files
docker-compose-test.yml
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:19-alpine

# Create app directory
WORKDIR /usr/src/app

ENV NODE_ENV=production

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --only=production

# Bundle app source
COPY . .

CMD [ "npm", "run", "start" ]
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: '3'
services:
log-tailer:
container_name: cloudflare-ddns
build:
dockerfile: Dockerfile
environment:
- CLOUDFLARE_API_KEY=
- DNS_URL=
- CRON_SCHEDULE=
volumes:
- /var/run/docker.sock:/var/run/docker.sock
labels:
- disableLogging=true
27 changes: 27 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "cloudflare-ddns",
"version": "0.0.1",
"description": "Auto update a cloudflare DNS record with your public IP",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node ./src/index",
"dev": "node -r dotenv/config ./src/index"
},
"repository": {
"type": "git",
"url": "git@github.com-personal:realshaunoneill/cloudflare-ddns.git"
},
"keywords": [
"cloudflare"
],
"author": "Shaun O'Neill",
"license": "ISC",
"dependencies": {
"cron": "^2.3.1",
"node-fetch": "^3.3.1"
},
"devDependencies": {
"dotenv": "^16.3.1"
}
}
196 changes: 196 additions & 0 deletions src/apiUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import fetch from "node-fetch";
import {
VERIFY_CLOUDFLARE_TOKEN_URL,
GET_ZONES_URL,
GET_ZONE_RECORDS_URL,
UPDATE_ZONE_RECORD_URL,
CREATE_ZONE_RECORD_URL,
} from "./constants.js";

const WEBHOOK_URL = process.env.WEBHOOK_URL;
const WEBHOOK_METHOD = process.env.WEBHOOK_METHOD;

const defaultHeaders = {
'Authorization': `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
}

export const verifyCloudflareToken = async () => {
try {
const response = await fetch(VERIFY_CLOUDFLARE_TOKEN_URL, {
method: 'GET',
headers: defaultHeaders,
});

if (response.ok) {
const json = await response.json();
if (json.result && json.result.status === 'active') {
return true;
}
return false;
}

// Check if the response code is 429 (too many requests)
if (response.status === 429) {
throw new Error('Too many requests');
}

// If the response code is not 429, throw an error
throw new Error(response.status);
} catch (error) {
console.error(error);
return false;
}
};

export const getPublicIpAddress = async () => {
try {
const response = await fetch('https://api.ipify.org?format=json', {
method: 'GET',
});

if (response.ok) {
const json = await response.json();
return json;
}

// Check if the response code is 429 (too many requests)
if (response.status === 429) {
throw new Error('Too many requests');
}

// If the response code is not 429, throw an error
throw new Error(response.status);
} catch (error) {
console.error(error);
}
};

export const getCloudflareZones = async () => {
try {
const response = await fetch(GET_ZONES_URL, {
method: 'GET',
headers: defaultHeaders,
});

if (response.ok) {
const json = await response.json();
return json;
}

// Check if the response code is 429 (too many requests)
if (response.status === 429) {
throw new Error('Too many requests');
}

// If the response code is not 429, throw an error
throw new Error(response.status);
} catch (error) {
console.error(error);
}
};

export const getCloudflareZoneRecords = async (zoneId) => {
try {
const response = await fetch(GET_ZONE_RECORDS_URL(zoneId), {
method: 'GET',
headers: defaultHeaders,
});
if (response.ok) {
const json = await response.json();
return json;
}

// Check if the response code is 429 (too many requests)
if (response.status === 429) {
throw new Error('Too many requests');
}

// If the response code is not 429, throw an error
throw new Error(response.status);
} catch (error) {
console.error(error);
}
};

export const updateCloudflareZoneRecord = async (zoneId, recordId, data) => {
try {
const response = await fetch(UPDATE_ZONE_RECORD_URL(zoneId, recordId), {
method: 'PUT',
headers: {
...defaultHeaders,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});

if (response.ok) {
const json = await response.json();
return json;
}

// Check if the response code is 429 (too many requests)
if (response.status === 429) {
throw new Error('Too many requests');
}

// If the response code is not 429, throw an error
throw new Error(response.status);
} catch (error) {
console.error(error);
}
};

export const createCloudflareZoneRecord = async (zoneId, data) => {
try {
const response = await fetch(CREATE_ZONE_RECORD_URL(zoneId), {
method: 'POST',
headers: {
...defaultHeaders,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok) {
const json = await response.json();
return json;
}

// Check if the response code is 429 (too many requests)
if (response.status === 429) {
throw new Error('Too many requests');
}

// If the response code is not 429, throw an error
throw new Error(response.status);
} catch (error) {
console.error(error);
}
};

export const sendWebhookRequest = async (data) => {
if (!WEBHOOK_URL || !WEBHOOK_METHOD) return;

try {
const response = await fetch(WEBHOOK_URL, {
method: WEBHOOK_METHOD,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});

if (response.ok) {
return response.status;
}

// Check if the response code is 429 (too many requests)
if (response.status === 429) {
throw new Error('Too many requests');
}

// If the response code is not 429, throw an error
throw new Error(response.status);
} catch (error) {
console.error(error);
}
};
5 changes: 5 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const VERIFY_CLOUDFLARE_TOKEN_URL = 'https://api.cloudflare.com/client/v4/user/tokens/verify';
export const GET_ZONES_URL = 'https://api.cloudflare.com/client/v4/zones';
export const GET_ZONE_RECORDS_URL = (zoneId) => `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`;
export const UPDATE_ZONE_RECORD_URL = (zoneId, recordId) => `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${recordId}`;
export const CREATE_ZONE_RECORD_URL = (zoneId) => `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`;
Loading

0 comments on commit 2137860

Please sign in to comment.