-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add initial implementation (#1)
- Loading branch information
Showing
21 changed files
with
1,308 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
name: CI | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
lint: | ||
name: Lint | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20 | ||
cache: npm | ||
- run: npm ci && npm --prefix=client ci && npm --prefix=worker ci | ||
- run: npm run lint |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.DS_Store | ||
node_modules | ||
dist | ||
.wrangler |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
**/node_modules | ||
**/dist | ||
**/*.json | ||
**/*.yml | ||
**/*.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"$schema": "https://json.schemastore.org/prettierrc", | ||
"tabWidth": 2, | ||
"semi": false, | ||
"singleQuote": true, | ||
"trailingComma": "none", | ||
"printWidth": 120 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
# dyndns | ||
# Dynamic DNS Using Cloudflare Workers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
**/.DS_Store | ||
|
||
**/.gitignore | ||
**/.gitattributes | ||
|
||
**/node_modules | ||
**/dist | ||
**/.wrangler | ||
|
||
**/Dockerfile | ||
**/.dockerignore | ||
|
||
**/.prettierignore | ||
**/prettierrc.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# check=skip=SecretsUsedInArgOrEnv | ||
|
||
FROM node:20.18.1-alpine AS build | ||
WORKDIR /app | ||
|
||
COPY package*.json ./ | ||
RUN npm ci | ||
|
||
COPY . . | ||
RUN npm run build | ||
|
||
FROM node:20.18.1-alpine AS deploy | ||
WORKDIR /app | ||
|
||
COPY package*.json ./ | ||
RUN npm ci --omit=dev && apk add --no-cache tini | ||
|
||
COPY --from=build /app/dist ./dist | ||
|
||
USER node | ||
|
||
ENV NODE_ENV=production | ||
|
||
ENV DDNS_URL='' | ||
ENV DDNS_SECRET='' | ||
ENV DDNS_UPDATE_INTERVAL='300' | ||
ENV DDNS_REQUEST_TIMEOUT='30' | ||
|
||
ENTRYPOINT ["/sbin/tini", "--"] | ||
CMD ["node", "--enable-source-maps", "dist/main.js"] |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"private": true, | ||
"name": "@meyfa/ddns-client", | ||
"type": "module", | ||
"main": "dist/main.js", | ||
"scripts": { | ||
"build": "tsc", | ||
"lint": "tsc --noEmit" | ||
}, | ||
"engines": { | ||
"node": ">=20", | ||
"npm": ">=9" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "20.17.9", | ||
"typescript": "5.7.2" | ||
}, | ||
"dependencies": { | ||
"@meyfa/ddns": "file:.." | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
export interface UpdateOptions { | ||
url: URL | ||
secret: string | ||
signal?: AbortSignal | ||
} | ||
|
||
export interface UpdateResponse { | ||
ip: string | ||
modified: boolean | ||
} | ||
|
||
export async function update(options: UpdateOptions): Promise<UpdateResponse> { | ||
const res = await fetch(options.url, { | ||
method: 'PUT', | ||
headers: { | ||
Authorization: `Bearer ${options.secret}` | ||
}, | ||
signal: options.signal | ||
}) | ||
|
||
if (!res.ok) { | ||
throw new Error(`Request failed: ${res.status} ${res.statusText}`) | ||
} | ||
|
||
return (await res.json()) as UpdateResponse | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
export interface Env { | ||
DDNS_URL: URL | ||
DDNS_SECRET: string | ||
DDNS_UPDATE_INTERVAL: number | ||
DDNS_REQUEST_TIMEOUT: number | ||
} | ||
|
||
export function validateEnvironment(env: NodeJS.ProcessEnv): Env { | ||
const result = { | ||
DDNS_URL: validateUrl(env, 'DDNS_URL'), | ||
DDNS_SECRET: validateString(env, 'DDNS_SECRET'), | ||
DDNS_UPDATE_INTERVAL: validatePositiveInteger(env, 'DDNS_UPDATE_INTERVAL'), | ||
DDNS_REQUEST_TIMEOUT: validatePositiveInteger(env, 'DDNS_REQUEST_TIMEOUT') | ||
} | ||
|
||
if (result.DDNS_UPDATE_INTERVAL < result.DDNS_REQUEST_TIMEOUT) { | ||
throw new Error('DDNS_UPDATE_INTERVAL must be greater than or equal to DDNS_REQUEST_TIMEOUT') | ||
} | ||
|
||
return result | ||
} | ||
|
||
function validateUrl(env: NodeJS.ProcessEnv, key: string): URL { | ||
const input = env[key] | ||
if (input == null || input === '' || !URL.canParse(input)) { | ||
throw new Error(`${key} must be a valid URL`) | ||
} | ||
return new URL(input) | ||
} | ||
|
||
function validateString(env: NodeJS.ProcessEnv, key: string): string { | ||
const input = env[key] | ||
if (input == null || input === '') { | ||
throw new Error(`${key} is required`) | ||
} | ||
return input | ||
} | ||
|
||
function validatePositiveInteger(env: NodeJS.ProcessEnv, key: string): number { | ||
const input = env[key] | ||
const error = `${key} must be a positive integer` | ||
if (input == null || input === '' || !/^\d+$/.test(input)) { | ||
throw new Error(error) | ||
} | ||
const value = Number.parseInt(input, 10) | ||
if (value <= 0) { | ||
throw new Error(error) | ||
} | ||
return value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
type LogLevel = 'info' | 'error' | ||
|
||
export function log(level: LogLevel, message: string) { | ||
const str = `${new Date().toISOString()} [${level}] ${message}` | ||
if (level === 'error') { | ||
process.stderr.write(str + '\n') | ||
return | ||
} | ||
process.stdout.write(str + '\n') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { update } from './client.js' | ||
import { validateEnvironment } from './config.js' | ||
import { log } from './log.js' | ||
import { setTimeout as delay } from 'node:timers/promises' | ||
|
||
const env = validateEnvironment(process.env) | ||
|
||
const abortController = new AbortController() | ||
for (const signal of ['SIGTERM', 'SIGINT'] as const) { | ||
process.once(signal, (signal) => { | ||
log('info', `Received ${signal}, exiting...`) | ||
abortController.abort() | ||
}) | ||
} | ||
|
||
function run() { | ||
log('info', 'Updating DDNS') | ||
|
||
const signal = AbortSignal.any([abortController.signal, AbortSignal.timeout(env.DDNS_REQUEST_TIMEOUT * 1000)]) | ||
|
||
update({ | ||
url: env.DDNS_URL, | ||
secret: env.DDNS_SECRET, | ||
signal | ||
}) | ||
.then((result) => { | ||
log('info', result.modified ? `Updated IP address: ${result.ip}` : `IP address unchanged: ${result.ip}`) | ||
}) | ||
.catch((err: unknown) => { | ||
log('error', err instanceof Error ? `${err.name}: ${err.message}` : String(err)) | ||
}) | ||
} | ||
|
||
while (!abortController.signal.aborted) { | ||
run() | ||
|
||
try { | ||
await delay(env.DDNS_UPDATE_INTERVAL * 1000, undefined, { signal: abortController.signal }) | ||
} catch (ignored: unknown) { | ||
// aborted | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"compilerOptions": { | ||
"lib": ["ES2022"], | ||
"target": "ES2022", | ||
"module": "NodeNext", | ||
"moduleResolution": "nodenext", | ||
"outDir": "./dist", | ||
"verbatimModuleSyntax": true, | ||
"strict": true, | ||
"esModuleInterop": true, | ||
"skipLibCheck": true, | ||
"allowJs": true, | ||
"declaration": false, | ||
"sourceMap": true | ||
}, | ||
"include": [ | ||
"src" | ||
] | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"private": true, | ||
"name": "@meyfa/ddns", | ||
"scripts": { | ||
"lint": "npm --prefix=./client run lint && npm --prefix=./worker run lint", | ||
"deploy": "npm --prefix=./worker run deploy" | ||
}, | ||
"engines": { | ||
"node": ">=20", | ||
"npm": ">=9" | ||
} | ||
} |
Oops, something went wrong.