Category: Code

Namecheap DDNS Updates via DD-WRT

DD-WRT has limit support for different DDNS update mechanisms.
A simple workaround is the following script.

Login to your router and go to Administration > Commands

Add the following to the startup script, modifying it for your details

cat < /tmp/namecheap.sh
KEY="YOUR NAME CHEAP DIRTY LONG KEY"
DDNS_DOMAIN="feeditout.com"
DDNS_HOST="dave-pc"
IP=\$(curl ifconfig.co)

curl -s -o temp.html "http://dynamicdns.park-your-domain.com/update?domain=\$DDNS_DOMAIN&password=\$KEY&host=\$DDNS_HOST&ip=\$IP"
EOF

chmod +x /tmp/namecheap.sh
/tmp/namecheap.sh

Finally, go to the Administration > Management and add a crontab to update it hourly.

0 */1 * * * /tmp/namecheap.sh

Enjoy ๐Ÿ˜‰


Golang script to pull bandwidth usage from digiweb

I wrote the following script to pull broadband usage form digiweb.

Its fairly hilarious that digiweb are so badly organized.
They are still using smart telecom forums for customers to access pertinent data.
Furthermore the connection is http rather than https.

Hereโ€™s to passing un-encrypted passwords over the wire! ๐Ÿ™‚

Digiweb figures are exactly 10% more that what DD-WRT reports. Interesting observation ๐Ÿ˜‰

Enjoy ๐Ÿ˜‰

package main

import (
    "fmt"
    "strconv"
    "net/http"
    "net/http/cookiejar"
    "crypto/tls"
    "io/ioutil"
    "crypto/md5"
    "encoding/hex"
    "net/url"
    "strings"
    "regexp"
)

var _smart_username = "username"
var _smart_password = "password"
var _modem_username = "0123456789"
var _modem_password = "password"
var _forum_endpoint = "http://support.smarttelecom.ie/forums/login.php?do=login"
var _usage_endpoint = "http://support.smarttelecom.ie/forums/smart_usage"

func GetMD5Hash(text string) string {
    hasher := md5.New()
    hasher.Write([]byte(text))
    return hex.EncodeToString(hasher.Sum(nil))
}

func getusage() {
    tr := &http.Transport {
        TLSClientConfig: &tls.Config { InsecureSkipVerify: true },
    }
    
    cookieJar, _ := cookiejar.New(nil)

    client1 := &http.Client { Transport: tr, Jar: cookieJar }
    
    form1 := url.Values{}
    form1.Add( "vb_login_username", _smart_username )
    form1.Add( "vb_login_password", "" )
    form1.Add( "s", "" )
    form1.Add( "securitytoken", "guest" )
    form1.Add( "do", "login" )
    form1.Add( "vb_login_md5password", GetMD5Hash( _smart_password ) )
    form1.Add( "vb_login_md5password_utf", GetMD5Hash( _smart_password ) )
    
    //fmt.Printf("%s", GetMD5Hash(_smart_password))
    
    req1, err := http.NewRequest( "POST", _forum_endpoint, strings.NewReader(form1.Encode()) )
    req1.Header.Add( "Content-Type", "application/x-www-form-urlencoded" )
    
    resp1, err := client1.Do( req1 )
    defer resp1.Body.Close()

    if err != nil {
        fmt.Printf( "Error : %s", err)
    }

    if resp1.StatusCode != 200 {
        fmt.Printf( "Error code: %s", strconv.Itoa( resp1.StatusCode ) )
    }
    
            
    client2 := &http.Client { Transport: tr, Jar: cookieJar }
    
    form2 := url.Values{}
    form2.Add( "user", _modem_username )
    form2.Add( "pass", _modem_password )
    form2.Add( "submit", "SUBMIT" )
    
    req2, err := http.NewRequest( "POST", _usage_endpoint, strings.NewReader(form2.Encode()) )
    req2.Header.Add( "Content-Type", "application/x-www-form-urlencoded" )
    
    resp2, err := client2.Do( req2 )
    defer resp2.Body.Close()
    
    if err != nil {
        fmt.Printf( "Error : %s", err)
    }

    if resp1.StatusCode != 200 {
        fmt.Printf( "Error code: %s", strconv.Itoa( resp1.StatusCode ) )
    }    

    bodyBytes2, err := ioutil.ReadAll( resp2.Body )
    if err != nil {
        fmt.Printf( "Error : %s", err )
    }
    
    //fmt.Printf( "%s\n", bodyBytes2 )
    
    re := regexp.MustCompile( "(?s)(.*)
" ) matches := re.FindAllString( string(bodyBytes2), -1 ) for i := len( matches )-1; i >= 0; i-- { reinout := regexp.MustCompile( "(?s)(.*?)" ) inout := reinout.FindAllString( matches[i] , -1 ) //fmt.Printf( "%v\n", inout[0][4:len(inout[0])-5] ) //fmt.Printf( "%v\n", inout[1][4:len(inout[1])-5] ) //fmt.Printf( "%v\n", inout[2][4:len(inout[2])-5] ) //fmt.Printf( "%v\n", inout[3][4:len(inout[3])-5] ) //fmt.Printf( "%v\n", inout[4][4:len(inout[4])-5] ) //fmt.Printf( "%v\n", inout[5][4:len(inout[5])-5] ) //fmt.Printf( "%v\n", inout[6][4:len(inout[6])-5] ) //fmt.Printf( "%v\n", inout[7][4:len(inout[7])-5] ) //fmt.Printf( "%v\n", inout[8][4:len(inout[8])-5] ) //fmt.Printf( "%v\n", inout[9][4:len(inout[9])-5] ) //fmt.Printf( "%v\n", inout[10][4:len(inout[10])-5] ) i := strings.Index(inout[11], "(") + 1 fmt.Printf( "%v\n", inout[11][i:len(inout[11])-10] ) } } func main() { getusage() }

Golang DD-WRT Bandwidth Usage with Conky

I setup a golang script to fetch the DD-WRT bandwidth usage for the previous 2 months, as well as the last 30 days(rolling).
Using conky then i can display it on my desktop.
My ISP (digiweb), donโ€™t provide any means to check your bandwidth.
Picture at end of post ๐Ÿ™‚

Golang DD-WRT script

package main

import (
    "fmt"
    "strconv"
    "net/http"
    "crypto/tls"
    "io/ioutil"
    "regexp"
    "time"
)

// You set these
var _ddwrt_ip = "10.1.1.1"
var _ddwrt_ssl = true
var _ddwrt_port = 443
var _ddwrt_user = "root"
var _ddwrt_pass = "password"

// don't set these
var _ddwrt_this_month = ""
var _ddwrt_last_month = ""

func printmonth(monthyear string) {
    tr := &http.Transport {
        TLSClientConfig: &tls.Config { InsecureSkipVerify: true },
    }

    client := &http.Client { Transport: tr }

    proto := "http"
    if _ddwrt_ssl == true {
      proto = "https"
    }

    req, err := http.NewRequest( "GET", proto + "://" + _ddwrt_ip + ":" + strconv.Itoa( _ddwrt_port ) + "/ttgraph.cgi?" + monthyear, nil )
    req.SetBasicAuth( _ddwrt_user, _ddwrt_pass )

    resp, err := client.Do( req )
    defer resp.Body.Close()

    if err != nil {
        fmt.Printf( "Error : %s", err)
    }

    if resp.StatusCode != 200 {
        fmt.Printf( "Error code: %s", strconv.Itoa( resp.StatusCode ) )
    }

    bodyBytes, err2 := ioutil.ReadAll( resp.Body )
    if err2 != nil {
        fmt.Printf( "Error : %s", err2 )
    }
    
    if len( _ddwrt_this_month ) == 0 {
        _ddwrt_this_month = string( bodyBytes )
    } else {
        _ddwrt_last_month = string( bodyBytes )
    }

    re := regexp.MustCompile( "(?s)
  • (.*?)
  • " ) matches := re.FindAllString( string( bodyBytes ), -1 ) rein := regexp.MustCompile( "(?s)Incoming: ([0-9]+)" ) reinmatches := rein.FindStringSubmatch( matches[0] ) reout := regexp.MustCompile( "(?s)Outgoing: ([0-9]+)" ) reoutmatches := reout.FindStringSubmatch( matches[0] ) fmt.Printf( "${goto 20}Down: %s ${goto 200}Up: %s\n", reinmatches[1], reoutmatches[1] ) } func parse30days() { var all = _ddwrt_last_month + _ddwrt_this_month re := regexp.MustCompile( "(?s)onmouseover=\"Show(.*?)onmouseout" ) matches := re.FindAllString( all, -1 ) var down int64 var up int64 var total int64 var days30 = 30 for i := len( matches )-1; i >= 0; i-- { reinout := regexp.MustCompile( "(?s)Incoming: ([0-9]+) MB / Outgoing: ([0-9]+) MB" ) inout := reinout.FindStringSubmatch( matches[i] ) r, _ := strconv.ParseInt( inout[1], 10, 64 ) if r == 0 { continue; } down = down + r t, _ := strconv.ParseInt( inout[2], 10, 64 ) up = up + t days30-- if days30 == 0 { break; } } total = down + up fmt.Printf( "\n${goto 20}Total 30 days:${goto 200}%d GB\n", total / 1024 ) } func main() { now := time.Now() monthyear := now.Format("01-2006") printmonth(monthyear) monthyear = now.AddDate(0,-1,0).Format("01-2006") printmonth(monthyear) parse30days() }

    Conky script Integration

    conky.config = {
        alignment = 'top_right',
        background = true,
        border_width = 1,
        cpu_avg_samples = 2,
    	default_color = 'white',
        default_outline_color = 'white',
        default_shade_color = 'white',
        draw_borders = false,
        draw_graph_borders = true,
        draw_outline = false,
        draw_shades = false,
        use_xft = true,
        font = 'DejaVu Sans Mono:size=12',
        gap_x = 50,
        gap_y = 50,
        double_buffer = true,
        minimum_height = 5,
    	minimum_width = 5,
        net_avg_samples = 2,
        no_buffers = true,
        out_to_console = false,
        out_to_stderr = false,
        extra_newline = false,
        own_window = true,
        own_window_class = 'Conky',
        own_window_type = 'desktop',
        own_window_transparent = true,
        stippled_borders = 0,
        update_interval = 3.0,
        uppercase = false,
        use_spacer = 'none',
        show_graph_scale = false,
        show_graph_range = false
    }
    
    conky.text = [[
    $sysname $kernel on $machine
    ${hr 2}
    
    ${color grey}Uptime:$color $uptime
    ${color grey}Frequency (in MHz):$color $freq
    ${color grey}Frequency (in GHz):$color $freq_g
    ${color grey}RAM Usage:$color $mem/$memmax - $memperc% ${membar 4}
    ${color grey}Swap Usage:$color $swap/$swapmax - $swapperc% ${swapbar 4}
    ${color grey}CPU Usage:$color $cpu% ${cpubar 4}
    ${color grey}Processes:$color $processes  ${color grey}Running:$color $running_processes
    
    
    ${color}File Systems
    ${hr 2}
    
    /${goto 80}$color${fs_used /}/${fs_size /} ${goto 250}${fs_bar 6 /}
    /home${goto 80}$color${fs_used /}/${fs_size /home} ${goto 250}${fs_bar 6 /home}
     
    ${color}Networking
    ${hr 2}
    
    ${goto 20}Up:$color ${upspeed wlp3s0} ${goto 200}${color grey}Down:$color ${downspeed wlp3s0}
    ${goto 20}${upspeedgraph wlp3s0 26,140 FFFFFF FFFFFF}${goto 200}${downspeedgraph wlp3s0 26,140 FFFFFF FFFFFF}
    ${execpi 3600 /usr/bin/go run /home/dave/.conky/ddwrt-bandwidth.go}
    
    ${color}Processes
    ${hr 2}
    
    Name                PID   CPU%   MEM%
    ${color lightgrey} ${top name 1} ${top pid 1} ${top cpu 1} ${top mem 1}
    ${color lightgrey} ${top name 2} ${top pid 2} ${top cpu 2} ${top mem 2}
    ${color lightgrey} ${top name 3} ${top pid 3} ${top cpu 3} ${top mem 3}
    ${color lightgrey} ${top name 4} ${top pid 4} ${top cpu 4} ${top mem 4}
    ${color lightgrey} ${top name 5} ${top pid 5} ${top cpu 5} ${top mem 5}
    ${color lightgrey} ${top name 6} ${top pid 6} ${top cpu 6} ${top mem 6}
    ${color lightgrey} ${top name 7} ${top pid 7} ${top cpu 7} ${top mem 7}
    ${color lightgrey} ${top name 8} ${top pid 8} ${top cpu 8} ${top mem 8}
    ${color lightgrey} ${top name 9} ${top pid 9} ${top cpu 9} ${top mem 9}
    
    ]]
    

    Enjoy
    rolling


    Debian Sid Intel I217-V Not Working

    After scouring for ages looking for this fix. Iโ€™ve decided to document it.
    It comes form a number of sources. Kudos to the individual people.

    Problem
    Rebooting from windows into Linux renders the NIC unusable. the classic โ€œlights are on but no one is homeโ€
    Some people advice disabling PXE etc in the bios. There is a better solution

    Identify the NIC

    root@dave-pc:/lib/systemd/system# lspci | grep Ether
    00:19.0 Ethernet controller: Intel Corporation Ethernet Connection I217-V (rev 04)

    Create a systemd oneshot service file

    cat <> /lib/systemd/system/intelnicreset.service
    [Unit]
    Description=Reset Intel Nic on Boot before it comes up
    Before=NetworkManager.service
    Wants=NetworkManager.service
    
    [Service]
    Type=oneshot
    ExecStart=/usr/bin/resetintelnic
    RemainAfterExit=no
    
    [Install]
    WantedBy=multi-user.target
    EOT

    Reset NIC bash file

    cat <> /usr/bin/resetintelnic
    #!/bin/bash
    
    #Get the PCI-Address of network card (Caution: This works ONLY with ONE NIC)
    PCI=`/usr/bin/lspci | /bin/egrep -i 'network|ethernet' | /usr/bin/cut -d' ' -f1`
    PCIPATH=`/usr/bin/find /sys -name *\${PCI} | /bin/egrep -i *pci0000*`
    /usr/bin/logger -t "ResetNIC" "Resetting PCI NIC ${PCIPATH}"
    
    #Reset the PCI Device completely (like Power-ON/Off)
    echo 1 >${PCIPATH}/reset
    EOT

    Make it executable

    chmod +x /usr/bin/resetintelnic

    Synology Filebot Autonaming cron job

    Having a synology NAS is great. However when dealing with 32tb, good file management is a must!
    I have 2 primary folders, Films and Series. The set of below scripts iterate the files and use the TVDB and MovieDB to clean up the file names.
    The second scripts downloads any missing subtitles for the media ๐Ÿ™‚

    File renaming

    &1" ) );
    
    foreach( $loglines as $logline )
    {
      $line = trim( $logline );
    
      if( $line == "" ) continue;
      if( preg_match( "/^Skipped.*$/", $line, $dontcare ) ) continue;
      if( preg_match( "/^Auto-detect movie from context.*$/", $line, $dontcare ) ) continue;
      if( preg_match( "/.*Rename movies using.*$/", $line, $dontcare ) ) continue;
    
      $ploglines[] = $line;
    }
    
    $frt = implode( "\r\n" , $ploglines );
    
    $body = "Dear user,\n\n $frt \n\nSincerely,\nSynology DiskStation\n\n";
    mail($email, 'DSM - Filebot Rename Report - Films', "$body");
    
    
    $log = "/volume1/homes/admin/cleanup/report-rename.txt";
    unlink( $log );
    touch( $log );
    
    if ($handle = opendir('/volume1/Entertainment/Series/'))
    {
      while (false !== ($entry = readdir($handle)))
      {
        if ($entry != "." && $entry != "..")
        { 
          $cmd = "filebot -r -rename \"/volume1/Entertainment/Series/$entry/\" --db TheTVDB 2>&1";
          $frt = shell_exec( $cmd );
          file_put_contents( $log, $frt . "\n\n", FILE_APPEND | LOCK_EX );
        }
      }
      closedir($handle);
    }
    
    $ploglines = array();
    $loglines = file( $log );
    
    foreach( $loglines as $logline )
    {
      $line = trim( $logline );
    
      if( $line == "" ) continue;
      if( preg_match( "/'^Skipped.*$/", $line, $dontcare ) ) continue;
      if( preg_match( "/^Fetching episode data.*$/", $line, $dontcare ) ) continue;
      if( preg_match( "/^Processed.$*/", $line, $dontcare ) ) continue;
      if( preg_match( "/^Done.*$/", $line, $dontcare ) ) continue;
      if( preg_match( "/^Failure.*$/", $line, $dontcare ) ) continue;
      if( preg_match( "/^No media files.*$/", $line, $dontcare ) ) continue;
    
      $ploglines[] = $line;
    }
    
    $frt = implode( "\r\n" , $ploglines );
    
    $body = "Dear user,\n\n $frt \n\nSincerely,\nSynology DiskStation\n\n";
    mail($email, "DSM - Filebot Rename Report - Series $entry", "$body");

    Subtitles scripts

    &1" );
    $body = "Dear user,\n\n $fst \n\nSincerely,\nSynology DiskStation\n\n";
    mail($email, 'DSM - Filebot Subtitles Report - Films', "$body");
    
    if ($handle = opendir('/volume1/Entertainment/Series/'))
    {
      while (false !== ($entry = readdir($handle)))
      {
        if ($entry != "." && $entry != "..")
        { 
          $cmd = "filebot -r -script fn:suball \"/volume1/Entertainment/Series/$entry/\" --lang en -non-strict --db TheTVDB 2>&1";
          $frt = shell_exec( $cmd );
          file_put_contents( $log, $frt . "\n\n", FILE_APPEND | LOCK_EX );
        }
      }
      closedir($handle);
    }
    
    $frt = file_get_contents( $log );
    $body = "Dear user,\n\n $frt \n\nSincerely,\nSynology DiskStation\n\n";
    mail('dave@fio.ie', "DSM - Filebot Subtitles Report - Series $entry", "$body");
    

    Cron Job

    ash-4.3# cat /etc/crontab 
    MAILTO=""
    PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/syno/sbin:/usr/syno/bin:/usr/local/sbin:/usr/local/bin
    #minute hour    mday    month   wday    who     command
    0       0       1       *       *       root    /usr/syno/bin/syno_disk_health_record
    0       0       *       *       3       root    /usr/bin/php /var/services/homes/admin/cleanup/filebot-rename.php
    0       0       *       *       5       root    /usr/bin/php /var/services/homes/admin/cleanup/filebot-subtitles.php
    5       3       *       *       6       root    /usr/syno/bin/synomyds --report_info
    0       3       *       *       1       root    /tmp/synoschedtask --run id=1
    0       3       13      *       *       root    /tmp/synoschedtask --run id=2
    11      2       *       *       4       root    /tmp/synoschedtask --run id=3