Friday, September 26, 2008

The big DOH!

So, as many of you know I attended LinuxWorld this year in San Francisco, CA. While I was there, I had the opportunity to take the RackSpace Breakfix challenge.

It was a test of 4 break fix challenge tasks to see who could repair the broken Linux server the fastest. Here are the 4 tasks to be completed. You had 20 minutes to complete the challenge.

1. Using the ‘ping’ utility, successfully ping ‘www.rackspace.com’ at 72.32.191.88.

2. Successfully run the command:
mysql world < /root/insert.sql

(Valid solution will leave the insert.sql file as is).

3. Successfully execute the command:
echo “this file is secret” >/tmp/secret.txt

(Valid solution will not affect functionality of the /tmp directory.)

4. Successfully start the Apache web server, without losing any configured functionality

I actually was one of the three persons that tied for third place taking 15 minutes; I probably would have won second if I had paid attention to the Apache error that was staring directly at me, but then after tying for third I missed the tie breaker by mere seconds (!!DOH!!). I guess it doesn't pay to never do email heh.

That said, tying for third place at a convention filled with geeks is still worthy of talking about. :-D

Anyway, here's the link to the challenge:

http://www.rackspace.com/blog/?p=67

Saturday, July 26, 2008

Frankentop gets a GAME PORT!


Frankentop has spent the last month enjoying it's added storage. I've tested many things including leaving the second drive on and copying files for hours with minimal added heat. Today, Frankentop is getting an internal XBox connector from formerly XBox's donor parts.

This hack was a little messier than the first, apparently I didn't get enough coffee in me before setting off to begin cutting into the laptop case. That said, it doesn't look bad either so meh! :-D

First thing first, I've found that I have really great luck using a sheet of paper to track my screws as I take the laptop apart. You can see in the image that I have broken out various parts of the laptop and I've created boxes that I'll use to keep the screws in separate piles. This makes it really simple to quickly tear down a laptop and help you remember where those screws go later. Additionally, you can use scotch tape to keep them together incase you are worried about bumping them.

I took the second spare connector out of the parts bin, and I used the dremel with a cutting / grinding attachment to shave the plastic down to where the part looks like just another connector. I then cut out an area in the notebook where I was going to mount the connection. It just so happens that there is exactly enough room for the new connection right next to where I mounted the hard drive power switch in the creation of Frankentop last month. I interfaced the connection via USB as before, connecting red to +5v, white to Data -, Green to Data+, and Black to ground using the USB connection on the motherboard right next to the connection used for the hard drive. Pin 4 on the Xbox connector is unused, I just de-soldered the connection and left it. I used electrical tape to shield the computer from contact with the ground wire mesh and didn't bother to connect it to anything since the Xbox connection is plastic. I also routed the cables as before which allowed the laptop to be put back together without creating any internal pressure points. I put Frankentop partly together to test the connection, and FRANKENTOP LIVES! I've included multiple pictures for your enjoyment. What's next? Who knows, there's still plenty of space left inside this case, and I haven't even touched the screen area yet.

Stay tuned for more Frankentop "upgrades".



Saturday, July 12, 2008

Background Buddy 3.05 released w/ source

Today I'm releasing a new version of Background Buddy. This release includes a major shift from closed to open source software. Versions 3.05 and later will now be licenced under the terms of the GNU GPL. Additionally, there is now a project on sourceforge.net to host future versions of both the source and the release versions of the software.

Major changes in this release:

• Removed all registration code
• Modified edition to signify "OSS"
• Corrected a few exception errors that still existed

For those that would like to see screenshots of Background Buddy, please browse to the Background Buddy blog post from June [ OR CLICK HERE ]

View Background Buddy on Sourceforge [ HERE ]
Download Background Buddy 3.05 [ HERE ]
Download Background Buddy 3.05 Source [ HERE ]

Thursday, July 10, 2008

The PhotonAPI Reference Guide and Download

The PhotonAPI Reference Guide

PhotonAPI provides robust methods for establishing connections, presenting text IO, and managing users that are connecting to a PhotonAPI built application. PhotonAPI is not limited to the creation of BBS systems, it can be used in an unlimited number of ways. Examples of it's use are visible in games like PhotonMUD, or even in smaller applications like YogiBOT. It can be used as an interface for any number of multiuser applications.

Configuration

Variables:
The following variables must be defined in a configuration file presented to the application, or in the main application itself. The variables must be defined prior to calling the modules into memory.

## Main BBS Path
$config{'home'}="/PATH/TO/APPLICATION";

## BBS Bin path (Currently unused)
$config{'bin'}="/bin";

## BBS Data path
$config{'data'}="/data";

## Pager / Teleconference message path
$config{'messages'}="/data/messages";

## Node information path
$config{'nodes'}="/data/nodes";

## Ansi & text files
$config{'text'}="/data/text";

## BBS Skin file
$config{'themes'}="/data/themes";

## User information path
$config{'users'}="/data/users";

## Script bin -> Path to door scripts
$config{'sbin'}="/sbin";

## Node info -> Drop files etc
$config{'doors'}=$config{'home'}."/doors";

## Passchr to echo at password prompt
$config{'passchr'}="*";

## Default Skin
$config{'theme'}="Your Theme File";

## Public/Private BBS
$config{'public'}="1";

## Force Full Name
$config{'usefullname'}="1";

## Force User's phone number
$config{'usephonenum'}="1";

## BBS's Name
$config{'systemname'}="Your BBS, or Application name";

## Sysop's Name
$config{'sysop'}="Your Handle, or Sysop";

## User account the application runs as
$config{'unixuser'}="chat";

## Default Sec level
$config{'defsecurity'}="10";

## Default Teleconference Channel
$config{'defchannel'}="MAIN";

## Default User selected skin
$config{'deftheme'}="Your Theme File";

## Teleconference Admin security level (hide / unhide ANY channels)
$config{'chanop'}="50";

## Delay (seconds) between processing events while waiting for commands
## Shouldn't need to change the event delay, it should handle hundreds
## of connections at "1"
$config{'eventdelay'}="1";

### System Information
## This defines the name of your server
$sysinfo{'servername'}="Your Application Name";

## Defines to the system the version of this application
$sysinfo{'version'}="Your Application Version";

## Defines your copyright notice
$sysinfo{'copyright'}="Your Copyright Notice";

## Defines the system host name as a system variable
chomp ($sysinfo{'host'}=`hostname`);

PhotonAPI

System Functions:

Function:
doevents

Provides:
Adds the capability of messaging. Doevents executes every 1 second while in the menuing system or a chat prompt, and processes incoming message events.

Arguments:
None

Example:
doevents();

Function:
whosonline

Provides:
Whosonline returns a listing of who is logged into the BBS system.

Arguments:
None

Example:
whosonline();

Function:
iamat

Provides:
Presents who's online data to the caller. It should be used in any major subroutine that takes a user away from the menuing system, or presents the user to the menuing system.

Arguments:
Who, and Location

Example:
iamat($info{'handle'},"Who's online");

Function:
errorout

Provides:
A clean method of trapping errors. It logs errors to logger, and sends the process to housekeeping to clean up open files before exiting.

Arguments:
Error Condition

Example:
open (in,"<$file") || errorout("Could not open $file");

Provides:
The quit routine when invoked provides the user with an option to leave the system or stay online. It presents exita and exitb from the theme file, and waits for an input of Yes, No, or Relogin.

Function:
quit

Arguments:
None

Example:
quit();

Function:
bye

Provides:
The housekeeping routine. When called, it presents goodbyemsg from the theme file, and then it does cleanup of any user activity and closes the process.

Arguments:
None

Example:
bye();

Function:
colorize

Provides:
Exposes the method of converting internal $COLOR codes to ANSI. It first checks to validate the user is capable of displaying ANSI by checking $info{'ansi'} and then processes color accordingly.

Arguments:
None

Example:
colorize();

Function:
applytheme

Provides:
applytheme reads in a theme file, and processes it into menu prompts.

Arguments:
theme file

Example:
applytheme('chizzle');
or
applytheme($info{'theme'});

Function:
lockfile

Provides:
Provides a simple file locking routine that enters a file into a locked state before it is opened by processes. If lockfile sees that a file is already locked it will sleep for 5 seconds, and forcefully remove the lock.

Arguments:
filename

Example:
lockfile($file);
open(in,"<$file") || errorout("Could not open $file");

Function:
unlockfile

Provides:
unlockfile provides a simple file unlock routine that compliments lockfile.

Arguments:
filename

Example:
open(in,"<$file") || errorout("Could not open $file");
unlockfile($file);

Function:
readconfig

Provides:
Exposes a method of reading in a configuration file, which is processed into key / value pairs. For security purposes readconfig requires files to be located in the BBS home/data directory or any subdirectory underneath.

Usage:
readconfig("filename");

Example:
readconfig("bbs.config");

Example config format:

BBSNAME=Intergate
BBSOP=Fewt

Example usage:
print "[".$config{'BBSNAME'}."]\n";

Example output:
[Intergate]

Function:
cbreak

Provides:
Configures the process to mask keystrokes from display. It in essence turns echoing off. It is used by other functions for text entry processing.

Arguments:
on, off

Example:
cbreak(on);
$key=getc(STDIN);
cbreak(off);

Function:
waitkey

Provides:
Method for single keystroke entry. It's most common use could be to wait for a user to press c to continue at a more prompt.

Arguments:
Default Key (accepts this input and uses it if a user just presses enter)

Example:
print "Press (C)ontinue (S)top ? (C/s) ";
$result=waitkey("C");

Function:
writeline

Provides:
Function to display text on screen in an expected format. It will delete any characters in front of it as it displays output, and is configurable to include a carriage return.

Arguments:
Text to output
CR (0 or 1)

Example:
writeline ($WHT."You are the new owner of the ".$YLW.$channel.$WHT." channel ..",1);

Function:
getline

Provides:
Function to input blocks of text with an expected format. It is used to process all non-instant input on the system.

Arguments:
Input types: phone, dob, chat, password, text
Character Length (0-255)
Default input (any text)
Delete input on CR (0,1)

Example:
$info{'location'}=getline(text,50,"The Internet",1);

Function:
readfile

Provides:
Exposes a function to present text files to users. It is commonly used to display help, welcome, or bulletin screens to users. Files read in and processed by readfile must be fully qualified, or located in the BBS home/data/text directory. readfile parses text files and replaces the following @CODES with system, or user data; or color information:

Action
Colors:
@LGN Light Green
@GRN Green
@RED Red
@BRN Brown
@PPL Purple
@LGR Light Grey
@GRY Grey
@YLW Yellow
@VLT Violet
@WHT White
@LTB Light Blue
@PNK Pink
@BLU Blue
@BLK Black

System Variables:
@SYSNM - BBS name
@MENU - Currently selected menu
@NODE - Node Number
@CONNECT - IP Address connected from
@OS - BBS Operating System
@CPU - CPU Make and Model
@HOST - hostname
@SYSTEM - system
@SPEED - cpuspeed
@TIME - Current Time
@DATE - Current Date
@TOTALCALLS - Number of calls
@USER - Users Name
@RNAME - Users Real Name
@DOB - Date of Birth
@PHONE - Phone Number
@LOCAL - User Location
@CREDITS - Number of available Credits (unused)
@TLEFT - Time left in minutes (unused)
@ID - Users system ID index number
@SEX - M / F
@EMAIL - Users Email Address
@DND - Do not disturb

Arguments:
Filename
Pause Prompt (0,1)
Absolute Path (0,1)

Example:
readfile($info{'home'}."/".$info{'text'}."/welcome".$info{'ext'});
readfile("telehelp.txt",1);
readfile("$config{'home'}$config{'messages'}/teleconf/$channel/message",1,1);

Function:
pause

Provides:
Standardized wait for key to continue prompt that can be re-used anywhere on the system. It reads the pause prompt text from the pause text in the theme, and waits for (C)ontinue, (N)o Pause, or (Q)uit. pause returns the key pressed to the caller.

Arguments:
None

Usage:
pause();

Function:
hi

Provides:
Adds the the method of detecting user connection information. It performs the following operations:
Detects Telnet or SSH connection
Determines the connection type as local or IP
Filters scp or sftp connections
Checks for duplicate IP, and kills all connections (if configured)
Detects ANSI capability
Assigns a node number
Checks IP against a list of banned IP addresses
Updates the total calls file if it exists

Arguments:
None

Example:
hi();
authenticate();

Function:
logger

Provides:
Provides a common logging method. It will send logs to the bbs's host via syslog. Logs are presented to syslog as local6.notice. It is important that syslog be modified to accept logs from local6.

Arguments:
Log data

Example:
logger($info{'handle'}." Logged off!");
logger("Saved ".$info{'handle'}." to record number ".$info{'id'});

PhotonAPI User functions:

Function:
authenticate

Provides:
Exposes a common framework for authenticating users. authenticate performs the following functions:

Displays login from the theme file
Accepts users handle, or NEW
Submits new user requests to newuser()
Determines if the user is local, if so:
Assigns the unix account name as the handle
Bypasses the password prompt
Processes a new user if user does not exist
Authenticates remote users

Arguments:
None

Example:
authenticate()
iamat($info{'handle'},"Heading to Chat");

Function:
finduser

Provides:
Provides a method of determining if a user exists on the system. If the user is found in the database, the user record is loaded into the system. finduser then returns "valid" or "invalid" based on the result of the search.

Arguments:
None

Example:
$info{'handle'}=getline(text,16,"",1)
$usercheck=finduser();

Function:
finduserid

Provides:
Searches the users.dat file, and returns the users ID. This function is only called by the updateuser function.

Arguments:
None

Example:
$info{'handle'}=getline(text,16,"",1);
$userid=finduserid();

Function:
loaduser

Provides:
Accepts the users ID as input, and loads the user record into memory. The user record is then accessible via the $info{} hash.

Arguments:
User ID

Example:
loaduser($id);

Function:
updateuser

Provides:
Updateuser provides the save function. It writes any new user data to the users .dat file.

Arguments:
None

Example:
updateuser();

Function:
newuser

Provides:
Provides a common method of adding new user accounts to the system. It is configurable by defining options in the configuration file, or the main application module itself. The following actions are taken by newuser:

Determine if it is a closed system (if so, hang up)
Present welcome.txt, and the agree prompt if it exists
Asks ANSI / ASCII choice (required)
Asks for users Full Name (if required)
Asks for phone number (if required)
Asks for location information (required)
Asks for email address (requred)
Asks for DOB (required)
Asks for Sex (required)
Asks for a Handle (required, bypassed if local)
Asks for and sets the new user's password

Arguments:
None

Example:
newuser();

Function:
chansi

Provides:
Exposes a function to toggle ansi color on and off.

Arguments:
None

Example:
if ($command =~/ANSI/i) {
chansi();
writeline($RST,1);
goto main;
}

Function:
chsex

Provides:
Allows you to change your sex from M to F or from F to M. Suspect it won't be used often.

Arguments:
None

Example:
if ($command =~/SEX/i) {
chsex();
writeline($RST,1);
goto main;
}

Function:
chphone

Provides:
Changes the users phone number.

Arguments:
None

Example:
if ($command =~/PHONE/i) {
chphone();
writeline($RST,1);
goto main;
}

Function:
chdob

Provides:
Changes the users date of birth.

Arguments:
None

Example:
if ($command =~/DOB/i) {
chdob();
writeline($RST,1);
goto main;
}

Function:
chrealname

Provides:
Changes the users name.

Arguments:
None

Example:
if ($command =~/NAME/i) {
chrealname();
writeline($RST,1);
goto main;
}

Function:
chpassword

Provides:
Changes the users password.

Arguments:
None

Example:
if ($command =~/PASSWORD/i) {
chpassword();
writeline($RST,1);
goto main;
}

Function:
chlocal

Provides:
Changes the users location.

Arguments:
None

Example:
if ($command =~/LOCATION/i) {
chlocal();
writeline($RST,1);
goto main;
}

Function:
chdnd

Provides:
Toggles do not disturb. Do not disturb mode supresses messages sent to the user by other users.

Arguments:
None

Example:
if ($command =~/DND/i) {
chdnd();
writeline($RST,1);
goto main;
}

Function:
chemail

Provides:
Changes the users email address.

Arguments:
None

Example:
if ($command =~/EMAIL/i) {
chemail();
writeline($RST,1);
goto main;
}

Download the PhotonAPI - [ HERE ]

So long XBox, thanks for all the parts.

It's been a good run. You've entertained for years, but now you are dead. It's been slow, and painful. First went component out with green and red dying. You struggled on for a while, at least two weeks before your audio shut down. Last night though was the end with a final flash of orange before powering off for the last time.

You'll be missed XBox, you'll be missed.

Being that XBox was flagged an organ doner, it's usable parts have been removed namely the power supply, muffin fan, controllers, and controller connectors. The supply and fan have been placed on ice waiting to be re-used and the controller plugs? Well they were immediately transplanted.

A pair of controller connectors were soldered on to an old USB hub which is now hooked to my main linux rig. The pair of controllers hooked to it work splendidly.

Jan 6 10:33:02 deathstar kernel: [ 6854.423583] usb 3-1: new full speed USB device using uhci_hcd and address 2
Jan 6 10:33:02 deathstar kernel: [ 6854.652141] usb 3-1: configuration #1 chosen from 1 choice
Jan 6 10:33:02 deathstar kernel: [ 6854.655075] hub 3-1:1.0: USB hub found
Jan 6 10:33:02 deathstar kernel: [ 6854.658024] hub 3-1:1.0: 4 ports detected
Jan 6 10:33:02 deathstar kernel: [ 6854.971245] usb 3-1.2: new full speed US
B device using uhci_hcd and address 3
Jan 6 10:33:03 deathstar kernel: [ 6855.083082] usb 3-1.2: configuration #1 chosen from 1 choice
Jan 6 10:33:03 deathstar kernel: [ 6855.086000] hub 3-1.2:1.0: USB hub found
Jan 6 10:33:03 deathstar kernel: [ 6855.088951] hub 3-1.2:1.0: 3 ports detected
Jan 6 10:33:03 deathstar kernel: [ 6855.402177] usb 3-1.4: new full speed USB device using uhci_hcd and address 4
Jan 6 10:33:03 deathstar kernel: [ 6855.514017] usb 3-1.4: configuration #1 chosen from 1 choice
Jan 6 10:33:03 deathstar kernel: [ 6855.516940] hub 3-1.4:1.0: USB hub found
Jan 6 10:33:03 deathstar kernel: [ 6855.519932] hub 3-1.4:1.0: 3 ports detected
Jan 6 10:33:03 deathstar kernel: [ 6855.833114] usb 3-1.2.1: new full speed USB device using uhci_hcd and address 5
Jan 6 10:33:03 deathstar kernel: [ 6855.944955] usb 3-1.2.1: configuration #1 chosen from 1 choice
Jan 6 10:33:03 deathstar kernel: [ 6855.947941] input: Microsoft Xbox Controller S as /class/input/input7
Jan 6 10:33:04 deathstar kernel: [ 6856.152328] usb 3-1.4.1: new full speed USB device using uhci_hcd and address 6
Jan 6 10:33:04 deathstar kernel: [ 6856.264162] usb 3-1.4.1: configuration #1 chosen from 1 choice
Jan 6 10:33:04 deathstar kernel: [ 6856.267150] input: Microsoft Xbox Controller S as /class/input/input8


The controller connector uses the same color code as a standard USB connection, however the pinout is as follows:

USB:
1. Red: VCC+
2. White: Data -
3. Green: Data +
4. Black: Ground

If looking at a female USB connector, pin one is to the left when the contacts face down. Just match the pins on the usb to the colors (as described above) on the XBox connector.

Diagrams for USB can be found [ HERE ] and for XBox [ HERE ]

(First published Jan 6, 2008 reposted on blogger)

HOW-TO: Create an automated apt-repository

Today we'll create an automated apt repository. For the automation, we'll use a simple script which is executed on a timer (in cron). This script will automatically detect and add new packages from an "incoming" directory. We'll sign our packages using a GPG key, and publish the site to users with apache. The repository example used will import i386 and amd64 packages for installation.

Starting with a fully patched Ubuntu host, we'll install the following debs:

apt-get install apache2 libapache2-mod-perl2 reprepro

If you would like to build custom debian packages (covered in a future article coming soon!), install the following additional packages.

apt-get install devscripts dh-make fakeroot build-essential linux-headers-`uname -r`

Once all of the packages have installed, edit the apache default site (this assumes that this server hosts no other sites, if so adjust accordingly).

Configure the default site as follows, changing the admin@fewt.com to a custom email address (Note: I'll de-ugly this as I figure out how to best post *ML on blogger):

NameVirtualHost *

<VirtualHost *>
ServerAdmin admin@fewt.com
DocumentRoot /var/www/
ServerName apt.asurion.com
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
<Directory "/var/www">
Options Indexes FollowSymLinks MultiViews
DirectoryIndex index.html
AllowOverride Options
Order allow,deny
allow from all
</Directory>
<Directory "/var/www/conf">
Order allow,deny
Deny from all
Satisfy all
</Directory>
<Directory "/var/www/db">
Order allow,deny
Deny from all
Satisfy all
</Directory>
</VirtualHost>

Save and quit by pressing escape, followed by : then wq and enter ({ESC}:wq{ENTER})

Restart apache with the following command:

apache2ctl restart

Next, cd into /var/www and create the default directories needed for reprepro:

cd /var/www
mkdir conf
mkdir override
mkdir incoming
chown you:bin incoming
chmod 0775 incoming

Next; Create the default files:

cd conf
vi distributions

Origin: Your Name or Company Here
Label: public private whatever
Suite: stable
Codename: hardy
SignWith: default
Architectures: i386 amd64 all source
Components: main non-free contrib
Description: Something like "Packages for my systems"

Now; Create an options file:

vi options

verbose
ask-passphrase
basedir /var/www

Save and quit by pressing escape, followed by : then wq and enter ({ESC}:wq{ENTER})

Create your GPG key for repository signing and make sure it's in root's keystore:

sudo -s
gpg --gen-key

Note: If you choose a password for signing, automated package processing will not work.

Now, export the public key for import into apt:

gpg --list-public-keys

Using the line that states "pub" look for the hex numbers after the / (Ex: pub 1024D/5F6DEDCD 2008-07-10)

We'll use my temporary key hex for this example, be sure to change it.

gpg --export 0x5F6DEDCD >repo.key

Whenever you connect a new system to this apt repository you will need to place a copy of the key into your working directory and issue the following command:

apt-key add repo.key

Note: In a future article I will walk you through inserting this key into the Ubuntu keychain package for installation, and into a custom package for management.

Now we'll create the initial repository, then add the automation to ensure packages placed in /var/www/incoming are processed. Be sure to replace "hardy" with the Codename from the distributions file.

cd /var/www
reprepro check hardy

Once we have a repository, lets create the automation:

mkdir ~/bin
cd ~/bin
vi process-debs.sh (Note: add the following content, change all instances of hardy to match the Codename: line in the distributions file.)

#!/bin/bash
cd /var/www
if [ -e "./db/lockfile" ]; then
echo Error creating lockfile './db/lockfile', quitting.
exit
fi

for item in incoming/*deb; do
if [ ! -e $item ]; then
exit
fi
if [ -e $item ]; then
reprepro includedeb hardy $item >/dev/null 2>&1 && (rm $item >/dev/null 2>&1; logger -t user:notice Added package [$item] to the repository)
fi
done

Save and quit by pressing escape, followed by : then wq and enter ({ESC}:wq{ENTER})

Make the script owned by root and executable by world by issuing:

chown root:bin ./process-debs.sh
chmod 755 ./process-debs.sh

Create an entry in crontab containing the following (change the time to whatever you want here):

*/5 * * * * /path/to/process-debs.sh >> /var/log/repository.log 2>&1

Add a debian package to /var/www/incoming and wait. Once the job executes you should see it move to the pool.

Add an entry to a host to use the new repository (replace "main non-free contrib" with the definition of components in the distribution file):

vi /etc/apt/sources.list.d/my.repo

deb http://yourserver hardy main non-free contrib

Save and quit by pressing escape, followed by : then wq and enter ({ESC}:wq{ENTER})

If you have added the key using the steps above, you can 'apt-get update' then 'apt-get install what-ever-you-put-in-your-repo'

Sunday, June 22, 2008

Laptop Hacking - Just a little more space

Like any geek, I have a ton of old drives. They are mostly used in external cases for backups or to run a VM on to do some work that I don't need on all the time.

The other day, I was digging through a drawer and I came across a 100GB PATA notebook drive that I had replaced in my personal laptop with a 100GB 7200RPM. I pondered the thought of putting it into an external case so I could do some Ubuntu package deployment testing when I realized it would be really very cool if I could make the drive fit IN the laptop.

So, I pulled the notebook apart and began walking through hacking my spare drive into it.

Here's the laptop before I did any work to it, as you can see it's just a run of the mill Compaq notebook. Single core AMD 3500 socket 939 w/ 2GB of DDR memory and a 100GB 7200 PATA drive. Once completed this laptop will have 200GB of total storage; 100GB 7200 RPM connected to the primary master, and 100GB 4200RPM connected via USB.

I kept track of each screw by storing each type in a small pile which I kept together with scotch tape. I labeled each pile to whatever the screws belonged to like case, display, keyboard, etc. This allowed me to completely disassemble and reassemble the laptop very quickly without error. I was sure to use the utmost caution whenever I was connecting and disconnecting double checking everything before continuing. Once I had the laptop open, I had to select an interface to connect to. The options were: Primary Slave, Secondary Slave, and USB. I ruled the first two out pretty quickly. While they weren't impossible, they involved significantly more effort as I would have had to de-solder the connections (44 pin header on the primary, and a 50 pin header on the secondary) and solder 44 individual connections in addition for finding a way to re-route the cables so the connections still worked for their native devices. Using the USB bus, I was able to drop the disk inline with the USB port by soldering connections directly to the motherboard. I added a small switch to I could turn the drive off when it wasn't in use to save power. Additionally, I can also use the USB port for other devices while the second hard drive is powered off. On the motherboard I soldered a total of 4 connections to USB (Pinout: 1: VCC, 2: Data+, 3: Data -, 4: Ground). I ran the connection from pin 1 (VCC) to a single pole single throw (SPST) mini-toggle switch which I mounted in an open area on the opposite side of the laptop from the hard drive and then ran the connection back to the USB input on the 2.5" drive adapter. I then connected the rest of the data and ground connections using the bundled USB cable without it's connectors because it's USB 2.0.

In order to make the drive fit properly, I needed to cut some of the metal on the right side of the laptop that protruded from the Kensington lock. Using a dremel and metal cutting tip I sliced about 1/2 of the two small sections away which allows me to retain complete functionality of the lock and doubles as a mount for the drive itself. Additionally, I shaved some of the plastic from an unused screw mount which was probably there from supporting an earlier model (there was nothing connected to it). Once I had the metal and plastic trimmed, I mounted the hard drive into the case and ensured that my newly added cables were not in the way of the DVD sled. Once I had the drive in place, I used a non-conductive heat shield from an old power supply to protect the drive electronics from the top of the DVD (note: the drive only looks upside down, the keyboard is actually on the other side so when complete it is mounted upright). I then began to re-assemble the notebook without bolting anything down to ensure that the computer still booted, and that it did see the drive (success woot!). Once tested, I completely re-assembled the notebook and was pleased to see that everything fit perfectly, and worked. If it wasn't for the toggle switch, you wouldn't know that there were two hard drives hacked into the laptop, as it looks just like it did before I started.

UPDATE: I found a great little rocker switch to replace the toggle, and I've installed it this morning. (June 28). Pics below.