28 กรกฎาคม 2569

Mikrotik script for update cloudflare DNS IP

 
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# USER CONFIGURATION
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# On the first run, leave cfRecordId empty "". 
# Once you get the ID from the Log, paste it here (e.g., :local cfRecordId "abcdef123456...")
:local cfRecordId ""

:local cfToken "YOUR_CLOUDFLARE_API_TOKEN"
:local cfZoneId "YOUR_CLOUDFLARE_ZONE_ID"
:local cfRecordName "ddns.domain.com"
:local wanInterface "ether1"

# Specify the DNS server used to resolve your Cloudflare domain IP (e.g., 1.1.1.1 or 8.8.8.8)
:local dnsServer "1.1.1.1"

# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# AUTOMATIC PROCESS (Determined by the cfRecordId status above)
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:local httpHeaders "Authorization: Bearer $cfToken"

# If cfRecordId is empty, fetch the ID from Cloudflare and print it to the log
:if ([:len $cfRecordId] = 0) do={
    :log warning "CF-DDNS: [STEP 1] cfRecordId is EMPTY. Fetching ID from Cloudflare..."
    
    :local getUrl "https://api.cloudflare.com/client/v4/zones/$cfZoneId/dns_records"
    :local fetchResult ""
    
    :do {
        :set fetchResult [/tool fetch url=$getUrl http-header-field=$httpHeaders as-value output=user]
    } on-error={
        :log error "CF-DDNS: Failed to connect to Cloudflare API. Check your Token/Zone ID."
    }

    :if ($fetchResult != "") do={
        :local responseData ($fetchResult->"data")
        :local idKeyword "\"id\":\""
        :local idPos [:find $responseData $idKeyword]
        
        :if ($idPos > 0) do={
            :local idStart ($idPos + [:len $idKeyword])
            :local idEnd [:find $responseData "\"" $idStart]
            :local fetchedId [:pick $responseData $idStart $idEnd]
            
            # Action required: Print the Record ID to the log for copying
            :log error "=========================================================="
            :log warning "CF-DDNS: [SUCCESS] Your Record ID is: $fetchedId"
            :log warning "CF-DDNS: Please copy this ID and paste it into ':local cfRecordId \"\"' at the top of this script!"
            :log error "=========================================================="
        } else={
            :log error "CF-DDNS: DNS Record Name not found on Cloudflare. Please check your spelling."
        }
    }
} else={
    # If cfRecordId is provided, proceed to check IP and update DDNS
    :local currentIp [/ip address get [/ip address find interface=$wanInterface] address]
    :set currentIp [:pick $currentIp 0 [:find $currentIp "/"]]

    :local cloudflareIp ""
    :do {
        # Resolve the domain using the specific DNS resolver configured above
        :set cloudflareIp [:resolve $cfRecordName server=$dnsServer]
    } on-error={
        :log info "CF-DDNS: Cannot resolve $cfRecordName via $dnsServer, proceeding to check update."
    }

    # Only execute API update if the WAN IP has changed
    :if ($currentIp != $cloudflareIp) do={
        :log info "CF-DDNS: IP changed from $cloudflareIp to $currentIp. Updating Cloudflare..."
        
        :local putUrl ("https://api.cloudflare.com/client/v4/zones/" . $cfZoneId . "/dns_records/" . $cfRecordId)
        :local putData "{\"type\":\"A\",\"name\":\"$cfRecordName\",\"content\":\"$currentIp\",\"ttl\":300,\"proxied\":false}"
        
        :do {
            /tool fetch url=$putUrl http-method=put http-data=$putData http-header-field=$httpHeaders as-value output=user
            :log info "CF-DDNS: Successfully updated $cfRecordName to $currentIp"
        } on-error={
            :log error "CF-DDNS: Failed to update IP on Cloudflare."
        }
    }
}

27 มิถุนายน 2569

Mikrotik container : zerotier router

/interface bridge
add name=containers
/ip address
add address=172.17.0.1/24 interface=containers
/interface veth
add address=172.17.0.2/24 gateway=172.17.0.1 name=veth-zerotier
/interface bridge port
add bridge=containers interface=veth-zerotier

/container envs
add list=zerotier key=ZEROTIER_ONE_GATEWAY_MODE value=both
add list=zerotier key=ZEROTIER_ONE_LOCAL_PHYS value=veth-zerotier
add list=zerotier key=ZEROTIER_ONE_NETWORK_IDS value=0d66ea7479dd0d63
add list=zerotier key=ZEROTIER_ONE_USE_IPTABLES_NFT value=true

/container mounts
add list=zerotier src=/zerotier-lib dst=/var/lib/zerotier-one

/container
add interface=veth-zerotier \
    name=zerotier:router \
    remote-image=ghcr.io/zyclonite/zerotier:router \
    root-dir=/disk1/zerotier \
    envlists=zerotier \
    mountlists=zerotier \
    logging=yes\
    start-on-boot=yes


Ref : https://github.com/zyclonite/zerotier-docker

16 มิถุนายน 2569

dig kdig doggo

DoT
dig @dns.google example.com +tls (+tls-ca)
kdig @dns.google example.com +tls (+tls-ca) (-d)
doggo @tls://dns.google example.com

DoH
dig @dns.google example.com +https (+tls-ca)
kdig @dns.google example.com +https (+tls-ca) (-d)
doggo @https://dns.google/dns-query example.com

dig ไม่มีการตรวจสอบ TLS certificate โดยค่าเริ่มต้น ต้องใช้ +tls-ca 
kdig ไม่มีการตรวจสอบ TLS certificate โดยค่าเริ่มต้น ต้องใช้ +tls-ca และใช้ -d เพื่อแสดงข้อมูล TLS certificate 
doggo มีการตรวจสอบ TLS Certificate โดยค่าเริ่มต้น (Default) อยู่แล้ว 

29 พฤษภาคม 2569

Mikrotik container : PowerDNS DNSdist

/interface veth
add address=10.0.0.20/24 gateway=10.0.0.1 name=veth-dnsdist

/interface bridge port
add bridge=br-lan interface=veth-dnsdist

/file
add name="/dnsdist-conf/dnsdist.conf" \
contents="setLocal('0.0.0.0:53')\n\
\n\
setACL(\"0.0.0.0/0\")\n\
\n\
webserver(\"0.0.0.0:8083\")\n\
setWebserverConfig({\n\
\tpassword=hashPassword(\"admin\"),\n\
\tapiKey=hashPassword(\"admin\"),\n\
\tacl=\"0.0.0.0/0\"\n\
})\n\
\n\
newServer({\n\
\tname=\"cloudflare-DoT\",\n\
\taddress=\"1.1.1.1:853\",\n\
\ttls=\"openssl\",\n\
\tsubjectName=\"1.1.1.1\",\n\
\tvalidateCertificates=true,\n\
\tpool=\"main-pool\",\n\
\n\
\thealthCheckMode = \"lazy\",\n\
\tcheckInterval = 30,\n\
\tmaxCheckFailures = 3,\n\
\trise = 2,\n\
\tcheckName = \"cloudflare-dns.com\"\n\
})\n\
\n\
newServer({\n\
\tname=\"google-DoT\",\n\
\taddress=\"8.8.8.8:853\",\n\
\ttls=\"openssl\",\n\
\tsubjectName=\"8.8.8.8\",\n\
\tvalidateCertificates=true,\n\
\tpool=\"main-pool\",\n\
\n\
\thealthCheckMode = \"lazy\",\n\
\tcheckInterval = 30,\n\
\tmaxCheckFailures = 3,\n\
\trise = 2,\n\
\tcheckName = \"dns.google\"\n\
})\n\
\n\
newServer({\n\
\tname=\"quad9-DoT\",\n\
\taddress=\"9.9.9.9:853\",\n\
\ttls=\"openssl\",\n\
\tsubjectName=\"9.9.9.9\",\n\
\tvalidateCertificates=true,\n\
\tpool=\"main-pool\",\n\
\n\
\thealthCheckMode = \"lazy\",\n\
\tcheckInterval = 30,\n\
\tmaxCheckFailures = 3,\n\
\trise = 2,\n\
\tcheckName = \"dns.quad9.com\"\n\
})\n\
\n\
newServer({\n\
\tname=\"cloudflare-DoH\",\n\
\taddress=\"1.1.1.1:443\",\n\
\ttls=\"openssl\",\n\
\tsubjectName=\"1.1.1.1\",\n\
\tdohPath=\"/dns-query\",\n\
\tvalidateCertificates=true,\n\
\tpool=\"main-pool\",\n\
\n\
\thealthCheckMode = \"lazy\",\n\
\tcheckInterval = 30,\n\
\tmaxCheckFailures = 3,\n\
\trise = 2,\n\
\tcheckName = \"cloudflare-dns.com\"\n\
})\n\
\n\
newServer({\n\
\tname=\"google-DoH\",\n\
\taddress=\"8.8.8.8:443\",\n\
\ttls=\"openssl\",\n\
\tsubjectName=\"8.8.8.8\",\n\
\tdohPath=\"/dns-query\",\n\
\tvalidateCertificates=true,\n\
\tpool=\"main-pool\",\n\
\n\
\thealthCheckMode = \"lazy\",\n\
\tcheckInterval = 30,\n\
\tmaxCheckFailures = 3,\n\
\trise = 2,\n\
\tcheckName = \"dns.google\"\n\
})\n\
\n\
newServer({\n\
\tname=\"quad9-DoH\",\n\
\taddress=\"9.9.9.9:443\",\n\
\ttls=\"openssl\",\n\
\tsubjectName=\"9.9.9.9\",\n\
\tdohPath=\"/dns-query\",\n\
\tvalidateCertificates=true,\n\
\tpool=\"main-pool\",\n\
\n\
\thealthCheckMode = \"lazy\",\n\
\tcheckInterval = 30,\n\
\tmaxCheckFailures = 3,\n\
\trise = 2,\n\
\tcheckName = \"dns.quad9.com\"\n\
})\n\
\n\
setServerPolicy(leastOutstanding)\n\
\n\
global_cache = newPacketCache(10000)\n\
getPool(\"main-pool\"):setCache(global_cache)\n\
\n\
addAction(AllRule(), PoolAction(\"main-pool\"))\n"

/container/mounts
add list=dnsdist-conf src="/dnsdist-conf/dnsdist.conf" dst="/etc/dnsdist/dnsdist.conf"

/container
add name=dnsdist \
interface=veth-dnsdist \
remote-image="registry-1.docker.io/powerdns/dnsdist-19:latest" \
mountlists=dnsdist-conf \
root-dir="/dnsdist-root" \
user=0 \
start-on-boot=yes \
logging=yes

Mikrotik container : AdGuard DNS Proxy

/interface veth
add address=10.0.0.20/24 gateway=10.0.0.1 name=veth-dnsproxy

/interface bridge port
add bridge=br-lan interface=veth-dnsproxy

/container
add name=dnsproxy \
interface=veth-dnsproxy \
remote-image=registry-1.docker.io/adguard/dnsproxy:latest \
root-dir=/dnsproxy \
cmd="--upstream-mode parallel \
-u h3://1.1.1.1/dns-query \
-u h3://8.8.8.8/dns-query \
-u h3://9.9.9.9/dns-query \
-u tls://1.1.1.1 \
-u tls://8.8.8.8 \
-u tls://9.9.9.9 \
-u quic://9.9.9.9" \
start-on-boot=yes \
logging=yes

02 มีนาคม 2569

Crowdsec command

cscli decisions list
ดูรายชื่อ IP ที่โดนแบนอยู่ทั้งหมด (Local + CAPI)

cscli decisions list --origin CAPI
cscli decisions list --origin CAPI | wc -l 
ดูเฉพาะ IP มหาโจรจากส่วนกลาง (Community List)

cscli decisions add --ip 1.2.3.4 --duration 1h
สั่งแบน IP 1.2.3.4 เป็นเวลา 1 ชั่วโมง

cscli decisions delete --ip 1.2.3.4
ปลดแบน IP 1.2.3.4 (Whitelist คืนมา)

cscli decisions delete --all
ล้างรายการแบนทั้งหมด (ใช้ตอนเผลอบล็อกตัวเองยกแผง)

cscli bouncers list
เช็คว่า Traefik Bouncer ยัง Online (✔️) อยู่ไหม

cscli bouncers add <name>
สร้าง API Key ชุดใหม่ (เอาไปใส่ใน Traefik Middleware)

cscli bouncers delete <name>
ลบ Bouncer ที่ไม่ได้ใช้งานแล้ว

cscli capi register
ลงทะเบียนเครื่องนี้กับส่วนกลาง (ทำครั้งแรกครั้งเดียว)

cscli capi status
เพื่อเช็คการเชื่อมต่อส่วนกลาง

cscli metrics
สำคัญมาก! ดูว่า CrowdSec อ่าน Log ไปกี่บรรทัด และบล็อกไปกี่ครั้ง

cscli alerts list
ดูเหตุการณ์ที่ CrowdSec ตรวจพบ (ใคร ทำอะไร ที่ไหน)

cscli alerts inspect <ID>
ดูรายละเอียดเชิงลึกของ Alert นั้นๆ (เช่น Log บรรทัดที่เป็นปัญหา)

cscli hub update
อัปเดตรายชื่อ Scenario ล่าสุด (เหมือน Update Antivirus)

cscli hub upgrade
สังอัปเกรด Scenario ที่ติดตั้งไว้ให้เป็นเวอร์ชันใหม่

cscli collections list
"ดูว่าเราติดตั้งการป้องกันอะไรไว้บ้าง (Traefik, HTTP, etc.)"

cscli collections install <name>
ติดตั้งการป้องกันเพิ่ม (เช่น crowdsecurity/wordpress)

22 ธันวาคม 2568

traefix wildcard SSL with muli provider

docker-compose.yml
services:
  traefik:
    image: traefik:v3.6
    container_name: traefik-test
    restart: unless-stopped
    networks:
      - traefik_net
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    environment:
      - CF_DNS_API_TOKEN=${CLOUDFLARE_TOKEN}
      - SPACESHIP_API_KEY=${SPACESHIP_API_KEY}
      - SPACESHIP_API_SECRET=${SPACESHIP_API_SECRET}
    volumes:
      - ./traefik:/etc/traefik:ro
      - ./letsencrypt:/letsencrypt
    command:
      - --configFile=/etc/traefik/traefik_config.yml

  php-apache:
    image: php:8.4-apache
    container_name: php-apache-test
    restart: unless-stopped
    networks:
      - traefik_net
    volumes:
      - ./php-src:/var/www/html

networks:
  traefik_net:
    driver: bridge


traefik_config.yml
entryPoints:
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: cloudflare-resolver
        domains:
          - main: "domain1.com"
            sans:
              - "*.domain1.com"

providers:
  file:
    filename: "/etc/traefik/dynamic_conf.yml"
    watch: true

certificatesResolvers:
  cloudflare-resolver:
    acme:
      email: your-email@example.com
      storage: /etc/traefik/acme-cloudflare.json
      dnsChallenge:
        provider: cloudflare

  spaceship-resolver:
    acme:
      email: your-email@example.com
      storage: /etc/traefik/acme-spaceship.json
      dnsChallenge:
        provider: spaceship # New in lego/traefik 2025


dynamic_config.yml
http:
  routers:
    # --- CLOUDFLARE DOMAIN (Inherits from Entrypoint) ---
    router1:
      rule: "Host(`app1.domain1.com`)"
      entryPoints: ["websecure"]
      service: "service1"
      tls: {} # Uses cloudflare-resolver & wildcard automatically

    router2:
      rule: "Host(`app2.domain1.com`)"
      entryPoints: ["websecure"]
      service: "service2"
      tls: {} # Uses cloudflare-resolver & wildcard automatically

    # --- SPACESHIP DOMAIN (Manual Override) ---
    router3:
      rule: "Host(`app3.domain2.com`)"
      entryPoints: ["websecure"]
      service: "service3"
      tls:
        certResolver: "spaceship-resolver"
        domains:
          - main: "domain2.com"
            sans: ["*.domain2.com"] # Triggers wildcard issuance

    router4:
      rule: "Host(`app4.domain2.com`)"
      entryPoints: ["websecure"]
      service: "service4"
      tls:
        certResolver: "spaceship-resolver"
        # No domains block needed! It reuses the cert from Router 3
        
  services:
    service1:
      loadBalancer:
        servers: [{ url: "http://172.18.0.11:80" }]
    service2:
      loadBalancer:
        servers: [{ url: "http://172.18.0.12:80" }]
    service3:
      loadBalancer:
        servers: [{ url: "http://172.18.0.13:80" }]
    service4:
      loadBalancer:
        servers: [{ url: "http://172.18.0.14:80" }]