network scanner with perl + Tk

perl Tk ip scanner application
perl Tk ip scanner application

I enhanced my command line ip scanner by adding a Tk gui and adding a few more features.  Above, I am scanning a range of contiguous addresses on my home network.

It can also accept a list of hosts from a csv file.

list of hosts in csv format
list of hosts in csv format

Here are the results from the scan of the above list.

perl Tk ip scanner results using csv file input
perl Tk ip scanner results using csv file input

I am forced against my will to use Windows 10 on my PC at work.  I also lack admin privileges, so I cannot install a free ip scanner application.  I do, however, have ActivePerl installed on my box, and it has tons of modules so I can write my own utilities.

I have several classes of ip devices (routers, switches, battery chargers, etc.) that I want to monitor and have created individual csv files for each.  This simple script is highly effective at discovering devices on a subnet, or to give a quick glance at what devices on a list are online.

 
     1	#!/usr/bin/perl -w
     2	use strict;
     3	use Tkx;
     4	use Tkx::Scrolled;
     5	use Net::Ping;
     6	use Time::HiRes qw(gettimeofday);
       
     7	#main window
     8	my $mw = Tkx::widget->new(".");
     9	$mw->g_wm_title("nnmap network scanner");
    10	$mw->g_wm_geometry("800x600");
       
    11	#global vars
    12	my $ip_range = '';
    13	my $tdelay = 250; 
       
    14	#create ping object
    15	my $p = Net::Ping->new('icmp');
    16	#hi res time
    17	$p->hires();
       
    18	#a single ip address
    19	my $host;
       
    20	#up / down devices
    21	my @online_devices; 
    22	my @offline_devices; 
       
       
    23	#main content frame
    24	my $mfrm = $mw->new_ttk__frame(-padding => "5 10");
    25	$mfrm->g_grid(-column => 1, -row => 0, -sticky => "news");
    26	$mfrm->new_ttk__label(-text => "ip range")->g_grid(-column => 1, -row => 1, -sticky => "nw", -padx => 3, -pady => 1);
    27	$mfrm->new_ttk__entry(-textvariable => \$ip_range, -width => 24)->g_grid(-column => 1, -row => 2, -sticky => "nw", -padx => 3, -pady => 1);
    28	$mfrm->new_ttk__label(-text => "delay msec")->g_grid(-column => 1, -row => 3, -sticky => "nw", -padx => 3, -pady => 1);
    29	$mfrm->new_ttk__spinbox(-from => 1, -to => 900, -width => 5, -textvariable => \$tdelay)->g_grid(-column => 1, -row => 4, -sticky => "nw", -padx => 1, -pady => 2);
    30	$mfrm->new_ttk__button(-text => "scan", -command => sub {scan_net($ip_range);})->g_grid(-column => 1, -row => 5, -sticky => "nw", -padx => 1, -pady => 2);
       
    31	my $ta = $mfrm->new_tkx_Scrolled(
    32	    'text',
    33	    -scrollbars => "e",
    34	    -width => 70,
    35	    -height => 33,
    36	    -state => "normal"
    37	);
    38	$ta->g_grid(-column => 2, -row => 1, -sticky => "e", -padx => 5, -pady => 5, -rowspan => 50);
       
    39	#colors
    40	$ta->tag_configure("success", -foreground=>"white", -background=>"green");
    41	$ta->tag_configure("failure", -foreground=>"white", -background=>"red");
    42	#fonts
    43	$ta->tag_configure("lgtxt", -font =>"r18", -relief=>"raised", -underline=>1);
       
    44	sub scan_net{
    45		my $func_name = (caller(0))[3];
    46		print "Called $func_name on line ". __LINE__."\n";
    47		
    48		print "ip range: $ip_range\n";
       
    49		#parse ip range
    50		#only for class c or smaller subnets
    51		#?'s in regex account for a single ip address to scan
    52		$ip_range =~ /(\d+\.\d+\.\d+)\.(\d+)-?(\d+)?/;
    53		my $network = $1;
    54		my $start_ip = $2;
    55		my $end_ip = $3;
    56		
    57		print "end_ip: $end_ip\tstart ip: $start_ip\n";
       
    58		#account for just one ip to scan
    59		if($end_ip eq ""){
    60			$end_ip = $start_ip; 
    61		}#end if
       
    62		#convert time delay
    63		my $ping_delay = $tdelay * 0.001;
       
    64		#empty text area
    65		$ta->delete("1.0", "end");
       
    66		#get t0 for benchmark
    67		my $t0 = gettimeofday();
       
    68		#line counter
    69		my $line_n = 1; 
       
    70		for(my $i=$start_ip;$i<=$end_ip;$i++) { 71 $host = "$network.$i"; 72 print "scanning host: $host\n"; 73 #list context, returns duration 74 my ($ret, $dur, $ip) = $p->ping($host, $ping_delay);
    75			
    76			#format time
    77			$dur = sprintf("%.6f", $dur); 	
       
    78			#results
    79			if($ret){
    80				#print WHITE ON_GREEN "$host is up  latency: $dur seconds", RESET;
    81				#form response string
    82				my $response = "$host is up  $dur seconds";
    83				my $res_len = length($response); 
    84				if($res_len < 70){#fill up ta rows with color
    85					my $n_sp = 70 - $res_len; 
    86					for(my $x=0;$x<$n_sp;$x++){ 87 $response = $response." "; 88 }#end for add sp 89 }#end if 90 $response = $response."\n"; 91 #print "res_len: $res_len\n"; 92 #print "\n"; 93 $ta->insert("$line_n.0", $response);
    94				$ta->tag_add("success", "$line_n.0", "$line_n.0 lineend");
    95				push @online_devices, $host;
    96			}#end if
    97			else{
    98				my $response = "$host down";
    99				my $res_len = length($response); 
   100				if($res_len < 70){
   101					my $n_sp = 70 - $res_len; 
   102					for(my $x=0;$x<$n_sp;$x++){ 103 $response = $response." "; 104 }#end for add sp 105 }#end if 106 $response = $response."\n"; 107 #print "res_len: $res_len\n"; 108 #print WHITE ON_RED "$host down", RESET; 109 #print "\n"; 110 $ta->insert("$line_n.0", $response);
   111				$ta->tag_add("failure", "$line_n.0", "$line_n.0 lineend");
   112				push @offline_devices, $host;
   113			}#end else		
       
   114			#increment line number
   115			$line_n++;
   116		
   117		}#end for
       
   118		#results
   119		#benchmarking results
   120		my $t1 = gettimeofday();
   121		my $elapsed = $t1 - $t0; 
   122		$elapsed = sprintf("%6f", $elapsed);
   123		print "\ntime elapsed: $elapsed....\n";
   124		my $up_sz = @online_devices;
   125		my $down_sz = @offline_devices;
   126		print "$up_sz devices online\n$down_sz devices offline\n";
       
   127		#form results
   128		my $results = "Results\ntime elapsed: $elapsed\n\n$up_sz devices online\n$down_sz devices offline\n";
   129		
   130		#insert results
   131		$ta->insert("$line_n.0", $results);
   132		$ta->tag_add("lgtxt", "$line_n.0", "$line_n.0 lineend");
   133	}#end scan_net
       
   134	sub openCSV{
   135		my $func_name = (caller(0))[3];
   136		print "Called $func_name on line ". __LINE__."\n";
       
   137		my $fn = Tkx::tk___getOpenFile();
   138		print "FILE: $fn\n";
       
   139		#open csv file
   140		open HF, $fn, or die $!;
       
   141		#empty text area
   142		$ta->delete("1.0", "end");
       
   143		#ping delay
   144		my $ping_delay = $tdelay*0.001;
       
   145		#get t0 for benchmark
   146		my $t0 = gettimeofday();
       
   147		#line number counter
   148		my $line_n = 1;
       
   149		while(){
   150			my $ipdevice = $_;
   151			my @ipdev = split(',', $ipdevice); 
   152			my $description = $ipdev[1]; 
   153			$host = $ipdev[2]; 		
   154			$host =~ s/\s+//;
   155			$description =~ s/\n//;
   156			
   157			#list context, returns duration
   158			my ($ret, $dur, $ip) = $p->ping($host, $ping_delay);
       
   159			print "scanning host: $host\n";
   160			
   161			#format time
   162			$dur = sprintf("%.6f", $dur); 	
   163			
   164			my $host_desc = $host."\t\t".$description;
   165			
   166			#results
   167			if($ret){
   168				#print WHITE ON_GREEN "$host $description is up  latency: $dur seconds", RESET;
   169				#print "\n";
   170				my $response = "$host $description is up  $dur sec";
   171				my $res_len = length($response); 
   172				if($res_len < 70){#fill up ta rows with color
   173					my $n_sp = 70 - $res_len; 
   174					for(my $x=0;$x<$n_sp;$x++){ 175 $response = $response." "; 176 }#end for add sp 177 }#end if 178 $response = $response."\n"; 179 $ta->insert("$line_n.0", $response);
   180				$ta->tag_add("success", "$line_n.0", "$line_n.0 lineend");
   181				push @online_devices, $host_desc;
   182			}#end if
   183			else{
   184				#print WHITE ON_RED "$host $description down", RESET;
   185				#print "\n";
   186				push @offline_devices, $host_desc;
   187				my $response = "$host $description down";
   188				my $res_len = length($response); 
   189				if($res_len < 70){#fill up ta rows with color
   190					my $n_sp = 70 - $res_len; 
   191					for(my $x=0;$x<$n_sp;$x++){ 192 $response = $response." "; 193 }#end for add sp 194 }#end if 195 $response = $response."\n"; 196 $ta->insert("$line_n.0", $response);
   197				$ta->tag_add("failure", "$line_n.0", "$line_n.0 lineend");
   198			}#end else
       
   199			$line_n++;
   200		}#end while
   201		
   202		close HF; 
       
   203		#results
   204		#benchmarking results
   205		my $t1 = gettimeofday();
   206		my $elapsed = $t1 - $t0; 
   207		$elapsed = sprintf("%6f", $elapsed);
   208		print "\ntime elapsed: $elapsed....\n";
   209		my $up_sz = @online_devices;
   210		my $down_sz = @offline_devices;
   211		print "$up_sz devices online\n$down_sz devices offline\n";
       
   212		#form results
   213		my $results = "Results\ntime elapsed: $elapsed\n\n$up_sz devices online\n$down_sz devices offline\n";
   214		
   215		#insert results
   216		$ta->insert("$line_n.0", $results);
   217		$ta->tag_add("lgtxt", "$line_n.0", "$line_n.0 lineend");
   218	}#end openCSV
       
   219	#menu
   220	Tkx::option_add("*tearOff", 0);
   221	$mw->configure(-menu => mk_menu($mw));
   222	sub mk_menu{
   223		my $mw = shift;
   224		my $menu = $mw->new_menu();
   225		my $file = $menu->new_menu( -tearoff => 0);
   226		$menu->add_cascade(
   227			-label => "File", 
   228			-underline => 0, 
   229			-menu => $file
   230		);
   231		$file->add_command(
   232			-label => "Open CSV File", 
   233			-underline => 0, 
   234			-command => sub {openCSV();}
   235		);
   236	          $file->add_command(
   237	              -label   => "Exit",
   238	              -underline => 1,
   239	              -command => [\&Tkx::destroy, $mw],
   240	          );
   241		return $menu;
   242	}#end mk_menu
       
   243	Tkx::MainLoop();

This is one of those utilities of mine that will probably continue to grow in complexity.

arduino nano bike speedometer

arduino nano bike speedometer and odometer
arduino nano bike speedometer and odometer

I tricked out my dad bike with an arduino nano based speedometer, odometer, clock, and temperature sensor.

A DS3231 RTC keeps the time and temperature.  I implemented a attachInterrupt() function to keep track of tire rotation via a reed switch.

reed switch for a bike speedometer / odomoeter using an arduino nano
reed switch for a bike speedometer / odomoeter using an arduino nano

This way, I do not have to programmatically monitor  the reed switch;  a routine is executed (in this case that calculates the distance traveled) each time the state of the digital pin the switch is connected to changes.

 //bike speedometer
#include 
#include "RTClib.h"
RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 10;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

float start, finished;
float elapsed, time;
//float circMetric=2.164; // wheel circumference relative to sensor position (in meters)
float circMetric=1; // wheel circumference relative to sensor position (in meters)
float circImperial; // using 1 kilometer = 0.621371192 miles
float speedk, speedm;    // holds calculated speed vales in metric and imperial
float miles_traveled = 0;
float feet_traveled = 0;
char miles[100];

void setup () {
  attachInterrupt(0, speedCalc, CHANGE); //digital pin 2
  start=millis();
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("cr3d0");
  lcd.setCursor(0,1);
  lcd.print("BIKE SPEEDOMETER!");
  delay(4500); 
  lcd.clear();
  Serial.begin(9600);
  circImperial=circMetric*.62137; // convert metric to imperial for MPH calculations

  //rtc stuff
  int temp = 0;

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }//end if

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }//end if rtc lost power
  
}//end setup

void speedCalc()
{
  elapsed=millis()-start;
  start=millis();
  speedk=((3600*circMetric)/elapsed) * 0.12; // km/h
  speedm=((3600*circImperial)/elapsed) * 0.12; // Miles per hour
  feet_traveled += 1.1; 
  miles_traveled = feet_traveled / 5280; 
  //sprintf(miles, "%.2f", miles_traveled);
  
}

void loop()
{
  DateTime now = rtc.now();  
  int temp = 0;

  lcd.clear();
  lcd.setCursor(0,0);
  //lcd.print(int(speedk));
  //lcd.print(" km/h ");
  lcd.print(int(speedm));
  lcd.print(" MPH   ");
  lcd.print(miles_traveled);
  lcd.print(" mi");;
  //lcd.setCursor(0,1);
  //lcd.print(int(elapsed));
  //lcd.print(" ms/rev      ");
  lcd.setCursor(0,1);
  lcd.print(now.hour(), DEC);
  lcd.print(':');
  lcd.print(now.minute(), DEC);
  lcd.print(':');
  lcd.print(now.second(), DEC);
  lcd.print(' ');
  temp = rtc.getTemperature() * 9/5 + 32;
  lcd.print(temp);
  lcd.print('F');
  delay(1000); // adjust for personal preference to minimise flicker
}

 

arduino bike speedometer
arduino bike speedometer

oui mac address identifier utility with perl

wireshark has a good online oui lookup utility that will tell you the manufacturer of a particular network interface card based on the first six bytes of the mac address referred to as the oui (organizationally unique identifier).  nmap, at least the version on my ubuntu machine, has this feature but I am dissatisfied with it’s accuracy or lack thereof.  Being fond of perl,  I searched cpan.org before I attempted to reinvent the wheel.  I found the Net::MAC::Vendor module.  I found it to be slow, and relied on querying an online database (which didn’t work well).

I decided to write my own to augment the functionality of my own network scanner.  I downloaded a local copy of the text file provided by wireshark and wrote the following script to scan my local arp cache and tell me the manufacturer of  each device on the network.

#!/usr/bin/perl -w
use strict;

#update arp table
`arp > arp.txt`;

open(AF, '<', 'arp.txt') or die $!;

while(){
	if($_ =~ /(\d+\.\d+\.\d+\.\d+).+(..:..:..)(:..:..:..)/){
		my $match_flag = 0; 
		my $ip = $1;
		my $oui = $2;
		print "$ip\t$oui$3\t";
		reset;
		open(OU, '<', 'oui_mac.txt') or die $!; 
			#open oui file and search
			my $i=0;
			while(){
				#print "$oui \n";
				if($_  =~ /$oui/i){
					print "$_";
					$match_flag = 1;
					last;
				}#end if
				$i++;
			}#end while
		close OU;
		if($match_flag == 0){
			print "NO OUI MATCH!\n";
		}#end if
	}#end if
}#end while

close AF;

Simple and effective.

mac oui lookup tool written in perl
mac oui lookup tool written in perl

subnet scanner utility in perl using Net::Ping

I often times have to assign an ipv4 address to a device at work and need to know what addresses are available on a particular subnet.  On other occasions, I just need to know what hosts are up / down on a subnet.  Normally, I would use nmap on my personal laptop, but only authorized devices are allowed on my company network, and I certainly want to stay out of trouble. Plus, I have to use windows 10 at work, and I do not have admin privelages.  I wrote a simple perl script that scans a user-defined range of ip addresses using the Net::Ping module from cpan.

This is the first of a suite of network utilities that I am writing to help troubleshoot network issues in cases where I do not have nmap available.   Here is a scan of a small portion of my home network:

Perl based subnet scanner using Net::Ping
Perl based subnet scanner using Net::Ping

Here is the source code so far:

 

#!/usr/bin/perl -w
use Net::Ping;
use Time::HiRes qw(gettimeofday);
use Term::ANSIColor;
use 5.010;
 
print "ping tester\n--------------------------\n";
#print "enter ip: ";

#get address
#chomp(my $host = );
#print "host: $host\n";

#create ping object
my $p = Net::Ping->new('icmp');
#hi res time
$p->hires();

#a single ip address
my $host;

#up / down devices
my @online_devices; 
my @offline_devices; 

#get address range from user
print "Enter IP address range: ";
chomp(my $ip_range = );
#print "range: $ip_range\n";
$ip_range =~ /(\d+\.\d+\.\d+)\.(\d+)-(\d+)/;
my $network = $1;
my $start_ip = $2;
my $end_ip = $3;
#print "start IP: $start_ip\nend IP: $end_ip\n";

#get t0 for benchmark
my $t0 = gettimeofday();

for(my $i=$start_ip;$i<=$end_ip;$i++)	{
	$host = "$network.$i";
	print "checking $host....\n";
	
	#list context, returns duration
	my ($ret, $dur, $ip) = $p->ping($host, 0.25);
	
	#format time
	$dur = sprintf("%.6f", $dur); 	
	
	#results
	if($ret){
		print colored  ("$host is up  latency: $dur seconds", 'white on_green');
		print "\n";
		push @online_devices, $host;
	}#end if
	else{
		print colored ("$host down", 'white on_red');
		print "\n";
		push @offline_devices, $host;
	}#end else		
	print "-----------------------------\n";
}#end for

#kill ping object
$p->close();

#benchmarking results
my $t1 = gettimeofday();
my $elapsed = $t1 - $t0; 
$elapsed = sprintf("%6f", $elapsed);
print "\ntime elapsed: $elapsed....\n";

#report file
open RP, ">", "report.txt" or die $!;

#results
my $up_sz = @online_devices;
my $down_sz = @offline_devices;
print "$up_sz devices online\n$down_sz devices offline\n";

print "\nOnline devices....\n------------------------------------------------------\n";
print RP "Online devices....\n------------------------------------------------------\n";
foreach $a (@online_devices){
	say $a;
	print RP $a."\n"; 
}#end foreach

print "\n\nOffnline devices....\n------------------------------------------------------\n";
print RP "Offline devices....\n------------------------------------------------------\n";
foreach $a (@offline_devices){
	say $a;
	print RP $a."\n"; 
}#end foreach

close RP;

remake of 80’s computer toy word game

80's computer word game
80’s computer word game

Way back on Christmas of ’87, my sister got a computer toy similar to this one.  I couldn’t put it down.  It had a word game with 8, 14-segment LEDs where you had to enter letters one at a time and guess the word before your tries ran out, sort of like wheel of fortune.  I got bored on Sunday night, and wrote a command line version.

perl command line word game
perl command line word game

It requires an external file, words.txt, from which it randomly chooses a word for each round.  Above is the game play.

Here is the source:

#!/usr/bin/perl -w
use Tie::File;
use strict;

#array of all letters
my @letters = 'a' .. 'z';
my $szletters = @letters;
my @used_letters;
my $n_tries = 0;
my $wf = 0;
my $iword = '';
my $letter = '';
my @wrd;

my @words;
tie @words, 'Tie::File', "words.txt" or die $!;

my $nw = @words;
#print "nwords: $nw \n";
my $num = int(rand($nw));

my $word = $words[$num];

#print "word: $word\n";

my $lngth = length($word);

for(my $l=0; $l < $lngth; $l++){
#print '-';
$iword = $iword."-";
}#end for
print "\n";

@wrd = split //, $word;
my $lef = 0;
do {
print $iword."\n";
print "enter a letter: ";
chomp($letter = );
#print "letter: $letter\n";

#clear out iword
$iword = '';

push @used_letters, $letter;
for(my $l=0;$l<$lngth;$l++){
foreach my $v (@used_letters){
if($wrd[$l] eq $v){
$iword = $iword.$v;
$lef = 1;#set flag true
}#end if
}#end foreach

unless($lef == 1){
$iword = $iword.'-';
}#end unless
$lef = 0;
}#end for

print "used letters: @used_letters \n";

if($iword eq $word){
print "$word\n";
print "YOU WIN!!!\n";
$wf = 1;
}#end if

} while($wf ==0);

kjvbible.tk: perl + CGI

KJV Bible
KJV Bible

I have been a Christian since I was 12.  I began reading the Bible for myself at 13.  I became more serious in my study at 20, and have been diligently studying ever since.  I enjoy it tremendously.  I really try to meditate on what I am studying; try to imagine what it must have been like for the people involved;  try to understand why a certain passage is in the Bible and it’s true meaning; is there something in between the lines I can deduct?   Along those lines, I like to hear what other people think about it.  Those with whose theology I agree with, and those I do not.  Those who reject it all together, and why they think that way.

I also read several Bible commentaries as I study.  There are so many of them out there.  Most of which, I have no interest in.  There is also so much software and Bible study websites out there that it can be bewildering.  I decided that I wanted to make my own Bible study website that suits me, and only contains the resources that I am interested in.

When I decided undertake this project several years ago, I had a successful free-lance website business called Nerd for Hire.  I was building and hosting custom designed PHP web applications, primarily serving real estate industry and used car lots in the greater Memphis, TN area.  I was  focused on PHP/MySQL, the then new jQuery, AJAX, CSS, etc., and hosting it all on a reseller account I had with Host Gator.  It was a nice side gig until Craigslist started charging $5 for dealers to post cars on their site.  Craigslist was essential for directing traffic to the dealer’s website where the whole inventory could be advertised.  I briefly moved over to backpage.com, but it just wasn’t the same.  Increasingly, my customers wanted incredible, custom-made web applications (my specialty), but didn’t want to pay much for them.  It became to much effort for too little financial reward, and I ended up folding.

 

This was around 2010.  Perl was unfashionable at this time, and perl/CGI was certainly not the tool of choice for web applications anymore as fast as PHP had gotten.  Never one to go along with fads,  I had been reading an entry-level perl book that was 8 years old at the time (got it for $0.01 + $3.99 shipping on Amazon),  and I was VERY interested in learning perl because it seemed like it could do everything: text processing, databases, windows applications, server side scripting,  etc.

perl books

I figure the best way to learn a new language is to do a big project in it.  I proceeded to write a CGI application in perl to read the King James Version Bible.  Am I KJV only? No.  Is it my favorite? Pretty much.  I forget where I found a SQL file of the KJV bible, but I did, and imported it into my home LAMP server via phpmyadmin.  I then wrote an object oriented CGI application to read the Bible on my home network.   I purposefully ignored any css styling, and just wanted a simple, stripped down design.  I began using it daily during my study, being really pleased with myself for having got that far.

I was using Power Bible CD at the time, and had really taken a liking to the Adam Clarke Bible Commentary.  I wanted to add it to my little project so I didn’t have to go between applications during my study.  I created a database schema and manually copied and pasted every single chapter of the Adam Clarke Commentary from studylight.org.  It was tedious, and painful, but it worked.  Once I had it in database form,  I added scripting so that the commentary was displayed side by side with the chapter in the Bible I was reading.  Soon after, I added the Joseph Benson Commentary in the same inefficient fashion.

After some time, I discovered the Internet Sacred Text Archive, www.sacred-texts.com, which contained numerous Bible commentaries and other interesting resources.  I got a little more sophisticated, and wrote scripts (wget + perl) to automatically download the commentaries chapter by chapter and save them as text files on my computer.  I then wrote scripts that  merged them all together into a csv file, and imported them into a MySQL database.  I did the same with a few Bible dictionaries, encyclopedias, etc. from the Internet Archive.  I was getting pretty good with perl’s regular expressions and amazed at how easy it was to do really powerful things in perl with such little effort.

Sacred-Texts.com
Sacred-Texts.com

So, I got my CGI application together the way I wanted it, and was still just using it on my internal LAMP server.  I had recently started renting a VPS from digitalocean.com for a for-sale-by-owner real estate project.  I decided to put my humble project on the internet, since I already had the hosting resources.  I decided to go with a free domain: www.kjvbible.tk.  I am still hosing it on a VPS from digitalocean.  Other sites I am hosting there are using PHP 7.  PHP is smoking fast, and comes with OPcache standard.  On the other hand, perl CGI is an anachronism, SUPER DUPER slow, and taxing on the CPU.  I really don’t care though.  It’s out there for the world to see.  Ad-free and just a simple, easy-to-use place to study the Bible.

Hebrew New Testament
Hebrew New Testament at www.kjvbible.tk