├── wrangler.toml ├── .prettierrc ├── .gitignore ├── package.json ├── .github └── workflows │ └── stale.yml ├── LICENSE_MIT ├── index.js ├── lib └── cloudflare.js ├── CODE_OF_CONDUCT.md └── README.md /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "dyndns" 2 | type = "webpack" 3 | account_id = "" 4 | workers_dev = true 5 | route = "" 6 | zone_id = "" 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "trailingComma": "all", 5 | "tabWidth": 2, 6 | "printWidth": 80 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /dist 3 | **/*.rs.bk 4 | Cargo.lock 5 | bin/ 6 | pkg/ 7 | wasm-pack.log 8 | worker/ 9 | node_modules/ 10 | .cargo-ok 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "worker", 4 | "version": "1.0.0", 5 | "description": "A template for kick starting a Cloudflare Workers project", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "format": "prettier --write '**/*.{js,css,json,md}'" 10 | }, 11 | "author": "Juan Ignacio Donoso ", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "prettier": "^1.18.2", 15 | "webpack": "^4.43.0", 16 | "webpack-cli": "^3.3.12", 17 | "webpack-node-externals": "^2.5.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | permissions: 12 | issues: write 13 | pull-requests: write 14 | 15 | steps: 16 | - uses: actions/stale@v3 17 | with: 18 | repo-token: ${{ secrets.GITHUB_TOKEN }} 19 | stale-issue-message: | 20 | This issue has been automatically marked as stale because it has been open for 21 days with no activity. It will be closed in 7 days if no further activity (Remove label or comment) occurs. 21 | If we missed this issue please reply to keep it active. 22 | Thanks for being a part of this project. 🙏 23 | stale-issue-label: "Stale" 24 | remove-stale-when-updated: true 25 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Ashley Williams 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | const Cloudflare = require('./lib/cloudflare') 3 | 4 | addEventListener('fetch', event => { 5 | event.respondWith(handleRequest(event.request)) 6 | }) 7 | 8 | /** 9 | * Respond to the request 10 | * @param {Request} request 11 | */ 12 | async function handleRequest(request) { 13 | 14 | // Get Credentials 15 | const b64auth = (request.headers.get("Authorization") || '').split(' ')[1] || '' 16 | const [username, password] = Buffer.from(b64auth, 'base64').toString().split(':') 17 | 18 | if(!username || !password) { 19 | return new Response('', { 20 | headers: { 21 | 'content-type': 'text/plain', 22 | }, 23 | status: 401, 24 | }) 25 | } 26 | 27 | // Parse Url 28 | const requestUrl = url.parse(request.url, true) 29 | 30 | if (requestUrl.pathname != '/update') { 31 | return new Response('', { 32 | headers: { 33 | 'content-type': 'text/plain', 34 | }, 35 | status: 404, 36 | }) 37 | } 38 | const query = requestUrl.query 39 | const hostname = query.hostname 40 | const ip = query.ip 41 | 42 | // Initialize cloudflare 43 | const cf = new Cloudflare({ 44 | token: password, 45 | }) 46 | 47 | const zone = await cf.findZone(username) 48 | const record = await cf.findRecord(zone, hostname) 49 | await cf.updateRecord(record, ip) 50 | 51 | return new Response('ok', { 52 | headers: { 53 | 'content-type': 'text/plain' 54 | }, 55 | status: 200 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /lib/cloudflare.js: -------------------------------------------------------------------------------- 1 | const Cloudflare = function(options) { 2 | this.cloudflare_url = 'https://api.cloudflare.com/client/v4' 3 | 4 | if(options.token) { 5 | this.token = options.token 6 | } 7 | 8 | this.findZone = async name => { 9 | var response = await fetch( 10 | `https://api.cloudflare.com/client/v4/zones?name=${name}`, 11 | { 12 | headers: { 13 | 'Content-Type': 'application/json', 14 | Authorization: `Bearer ${this.token}`, 15 | }, 16 | }, 17 | ) 18 | var body = await response.json() 19 | return body.result[0] 20 | } 21 | 22 | this.findRecord = async (zone, name) => { 23 | var response = await fetch( 24 | `https://api.cloudflare.com/client/v4/zones/${zone.id}/dns_records?name=${name}`, 25 | { 26 | headers: { 27 | 'Content-Type': 'application/json', 28 | Authorization: `Bearer ${this.token}`, 29 | }, 30 | }, 31 | ) 32 | var body = await response.json() 33 | return body.result[0] 34 | } 35 | 36 | this.updateRecord = async (record, value) => { 37 | record.content = value 38 | var response = await fetch( 39 | `https://api.cloudflare.com/client/v4/zones/${record.zone_id}/dns_records/${record.id}`, 40 | { 41 | method: 'PUT', 42 | headers: { 43 | 'Content-Type': 'application/json', 44 | Authorization: `Bearer ${this.token}`, 45 | }, 46 | body: JSON.stringify(record), 47 | }, 48 | ) 49 | var body = await response.json() 50 | return body.result[0] 51 | } 52 | } 53 | 54 | module.exports = Cloudflare -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at ag_dubs@cloudflare.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Dynamic DNS backend for Inadyn 2 | 3 | This is a small application that exposes an *inadyn* compatible API and uses Cloudflare's API to update the IP of one of your DNS A records. 4 | 5 | [inadyn](https://github.com/troglobit/inadyn) is an open-source application that supports different dynamic DNS providers. It's used by the UDM/UDM-pro Unifi OS under the hood to update your public IP. 6 | 7 | ## Why? 8 | 9 | I have an Unifi Dream Machine, and I want to update my home DNS when my IP changes. But the Unifi controller dynamic DNS service doesn't support Cloudflare as one of the providers. 10 | I hope this is a temporary solution as [version 2.6](https://github.com/troglobit/inadyn/releases/tag/v2.6) of Inadyn already natively support Cloudflare. 11 | 12 | ## Create and deploy the worker 13 | 14 | We'll use Cloudflare's wrangler CLI to build and deploy the service worker. 15 | 16 | > You can run the following steps on your computer. You don't need to ssh into the UDM terminal. 17 | 18 | 1. Install. [Wrangler Installation](https://github.com/cloudflare/wrangler#installation) 19 | 20 | ```bash 21 | # Install 22 | npm install -g @cloudflare/wrangler 23 | 24 | 2. Config wrangler by following the instructions. You will need to create an API key with permissions to deploy a worker. 25 | 26 | ```bash 27 | wrangler config 28 | ``` 29 | 30 | > TIP: Use the "Edit Cloudflare Workers" Template, choose "All accounts and the zone you will use. 31 | 32 | Copy the API key, and paste it in the terminal to complete the command above. 33 | 34 | ### Deploy the worker 35 | 36 | You need to add your account id to the provided `wrangler.toml` file. You can get it from the Cloudflare manage worker page (on the sidebar) 37 | 38 | 1. Enable your workers subdomain. This is a subdomain on where the inadyn worker will be exposed. 39 | 40 | ```bash 41 | wrangler subdomain 42 | ``` 43 | 44 | 2. Publish the worker 45 | 46 | ```bash 47 | $ wrangler publish 48 | ✨ Built successfully, built project size is 12 KiB. 49 | ✨ Successfully published your script to 50 | https://dyndns..workers.dev 51 | ``` 52 | 53 | > TIP: That hostname will be used on the next step on the Unifi UI 54 | 55 | 3. Create another API token so the worker can update your records. Go to https://dash.cloudflare.com/profile/api-tokens and select "Create custom token" 56 | 57 | On the permission section, select 58 | 59 | ![image](https://user-images.githubusercontent.com/228037/118659879-b2b66f80-b7bb-11eb-8321-d9be6537a751.png) 60 | 61 | > Copy the API key. You will use it as *password* in the next step 62 | 63 | ## Setup Unifi controller 64 | 65 | Go to your unifi controller Dynamic Dns section and setup the following 66 | 67 | - `service`: choose anything, it doesn't matter 68 | - `hostname`: the name of the record you want to update (e.g. `subdomain.mydomain.com`) 69 | - `username`: the name of the zone where the record is defined. (e.g. `mydomain.com`) 70 | - `password`: a Cloudflare api token with `dns:edit` and `zone:read` permissions 71 | - `server`: the Cloudflare Worker DNS plus the path `dyndns..workers.dev/update?hostname=%h&ip=%i` 72 | 73 | ![image](https://user-images.githubusercontent.com/228037/118659811-a3cfbd00-b7bb-11eb-8798-5a4a313c6188.png) 74 | 75 | 76 | > Note: you might need to escape an extra slash between the hostname of your worker and the path due to a bug in the controller ui. 77 | > `dyndns..workers.dev/\/update?hostname=%h&ip=%i` 78 | > At least as of UDM controller version 6.1.71 you no longer need this 79 | 80 | ## Debugging 81 | 82 | You can login into you UnifiOS terminal and run the following command to se how the configuration is working. 83 | 84 | ``` 85 | inadyn -1 -l debug -n -f /run/inadyn.conf 86 | ``` 87 | 88 | You can also look at the logs from the background process from the UDM 89 | 90 | ``` 91 | cat /var/log/messages | grep inadyn 92 | ``` 93 | --------------------------------------------------------------------------------