Compare commits
19
Commits
dev
..
arm64v8-0.1.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
085de10ca1 | ||
|
|
85575ce073 | ||
|
|
b754494a98 | ||
|
|
6ac866ef5e | ||
|
|
426826532d | ||
|
|
82b132c68e | ||
|
|
3f89d3ddd9 | ||
|
|
ff8e1d9cd9 | ||
|
|
2d5ef49071 | ||
|
|
76a9a7261f | ||
|
|
7e67e971ae | ||
|
|
fad60b7019 | ||
|
|
2eadac07bb | ||
|
|
e129945fde | ||
|
|
2d9bfafffa | ||
|
|
823151dc98 | ||
|
|
7bd574c3a3 | ||
|
|
e7c3a83ae2 | ||
|
|
24a2e20641 |
@@ -1,5 +1,5 @@
|
||||
# Alpine :: Traefik Labels
|
||||
    
|
||||
    
|
||||
|
||||
Run Traefik Labels based on Alpine Linux. Small, lightweight, secure and fast 🏔️
|
||||
|
||||
@@ -49,11 +49,11 @@ docker run --name traefik-rfc2136-demo \
|
||||
-l "traefik/http/services/demo.domain.com/loadbalancer/servers/0/url=http://fqdn-of-docker-node:8080" \
|
||||
-l "rfc2136/WAN/server=ns.domain.com" \
|
||||
-l "rfc2136/WAN/key=algo:name:secret" \
|
||||
-l "rfc2136/WAN/nsupdate=update add foo.domain.com A 175.12.41.11" \
|
||||
-l "rfc2136/WAN/nsupdate=update add foo.domain.com TXT \"hello from traefik-labels!\"" \
|
||||
-l "rfc2136/WAN/nsupdate=update add foo.domain.com 300 A 175.12.41.11" \
|
||||
-l "rfc2136/WAN/nsupdate=update add foo.domain.com 300 TXT \"hello from traefik-labels!\"" \
|
||||
-l "rfc2136/LAN/server=ns.domain.local" \
|
||||
-l "rfc2136/LAN/key=algo:name:secret" \
|
||||
-l "rfc2136/LAN/nsupdate=update add foo.domain.local A 192.168.12.54" \
|
||||
-l "rfc2136/LAN/nsupdate=update add foo.domain.local 300 A 192.168.12.54" \
|
||||
-d 11notes/nginx:stable
|
||||
```
|
||||
|
||||
@@ -70,11 +70,13 @@ docker run --name traefik-rfc2136-demo \
|
||||
| Parameter | Value | Default |
|
||||
| --- | --- | --- |
|
||||
| `TZ` | [Time Zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | null |
|
||||
| `DEBUG` | Show debug information | null |
|
||||
| `LABELS_REDIS_URL` | the redis URL to connect, use rediss:// for SSL | rediss://localhost:6379/0 |
|
||||
| `LABELS_INTERVAL` | in what interval container information is pulled in seconds | 300 |
|
||||
| `LABELS_TIMEOUT` | how many seconds after an interval the keys should stay till they expire in seconds | 30 |
|
||||
| `LABELS_WEBHOOK` | URL to call on each event or poll for each container | |
|
||||
| `LABELS_WEBHOOK_AUTH_BASIC` | Basic authentication to use in the form of "username:password" for the webhook | |
|
||||
| `LABELS_RFC2136_ONLY_UPDATE_ON_CHANGE` | Only update DNS entries if they are new or changed (will use dig on each call to the set server) | false |
|
||||
|
||||
## Parent image
|
||||
* [11notes/node:stable](https://hub.docker.com/r/11notes/node)
|
||||
|
||||
+3
-2
@@ -8,7 +8,6 @@
|
||||
|
||||
# :: Header
|
||||
FROM 11notes/node:stable
|
||||
COPY --from=util /util/node/util.js /labels/lib
|
||||
ENV APP_ROOT=/labels
|
||||
|
||||
# :: Run
|
||||
@@ -16,11 +15,13 @@
|
||||
|
||||
# :: prepare image
|
||||
RUN set -ex; \
|
||||
mkdir -p ${APP_ROOT}; \
|
||||
mkdir -p ${APP_ROOT}/lib; \
|
||||
apk --no-cache add \
|
||||
bind-tools; \
|
||||
apk --no-cache upgrade;
|
||||
|
||||
COPY --from=util /util/node/util.js /labels/lib
|
||||
|
||||
# :: install
|
||||
RUN set -ex; \
|
||||
cd ${APP_ROOT}; \
|
||||
|
||||
+3
-2
@@ -13,7 +13,6 @@
|
||||
|
||||
# :: Header
|
||||
FROM arm64v8/node:20.10.0-alpine3.19
|
||||
COPY --from=util /util/node/util.js /labels/lib
|
||||
COPY --from=qemu qemu-aarch64-static /usr/bin
|
||||
ENV APP_ROOT=/labels
|
||||
|
||||
@@ -22,11 +21,13 @@
|
||||
|
||||
# :: prepare image
|
||||
RUN set -ex; \
|
||||
mkdir -p ${APP_ROOT}; \
|
||||
mkdir -p ${APP_ROOT}/lib; \
|
||||
apk --no-cache add \
|
||||
bind-tools; \
|
||||
apk --no-cache upgrade;
|
||||
|
||||
COPY --from=util /util/node/util.js /labels/lib
|
||||
|
||||
# :: install
|
||||
RUN set -ex; \
|
||||
cd ${APP_ROOT}; \
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
curl -Lo manifest-tool https://github.com/estesp/manifest-tool/releases/download/v0.9.0/manifest-tool-linux-amd64
|
||||
chmod +x manifest-tool
|
||||
./manifest-tool push from-spec multi-arch-manifest.yaml
|
||||
@@ -0,0 +1,11 @@
|
||||
image: 11notes/traefik-labels:latest
|
||||
manifests:
|
||||
- image: 11notes/traefik-labels:amd64-0.1.9
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: 11notes/traefik-labels:arm64v8-0.1.9
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
variant: v8
|
||||
@@ -0,0 +1,27 @@
|
||||
const { spawn } = require('node:child_process');
|
||||
const { elevenLogJSON } = require('/labels/lib/util.js');
|
||||
|
||||
exports.dig = async(resolver, type, record) => {
|
||||
return(new Promise((resolve, reject) => {
|
||||
const dig = spawn('/usr/bin/dig', ['+short', '+answer', type, record, `@${resolver}`]);
|
||||
const io = {stdout:'', stderr:''};
|
||||
dig.stderr.on('data', data => {io.stderr += data.toString()});
|
||||
dig.stdout.on('data', data => {io.stdout += (data.toString()).replace(/[\r\n]*$/ig, '')});
|
||||
dig.on('error', error => {reject(error)});
|
||||
dig.on('close', code =>{
|
||||
elevenLogJSON('debug', {method:'dig()', params:{resolver:resolver, type:type, record:record}, io:io});
|
||||
switch(true){
|
||||
case /no servers could be reached/ig.test(io.stdout): io.stderr += io.stdout; break;
|
||||
}
|
||||
if(code === 0){
|
||||
if(io.stderr.length > 0){
|
||||
reject(io.stderr);
|
||||
}else{
|
||||
resolve(io.stdout);
|
||||
}
|
||||
}else{
|
||||
reject(io.stderr);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
+59
-20
@@ -3,13 +3,23 @@ process.once('SIGINT', () => process.exit(0));
|
||||
|
||||
const Docker = require('dockerode');
|
||||
const redis = require('redis');
|
||||
const { nsupdate } = requore('./nsupdate');
|
||||
const { logJSON } = require('/labels/lib/util.js');
|
||||
const { nsupdate } = require('./nsupdate');
|
||||
const { dig } = require('./dig');
|
||||
const { elevenLogJSON } = require('/labels/lib/util.js');
|
||||
|
||||
const ENV_REDIS_INTERVAL = parseInt(process.env?.LABELS_INTERVAL || 300);
|
||||
const ENV_REDIS_TIMEOUT = parseInt(process.env?.LABELS_TIMEOUT|| 30);
|
||||
const ENV_LABELS_WEBHOOK = process.env?.LABELS_WEBHOOK;
|
||||
const ENV_LABELS_WEBHOOK_AUTH_BASIC = process.env?.LABELS_WEBHOOK_AUTH_BASIC;
|
||||
const ENV_LABELS_RFC2136_ONLY_UPDATE_ON_CHANGE = process.env?.LABELS_RFC2136_ONLY_UPDATE_ON_CHANGE || false;
|
||||
|
||||
elevenLogJSON('info', {config:{
|
||||
LABELS_INTERVAL :ENV_REDIS_INTERVAL,
|
||||
LABELS_TIMEOUT:ENV_REDIS_TIMEOUT,
|
||||
LABELS_WEBHOOK:ENV_LABELS_WEBHOOK,
|
||||
LABELS_WEBHOOK_AUTH_BASIC:((ENV_LABELS_WEBHOOK_AUTH_BASIC) ? true: false),
|
||||
LABELS_RFC2136_ONLY_UPDATE_ON_CHANGE:ENV_LABELS_RFC2136_ONLY_UPDATE_ON_CHANGE
|
||||
}});
|
||||
|
||||
class Labels{
|
||||
#docker;
|
||||
@@ -27,10 +37,6 @@ class Labels{
|
||||
}
|
||||
|
||||
async watch(){
|
||||
if(ENV_LABELS_WEBHOOK){
|
||||
logJSON('info', `using webhook ${ENV_LABELS_WEBHOOK}`);
|
||||
}
|
||||
|
||||
this.#redis = await redis.createClient({
|
||||
url:process.env.LABELS_REDIS_URL,
|
||||
pingInterval:30000,
|
||||
@@ -41,7 +47,6 @@ class Labels{
|
||||
|
||||
this.#redis.connect();
|
||||
this.#redis.on('ready', ()=>{
|
||||
logJSON('info', 'successfully connected to redis');
|
||||
(async() => {
|
||||
await this.dockerPoll();
|
||||
})();
|
||||
@@ -49,7 +54,7 @@ class Labels{
|
||||
});
|
||||
|
||||
this.#redis.on('error', error =>{
|
||||
logJSON('error', error);
|
||||
elevenLogJSON('error', error);
|
||||
});
|
||||
|
||||
setInterval(async() => {
|
||||
@@ -60,7 +65,7 @@ class Labels{
|
||||
dockerEvents(){
|
||||
this.#docker.getEvents({}, (error, data) => {
|
||||
if(error){
|
||||
logJSON('error', error);
|
||||
elevenLogJSON('error', error);
|
||||
}else{
|
||||
data.on('data', async(chunk) => {
|
||||
const event = JSON.parse(chunk.toString('utf8'));
|
||||
@@ -84,7 +89,7 @@ class Labels{
|
||||
}
|
||||
});
|
||||
}catch(e){
|
||||
logJSON('error', e);
|
||||
elevenLogJSON('error', e);
|
||||
}finally{
|
||||
this.#poll = false;
|
||||
}
|
||||
@@ -98,6 +103,7 @@ class Labels{
|
||||
if(!error){
|
||||
const update = (/start|poll/i.test(status)) ? true : false;
|
||||
const container = {
|
||||
name:(data?.Name || data?.id).replace(/^\//i, ''),
|
||||
event:status,
|
||||
labels:{
|
||||
traefik:[],
|
||||
@@ -109,35 +115,37 @@ class Labels{
|
||||
WAN:{server:'', key:'', commands:[]},
|
||||
LAN:{server:'', key:'', commands:[]},
|
||||
}
|
||||
|
||||
elevenLogJSON('info', {container:container.name, event:status, method:'inspect'});
|
||||
|
||||
for(const label in data?.Config?.Labels){
|
||||
switch(true){
|
||||
case /traefik\//i.test(label):
|
||||
if(update){
|
||||
await this.#redis.set(label, data?.Config?.Labels[label], {EX:ENV_REDIS_INTERVAL + ENV_REDIS_TIMEOUT});
|
||||
await this.#redis.set(label, data.Config.Labels[label], {EX:ENV_REDIS_INTERVAL + ENV_REDIS_TIMEOUT});
|
||||
}else{
|
||||
await this.#redis.del(label);
|
||||
}
|
||||
container.labels.traefik[label] = data?.Config?.Labels[label];
|
||||
container.labels.traefik[label] = data.Config.Labels[label];
|
||||
break;
|
||||
|
||||
case /rfc2136\//i.test(label):
|
||||
container.labels.rfc2136[label] = data?.Config?.Labels[label];
|
||||
container.labels.rfc2136[label] = data.Config.Labels[label];
|
||||
const type = ((label.match(/rfc2136\/WAN\//i)) ? 'WAN' : 'LAN');
|
||||
switch(true){
|
||||
case /rfc2136\/\S+\/server/i.test(label):
|
||||
rfc2136[type].server = data?.Config?.Labels[label];
|
||||
rfc2136[type].server = data.Config.Labels[label];
|
||||
break;
|
||||
|
||||
case /rfc2136\/\S+\/key/i.test(label):
|
||||
rfc2136[type].key = data?.Config?.Labels[label];
|
||||
rfc2136[type].key = data.Config.Labels[label];
|
||||
break;
|
||||
|
||||
default:
|
||||
if(!update){
|
||||
data?.Config?.Labels[label] = data?.Config?.Labels[label].replace(/update add/i, 'update delete');
|
||||
data.Config.Labels[label] = data.Config.Labels[label].replace(/update add/i, 'update delete');
|
||||
}
|
||||
rfc2136[type].commands.push(data?.Config?.Labels[label]);
|
||||
rfc2136[type].commands.push(data.Config.Labels[label]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -145,10 +153,24 @@ class Labels{
|
||||
|
||||
for(const type in rfc2136){
|
||||
if(rfc2136[type].commands.length > 0 && rfc2136[type].server && rfc2136[type].key){
|
||||
if(ENV_LABELS_RFC2136_ONLY_UPDATE_ON_CHANGE){
|
||||
for(let i=0; i<rfc2136[type].commands.length; i++){
|
||||
if(await this.rfc2136KnownRecord(rfc2136[type].server, rfc2136[type].commands[i])){
|
||||
rfc2136[type].commands.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
try{
|
||||
await nsupdate(rfc2136[type].server, rfc2136[type].key, rfc2136[type].commands);
|
||||
if(rfc2136[type].commands.length > 0){
|
||||
elevenLogJSON('info', {container:container.name, event:status, method:`nsupdate ${rfc2136[type].server}`});
|
||||
await nsupdate(rfc2136[type].server, rfc2136[type].key, rfc2136[type].commands);
|
||||
}else{
|
||||
if(ENV_LABELS_RFC2136_ONLY_UPDATE_ON_CHANGE){
|
||||
elevenLogJSON('info', {container:container.name, event:status, method:`nsupdate ${rfc2136[type].server} skipped due to same record data`});
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
logJSON('error', e);
|
||||
elevenLogJSON('error', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,7 +181,7 @@ class Labels{
|
||||
(update) ? 'PUT' : 'DELETE'
|
||||
), body:JSON.stringify(container), headers:this.#webhook.headers, signal:AbortSignal.timeout(2500)});
|
||||
}catch(e){
|
||||
logJSON('error', e);
|
||||
elevenLogJSON('error', {method:'fetch(webhook)', error:e});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +190,23 @@ class Labels{
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
async rfc2136KnownRecord(server, nsupdate){
|
||||
const matches = nsupdate.match(/update add (\S+) \d+ (\S+) (\S+)/i);
|
||||
if(matches && matches.length >= 4){
|
||||
try{
|
||||
const record = await dig(server, matches[2], matches[1]);
|
||||
const match = (
|
||||
(record.match(new RegExp(matches[3], 'ig'))) ? true : false
|
||||
);
|
||||
elevenLogJSON('debug', {method:'rfc2136KnownRecord()', params:{server:server, nsupdate:nsupdate}, match:{A:record, B:matches[3], match:match}});
|
||||
return(match);
|
||||
}catch(e){
|
||||
elevenLogJSON('error', e);
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Labels().watch();
|
||||
@@ -1,22 +1,28 @@
|
||||
const { spawn } = require('node:child_process');
|
||||
const { elevenLogJSON } = require('/labels/lib/util.js');
|
||||
|
||||
exports.nsupdate = async(server, key, commands) => {
|
||||
|
||||
commands.unshift(`server ${server}`);
|
||||
commands.push('send');
|
||||
commands.push('quit');
|
||||
|
||||
return(new Promise((resolve, reject) => {
|
||||
elevenLogJSON('debug', {method:'nsupdate()', params:{server:server, key:'******', commands:commands}});
|
||||
const nsupdate = spawn('/usr/bin/nsupdate', ['-y', key]);
|
||||
let errors = '';
|
||||
nsupdate.stderr.on('data', data =>{errors += data.toString()});
|
||||
const io = {stdout:'', stderr:''};
|
||||
nsupdate.stderr.on('data', data => {io.stderr += data.toString()});
|
||||
nsupdate.stdout.on('data', data => {io.stdout += (data.toString()).replace(/[\r\n]*$/ig, '')});
|
||||
nsupdate.on('error', error => {reject(error)});
|
||||
nsupdate.on('exit', code =>{
|
||||
nsupdate.on('close', code =>{
|
||||
if(code === 0){
|
||||
if(errors.length > 0){
|
||||
reject(errors);
|
||||
if(io.stderr.length > 0){
|
||||
reject(io.stderr);
|
||||
}else{
|
||||
resolve(true);
|
||||
resolve(io.stdout);
|
||||
}
|
||||
}else{
|
||||
reject(errors);
|
||||
reject(io.stderr);
|
||||
}
|
||||
});
|
||||
for(const command of commands){
|
||||
|
||||
Reference in New Issue
Block a user