#!/usr/bin/perl -w # ..::[ The Bitcoin Foundation ]::.. # Who: mod6 # GPG Finger Print: 027A 8D7C 0FB8 A166 4372 0F40 7217 05A8 B71E ADAF # What: Perl Script to Automate Bitcoin v0.5.3 Patching & Compilation # When: 20150212 # Version: v0.0.8.1 # NOTE: If you don't have the dependencies already plugged in on # Debian 6 (squeeze), you'll want to run following commands: # # sudo apt-get update && sudo apt-get -y upgrade # sudo apt-get install -y build-essential libssl-dev libdb4.8-dev \ # libdb4.8++-dev libboost-all-dev curl gnupg my $who = "mod6 0x721705A8B71EADAF"; my $when = "20150212"; my $where = "http://thebitcoin.foundation/"; my $version = "v0.0.8.1"; print "#####################################################################\n"; print "## ##\n"; print "## ..::[ The Bitcoin Foundation Patch & Compile Script ]::.. ##\n"; print "## ##\n"; print "## author: $who ##\n"; print "## when: $when ##\n"; print "## where: $where ##\n"; print "## version: $version ##\n"; print "## ##\n"; print "## If you should encounter problems with this script, ##\n"; print "## please enquire with mod6 at irc.freenode.net channel ##\n"; print "## #bitcoin-assets or write to modsix at gmail dot com ##\n"; print "## Thank you! ##\n"; print "## ##\n"; print "#####################################################################\n"; ############################################################################### # Bitcoin Dev Home # XXX Make sure that you set HOME to your build directory! my $HOME = "CHANGEME"; # XXX Set this to TRUE to clean out all build components from build directory my $FULL_CLEAN = "TRUE"; ############################################################################### my $os = ""; my $SHA256 = "/usr/bin/sha256sum"; #Default my $v053_base = "bitcoin-bitcoin-a8def6b"; my $pruned = "bitcoin-v0_5_3_1"; my $preferred_ssl_version = "1.0.1g"; my %artifacts = ( v053_base => { name => "v0.5.3-0-gd05c03a.tar.gz", url => "http://thebitcoin.foundation/v0.5.3-0-gd05c03a.tar.gz", hash => "aab1f8ea8c7f131ff69dfa3b9437ba35531018be760132dd6373f41a591f6382", order => "0" }, chicken => { name => "chicken.tar.gz", url => "http://thebitcoin.foundation/chicken.tar.gz", hash => "63164ea54f042226a6aa8768b98b0f323d66f08693c6fd271a850c00308517ad", patch => "bitcoin-asciilifeform.1.patch", sig => "bitcoin-asciilifeform.1.patch.sig", order => "1" }, rm_rf_upnp => { name => "rm_rf_upnp.tar.gz", url => "http://thebitcoin.foundation/rm_rf_upnp.tar.gz", hash => "f5f27b806b90b0ffd3d2d73e240eba02f13c5dec3d693b37e08686d04164861a", patch => "rm_rf_upnp.patch", sig => "rm_rf_upnp.patch.sig", order => "2" }, https => { name => "https-snipsnip.tar.gz", url => "http://thebitcoin.foundation/https-snipsnip.tar.gz", hash => "047844a601bc575d7660826b02f0d640f02ae84cd641f45552c825b72bc116f4", patch => "bitcoin-asciilifeform.2-https_snipsnip.patch", sig => "bitcoin-asciilifeform.2-https_snipsnip.patch.sig", order => "3" }, alert => { name => "turdmeister-alert-snip.tar.gz", url => "http://thebitcoin.foundation/turdmeister-alert-snip.tar.gz", hash => "39b5aac2b6de016eda23a96fe6f9a565dbbe4f4aa46e8584c102ee71fa4fe7f4", patch => "bitcoin-asciilifeform.3-turdmeister-alert-snip.patch", sig => "bitcoin-asciilifeform.3-turdmeister-alert-snip.patch.sig", order => "4" }, win32 => { name => "goodbye-win32.tar.gz", url => "http://thebitcoin.foundation/goodbye-win32.tar.gz", hash => "5149a481629e0f10b659d38fee0736e0b6e631ab3bbeb6cde449aa61226cdbb3", patch => "bitcoin-asciilifeform.4-goodbye-win32.patch", sig => "bitcoin-asciilifeform.4-goodbye-win32.patch.sig", order => "5" }, db_config => { name => "db_config.tar.gz", url => "http://thebitcoin.foundation/db_config.tar.gz", hash => "b315dc1494334d24606622b7bd3a2569b53e5ad970971ee666ae7adc28fc7d65", patch => "bitcoin-v0_5_3-db_config.6.patch", sig => "bitcoin-v0_5_3-db_config.6.patch.sig", order => "6" }, rev_bump => { name => "rev_bump.tar.gz", url => "http://thebitcoin.foundation/rev_bump.tar.gz", hash => "5efe5de69b6fdb97ff445a1a37a5cc227dd77b59a8553b3ee235a378f352903a", patch => "bitcoin-v0_5_3_1-rev_bump.7.patch", sig => "bitcoin-v0_5_3_1-rev_bump.7.patch.sig", order => "7" } ); # Check to make sure HOME is set to something other than CHANGEME if($HOME =~ m/CHANGEME/) { print "Error! HOME variable not set!\n"; print "Be sure to set HOME to your build directory.\n"; exit 1; } chdir("$HOME") or die $!; sub checkOS { my @dmesg = `dmesg | grep -e ".*Linux.*"`; foreach(@dmesg) { if($_ =~ m/Linux/i) { print "OS Type Detected: Linux\n"; $os = "LINUX"; last; } } if($os eq "") { @dmesg = `dmesg | grep -e ".*FreeBSD.*"`; foreach(@dmesg) { print "OS Type Detected: FreeBSD\n"; $os = "FREEBSD"; last; } } if($os eq "") { @dmesg = `dmesg | grep -e ".*OpenBSD.*"`; foreach(@dmesg) { print "OS Type Detected: OpenBSD\n"; $os = "OPENBSD"; last; } } } sub checkTools() { @tools = ("curl", "gpg"); foreach(@tools) { my $r = `which $_`; if($r eq "") { print "\`$_\` not found on local environment.\n"; print "Install \`$_\` and try again.\n"; exit 1; } } } sub checkOpenSSL { # Check OpenSSL Environement Version @openssl_version = `openssl version -a`; $env_ssl_version = shift @openssl_version; $env_ssl_version =~ /^OpenSSL ([0-9]\.[0-9]\.[0-9][a-z]*) .*$/; if($1 !~ m/$preferred_ssl_version/) { print "You have OpenSSL Version $1 installed in this environment.\n"; print "Consider building and installing OpenSSL version 1.0.1g\n"; exit 1; } } sub pullArtifacts { # Download Aritifacts foreach my $key (keys %artifacts) { print "Downloading $artifacts{$key}{name}\n"; `curl $artifacts{$key}{url} -s -o $artifacts{$key}{name}`; } } sub verifyArtifactHashes { if($os eq "FREEBSD") { $SHA256 = "/sbin/sha256"; } elsif($os eq "OPENBSD") { $SHA256 = "/bin/sha256"; } # Verify Artifact Hashes foreach my $key (keys %artifacts) { print "Checking $artifacts{$key}{name} hash...\n"; my $hash = `$SHA256 $artifacts{$key}{name}`; if($os eq "FREEBSD" or $os eq "OPENBSD") { $hash =~ /.*= (.*)$/; $hash = $1; } else { $hash =~ /^(.*) .*$/; $hash = $1; } if($hash !~ m/$artifacts{$key}{hash}/) { print "Error! $artifacts{$key}{name} hash did not match!\n"; print "found hash: |$hash|\n"; print " expected: |$artifacts{$key}{hash}|\n"; exit 1; } else { print " >> $artifacts{$key}{name} hash verified.\n"; } } } sub unpackArchives { # Unpack Artifact Archives foreach my $key (keys %artifacts) { print "Unpacking $artifacts{$key}{name}\n"; if($os ne "OPENBSD") { if( $key =~ m/chicken/ or $key =~ m/https/ or $key =~ m/alert/ or $key =~ m/win32/) { `mkdir -p $key && tar -xf $artifacts{$key}{name} -C $key`; } else { `tar -xf $artifacts{$key}{name}`; } } else { if( $key =~ m/chicken/ or $key =~ m/https/ or $key =~ m/alert/ or $key =~ m/win32/) { $tar = substr($artifacts{$key}{name}, 0, -3); `mkdir -p $key `; `gunzip $artifacts{$key}{name} && tar -xf $tar -C $key`; } else { $tar = substr($artifacts{$key}{name}, 0, -3); `gunzip $artifacts{$key}{name} && tar -xf $tar`; } } } } sub getPublicKeys { # Get PGP Public Keys my $begin_pgp_key = "-----BEGIN PGP PUBLIC KEY BLOCK-----"; my $end_pgp_key = "-----END PGP PUBLIC KEY BLOCK-----"; my %pubKeyURL = ( ben_vulpes => "http://pgp.mit.edu/pks/lookup?op=get&search=0x2AFA1A9FD2D031DA", mod6 => "http://pgp.mit.edu/pks/lookup?op=get&search=0x721705A8B71EADAF", asciilifeform => "http://pgp.mit.edu/pks/lookup?op=get&search=0xB98228A001ABFFC7" ); print "Downloading public keys...\n"; `mkdir -p pubkeys`; foreach my $key (keys %pubKeyURL) { my $part1 = "curl -s \"$pubKeyURL{$key}\" "; my $part2 = "| sed -n \"/$begin_pgp_key/,/$end_pgp_key/p\" "; my $part3 = "> pubkeys/$key.pub.asc"; my $fetch = $part1 . $part2 . $part3; `$fetch`; } } sub importPublicKeys { # Verify GPG PubKey import print "Importing public keys to .gnupg keyring...\n"; my $part1 = "gpg --homedir $HOME/.gnupg "; my $part2 = "--logger-fd 1 --import $HOME/pubkeys/*.asc "; my $part3 = "> $HOME/gpg_import.log"; my $gpg_import = $part1 . $part2 . $part3; `$gpg_import`; my @import_log = `cat gpg_import.log`; foreach(@import_log) { if($_ =~ /^.*imported: (\d+).*$/) { if($1 ne "3") { print "Error! GnuPG did not import 3 keys!\n"; exit 1; } } } } sub verifySignatures { print "Verifing patch signatures..\n"; my @sorted = sort { $artifacts{$a}{order} <=> $artifacts{$b}{order} } keys %artifacts; shift @sorted; # Remove 'v053_base' from sorted list foreach my $key (@sorted) { my $flag = "FALSE"; my $part1 = "gpg --homedir $HOME/.gnupg --logger-fd 1 "; my $part2 = "--verify $key/$artifacts{$key}{sig} "; my $part3 = "> $key\_sig.log"; my $verify = $part1 . $part2 . $part3; `$verify`; my @verify = `cat $key\_sig.log`; foreach(@verify) { if($_ =~ /^.*Good signature.*$/) { $flag = "TRUE"; print " >> Signature verified for $artifacts{$key}{sig}\n"; } } if($flag =~ m/FALSE/) { print "Error! Possible bad signature on:\n"; print "$HOME/$key/$artifacts{$key}{sig}\n"; exit 1; } } } sub pruneFiles { # Prune files per Chicken Manifest from v0.5.3 base code. print "Pruning files from v0.5.3 codebase not contained in the manifest...\n"; my $chickenManifest = "$HOME/chicken/bitcoin-0.5.3-no-crud.sha256.manifest"; `mkdir -p $HOME/$pruned`; # Prune from the original v053_base directory chdir("$HOME/$v053_base") or die $!; # Extract filenames from manifest and copy them into new directory if($os eq "FREEBSD" or $os eq "OPENBSD") { my $part1 = "/bin/sh -c \'cut -d \" \" -f 3- $chickenManifest "; my $part2 = "| cpio -pdm $HOME/$pruned\'"; my $prune = $part1 . $part2; `$prune`; } else { my $part1 = "for i in \`cut -d \' \' -f 3- $chickenManifest\`; "; my $part2 = "do cp --parents \$i $HOME/$pruned; done"; my $prune = $part1 . $part2; `$prune`; } # Change dir to HOME to check file count after pruning chdir("$HOME") or die $!; my $count1 = 0; my $count2 = 0; if($os eq "FREEBSD" or $os eq "OPENBSD") { $count1 = `find -X $pruned -type f -print0 | xargs -0 sha256 | wc -l`; $count2 = `find -X $pruned -type f -print0 | xargs -0 sha256 | wc -l`; } else { $count1 = `find $pruned -xtype f -print0 | xargs -0 sha256sum | wc -l`; $count2 = `cat chicken/bitcoin-0.5.3-no-crud.sha256.manifest | wc -l`; } if($count1 ne $count2) { print "Error! The resulting file count after pruning did not match\n"; print "the file count from the manifest.\n"; print "post-pruning: $count1\n"; print " manifest: $count2\n"; exit 1; } } sub applyPatches { # Apply Submitted v0.5.3 Patches print "Patching v0.5.3 source files...\n"; my @sorted = sort { $artifacts{$a}{order} <=> $artifacts{$b}{order} } keys %artifacts; shift @sorted; # Remove 'v053_base' from sorted list foreach my $key (@sorted) { if($os eq "FREEBSD" or $os eq "OPENBSD") { `patch --posix -E -d $pruned/src < $key/$artifacts{$key}{patch}`; } else { `patch -d $pruned/src < $key/$artifacts{$key}{patch}`; } print " >> Patched: $key/$artifacts{$key}{patch}\n"; } } sub cleanUp { if($FULL_CLEAN =~ m/TRUE/i) { print "Performing FULL CLEAN...\n"; `rm -rf chicken`; `rm -rf rm_rf_upnp`; `rm -rf https`; `rm -rf alert`; `rm -rf win32`; `rm -rf db_config`; `rm -rf rev_bump`; `rm -rf pubkeys`; `rm -rf .gnupg`; `rm -rf *.tar.gz`; `rm -rf *.log`; `rm -rf $v053_base`; } } sub compile { # Compile Patched Source: chdir("$HOME/$pruned/src") or die $!; print "Compiling bitcoind...\n"; `make -f makefile.unix`; print "Done!\n"; } sub main { # Execute subroutines checkOS(); checkTools(); #checkOpenSSL(); pullArtifacts(); verifyArtifactHashes(); unpackArchives(); getPublicKeys(); importPublicKeys(); verifySignatures(); pruneFiles(); applyPatches(); cleanUp(); if($os eq "LINUX") { compile(); } } main();