mirror of
https://github.com/cupcakearmy/docker-ddns-cloudflare.git
synced 2025-09-10 15:00:41 +00:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
144d5abeb6 | |||
0b6e6e07dd | |||
|
43bc492c49 | ||
|
b9f77f24ca | ||
18a5a40b43 | |||
37cdc12439 | |||
02c27afd29 | |||
04b3acc509 | |||
a1af82501c | |||
fb7416dbec | |||
54eb94135b | |||
47c0adbfb6 | |||
|
d25c33d015 | ||
|
c007fbb848 | ||
|
f6d825b843 | ||
10b4d5473b | |||
157ce6b7b6 | |||
8904ffcbdd | |||
832bc1993a |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
*
|
||||||
|
!src
|
||||||
|
!tsconfig.json
|
||||||
|
!package.json
|
||||||
|
!pnpm-lock.yaml
|
26
.github/workflows/build.yml
vendored
Normal file
26
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Build Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: build
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
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
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: false
|
42
.github/workflows/docker.yml
vendored
42
.github/workflows/docker.yml
vendored
@@ -1,39 +1,47 @@
|
|||||||
name: ci
|
name: Publish Docker image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
release:
|
||||||
push:
|
types: [published]
|
||||||
tags:
|
|
||||||
- "v*.*.*"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
|
||||||
- name: Docker Labels
|
- name: Docker Labels
|
||||||
id: meta
|
id: meta
|
||||||
uses: crazy-max/ghaction-docker-meta@v2
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: cupcakearmy/ddns-cloudflare
|
images: |
|
||||||
|
cupcakearmy/ddns-cloudflare
|
||||||
|
ghcr.io/${{ github.repository }}
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: docker_build
|
uses: docker/build-push-action@v3
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
- name: Image digest
|
|
||||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.env
|
.env
|
||||||
ip.log
|
ip.log
|
||||||
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
|
.vscode
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
# Required
|
# Required
|
||||||
EMAIL=my@mail.com
|
TOKEn=myapitoken
|
||||||
KEY=my_api_key
|
|
||||||
ZONE=example.org
|
ZONE=example.org
|
||||||
DNS_RECORD=some.example.org
|
DNS_RECORD=some.example.org
|
||||||
|
|
||||||
# Optional
|
# Optional
|
||||||
|
#PROXIED=false
|
||||||
#CRON=* * * * *
|
#CRON=* * * * *
|
||||||
#RESOLVER=http://ipv4.icanhazip.com/
|
#RESOLVER=http://ipv4.icanhazip.com/
|
||||||
|
#LOG_LEVEL=debug
|
||||||
|
82
CHANGELOG.md
82
CHANGELOG.md
@@ -5,13 +5,89 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.3.4] - 2024-02-27
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Updated dependencies.
|
||||||
|
|
||||||
|
## [1.3.3] - 2023-08-20
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Updated dependencies.
|
||||||
|
|
||||||
|
## [1.3.2] - 2023-06-24
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Updated dependencies.
|
||||||
|
|
||||||
|
## [1.3.1] - 2022-11-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `y` is now a valid truthy value.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Updated dependencies.
|
||||||
|
|
||||||
|
## [1.3.0] - 2022-10-20
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Login with username and password as it's considered deprecated.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `LOG_LEVEL` env variable.
|
||||||
|
- Config parsing and validation.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Removed Cloudflare SDK due to outdated and bloated package.
|
||||||
|
|
||||||
|
## [1.2.1] - 2022-05-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for `proxied` parameter thanks to @borisbm.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Updated dependencies.
|
||||||
|
|
||||||
|
## [1.2.0] - 2022-02-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Sigterm and Sigkill hooks for graceful shutdown
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Multistage steps to reduce image size
|
||||||
|
|
||||||
|
## [1.1.1] - 2022-02-07
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Updated dependencies.
|
||||||
|
|
||||||
|
## [1.1.0] - 2021-10-22
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Added typescript for type safety.
|
||||||
|
- Switched to pnpm.
|
||||||
|
|
||||||
## [1.0.0] - 2021-05-05
|
## [1.0.0] - 2021-05-05
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- automated build & tagging
|
- Automated build & tagging.
|
||||||
- arm images
|
- Arm images.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- updated dependencies
|
- Updated dependencies.
|
||||||
|
26
Dockerfile
26
Dockerfile
@@ -1,9 +1,23 @@
|
|||||||
FROM node:14-alpine
|
FROM node:20-alpine as base
|
||||||
|
# PNPM
|
||||||
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
RUN corepack enable
|
||||||
|
# Setup
|
||||||
|
ENV CI=true
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
ADD ./package.json ./pnpm-lock.yaml ./
|
||||||
|
|
||||||
ADD ./package.json yarn.lock ./
|
|
||||||
RUN yarn
|
|
||||||
ADD ./script.js ./
|
|
||||||
|
|
||||||
CMD ["node", "script.js"]
|
FROM base as builder
|
||||||
|
RUN pnpm install
|
||||||
|
ADD . .
|
||||||
|
RUN pnpm run build
|
||||||
|
|
||||||
|
FROM base
|
||||||
|
RUN pnpm install --prod
|
||||||
|
COPY --from=builder /app/dist/ /app/dist/
|
||||||
|
|
||||||
|
STOPSIGNAL SIGTERM
|
||||||
|
|
||||||
|
CMD ["pnpm", "start"]
|
||||||
|
32
README.md
32
README.md
@@ -1,7 +1,8 @@
|
|||||||
# Docker DDNS Cloudflare
|
# Docker DDNS Cloudflare
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
## Features 🌈
|
## Features 🌈
|
||||||
|
|
||||||
@@ -11,19 +12,18 @@
|
|||||||
|
|
||||||
## Quickstart 🚀
|
## Quickstart 🚀
|
||||||
|
|
||||||
1. Get your api token [here](https://dash.cloudflare.com/profile/api-tokens) (Top right -> My Profile -> API Tokens)
|
1. Get your API Token [here](https://dash.cloudflare.com/profile/api-tokens) (Top right -> My Profile -> API Tokens)
|
||||||
|
|
||||||
Click create token. You can then use the Edit DNS Zone template. Give it a name.
|
Click create token. You can then use the Edit DNS Zone template. Give it a name.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
2. Create an `.env` file:
|
2. Create an `.env` file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
EMAIL=my@mail.com
|
TOKEN=mytoken
|
||||||
KEY=my_api_key
|
|
||||||
ZONE=example.org
|
ZONE=example.org
|
||||||
DNS_RECORD=some.example.org
|
DNS_RECORD=some.example.org
|
||||||
|
PROXIED=false
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Run the container
|
3. Run the container
|
||||||
@@ -32,12 +32,6 @@ DNS_RECORD=some.example.org
|
|||||||
docker run -d --name ddns --restart always --env-file .env cupcakearmy/ddns-cloudflare
|
docker run -d --name ddns --restart always --env-file .env cupcakearmy/ddns-cloudflare
|
||||||
```
|
```
|
||||||
|
|
||||||
To check logs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker logs ddns
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker-Copmose
|
### Docker-Copmose
|
||||||
|
|
||||||
With docker-compose:
|
With docker-compose:
|
||||||
@@ -49,6 +43,18 @@ cp .sample.env .env
|
|||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## ENV Reference
|
||||||
|
|
||||||
|
| Env | Default | Description |
|
||||||
|
| ------------ | ------------------------ | ----------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `TOKEN` | | API Token. |
|
||||||
|
| `ZONE` | | Cloudflare zone where your domain is. |
|
||||||
|
| `DNS_RECORD` | | The actual DNS record that should be updated. |
|
||||||
|
| `PROXIED` | `true` | Whether the record is proxied by CloudFlare or not. |
|
||||||
|
| `CRON` | `*/5 * * * *` | Frequency of updates. |
|
||||||
|
| `RESOLVER` | `https://api.ipify.org/` | The endpoint used to determine your public ip. |
|
||||||
|
| `LOG_LEVEL` | `info` | Log level to run at. See [winston](https://github.com/winstonjs/winston#logging-levels) for possible values |
|
||||||
|
|
||||||
## Customize
|
## Customize
|
||||||
|
|
||||||
### Custom CRON
|
### Custom CRON
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
version: '3.7'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
ddns:
|
ddns:
|
||||||
|
25
package.json
25
package.json
@@ -1,14 +1,23 @@
|
|||||||
{
|
{
|
||||||
|
"version": "1.3.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docker:build": "docker build -t cupcakearmy/ddns-cloudflare:lastest .",
|
"build": "tsc",
|
||||||
"docker:push": "docker push cupcakearmy/ddns-cloudflare:lastest",
|
"dev": "tsc -w",
|
||||||
"docker:publish": "yarn run docker:build && yarn run docker:push"
|
"start": "node ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.1",
|
"axios": "^1.6.7",
|
||||||
"cloudflare": "^2.7.0",
|
"dotenv": "^16.4.5",
|
||||||
"cron": "^1.8.2",
|
"node-cron": "^3.0.3",
|
||||||
"dotenv": "^8.2.0"
|
"winston": "^3.11.0"
|
||||||
}
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.20",
|
||||||
|
"@types/node-cron": "^3.0.11",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@8.15.4"
|
||||||
}
|
}
|
||||||
|
310
pnpm-lock.yaml
generated
Normal file
310
pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
axios:
|
||||||
|
specifier: ^1.6.7
|
||||||
|
version: 1.6.7
|
||||||
|
dotenv:
|
||||||
|
specifier: ^16.4.5
|
||||||
|
version: 16.4.5
|
||||||
|
node-cron:
|
||||||
|
specifier: ^3.0.3
|
||||||
|
version: 3.0.3
|
||||||
|
winston:
|
||||||
|
specifier: ^3.11.0
|
||||||
|
version: 3.11.0
|
||||||
|
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^20.11.20
|
||||||
|
version: 20.11.20
|
||||||
|
'@types/node-cron':
|
||||||
|
specifier: ^3.0.11
|
||||||
|
version: 3.0.11
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.3.3
|
||||||
|
version: 5.3.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
/@colors/colors@1.6.0:
|
||||||
|
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
||||||
|
engines: {node: '>=0.1.90'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@dabh/diagnostics@2.0.3:
|
||||||
|
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
|
||||||
|
dependencies:
|
||||||
|
colorspace: 1.1.4
|
||||||
|
enabled: 2.0.0
|
||||||
|
kuler: 2.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@types/node-cron@3.0.11:
|
||||||
|
resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/node@20.11.20:
|
||||||
|
resolution: {integrity: sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==}
|
||||||
|
dependencies:
|
||||||
|
undici-types: 5.26.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/triple-beam@1.3.5:
|
||||||
|
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/async@3.2.5:
|
||||||
|
resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/axios@1.6.7:
|
||||||
|
resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==}
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.5
|
||||||
|
form-data: 4.0.0
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/color-convert@1.9.3:
|
||||||
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
|
dependencies:
|
||||||
|
color-name: 1.1.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/color-name@1.1.3:
|
||||||
|
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/color-name@1.1.4:
|
||||||
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/color-string@1.9.1:
|
||||||
|
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||||
|
dependencies:
|
||||||
|
color-name: 1.1.4
|
||||||
|
simple-swizzle: 0.2.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/color@3.2.1:
|
||||||
|
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
|
||||||
|
dependencies:
|
||||||
|
color-convert: 1.9.3
|
||||||
|
color-string: 1.9.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/colorspace@1.1.4:
|
||||||
|
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
|
||||||
|
dependencies:
|
||||||
|
color: 3.2.1
|
||||||
|
text-hex: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/dotenv@16.4.5:
|
||||||
|
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/enabled@2.0.0:
|
||||||
|
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/fecha@4.2.3:
|
||||||
|
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/fn.name@1.1.0:
|
||||||
|
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/follow-redirects@1.15.5:
|
||||||
|
resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/form-data@4.0.0:
|
||||||
|
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
mime-types: 2.1.35
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/inherits@2.0.4:
|
||||||
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/is-arrayish@0.3.2:
|
||||||
|
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/is-stream@2.0.1:
|
||||||
|
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/kuler@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/logform@2.6.0:
|
||||||
|
resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@colors/colors': 1.6.0
|
||||||
|
'@types/triple-beam': 1.3.5
|
||||||
|
fecha: 4.2.3
|
||||||
|
ms: 2.1.3
|
||||||
|
safe-stable-stringify: 2.4.3
|
||||||
|
triple-beam: 1.4.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/ms@2.1.3:
|
||||||
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/node-cron@3.0.3:
|
||||||
|
resolution: {integrity: sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
dependencies:
|
||||||
|
uuid: 8.3.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/one-time@1.0.0:
|
||||||
|
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
|
||||||
|
dependencies:
|
||||||
|
fn.name: 1.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/proxy-from-env@1.1.0:
|
||||||
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/readable-stream@3.6.2:
|
||||||
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dependencies:
|
||||||
|
inherits: 2.0.4
|
||||||
|
string_decoder: 1.3.0
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/safe-buffer@5.2.1:
|
||||||
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/safe-stable-stringify@2.4.3:
|
||||||
|
resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/simple-swizzle@0.2.2:
|
||||||
|
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
||||||
|
dependencies:
|
||||||
|
is-arrayish: 0.3.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/stack-trace@0.0.10:
|
||||||
|
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/string_decoder@1.3.0:
|
||||||
|
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/text-hex@1.0.0:
|
||||||
|
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/triple-beam@1.4.1:
|
||||||
|
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
|
||||||
|
engines: {node: '>= 14.0.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/typescript@5.3.3:
|
||||||
|
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/undici-types@5.26.5:
|
||||||
|
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/util-deprecate@1.0.2:
|
||||||
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/uuid@8.3.2:
|
||||||
|
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/winston-transport@4.7.0:
|
||||||
|
resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
dependencies:
|
||||||
|
logform: 2.6.0
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
triple-beam: 1.4.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/winston@3.11.0:
|
||||||
|
resolution: {integrity: sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@colors/colors': 1.6.0
|
||||||
|
'@dabh/diagnostics': 2.0.3
|
||||||
|
async: 3.2.5
|
||||||
|
is-stream: 2.0.1
|
||||||
|
logform: 2.6.0
|
||||||
|
one-time: 1.0.0
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
safe-stable-stringify: 2.4.3
|
||||||
|
stack-trace: 0.0.10
|
||||||
|
triple-beam: 1.4.1
|
||||||
|
winston-transport: 4.7.0
|
||||||
|
dev: false
|
69
script.js
69
script.js
@@ -1,69 +0,0 @@
|
|||||||
const { readFileSync, writeFileSync, existsSync } = require('fs')
|
|
||||||
const Cloudflare = require('cloudflare')
|
|
||||||
const Axios = require('axios')
|
|
||||||
const { CronJob } = require('cron')
|
|
||||||
|
|
||||||
require('dotenv').config()
|
|
||||||
|
|
||||||
const { EMAIL, KEY, ZONE, DNS_RECORD, CRON, RESOLVER } = process.env
|
|
||||||
|
|
||||||
const cf = Cloudflare({
|
|
||||||
email: EMAIL,
|
|
||||||
key: KEY,
|
|
||||||
})
|
|
||||||
|
|
||||||
function log(message) {
|
|
||||||
const timestamp = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')
|
|
||||||
console.log(timestamp + '\t' + message)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getCurrentIp() {
|
|
||||||
const { data } = await Axios({
|
|
||||||
url: RESOLVER || 'https://api.ipify.org/',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkIfUpdateIsRequired() {
|
|
||||||
const LOG = './ip.log'
|
|
||||||
const current = await getCurrentIp()
|
|
||||||
const saved = existsSync(LOG) ? readFileSync(LOG, 'utf-8') : null
|
|
||||||
|
|
||||||
if (current === saved) return false
|
|
||||||
else {
|
|
||||||
writeFileSync(LOG, current, { encoding: 'utf-8' })
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function update(newIP) {
|
|
||||||
const { result: zones } = await cf.zones.browse({ name: ZONE })
|
|
||||||
if (!zones.length) throw new Error(`No ZONE "${ZONE}" found`)
|
|
||||||
|
|
||||||
const zoneId = zones[0].id
|
|
||||||
const { result: records } = await cf.dnsRecords.browse(zoneId, { name: DNS_RECORD, type: 'A' })
|
|
||||||
|
|
||||||
const recordAlreadyExists = records.length
|
|
||||||
if (recordAlreadyExists) {
|
|
||||||
const { id: recordId, ...rest } = records[0]
|
|
||||||
await cf.dnsRecords.edit(zoneId, recordId, { ...rest, content: newIP })
|
|
||||||
log(`Updated:\t${DNS_RECORD} → ${newIP} `)
|
|
||||||
} else {
|
|
||||||
await cf.dnsRecords.add(zoneId, {
|
|
||||||
name: DNS_RECORD,
|
|
||||||
type: 'A',
|
|
||||||
content: newIP,
|
|
||||||
})
|
|
||||||
log(`Created:\t${DNS_RECORD} → ${newIP} `)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const changed = await checkIfUpdateIsRequired()
|
|
||||||
log(`Running. Update required: ${!!changed}`)
|
|
||||||
if (changed) await update(changed).catch((e) => console.error(e.message))
|
|
||||||
}
|
|
||||||
|
|
||||||
new CronJob(CRON || '*/5 * * * *', main, null, true, null, null, true)
|
|
||||||
log('Started service.')
|
|
1
src/cache.ts
Normal file
1
src/cache.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const Cache = new Map<'ip' | 'zone', string>()
|
126
src/cloudflare.ts
Normal file
126
src/cloudflare.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import { Cache } from './cache.js'
|
||||||
|
import { Config } from './config.js'
|
||||||
|
import { logger } from './logger.js'
|
||||||
|
|
||||||
|
type DNSRecord = {
|
||||||
|
id: string
|
||||||
|
zone_id: string
|
||||||
|
zone_name: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
content: string
|
||||||
|
proxiable: boolean
|
||||||
|
proxied: boolean
|
||||||
|
ttl: number // 1 means automatic
|
||||||
|
locked: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRecordCreate = Pick<DNSRecord, 'name' | 'type' | 'ttl' | 'proxied' | 'content'>
|
||||||
|
type DNSRecordPatch = Partial<DNSRecordCreate>
|
||||||
|
|
||||||
|
const Base = axios.create({
|
||||||
|
baseURL: 'https://api.cloudflare.com/client/v4',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${Config.auth.token}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const API = {
|
||||||
|
zones: {
|
||||||
|
async findByName(name: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const { data } = await Base({
|
||||||
|
url: '/zones',
|
||||||
|
params: {
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return data.result[0].id
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
records: {
|
||||||
|
async create(zoneId: string, zone: DNSRecordCreate): Promise<void> {
|
||||||
|
await Base({
|
||||||
|
url: `/zones/${zoneId}/dns_records`,
|
||||||
|
method: 'POST',
|
||||||
|
data: zone,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async remove(zoneId: string, recordId: string): Promise<void> {
|
||||||
|
await Base({
|
||||||
|
url: `/zones/${zoneId}/dns_records/${recordId}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async patch(zoneId: string, recordId: string, data: DNSRecordPatch): Promise<void> {
|
||||||
|
await Base({
|
||||||
|
url: `/zones/${zoneId}/dns_records/${recordId}`,
|
||||||
|
method: 'PATCH',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async find(zoneId: string): Promise<DNSRecord[]> {
|
||||||
|
const { data } = await Base({
|
||||||
|
url: `/zones/${zoneId}/dns_records`,
|
||||||
|
params: {
|
||||||
|
type: 'A',
|
||||||
|
name: Config.dns.record,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return data.result as DNSRecord[]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function update(ip: string) {
|
||||||
|
// Find zone
|
||||||
|
if (!Cache.has('zone')) {
|
||||||
|
logger.debug('Fetching zone')
|
||||||
|
const zone = await API.zones.findByName(Config.dns.zone)
|
||||||
|
if (!zone) {
|
||||||
|
logger.error(`Zone "${Config.dns.zone}"" not found`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
Cache.set('zone', zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
const zoneId = Cache.get('zone')!
|
||||||
|
logger.debug(`Zone ID: ${zoneId}`)
|
||||||
|
|
||||||
|
// Set record
|
||||||
|
const records = await API.records.find(zoneId)
|
||||||
|
|
||||||
|
logger.debug('Updating record', ip)
|
||||||
|
|
||||||
|
switch (records.length) {
|
||||||
|
case 0:
|
||||||
|
// Create DNS Record
|
||||||
|
logger.debug('Creating DNS record')
|
||||||
|
await API.records.create(zoneId, {
|
||||||
|
content: ip,
|
||||||
|
name: Config.dns.record,
|
||||||
|
proxied: Config.dns.proxied,
|
||||||
|
ttl: 1,
|
||||||
|
type: 'A',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
case 1:
|
||||||
|
// Only one record, thats fine
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// More than one record, delete all but the first
|
||||||
|
logger.debug('Deleting other DNS records')
|
||||||
|
for (const record of records.slice(1)) {
|
||||||
|
await API.records.remove(zoneId, record.id)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the remaining record
|
||||||
|
await API.records.patch(zoneId, records[0]!.id, { content: ip, proxied: Config.dns.proxied })
|
||||||
|
}
|
50
src/config.ts
Normal file
50
src/config.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { config } from 'dotenv'
|
||||||
|
import { validate } from 'node-cron'
|
||||||
|
|
||||||
|
config()
|
||||||
|
|
||||||
|
function getEnv(key: string, fallback: string, parse?: undefined, validator?: (s: string) => boolean): string
|
||||||
|
function getEnv<T>(key: string, fallback: T, parse: (value: string) => T, validator?: (T: string) => boolean): T
|
||||||
|
function getEnv<T>(
|
||||||
|
key: string,
|
||||||
|
fallback: T,
|
||||||
|
parse?: (value: string) => T,
|
||||||
|
validator?: (s: string | T) => boolean
|
||||||
|
): T | string {
|
||||||
|
const value = process.env[key]
|
||||||
|
const parsed = value === undefined ? fallback : parse ? parse(value) : value
|
||||||
|
if (validator && !validator(parsed)) {
|
||||||
|
console.error(`Invalid or missing value for ${key}: ${value}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBoolean(value: string): boolean {
|
||||||
|
value = value.toLowerCase()
|
||||||
|
const truthy = ['true', 'yes', '1', 'y']
|
||||||
|
return truthy.includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPresent(s: string): boolean {
|
||||||
|
return s.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Config = {
|
||||||
|
version: getEnv('npm_package_version', 'unknown'),
|
||||||
|
logging: {
|
||||||
|
level: getEnv('LOG_LEVEL', 'info'),
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
token: getEnv('TOKEN', '', undefined, isPresent),
|
||||||
|
},
|
||||||
|
dns: {
|
||||||
|
zone: getEnv('ZONE', '', undefined, isPresent),
|
||||||
|
record: getEnv('DNS_RECORD', '', undefined, isPresent),
|
||||||
|
proxied: getEnv('PROXIED', false, parseBoolean),
|
||||||
|
},
|
||||||
|
runner: {
|
||||||
|
cron: getEnv('CRON', '*/5 * * * *', undefined, (s) => validate(s)),
|
||||||
|
resolver: getEnv('RESOLVER', 'https://api.ipify.org'),
|
||||||
|
},
|
||||||
|
}
|
22
src/index.ts
Normal file
22
src/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { schedule } from 'node-cron'
|
||||||
|
import process from 'process'
|
||||||
|
|
||||||
|
import { Config } from './config.js'
|
||||||
|
import { logger } from './logger.js'
|
||||||
|
import { loop } from './runner.js'
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const cron = schedule(Config.runner.cron, loop)
|
||||||
|
logger.info('Started service.', { version: Config.version })
|
||||||
|
logger.debug('Config', Config)
|
||||||
|
|
||||||
|
function terminate() {
|
||||||
|
logger.info('Stopping service.')
|
||||||
|
cron.stop()
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
process.on('SIGINT', terminate)
|
||||||
|
process.on('SIGTERM', terminate)
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
22
src/ip.ts
Normal file
22
src/ip.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import { Cache } from './cache.js'
|
||||||
|
import { Config } from './config.js'
|
||||||
|
|
||||||
|
export async function getCurrentIp(): Promise<string> {
|
||||||
|
const { data } = await axios({
|
||||||
|
url: Config.runner.resolver,
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
return data as string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkIfUpdateIsRequired(newIP: string): boolean {
|
||||||
|
// Check if IP has changed.
|
||||||
|
const current = Cache.get('ip')
|
||||||
|
if (newIP !== current) {
|
||||||
|
Cache.set('ip', newIP)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
12
src/logger.ts
Normal file
12
src/logger.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import winston from 'winston'
|
||||||
|
|
||||||
|
import { Config } from './config.js'
|
||||||
|
|
||||||
|
export const logger = winston.createLogger({
|
||||||
|
level: Config.logging.level,
|
||||||
|
transports: [
|
||||||
|
new winston.transports.Console({
|
||||||
|
format: winston.format.combine(winston.format.timestamp(), winston.format.colorize(), winston.format.simple()),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
18
src/runner.ts
Normal file
18
src/runner.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { update } from './cloudflare.js'
|
||||||
|
import { checkIfUpdateIsRequired, getCurrentIp } from './ip.js'
|
||||||
|
import { logger } from './logger.js'
|
||||||
|
|
||||||
|
export async function loop() {
|
||||||
|
const ip = await getCurrentIp()
|
||||||
|
const changed = checkIfUpdateIsRequired(ip)
|
||||||
|
logger.info(`Running. Update required: ${!!changed}`)
|
||||||
|
if (changed) {
|
||||||
|
try {
|
||||||
|
await update(ip)
|
||||||
|
logger.info('Successfully updated DNS record')
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e)
|
||||||
|
logger.error('Failed to update DNS record')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
202
yarn.lock
202
yarn.lock
@@ -1,202 +0,0 @@
|
|||||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
||||||
# yarn lockfile v1
|
|
||||||
|
|
||||||
|
|
||||||
agent-base@^4.3.0:
|
|
||||||
version "4.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
|
|
||||||
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
|
|
||||||
dependencies:
|
|
||||||
es6-promisify "^5.0.0"
|
|
||||||
|
|
||||||
autocreate@^1.1.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/autocreate/-/autocreate-1.2.0.tgz#522167992c4172c15479e5f88f3486a452a40cba"
|
|
||||||
integrity sha1-UiFnmSxBcsFUeeX4jzSGpFKkDLo=
|
|
||||||
|
|
||||||
axios@^0.21.1:
|
|
||||||
version "0.21.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
|
|
||||||
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
|
|
||||||
dependencies:
|
|
||||||
follow-redirects "^1.10.0"
|
|
||||||
|
|
||||||
capture-stack-trace@^1.0.0:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
|
|
||||||
integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==
|
|
||||||
|
|
||||||
cloudflare@^2.7.0:
|
|
||||||
version "2.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/cloudflare/-/cloudflare-2.7.0.tgz#ff9249a63ff9b662bd863da21e965c8f82f76012"
|
|
||||||
integrity sha512-yhroBpn2VBczFwiRLpyUc431XiWE+xNs8YvtjAsj1vEA1pVwhpje6BzgLW5iZbulmCuPX48lvX8HizeMWk713g==
|
|
||||||
dependencies:
|
|
||||||
autocreate "^1.1.0"
|
|
||||||
es-class "^2.1.1"
|
|
||||||
got "^6.3.0"
|
|
||||||
https-proxy-agent "^2.1.1"
|
|
||||||
object-assign "^4.1.0"
|
|
||||||
should-proxy "^1.0.4"
|
|
||||||
url-pattern "^1.0.3"
|
|
||||||
|
|
||||||
create-error-class@^3.0.0:
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
|
|
||||||
integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=
|
|
||||||
dependencies:
|
|
||||||
capture-stack-trace "^1.0.0"
|
|
||||||
|
|
||||||
cron@^1.8.2:
|
|
||||||
version "1.8.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/cron/-/cron-1.8.2.tgz#4ac5e3c55ba8c163d84f3407bde94632da8370ce"
|
|
||||||
integrity sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==
|
|
||||||
dependencies:
|
|
||||||
moment-timezone "^0.5.x"
|
|
||||||
|
|
||||||
debug@^3.1.0:
|
|
||||||
version "3.2.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
|
||||||
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
|
|
||||||
dependencies:
|
|
||||||
ms "^2.1.1"
|
|
||||||
|
|
||||||
dotenv@^8.2.0:
|
|
||||||
version "8.5.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.5.1.tgz#e3a4c7862daba51b92bce0da5c349f11faa28663"
|
|
||||||
integrity sha512-qC1FbhCH7UH7B+BcRNUDhAk04d/n+tnGGB1ctwndZkVFeehYJOn39pRWWzmdzpFqImyX1KB8tO0DCHLf8yRaYQ==
|
|
||||||
|
|
||||||
duplexer3@^0.1.4:
|
|
||||||
version "0.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
|
||||||
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
|
|
||||||
|
|
||||||
es-class@^2.1.1:
|
|
||||||
version "2.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/es-class/-/es-class-2.1.1.tgz#6ec2243b5a1e3581c0b7eecee0130c9c0d6fb2b7"
|
|
||||||
integrity sha1-bsIkO1oeNYHAt+7O4BMMnA1vsrc=
|
|
||||||
|
|
||||||
es6-promise@^4.0.3:
|
|
||||||
version "4.2.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
|
|
||||||
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
|
|
||||||
|
|
||||||
es6-promisify@^5.0.0:
|
|
||||||
version "5.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
|
||||||
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
|
|
||||||
dependencies:
|
|
||||||
es6-promise "^4.0.3"
|
|
||||||
|
|
||||||
follow-redirects@^1.10.0:
|
|
||||||
version "1.14.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.0.tgz#f5d260f95c5f8c105894491feee5dc8993b402fe"
|
|
||||||
integrity sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg==
|
|
||||||
|
|
||||||
get-stream@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
|
||||||
integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
|
|
||||||
|
|
||||||
got@^6.3.0:
|
|
||||||
version "6.7.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
|
|
||||||
integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=
|
|
||||||
dependencies:
|
|
||||||
create-error-class "^3.0.0"
|
|
||||||
duplexer3 "^0.1.4"
|
|
||||||
get-stream "^3.0.0"
|
|
||||||
is-redirect "^1.0.0"
|
|
||||||
is-retry-allowed "^1.0.0"
|
|
||||||
is-stream "^1.0.0"
|
|
||||||
lowercase-keys "^1.0.0"
|
|
||||||
safe-buffer "^5.0.1"
|
|
||||||
timed-out "^4.0.0"
|
|
||||||
unzip-response "^2.0.1"
|
|
||||||
url-parse-lax "^1.0.0"
|
|
||||||
|
|
||||||
https-proxy-agent@^2.1.1:
|
|
||||||
version "2.2.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
|
|
||||||
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
|
|
||||||
dependencies:
|
|
||||||
agent-base "^4.3.0"
|
|
||||||
debug "^3.1.0"
|
|
||||||
|
|
||||||
is-redirect@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
|
|
||||||
integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
|
|
||||||
|
|
||||||
is-retry-allowed@^1.0.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
|
|
||||||
integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
|
|
||||||
|
|
||||||
is-stream@^1.0.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
|
||||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
|
||||||
|
|
||||||
lowercase-keys@^1.0.0:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
|
|
||||||
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
|
|
||||||
|
|
||||||
moment-timezone@^0.5.x:
|
|
||||||
version "0.5.33"
|
|
||||||
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c"
|
|
||||||
integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==
|
|
||||||
dependencies:
|
|
||||||
moment ">= 2.9.0"
|
|
||||||
|
|
||||||
"moment@>= 2.9.0":
|
|
||||||
version "2.29.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
|
||||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
|
||||||
|
|
||||||
ms@^2.1.1:
|
|
||||||
version "2.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
|
||||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
|
||||||
|
|
||||||
object-assign@^4.1.0:
|
|
||||||
version "4.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
|
||||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
|
||||||
|
|
||||||
prepend-http@^1.0.1:
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
|
||||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
|
||||||
|
|
||||||
safe-buffer@^5.0.1:
|
|
||||||
version "5.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
|
||||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
|
||||||
|
|
||||||
should-proxy@^1.0.4:
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/should-proxy/-/should-proxy-1.0.4.tgz#c805a501abf69539600634809e62fbf238ba35e4"
|
|
||||||
integrity sha1-yAWlAav2lTlgBjSAnmL78ji6NeQ=
|
|
||||||
|
|
||||||
timed-out@^4.0.0:
|
|
||||||
version "4.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
|
|
||||||
integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
|
|
||||||
|
|
||||||
unzip-response@^2.0.1:
|
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
|
|
||||||
integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
|
|
||||||
|
|
||||||
url-parse-lax@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
|
|
||||||
integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=
|
|
||||||
dependencies:
|
|
||||||
prepend-http "^1.0.1"
|
|
||||||
|
|
||||||
url-pattern@^1.0.3:
|
|
||||||
version "1.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/url-pattern/-/url-pattern-1.0.3.tgz#0409292471b24f23c50d65a47931793d2b5acfc1"
|
|
||||||
integrity sha1-BAkpJHGyTyPFDWWkeTF5PStaz8E=
|
|
Reference in New Issue
Block a user