Showing posts with label script. Show all posts
Showing posts with label script. Show all posts

Friday, June 16, 2017

Merge A Data Set With A Template File To Generate Output Files

For something I'm working on I need to be able to create a large number of files by filling in fields in a template file with entries from a data set. You'd think that would be easy with Linux but I couldn't find a way to do it. (This will be where people tell me a thousand different ways to do it) I didn't think what I wanted was complicated so I wrote SimpleMerge to take care of it. It is a basic Python script that takes data from a tab delimited data file and fills in data fields in a template file.

The first row of the data file are the field identifiers to find and replace and the other rows are just data. This file can be easily generated from a spreadsheet program. The template file contains the structure of the file you intend to create, just with field identifiers in the place of real data.

I haven't done extensive testing on the program but it seems to work fine.  It handles UTF-8 file encoding and maintains the line endings of the template file for both UNIX and Windows systems. The following command generates the two files File1.txt and File2.txt as seen in the block diagram below.

SimpleMerge.py template.txt Data.txt


Block Diagram
Simple Merge Block Diagram
You can use this method on any file really, even SVG files.  Hint hint wink wink.  You can go from this template file.....

Periodic Table Symbols
SVG Template

to this in a matter of minutes. Just by replacing colour and three text fields.

Periodic Table Symbols
Generated Images

I make no guarantee as to how well this works. So my advice is to back things up before using it. Have fun.
Get The Code!
.

Sunday, October 30, 2016

Create Compressed Encrypted Backups Only When Files Change

Most of the files that I back up aren't really that important, but some of them contain personal information that I'd like to keep private.  My usual strategy is to sync things to Google Drive, but I do so on the assumption that one day a data breach will make everything visible.  So I needed a way to encrypt some items before backing them up.  Writing a PowerShell script seemed the best way to accomplish this.

In my last post I described a method to generate hashes for files and directories.  My intent for this is to be able to tell if they have changed and need their backups replaced.  Using this as a starting point I was able to create a script to compress and encrypt items ready to sync them to Google Drive.  It's not too complicated but needs some explanation.

A naming strategy for the backups was needed and the solution that seemed to fit best was to use the following format.

YYYYMMDD_XXXXXXXX_<Orginal Item Name>.tar.gpg

YYYYMMDD - represents the date the backup was created
XXXXXXXX - are the last 8 hex characters of the item hash of the backed up data (8 is enough, I didn't want to make the filenames too long.)
<Orginal Item Name > - is the original name of the file or directory
.tar.gpg - denotes that the that the file is an encrypted archive

For example, something like test.txt may become 20161029_A4F88BC1_test.txt.tar.gpg

The backup process is as follows.  Each file or directory in the specified input directory is processed by first calculating its hash.  The output directory is searched to see if a backup of the data already exists.  A valid match is found if the fingerprint and original name in the filename matches the data that is being considered for backup.  If so, the process doesn't need to continue as there is already a backup.  To change an encrypted backup for no reason means it would have to be re-uploaded.

If the data has changed since the last backup, or no backup exists, a new one is created.  The file or directory is added to a tar archive, compressed, and encrypted.  This is all done in a temporary folder created inside the system temp directory.  If the encryption process succeeds, the old backups are removed and replaced by the new one.

Back up output
Command line output of script
.
Backed up Files
Encrypted files in the the Windows explorer
I was determined to make sure that the script supports Unicode file names, but unfortunately gpg can't handle files with unicode characters in the name.  To get around this the file is redirected into and out of the command so that gpg only deals with the data.  This causes a problem though.  If the encryption step fails, the output file is still created but 0 bytes are redirected to it.  To make sure this isn't a problem the program checks to see in the gpg command completed successfully before replacing the backup. 

gpg encryption command
How to encrypt files with Unicode filenames
I really like encrypting back ups with public key cryptography.  There are no passwords to accidentally leave in scripts that can lead to security problems.

 Get The Code!
Get The Code

Saturday, October 15, 2016

Generating Directory Hashes

In my ongoing efforts to back up my files, I have a directory structure that I want to archive, but only if it has changed.  The reason for this is that the back ups are compressed and every time they get changed the whole archive needs to be re-uploaded.  Unfortunately the internet in Australia makes this a daunting task.  So I need a way to know if a directory has changed.  There are a couple ways I could do this, I could use time stamps to see if files or directories have been modified, or I could look at file contents.  I decided to look at the file contents, and basically create a "hash" for files and directories.  This would allow me to compare values over time.

Most people reading this would be familiar with taking the MD5 hash of a file and what that means.  It gives you a fingerprint of the file contents, and if any of the contents change, the hash value changes dramatically.  That's great, but it's only for files and it completely ignores the file name.  To me, a directory structure has changed if even a file name has changed.  I explored using something like an archiving format like tar to bundle up the directory structure with file contents and then taking a hash, but there's no guarantee that one implementation of tar will give exactly the same results as another, i.e. it's not deterministic.  This would give different hash values and is useless.

To overcome these problems I came up with something that I think is reasonably simple that only takes into account changes in directory structure, file and directory names, and file contents when determining if something has changed.

  • A directory structure can have files and sub-directories.
  • A file hash is equal to MD5(MD5(file contents) XOR MD5(UTF-8 byte array of name))
  • A directory hash is equal to MD5((directory contents) XOR MD5(UTF-8 byte array of name))
  • The content of a directory is equal to the XOR of the hashes of all files and directories it contains in the level below it.

Let me just state now that I know MD5 isn't secure, this isn't a security thing, I just need a fast way to get a file checksum.

So with these basic rules I wrote a PowerShell script so that we can take the hash of files and directories to a create a fingerprint so that they can be compared to future versions.  In the test below I created a directory structure with some test files to play around with.  Some directories are junction points and symlinks.  Some files are hardlinks and symlinks.  The script can be configured to ignore junction points and symlinks, not hardlinks as these are indistinguishable from other files.  I also threw in some unicode file and directory just to make sure every thing works as expected.

In the image below, each item in the directory structure has its own box with 4 different hexadecimal strings.  The red string describes the content hash.  If it's a file, that's just the normal MD5 hash of the file, if it's a directory it's the combined XOR of the all files and directories in the level below it.  The green string is the MD5 hash of the byte array of the name of the file or directory.  The blue string is the XOR of the content hash and the name hash.  The black string with green highlighting is the MD5 hash of the XOR result. (I'll get back to why this is done later)

directory structure
Calculating a directory hash
The implementation isn't too hard.  First create a function that calculates our version of a file hash.  Then create a function that can create a directory hash that calculates the hash of all items in it, along with the other operations needed to create the directory hash.  By recursion this will then explore the directory tree.

It may seem excessive to perform a hash on the result of the XOR value, but in the scenario below I'll show how you can get the same hash for two different directory contents if you don't do it.

File Hashes
No final hash can lead to different directories with equal hashes
You can see in the image above that if you swap the content of two files and don't do a final hash you can end up in a situation where they can give equal content hashes and if they happen to be in directories with the same name, those directories will have the same hash value. Hashing the values of the XOR prevents this as can be seen below.

File hashes
Adding a final hash leads to directories with different hashes
You can now differentiate between the directories as they have different hashes.  The final MD5 operation basically "scrambles" the information of the content and name hash before it can propagate to the level above.  Without it, and because of the associative and commutative properties of the XOR function you can end up with equal XOR results.

Get the code!
As with a lot of my projects they're a little rough.  I'd love for someone to take the ball and run with it to create a more professional version.  I think I've provided enough information to get people started.

Tuesday, October 4, 2016

Back up Git repositories to Google Drive

I'm trying to come up with a decent backup strategy and I'm almost there.  Figuring out a way to back up git repositories was a little confusing though.  I use GitHub to host repositories that I'm working on locally, and that's an OKish backup, but I don't check every file into Git.  For example, if I'm working on an electronics design I don't really want the manual for the micro-controller to be tracked by version control, but I do want a backup of the manual just in case they change it for some reason.  So for files like this I keep them with all the others and add them to the .gitignore file.  This is great, but they're not backed up anywhere.

Normally I use Google Drive for my backups.  There are other services that are probably better and have desktop syncing apps that are more polished, but I can easily access files from any device and I trust Google not to go broke in 6 months.  So a simple solution to my problem might be to store local repositories in the Google Drive directory.  That may work, but I just don't trust Git and the Google Drive app to get along together.  So what I ended up doing was just copying a backup of the repository to Google Drive.

This works, but if you copy files to the Google Drive directory and overwrite the old versions it wants to re-upload everything even if the files are unchanged.  You could do a copy where only newer files are overwritten but then another problem arises, files that are deleted from the repository remain in the backup taking up space.  That might be ideal in some situations but I want this basically to be a mirror of the current state of the local repository folder.  In reality what I want is a one way sync to the backup location.  Luckily the robocopy command can manage this.

cmd /k robocopy "Repositories To Backup" "Backup Location" /e /purge

By placing the above command in a batch file, anything new in the "Repositories To Backup" directory will be copied to the "Backup Location".  Don't worry about the cmd /k part, it just lets the command window stay open after it runs robocopy.  By default robocopy copies a file if it's changed in any way.  If unchanged, it will just skip the file.  This will prevent Drive from wanting to upload the file again.  The /e option means it will also copy empty subdirectories and the /purge option means that it will delete files from the backup location that don't appear in the source directory.  This keeps the backup location synced to the source location.

I keep all my git repositories in a Projects folder, so I just set the "repositories to backup" to the this folder, so that when I run the batch file it backs up all the repositories at once.  I run the batch script it manually, but you could schedule it to run automatically too.  I know it's not the best solution, but it works for me.

Sunday, June 26, 2016

Find & Copy Files While Adding The MD5 Hash To The Filename

Today I thought I'd show you a small script I'm using to decommission an old computer.  To make sure important files weren't deleted I wrote a BASH script that finds all files on a drive with a certain extension and copies them to an external drive.  This works well but sometimes two different files have the same name.  To make sure one doesn't overwrite the other, the file name is appended to its MD5 hash.  I know MD5 has security issues, but it's fine for this.

#!/bin/bash

find /mnt/sda3 -iname '*.pdf' -o -iname '*.svg'|while read line; do
    fname=$(basename "$line")
    md5=$(md5sum "$line" | awk '{print $1}')
    echo "'$line'"
    echo "'$md5'"
    cp -n "$line" /mnt/sdb1/"$md5""$fname"
done


The find command specifies the file types to look for, and where to look, in this case /mnt/sda3.  A while loop then processes each result by copying the file to an external drive (/mnt/sdb1) and changing it's name by adding the md5 hash to the front of it.  Copy is set to not overwrite any duplicate file names quietly. That's fine.  Two files with the same name and MD5 hash are exactly the same so you don't need both copies.

The script is written in BASH because I'm using a live version of Linux to recover the files on the computer.  It's an old Windows computer, but I'd rather do this with Puppy Linux than Vista.  Anyway, if you plan on using the script, do some small tests first.