Compare commits
25 Commits
eb0549a511
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 78cadee871 | |||
| 82b5ed0b3c | |||
| 28f5802f41 | |||
| 9d3b704496 | |||
| daa014c4e8 | |||
| f032025956 | |||
| b55882ecde | |||
| 65d134c4ff | |||
| 60be955549 | |||
| a13c18f93d | |||
| ae9c16e405 | |||
| ac052d190a | |||
| 94b5cae9ae | |||
| 0adabd3efe | |||
| bab7810cd3 | |||
| a9590c0dce | |||
| 4f80ad381a | |||
| 16051c01c2 | |||
| c1300f968d | |||
| 71a52ec3d3 | |||
| 57e7f870cf | |||
| db2b7fb39d | |||
| 7b6848eb82 | |||
| 985d7f735a | |||
| b93b7d52f7 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,8 @@
|
|||||||
|
.envrc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pyc
|
*.pyc
|
||||||
.terraform/
|
.terraform/
|
||||||
terraform.tfstate
|
terraform.tfstate
|
||||||
terraform.tfstate.backup
|
terraform.tfstate.backup
|
||||||
|
*.log
|
||||||
|
log.txt
|
||||||
|
|||||||
0
20140204.nue1.buildimage/buildimage.sh
Executable file → Normal file
0
20140204.nue1.buildimage/buildimage.sh
Executable file → Normal file
0
20140204.nue1.buildimage/detect-mirror.sh
Executable file → Normal file
0
20140204.nue1.buildimage/detect-mirror.sh
Executable file → Normal file
0
20140204.nue1.buildimage/gen.sh
Executable file → Normal file
0
20140204.nue1.buildimage/gen.sh
Executable file → Normal file
12
20200627.videosort/makefile.archive
Normal file
12
20200627.videosort/makefile.archive
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
SNEAK_KEY_ID := 5539AD00DE4C42F3AFE11575052443F4DF2A55C2
|
||||||
|
|
||||||
|
check:
|
||||||
|
gpg SHASUMS.sig && shasum -c SHASUMS
|
||||||
|
|
||||||
|
par:
|
||||||
|
par2 create -v -u -m2000 -rm250000 iphone.stills.par2 *.gpg
|
||||||
|
|
||||||
|
gen:
|
||||||
|
rm -f .DS_Store SHASUMS
|
||||||
|
find . -type f -exec shasum {} \; | tee SHASUMS
|
||||||
|
gpg -u $(SNEAK_KEY_ID) -a --output SHASUMS.sig --detach-sig SHASUMS
|
||||||
11
20200627.videosort/sort.sh
Normal file
11
20200627.videosort/sort.sh
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
for FN in *.MOV *.MP4 *.MTS *.mov *.mp4; do
|
||||||
|
MTIME="$(stat -f "%Sm" "$FN")"
|
||||||
|
# eg "Sep 13 05:02:26 2019"
|
||||||
|
NP="$(date -j -f "%b %d %T %Y" "$MTIME" "+%Y/%Y-%m/%Y-%m-%d")"
|
||||||
|
if [[ -e "$FN" ]]; then
|
||||||
|
mkdir -p "$NP"
|
||||||
|
mv "$FN" "$NP"
|
||||||
|
fi
|
||||||
|
done
|
||||||
15
2021-11-09-wireguard-config-renamer/run.sh
Normal file
15
2021-11-09-wireguard-config-renamer/run.sh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
SERVERS="$(curl -sS https://api.mullvad.net/www/relays/all/ |
|
||||||
|
jq -jr '.[] | .hostname, ",", .city_code, ",", .type, ",", .provider, "\n"' |
|
||||||
|
grep wireguard)"
|
||||||
|
|
||||||
|
for LINE in $SERVERS ; do
|
||||||
|
hostname="$(echo $LINE | awk -F, '{print $1}')"
|
||||||
|
city="$(echo $LINE | awk -F, '{print $2}')"
|
||||||
|
provider="$(echo $LINE | awk -F, '{print $4}')"
|
||||||
|
short="$(echo $hostname | awk -F'-' '{print $1}')"
|
||||||
|
echo mv mullvad-$short.conf $short-$city-$provider.conf
|
||||||
|
echo mv $short-wireguard.conf $short-$city-$provider.conf
|
||||||
|
done
|
||||||
37
2022-01-19.migrate-nostromo/Makefile.usb-disk-backup
Normal file
37
2022-01-19.migrate-nostromo/Makefile.usb-disk-backup
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
HOMEEXCLUDE := --exclude /.Trash \
|
||||||
|
--exclude .DS_Store \
|
||||||
|
--exclude /.Spotlight-V100 \
|
||||||
|
--exclude /.cache \
|
||||||
|
--exclude /.fseventsd \
|
||||||
|
--exclude /Library/Caches \
|
||||||
|
--exclude /Library/Mail \
|
||||||
|
--exclude /Library/Metadata/CoreSpotlight \
|
||||||
|
--exclude /tmp
|
||||||
|
|
||||||
|
ROOTEXCLUDE := --exclude /.Trash \
|
||||||
|
--exclude /proc \
|
||||||
|
--exclude /dev \
|
||||||
|
--exclude /.fseventsd \
|
||||||
|
--exclude .DS_Store \
|
||||||
|
--exclude /System/Volumes/Data/Volumes \
|
||||||
|
--exclude /private/var/vm \
|
||||||
|
--exclude /System/Volumes/Data/nix \
|
||||||
|
--exclude System/Volumes/Data/Users/sneak \
|
||||||
|
--exclude /System/Volumes/Data/.fseventsd \
|
||||||
|
--exclude /System/Volumes/Data/.Spotlight-V100 \
|
||||||
|
--exclude /System/Volumes/Data/private/var/folders \
|
||||||
|
--exclude /Volumes
|
||||||
|
|
||||||
|
OPTS := -avP --delete --delete-excluded --delete-before
|
||||||
|
|
||||||
|
HOMEOPTS := $(OPTS) $(HOMEEXCLUDE)
|
||||||
|
ROOTOPTS := $(OPTS) $(ROOTEXCLUDE)
|
||||||
|
|
||||||
|
default: synchome syncroot
|
||||||
|
|
||||||
|
synchome:
|
||||||
|
rsync $(HOMEOPTS) $(HOME)/ ./2021-01-12.nostromo.sneakhome/ | tee -a $(shell date +%Y-%m-%d).homesync.log
|
||||||
|
|
||||||
|
syncroot:
|
||||||
|
rsync $(ROOTOPTS) / ./2021-01-12.nostromo.root/ | tee -a $(shell date +%Y-%m-%d).rootsync.log
|
||||||
|
|
||||||
21
2022-01-19.migrate-nostromo/temp-lstor1-backup.sh
Normal file
21
2022-01-19.migrate-nostromo/temp-lstor1-backup.sh
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DST="root@lstor1.local:/srv/lstor1/backup/temp-nostromo-migration"
|
||||||
|
SRC=""
|
||||||
|
|
||||||
|
sudo rsync -avP \
|
||||||
|
--delete --delete-excluded \
|
||||||
|
--exclude /proc \
|
||||||
|
--exclude /dev \
|
||||||
|
--exclude /.fseventsd \
|
||||||
|
--exclude /private/var/vm \
|
||||||
|
--exclude /System/Volumes/Data/Volumes \
|
||||||
|
--exclude /System/Volumes/Data/nix \
|
||||||
|
--exclude /System/Volumes/Data/.fseventsd \
|
||||||
|
--exclude /System/Volumes/Data/.Spotlight-V100 \
|
||||||
|
--exclude /System/Volumes/Data/Users/sneak/.Trash \
|
||||||
|
--exclude /System/Volumes/Data/Users/sneak/.cache \
|
||||||
|
--exclude /System/Volumes/Data/Users/sneak/Library/Caches \
|
||||||
|
--exclude /System/Volumes/Data/private/var/folders \
|
||||||
|
--exclude /Volumes \
|
||||||
|
"$SRC"/ "$DST"/
|
||||||
46
2022-02-09-ambientweather/aw.mjs
Normal file
46
2022-02-09-ambientweather/aw.mjs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env zx
|
||||||
|
|
||||||
|
const api = "https://lightning.ambientweather.net/devices";
|
||||||
|
const outputDir = `${process.env.HOME}/tmp/weatherfetch`;
|
||||||
|
const queryString =
|
||||||
|
"%24publicBox%5B0%5D%5B0%5D=-115.37389456264378&%24publicBox%5B0%5D%5B1%5D=36.1098453902911&%24publicBox%5B1%5D%5B0%5D=-115.1709572152316&%24publicBox%5B1%5D%5B1%5D=36.24422733946878&%24limit=500";
|
||||||
|
const userAgent =
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36";
|
||||||
|
|
||||||
|
async function fetchWeather() {
|
||||||
|
const cw = await $`curl ${api}?${queryString} \
|
||||||
|
-H 'accept: application/json' \
|
||||||
|
-H 'authority: lightning.ambientweather.net' \
|
||||||
|
-H 'user-agent: '${userAgent} \
|
||||||
|
-H 'origin: https://ambientweather.net' \
|
||||||
|
-H 'sec-fetch-site: same-site' \
|
||||||
|
-H 'sec-fetch-mode: cors' \
|
||||||
|
-H 'sec-fetch-dest: empty' \
|
||||||
|
-H 'referer: https://ambientweather.net/' \
|
||||||
|
-H 'accept-language: en-US,en;q=0.9' \
|
||||||
|
--compressed 2>/dev/null`;
|
||||||
|
const o = await JSON.parse(cw);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
function minToMs(minutes) {
|
||||||
|
return minutes * 60 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await $`test -d ${outputDir} || mkdir -p ${outputDir}`;
|
||||||
|
while (true) {
|
||||||
|
await oneLoop();
|
||||||
|
await sleep(minToMs(30));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function oneLoop() {
|
||||||
|
const now = await $`date -u +"%Y%m%dT%H%M%SZ"`;
|
||||||
|
const data = await fetchWeather();
|
||||||
|
|
||||||
|
fs.writeFileSync(outputDir + "/latest.json", JSON.stringify(data));
|
||||||
|
await $`cp ${outputDir}/latest.json ${outputDir}/${now}.json`;
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
51
2022-09-16.signalattachments/process.sh
Normal file
51
2022-09-16.signalattachments/process.sh
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
for FN in $(file * | grep -i png | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.png
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep -i jpeg | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.jpg
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep -i gif | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.gif
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep -i 'Web/P' | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.webp
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep -i mp4 | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.mp4
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep -i pdf | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.pdf
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep -i "\.M4A" | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.m4a
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep "MPEG ADTS, AAC" | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.aac
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep "EPUB" | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.epub
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep "Zip archive" | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.zip
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep "Unicode text" | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.txt
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep "ASCII text" | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.txt
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep "empty" | awk -F':' '{print $1}') ; do
|
||||||
|
rm -v $FN
|
||||||
|
done
|
||||||
|
for FN in $(file * | grep "data" | awk -F':' '{print $1}') ; do
|
||||||
|
mv -v $FN ../out/$FN.dat
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
fdupes -d -q -N ../out
|
||||||
|
|
||||||
|
f2 -r '{{mtime.YYYY}}-{{mtime.MM}}/{{mtime.YYYY}}-{{mtime.MM}}-{{mtime.DD}}.{{f}}{{ext}}' -x
|
||||||
8
2022-11-08-lunar-eclipse-timelapse/Makefile
Normal file
8
2022-11-08-lunar-eclipse-timelapse/Makefile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
default: output
|
||||||
|
|
||||||
|
output:
|
||||||
|
time bash gen.sh
|
||||||
|
du -sh output
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rfv output tmp.sh
|
||||||
61
2022-11-08-lunar-eclipse-timelapse/gen.sh
Normal file
61
2022-11-08-lunar-eclipse-timelapse/gen.sh
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#-c:v libx264 \
|
||||||
|
#-c:v libx265 \
|
||||||
|
#-vf "scale=(iw*sar)*max($W/(iw*sar)\,$H/ih):ih*max($W/(iw*sar)\,$H/ih), crop=$W:$H" \
|
||||||
|
#-s:v ${W}x${H} \
|
||||||
|
W="3840"
|
||||||
|
H="2160"
|
||||||
|
|
||||||
|
DIR1="10621108"
|
||||||
|
DIR2="10721108"
|
||||||
|
|
||||||
|
#-vcodec libx264 \
|
||||||
|
#-crf 17 \
|
||||||
|
#-preset slow \
|
||||||
|
|
||||||
|
function main {
|
||||||
|
convertRaws
|
||||||
|
makeBigVersion
|
||||||
|
makeSmallVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeBigVersion {
|
||||||
|
#-c:v hevc_videotoolbox -q:v 100 \
|
||||||
|
#-vf scale=-2:${H} \
|
||||||
|
ffmpeg -framerate 24 \
|
||||||
|
-pattern_type glob \
|
||||||
|
-i 'output/frames/*.png' \
|
||||||
|
-c:v prores -profile:v 3 \
|
||||||
|
-pix_fmt yuv422p10 \
|
||||||
|
output/big.mov
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSmallVersion {
|
||||||
|
ffmpeg -i output/big.mov \
|
||||||
|
-vf scale=-2:1080 \
|
||||||
|
-c:v h264_videotoolbox \
|
||||||
|
-b:v 12000K \
|
||||||
|
output/preview.1080p.mp4
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertRaws {
|
||||||
|
if [[ -e output/frames/done ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
mkdir -p output/frames
|
||||||
|
rm -rf tmp.sh
|
||||||
|
for i in $DIR1/*.ARW; do
|
||||||
|
BN="$(basename ${i%.*})"
|
||||||
|
echo sips -s format png $i --out "output/frames/1${BN}.png"
|
||||||
|
echo sips -s format png $i --out "output/frames/1${BN}.png" >> tmp.sh
|
||||||
|
done
|
||||||
|
for i in $DIR2/*.ARW; do
|
||||||
|
BN="$(basename ${i%.*})"
|
||||||
|
echo sips -s format png $i --out "output/frames/2${BN}.png"
|
||||||
|
echo sips -s format png $i --out "output/frames/2${BN}.png" >> tmp.sh
|
||||||
|
done
|
||||||
|
parallel --eta -j $(nproc) bash -c < tmp.sh && touch output/frames/done
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
26
2022-external-backup/Makefile
Normal file
26
2022-external-backup/Makefile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
TARGET := ./berlin.sneak.fs.NNNY-cyberdyne-backup-01
|
||||||
|
|
||||||
|
default: backup
|
||||||
|
|
||||||
|
backup: do_file_backup write_checksum
|
||||||
|
|
||||||
|
write_checksum:
|
||||||
|
cd $(TARGET)/fs && find . -type f -print0 | xargs -0 sha1sum > ../.SHASUMS.tmp
|
||||||
|
mv ./.SHASUMS.tmp ./$(TARGET)/SHASUMS.txt
|
||||||
|
|
||||||
|
#--exclude=/tmp \
|
||||||
|
|
||||||
|
do_file_backup:
|
||||||
|
rsync -avP \
|
||||||
|
--exclude=/.cache \
|
||||||
|
--exclude=/.nvm \
|
||||||
|
--exclude=/.Trash \
|
||||||
|
--exclude=/Library/Caches \
|
||||||
|
--exclude=/Library/Mail \
|
||||||
|
--exclude=/Library/Developer \
|
||||||
|
--exclude=.DS_Store \
|
||||||
|
--delete-before \
|
||||||
|
--delete-excluded \
|
||||||
|
$(HOME)/ $(TARGET)/fs/
|
||||||
|
echo '# $(shell date -u)' > $(TARGET)/lastbackup.txt
|
||||||
|
date -u '+%s' >> $(TARGET)/lastbackup.txt
|
||||||
27
2023-01-13-nostromo-decommission/run.sh
Normal file
27
2023-01-13-nostromo-decommission/run.sh
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
DEST="$HOME/tmp/2023-01-13-nostromo.oldhome"
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
rsync -avP \
|
||||||
|
--exclude=/Library/Syncthing \
|
||||||
|
--exclude=/Library/Mail \
|
||||||
|
--exclude=/Library/Caches \
|
||||||
|
--exclude=/Library/Metadata \
|
||||||
|
--exclude=/Library/News \
|
||||||
|
--exclude=/Library/Python \
|
||||||
|
--exclude=/Library/Containers/com.utmapp.UTM \
|
||||||
|
--exclude=/Library/Application?Support/Signal \
|
||||||
|
--exclude=/.Trash \
|
||||||
|
--exclude=/.cache \
|
||||||
|
--exclude=/tmp \
|
||||||
|
--exclude=/go \
|
||||||
|
--exclude=.DS_Store \
|
||||||
|
--exclude=/Documents \
|
||||||
|
--delete-excluded \
|
||||||
|
--delete \
|
||||||
|
$HOME/ $DEST/home/
|
||||||
|
|
||||||
|
tar -c $DEST/home 2>/dev/null |
|
||||||
|
pv --size $(du -sb $DEST/home | awk '{print $1}') |
|
||||||
|
zstdmt |
|
||||||
|
age -r $SNEAK_LONGTERM_ARCHIVE_AGE_PUB |
|
||||||
|
ssh root@lstor1 "cat > /srv/backup/hosts/nostromo/2023-01-13-nostromo-oldhome.tzst.age"
|
||||||
140
archive-to-cloud/tocloud
Normal file
140
archive-to-cloud/tocloud
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o pipefail
|
||||||
|
set -e
|
||||||
|
#set -x
|
||||||
|
|
||||||
|
# decrypt like so:
|
||||||
|
#
|
||||||
|
# gpg -d ~/.paths/sneak-sync/secrets/backup-encryption-keys/2022-11-16.sneak-longterm-archive-age-key.gpg 2>/dev/null |
|
||||||
|
# age -d -i - priv.age | tail -1 2>/dev/null |
|
||||||
|
# age -d -i - archive.age
|
||||||
|
|
||||||
|
YYYYMMDD="$(date -u +%Y-%m-%d)"
|
||||||
|
YYYY="$(date -u +%Y)"
|
||||||
|
MM="$(date -u +%m)"
|
||||||
|
THIS="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||||
|
MY_PGP="5539AD00DE4C42F3AFE11575052443F4DF2A55C2"
|
||||||
|
MY_LONGTERM_AGE_PUBKEY="age1278m9q7dp3chsh2dcy82qk27v047zywyvtxwnj4cvt0z65jw6a7q5dqhfj"
|
||||||
|
TD="$(mktemp -d)"
|
||||||
|
|
||||||
|
LOGDIR="$HOME/Documents/_SYSADMIN/$YYYY-$MM/$YYYYMMDD"
|
||||||
|
if [[ ! -d "$LOGDIR" ]]; then
|
||||||
|
mkdir -p "$LOGDIR"
|
||||||
|
fi
|
||||||
|
exec > >(tee -a $LOGDIR/$YYYYMMDD.$(date -u +%s).tocloud-backup.log) 2>&1
|
||||||
|
|
||||||
|
function on_exit {
|
||||||
|
rm -rf "$TD"
|
||||||
|
}
|
||||||
|
|
||||||
|
function on_terminate {
|
||||||
|
echo "### Cleaning up..."
|
||||||
|
rm -rfv "$TD"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap on_exit ERR EXIT
|
||||||
|
trap on_terminate SIGINT SIGTERM
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
echo "usage: $0 <backupname> <dir>" > /dev/stderr
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#function getStorageBoxCredentials {
|
||||||
|
# gpg -d $HOME/.paths/sneak-sync/secrets/credentials/storagebox-offsite-backup-subaccount.json.gpg
|
||||||
|
#}
|
||||||
|
|
||||||
|
function main {
|
||||||
|
if [[ $# -ne 2 ]]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$2" ]]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$2" ]]; then
|
||||||
|
SRC="$(cd "$2" && pwd -P)"
|
||||||
|
else
|
||||||
|
SRC="$2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -r "$SRC" ]]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUPNAME="$YYYYMMDD.$1.$(date +%s)"
|
||||||
|
time do_backup "$BACKUPNAME" "$SRC"
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_backup {
|
||||||
|
|
||||||
|
BACKUPNAME="$1"
|
||||||
|
SRC="$2"
|
||||||
|
|
||||||
|
cd "$TD"
|
||||||
|
mkdir "$BACKUPNAME"
|
||||||
|
cd "$TD/$BACKUPNAME"
|
||||||
|
echo "### Beginning backup $BACKUPNAME"
|
||||||
|
echo "### Temporary Working Directory: $TD"
|
||||||
|
AGE_PRIV=$(age-keygen 2> ./pub.txt)
|
||||||
|
age -r $MY_LONGTERM_AGE_PUBKEY <<< "$AGE_PRIV" > ./priv.age
|
||||||
|
PUB="$(awk -F' ' '{print $3}' < ./pub.txt)"
|
||||||
|
echo "### Backup Archive Session Pubkey: $PUB"
|
||||||
|
echo "$PUB" > ./pub.txt # overwrite non-clean one
|
||||||
|
gpg --trust-model always \
|
||||||
|
--compress-algo none \
|
||||||
|
-r $MY_PGP --encrypt \
|
||||||
|
-a <<< "$AGE_PRIV" \
|
||||||
|
> ./priv.sneak-pgp-DF2A55C2.asc
|
||||||
|
echo "### Backup Source Size: $(du -sh "$SRC" | awk '{print $1}')"
|
||||||
|
echo "### Indexing backup..."
|
||||||
|
(find "$SRC" -type f \( -exec sha1sum {} \; \)) |
|
||||||
|
tee /dev/stderr |
|
||||||
|
age -r $PUB > "$TD/$BACKUPNAME/archive-sums.txt.age"
|
||||||
|
echo "### Compressing backup..."
|
||||||
|
tar -P -c "$SRC" |
|
||||||
|
nice -n 20 zstd --compress -T0 -10 |
|
||||||
|
pv --delay-start 3 --progress --eta --size $(du -sb "$SRC" | awk '{print $1}') |
|
||||||
|
age -r $PUB |
|
||||||
|
split -d -b 1G -a 4 - $TD/$BACKUPNAME/archive.tar.zst.age.
|
||||||
|
COUNT="$(cd "$TD/$BACKUPNAME" && ls -1 archive.tar.zst.age.* | wc -l | awk '{print $1}')"
|
||||||
|
if [[ "$COUNT" -eq 1 ]]; then
|
||||||
|
mv "$TD/$BACKUPNAME/archive.tar.zst.age.0000" "$TD/$BACKUPNAME/archive.tar.zst.age"
|
||||||
|
fi
|
||||||
|
cd "$TD/$BACKUPNAME"
|
||||||
|
echo "### Backup Compressed Archive Size: $(du -sh "$TD/$BACKUPNAME" | awk '{print $1}')"
|
||||||
|
echo "### Creating Checksums..."
|
||||||
|
shasum archive.tar.zst.age* archive-sums.txt.age | tee -a SHASUMS.txt
|
||||||
|
echo "### Signing Checksums..."
|
||||||
|
gpg --default-key $MY_PGP --output SHASUMS.txt.gpg --detach-sig SHASUMS.txt
|
||||||
|
#tar -c . | pv --progress --eta --size $(du -sb "$TD/$BACKUPNAME" | awk '{print $1}') |
|
||||||
|
#ssh fsn1-storagebox-10T "mkdir -p $BACKUPNAME ; cd $BACKUPNAME && tar xvf -"
|
||||||
|
#while ! rsync -avvvcP --delete "$TD/$BACKUPNAME/" fsn1-storagebox-10T:"$BACKUPNAME"/
|
||||||
|
# sleep 1
|
||||||
|
#done
|
||||||
|
echo "### Uploading data..."
|
||||||
|
# i want to use rsync here but rclone gives much better total
|
||||||
|
# progress/ETA display.
|
||||||
|
rclone sync \
|
||||||
|
--retries 99999 \
|
||||||
|
--progress \
|
||||||
|
--stats-unit bits \
|
||||||
|
--stats-one-line -v \
|
||||||
|
"$TD/$BACKUPNAME" \
|
||||||
|
fsn1-storagebox-10T:"$BACKUPNAME"/ 2>&1
|
||||||
|
# belt and suspenders
|
||||||
|
echo "### Verifying uploaded data checksums..."
|
||||||
|
rsync -acP "$TD/$BACKUPNAME/" fsn1-storagebox-10T:"$BACKUPNAME"/
|
||||||
|
RETVAL="$?"
|
||||||
|
if [[ "$RETVAL" -eq 0 ]]; then
|
||||||
|
echo "### Backup successful."
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "### Problem detected."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
0
attic/02-borg-via-ssh.sh
Executable file → Normal file
0
attic/02-borg-via-ssh.sh
Executable file → Normal file
0
bashlib/bashlib.sh
Executable file → Normal file
0
bashlib/bashlib.sh
Executable file → Normal file
0
bin/backup-dir
Executable file → Normal file
0
bin/backup-dir
Executable file → Normal file
0
bin/check-dir-sig
Executable file → Normal file
0
bin/check-dir-sig
Executable file → Normal file
0
bin/detachsign-file
Executable file → Normal file
0
bin/detachsign-file
Executable file → Normal file
@@ -1,38 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -x
|
|
||||||
|
|
||||||
KEYSERVER="hkps.pool.sks-keyservers.net"
|
|
||||||
#KEYSERVER="pgp.mit.edu"
|
|
||||||
|
|
||||||
KEYS=""
|
|
||||||
KEYS+=" 5539AD00DE4C42F3AFE11575052443F4DF2A55C2" #sneak@sneak.berlin
|
|
||||||
KEYS+=" FF2530A4F3F152E8865FC17CA833B7CE3F2CC6FC" #JP Yubi 6192
|
|
||||||
KEYS+=" 078BBD04FBE35D665180EF8A476509F749BFD1AD" #JP Yubi 2811
|
|
||||||
KEYS+=" 7D1123A022FC90DF389EC65F233196C180B1C28F" #JP Yubi 6643
|
|
||||||
KEYS+=" F5A3A83B9E65EA3E1A2A48E5FDA763BDDCFAB8AC" #Yubikey 4928206 20160927
|
|
||||||
KEYS+=" B044B27DCB86641AFE3AB274779D07C54EB7CF85" #Yubikey 4953582 20160927
|
|
||||||
#KEYS+=" 3FF8DE63855070F8B9CB0D9A67B4CD26470681DA" #Yubikey 4CNano 06931602 20171018
|
|
||||||
|
|
||||||
KEYS+=" 1CA168D7E842DFD1745815006F291E6D9AA87738"
|
|
||||||
KEYS+=" 726D577AFB82E64049B62A8DA763B92AD841A706" # Yubi 7192344 20180613 pris
|
|
||||||
KEYS+=" 04630E42D244BC80717D28D51280F730A9AD633C"
|
|
||||||
KEYS+=" 5D48805E38B8C2E04103C7728CBD64834BC043EA"
|
|
||||||
KEYS+=" 8904EE6400E7B7409CE00AA92084C4563F360B45"
|
|
||||||
KEYS+=" FE65DB157D8BF9E4FEDF50DA927353E2C4507A5B"
|
|
||||||
KEYS+=" 27CF8E00190D7AF340D8AE55A6C1C5C2083CB579"
|
|
||||||
|
|
||||||
GARGS=""
|
|
||||||
GARGS+=" --trust-model always"
|
|
||||||
#GARGS+=" --compress-algo bzip2"
|
|
||||||
GARGS+=" --compress-algo none"
|
|
||||||
#GARGS+=" --bzip2-compress-level 9"
|
|
||||||
|
|
||||||
for KEY in $KEYS ; do
|
|
||||||
if ! gpg --list-key $KEY 2>&1 > /dev/null ; then
|
|
||||||
gpg --recv-key --keyserver $KEYSERVER $KEY
|
|
||||||
fi
|
|
||||||
GARGS+=" -r $KEY"
|
|
||||||
done
|
|
||||||
|
|
||||||
gpg $GARGS --encrypt "$*"
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -x
|
|
||||||
|
|
||||||
KEYSERVER="hkps.pool.sks-keyservers.net"
|
|
||||||
#KEYSERVER="pgp.mit.edu"
|
|
||||||
|
|
||||||
KEYS=""
|
|
||||||
KEYS+=" 5539AD00DE4C42F3AFE11575052443F4DF2A55C2" #sneak@sneak.berlin
|
|
||||||
KEYS+=" FF2530A4F3F152E8865FC17CA833B7CE3F2CC6FC" #JP Yubi 6192
|
|
||||||
KEYS+=" 078BBD04FBE35D665180EF8A476509F749BFD1AD" #JP Yubi 2811
|
|
||||||
KEYS+=" 7D1123A022FC90DF389EC65F233196C180B1C28F" #JP Yubi 6643
|
|
||||||
KEYS+=" F5A3A83B9E65EA3E1A2A48E5FDA763BDDCFAB8AC" #Yubikey 4928206 20160927
|
|
||||||
KEYS+=" B044B27DCB86641AFE3AB274779D07C54EB7CF85" #Yubikey 4953582 20160927
|
|
||||||
#KEYS+=" 3FF8DE63855070F8B9CB0D9A67B4CD26470681DA" #Yubikey 4CNano 06931602 20171018
|
|
||||||
|
|
||||||
KEYS+=" 1CA168D7E842DFD1745815006F291E6D9AA87738"
|
|
||||||
KEYS+=" 726D577AFB82E64049B62A8DA763B92AD841A706" # Yubi 7192344 20180613 pris
|
|
||||||
KEYS+=" 04630E42D244BC80717D28D51280F730A9AD633C"
|
|
||||||
KEYS+=" 5D48805E38B8C2E04103C7728CBD64834BC043EA"
|
|
||||||
KEYS+=" 8904EE6400E7B7409CE00AA92084C4563F360B45"
|
|
||||||
KEYS+=" FE65DB157D8BF9E4FEDF50DA927353E2C4507A5B"
|
|
||||||
KEYS+=" 27CF8E00190D7AF340D8AE55A6C1C5C2083CB579"
|
|
||||||
|
|
||||||
GARGS=""
|
|
||||||
GARGS+=" -a"
|
|
||||||
GARGS+=" --trust-model always"
|
|
||||||
#GARGS+=" --compress-algo bzip2"
|
|
||||||
GARGS+=" --compress-algo none"
|
|
||||||
#GARGS+=" --bzip2-compress-level 9"
|
|
||||||
|
|
||||||
for KEY in $KEYS ; do
|
|
||||||
if ! gpg --list-key $KEY 2>&1 > /dev/null ; then
|
|
||||||
gpg --recv-key --keyserver $KEYSERVER $KEY
|
|
||||||
fi
|
|
||||||
GARGS+=" -r $KEY"
|
|
||||||
done
|
|
||||||
|
|
||||||
gpg $GARGS --encrypt "$*"
|
|
||||||
23
bin/file-by-date
Normal file
23
bin/file-by-date
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
for FN in *; do
|
||||||
|
if [[ -f "$FN" ]]; then
|
||||||
|
MTIME="$(stat -f "%Sm" "$FN")"
|
||||||
|
if [[ ! -z "$MTIME" ]]; then
|
||||||
|
# eg "Sep 13 05:02:26 2019"
|
||||||
|
YYYY="$(date -j -f "%b %d %T %Y" "$MTIME" "+%Y")"
|
||||||
|
MM="$(date -j -f "%b %d %T %Y" "$MTIME" "+%m")"
|
||||||
|
DD="$(date -j -f "%b %d %T %Y" "$MTIME" "+%d")"
|
||||||
|
TD="${YYYY}/${YYYY}-${MM}/${YYYY}-${MM}-${DD}"
|
||||||
|
if [[ ! -d ./"$TD" ]]; then
|
||||||
|
mkdir -p ./"$TD"
|
||||||
|
fi
|
||||||
|
if [[ ! -e ./"${TD}"/"$FN" ]]; then
|
||||||
|
mv ./"$FN" ./"$TD"/"$FN"
|
||||||
|
echo mv ./"$FN" ./"$TD"/"$FN"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
0
bin/nue3.docker
Executable file → Normal file
0
bin/nue3.docker
Executable file → Normal file
25
bin/rename-images-by-mtime
Normal file
25
bin/rename-images-by-mtime
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# be advised: this script is dumb and you should use
|
||||||
|
# https://github.com/ayoisaiah/f2
|
||||||
|
# instead
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
for FN in *.jpg *.png *.jpeg *.heic *.gif *.GIF *.JPG *.PNG *.JPEG *.HEIC; do
|
||||||
|
if [[ -e "$FN" ]]; then
|
||||||
|
MTIME="$(stat -f "%Sm" "$FN")"
|
||||||
|
if [[ ! -z "$MTIME" ]]; then
|
||||||
|
# eg "Sep 13 05:02:26 2019"
|
||||||
|
NP="$(date -j -f "%b %d %T %Y" "$MTIME" "+%Y-%m-%dT%H%M%S")"
|
||||||
|
EXT="${FN##*.}"
|
||||||
|
LOWEREXT="$(echo "$EXT" | tr 'A-Z' 'a-z')"
|
||||||
|
TARGET="$NP.$LOWEREXT"
|
||||||
|
if [[ -e "$FN" ]]; then
|
||||||
|
if [[ ! -e "$TARGET" ]]; then
|
||||||
|
mv "$FN" "$TARGET"
|
||||||
|
echo mv "$FN" "$TARGET"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
25
bin/rename-videos-by-mtime
Normal file
25
bin/rename-videos-by-mtime
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# be advised: this script is dumb and you should use
|
||||||
|
# https://github.com/ayoisaiah/f2
|
||||||
|
# instead
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
for FN in *.MOV *.MP4 *.MTS *.mov *.mp4; do
|
||||||
|
if [[ -e "$FN" ]]; then
|
||||||
|
MTIME="$(stat -f "%Sm" "$FN")"
|
||||||
|
if [[ ! -z "$MTIME" ]]; then
|
||||||
|
# eg "Sep 13 05:02:26 2019"
|
||||||
|
NP="$(date -j -f "%b %d %T %Y" "$MTIME" "+%Y-%m-%dT%H%M%S")"
|
||||||
|
EXT="${FN##*.}"
|
||||||
|
LOWEREXT="$(echo "$EXT" | tr 'A-Z' 'a-z')"
|
||||||
|
TARGET="$NP.$LOWEREXT"
|
||||||
|
if [[ -e "$FN" ]]; then
|
||||||
|
if [[ ! -e "$TARGET" ]]; then
|
||||||
|
mv "$FN" "$TARGET"
|
||||||
|
echo mv "$FN" "$TARGET"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
0
bin/setup-docker-machine
Executable file → Normal file
0
bin/setup-docker-machine
Executable file → Normal file
0
bin/setup-osx-ramdisk
Executable file → Normal file
0
bin/setup-osx-ramdisk
Executable file → Normal file
0
bin/sign-dir
Executable file → Normal file
0
bin/sign-dir
Executable file → Normal file
7
bin/ubtc
Normal file
7
bin/ubtc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
UBTC="$1"
|
||||||
|
BTC="$(echo "scale=6 ; $UBTC / 1000000" | bc)"
|
||||||
|
BTCUSD="$(curl -sS https://blockchain.info/ticker | jq -r '.USD.last')"
|
||||||
|
echo \$$(echo "scale=2 ; ( $BTC * $BTCUSD ) /1.00" | bc) USD
|
||||||
|
|
||||||
0
bitcoin.bbescraper/fetch.sh
Executable file → Normal file
0
bitcoin.bbescraper/fetch.sh
Executable file → Normal file
0
bitcoin.blockchain/test.py
Executable file → Normal file
0
bitcoin.blockchain/test.py
Executable file → Normal file
0
bitcoin.inflationchart/bcinflation.pl
Executable file → Normal file
0
bitcoin.inflationchart/bcinflation.pl
Executable file → Normal file
0
bitcoin.shallow/generate.py
Executable file → Normal file
0
bitcoin.shallow/generate.py
Executable file → Normal file
0
bitcoin.txvolume/txcount.pl
Executable file → Normal file
0
bitcoin.txvolume/txcount.pl
Executable file → Normal file
271
btcphrasechecker/README.md
Normal file
271
btcphrasechecker/README.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# Bitcoin Phrase Checker
|
||||||
|
|
||||||
|
A modular and extensible Go program that derives Bitcoin addresses from a BIP39 mnemonic phrase and provides comprehensive transaction history and balance analysis using public APIs.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Core Functionality
|
||||||
|
- **BIP39 Support**: Validates and processes mnemonic phrases (12/24 words) with optional passphrases
|
||||||
|
- **Multi-Path Derivation**: Automatically derives addresses from standard Bitcoin derivation paths:
|
||||||
|
- **BIP44 (Legacy)**: `m/44'/0'/0'/0` - P2PKH addresses (starting with `1...`)
|
||||||
|
- **BIP49 (SegWit)**: `m/49'/0'/0'/0` - P2SH-wrapped SegWit (starting with `3...`)
|
||||||
|
- **BIP84 (Native SegWit)**: `m/84'/0'/0'/0` - Bech32 addresses (starting with `bc1...`)
|
||||||
|
- **Comprehensive Analysis**: For each address, displays:
|
||||||
|
- Current balance
|
||||||
|
- Total received amount
|
||||||
|
- Total sent amount
|
||||||
|
- Transaction count
|
||||||
|
- Derivation path
|
||||||
|
|
||||||
|
### Transaction History
|
||||||
|
- **Chronological Display**: Shows all transactions sorted by date (YYYY-MM-DD format)
|
||||||
|
- **Running Balance**: Calculates and displays balance after each transaction
|
||||||
|
- **Transaction Types**: Identifies received, sent, and self-transfer transactions
|
||||||
|
- **Detailed Statistics**:
|
||||||
|
- Total received amount and count
|
||||||
|
- Total sent amount and count
|
||||||
|
- Transaction confirmation status
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- **Modular Design**: Organized into separate packages for easy extension
|
||||||
|
- `types/`: Common interfaces and data structures
|
||||||
|
- `bitcoin/`: Bitcoin-specific implementation (derivation, API, analysis)
|
||||||
|
- Ready for additional blockchain support (e.g., Ethereum)
|
||||||
|
- **Testable**: Comprehensive test suite with network tests using known mnemonic
|
||||||
|
- **Public API Integration**: Uses Blockstream.info API with built-in rate limiting
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone or download the project
|
||||||
|
cd btcphrasechecker
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
# Build
|
||||||
|
go build -o btcphrasechecker
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check wallet with mnemonic only
|
||||||
|
./btcphrasechecker -mnemonic "your twelve or twenty four word mnemonic phrase here"
|
||||||
|
|
||||||
|
# With optional passphrase
|
||||||
|
./btcphrasechecker -mnemonic "your mnemonic phrase" -passphrase "your passphrase"
|
||||||
|
|
||||||
|
# Customize number of addresses to check (default: 20)
|
||||||
|
./btcphrasechecker -mnemonic "your mnemonic" -count 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Bitcoin Wallet Analysis
|
||||||
|
======================
|
||||||
|
|
||||||
|
Checking BIP44 (Legacy): m/44'/0'/0'/0 - P2PKH addresses (1...)
|
||||||
|
-----------------------------------------------------------
|
||||||
|
1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA
|
||||||
|
Path: m/44'/0'/0'/0/0
|
||||||
|
Balance: 0.00000000 BTC
|
||||||
|
Received: 0.01146203 BTC
|
||||||
|
Sent: 0.01146203 BTC
|
||||||
|
Txs: 46
|
||||||
|
|
||||||
|
Checking BIP49 (SegWit): m/49'/0'/0'/0 - P2SH-wrapped SegWit (3...)
|
||||||
|
-----------------------------------------------------------
|
||||||
|
37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf
|
||||||
|
Path: m/49'/0'/0'/0/0
|
||||||
|
Balance: 0.00000000 BTC
|
||||||
|
Received: 0.06783787 BTC
|
||||||
|
Sent: 0.06783787 BTC
|
||||||
|
Txs: 22
|
||||||
|
|
||||||
|
Checking BIP84 (Native SegWit): m/84'/0'/0'/0 - Bech32 addresses (bc1...)
|
||||||
|
-----------------------------------------------------------
|
||||||
|
bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu
|
||||||
|
Path: m/84'/0'/0'/0/0
|
||||||
|
Balance: 0.00000000 BTC
|
||||||
|
Received: 0.04021550 BTC
|
||||||
|
Sent: 0.04021550 BTC
|
||||||
|
Txs: 166
|
||||||
|
|
||||||
|
Fetching transaction history...
|
||||||
|
|
||||||
|
======================
|
||||||
|
Summary
|
||||||
|
======================
|
||||||
|
Total Balance: 0.00000000 BTC
|
||||||
|
Total Received: 0.16710427 BTC (94 transactions)
|
||||||
|
Total Sent: 0.16710427 BTC (91 transactions)
|
||||||
|
Total Transactions: 360
|
||||||
|
Active Addresses: 44
|
||||||
|
|
||||||
|
Active Addresses Summary:
|
||||||
|
-----------------------------------------------------------
|
||||||
|
1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA
|
||||||
|
Path: m/44'/0'/0'/0/0
|
||||||
|
Balance: 0.00000000 BTC
|
||||||
|
Received: 0.01146203 BTC
|
||||||
|
Sent: 0.01146203 BTC
|
||||||
|
Txs: 46
|
||||||
|
|
||||||
|
======================
|
||||||
|
Transaction History
|
||||||
|
======================
|
||||||
|
Date Type Confirmed Amount (BTC) Balance (BTC) TxID
|
||||||
|
---------------------------------------------------------------------------------------------------------------------------
|
||||||
|
2014-11-09 03:31:39 + Received Yes 0.00130000 0.00130000 d6f136091b72cb4f...
|
||||||
|
2014-11-18 07:42:59 - Sent Yes 0.00130000 0.00000000 f0ac3836d27b2914...
|
||||||
|
2015-02-13 04:23:20 + Received Yes 0.00016924 0.00016924 762ff05a44abf82e...
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The project includes a comprehensive test suite that uses the well-known test mnemonic:
|
||||||
|
|
||||||
|
```
|
||||||
|
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# Run tests with verbose output
|
||||||
|
go test -v ./...
|
||||||
|
|
||||||
|
# Run specific test
|
||||||
|
go test -v ./bitcoin -run TestDeriveAddresses
|
||||||
|
|
||||||
|
# Run with network timeout (tests make real API calls)
|
||||||
|
go test ./... -timeout 5m
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Coverage
|
||||||
|
|
||||||
|
- **Derivation Tests**: Verifies correct address generation for all BIP standards
|
||||||
|
- **API Tests**: Tests Blockstream API integration with real network calls
|
||||||
|
- **Integration Tests**: End-to-end wallet analysis with the test mnemonic
|
||||||
|
- **Passphrase Tests**: Validates different seeds from different passphrases
|
||||||
|
- **Known Address Tests**: Validates against known good addresses
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
btcphrasechecker/
|
||||||
|
├── main.go # Main entry point and CLI handling
|
||||||
|
├── main_test.go # Integration tests
|
||||||
|
├── go.mod # Go module dependencies
|
||||||
|
├── README.md # This file
|
||||||
|
│
|
||||||
|
├── types/
|
||||||
|
│ └── types.go # Common types and interfaces
|
||||||
|
│
|
||||||
|
└── bitcoin/
|
||||||
|
├── derivation.go # BIP32/44/49/84 address derivation
|
||||||
|
├── derivation_test.go # Derivation tests
|
||||||
|
├── api.go # Blockstream API client
|
||||||
|
├── api_test.go # API tests
|
||||||
|
└── analyzer.go # Wallet analysis logic
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extensibility
|
||||||
|
|
||||||
|
The codebase is designed to be easily extensible for additional blockchain support:
|
||||||
|
|
||||||
|
1. **Add New Chain**: Implement the `ChainAnalyzer` interface in `types/types.go`
|
||||||
|
2. **Create Package**: Add a new package (e.g., `ethereum/`) with chain-specific logic
|
||||||
|
3. **Update Main**: Add chain selection logic in `main.go`
|
||||||
|
|
||||||
|
Example interface:
|
||||||
|
```go
|
||||||
|
type ChainAnalyzer interface {
|
||||||
|
DeriveAddresses(seed []byte, count int) ([]AddressInfo, error)
|
||||||
|
GetAddressInfo(address string) (balance, txCount uint64, received, sent uint64, err error)
|
||||||
|
GetTransactions(address string) ([]Transaction, error)
|
||||||
|
GetChain() Chain
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
- **Never share your mnemonic phrase** - This tool is for authorized wallet analysis only
|
||||||
|
- **Read-only**: This tool only reads blockchain data; it cannot spend funds
|
||||||
|
- **Public API**: Uses Blockstream.info public API which may log requests
|
||||||
|
- **Privacy**: For maximum privacy, consider using a VPN or running your own Bitcoin node with API
|
||||||
|
- **Rate Limiting**: 100ms delay between requests to avoid API throttling
|
||||||
|
- **No Key Export**: Private keys are never exposed or stored
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- `github.com/tyler-smith/go-bip39` - BIP39 mnemonic implementation
|
||||||
|
- `github.com/btcsuite/btcd` - Bitcoin library suite including:
|
||||||
|
- Address generation and encoding
|
||||||
|
- BIP32 HD key derivation
|
||||||
|
- Script building for P2SH-wrapped SegWit
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -o btcphrasechecker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
go test -cover ./...
|
||||||
|
|
||||||
|
# Run with race detection
|
||||||
|
go test -race ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Format code
|
||||||
|
go fmt ./...
|
||||||
|
|
||||||
|
# Lint code (requires golangci-lint)
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
# Vet code
|
||||||
|
go vet ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
- [ ] Ethereum support
|
||||||
|
- [ ] Bitcoin testnet support
|
||||||
|
- [ ] Custom derivation paths
|
||||||
|
- [ ] Export to CSV/JSON
|
||||||
|
- [ ] Local Bitcoin Core RPC support
|
||||||
|
- [ ] UTXO tracking
|
||||||
|
- [ ] Multi-signature wallet support
|
||||||
|
- [ ] Hardware wallet integration
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please ensure:
|
||||||
|
- All tests pass
|
||||||
|
- Code is formatted with `go fmt`
|
||||||
|
- New features include tests
|
||||||
|
- Security best practices are followed
|
||||||
209
btcphrasechecker/bitcoin/analyzer.go
Normal file
209
btcphrasechecker/bitcoin/analyzer.go
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
package bitcoin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"btcphrasechecker/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Analyzer implements the ChainAnalyzer interface for Bitcoin
|
||||||
|
type Analyzer struct {
|
||||||
|
addressCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnalyzer creates a new Bitcoin analyzer
|
||||||
|
func NewAnalyzer(addressCount int) *Analyzer {
|
||||||
|
return &Analyzer{
|
||||||
|
addressCount: addressCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveAddresses derives Bitcoin addresses from seed
|
||||||
|
func (a *Analyzer) DeriveAddresses(seed []byte, count int) ([]types.AddressInfo, error) {
|
||||||
|
return DeriveAddresses(seed, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddressInfo fetches address information
|
||||||
|
func (a *Analyzer) GetAddressInfo(address string) (balance, txCount uint64, received, sent uint64, err error) {
|
||||||
|
return GetAddressInfo(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactions fetches transactions for an address
|
||||||
|
func (a *Analyzer) GetTransactions(address string) ([]types.Transaction, error) {
|
||||||
|
return GetTransactions(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChain returns the chain type
|
||||||
|
func (a *Analyzer) GetChain() types.Chain {
|
||||||
|
return types.ChainBitcoin
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnalyzeWallet performs comprehensive wallet analysis
|
||||||
|
func AnalyzeWallet(seed []byte, addressCount int, verbose bool) (*types.WalletSummary, error) {
|
||||||
|
summary := &types.WalletSummary{
|
||||||
|
ActiveAddresses: make([]types.AddressInfo, 0),
|
||||||
|
TransactionHistory: make([]types.TransactionDetail, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive all addresses
|
||||||
|
addresses, err := DeriveAddresses(seed, addressCount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("Bitcoin Wallet Analysis")
|
||||||
|
fmt.Println("======================")
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each derivation path
|
||||||
|
pathStats := make(map[string]struct {
|
||||||
|
count int
|
||||||
|
received uint64
|
||||||
|
sent uint64
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, pathInfo := range StandardPaths {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("Checking %s: %s - %s\n", pathInfo.Name, pathInfo.Path, pathInfo.Desc)
|
||||||
|
fmt.Println("-----------------------------------------------------------")
|
||||||
|
}
|
||||||
|
|
||||||
|
pathAddresses := filterByPath(addresses, pathInfo.Path)
|
||||||
|
|
||||||
|
for _, addr := range pathAddresses {
|
||||||
|
balance, txCount, received, sent, err := GetAddressInfo(addr.Address)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.Balance = balance
|
||||||
|
addr.TxCount = int(txCount)
|
||||||
|
addr.Received = received
|
||||||
|
addr.Sent = sent
|
||||||
|
|
||||||
|
if txCount > 0 {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" %s\n", addr.Address)
|
||||||
|
fmt.Printf(" Path: %s\n", addr.Path)
|
||||||
|
fmt.Printf(" Balance: %.8f BTC\n", float64(balance)/100000000.0)
|
||||||
|
fmt.Printf(" Received: %.8f BTC\n", float64(received)/100000000.0)
|
||||||
|
fmt.Printf(" Sent: %.8f BTC\n", float64(sent)/100000000.0)
|
||||||
|
fmt.Printf(" Transactions: %d\n", txCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.TotalBalance += balance
|
||||||
|
summary.TotalReceived += received
|
||||||
|
summary.TotalSent += sent
|
||||||
|
summary.TotalTxCount += int(txCount)
|
||||||
|
summary.ActiveAddresses = append(summary.ActiveAddresses, addr)
|
||||||
|
|
||||||
|
// Update path stats
|
||||||
|
ps := pathStats[pathInfo.Path]
|
||||||
|
ps.count++
|
||||||
|
ps.received += received
|
||||||
|
ps.sent += sent
|
||||||
|
pathStats[pathInfo.Path] = ps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limiting
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchTransactionHistory fetches and processes all transactions for active addresses
|
||||||
|
func FetchTransactionHistory(summary *types.WalletSummary) error {
|
||||||
|
var allTransactions []types.TransactionDetail
|
||||||
|
receiveTxMap := make(map[string]bool)
|
||||||
|
sendTxMap := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, addr := range summary.ActiveAddresses {
|
||||||
|
if addr.TxCount == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
txs, err := GetTransactions(addr.Address)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tx := range txs {
|
||||||
|
txType := "self"
|
||||||
|
amount := uint64(0)
|
||||||
|
|
||||||
|
if tx.Received > 0 && tx.Sent == 0 {
|
||||||
|
txType = "received"
|
||||||
|
amount = tx.Received
|
||||||
|
receiveTxMap[tx.TxID] = true
|
||||||
|
} else if tx.Sent > 0 && tx.Received == 0 {
|
||||||
|
txType = "sent"
|
||||||
|
amount = tx.Sent
|
||||||
|
sendTxMap[tx.TxID] = true
|
||||||
|
} else if tx.Received > 0 && tx.Sent > 0 {
|
||||||
|
txType = "self"
|
||||||
|
amount = tx.Received
|
||||||
|
}
|
||||||
|
|
||||||
|
detail := types.TransactionDetail{
|
||||||
|
TxID: tx.TxID,
|
||||||
|
Time: tx.Time,
|
||||||
|
BlockHeight: tx.BlockHeight,
|
||||||
|
Confirmed: tx.Confirmed,
|
||||||
|
Type: txType,
|
||||||
|
Amount: amount,
|
||||||
|
Address: addr.Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
allTransactions = append(allTransactions, detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limiting
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by time (oldest first)
|
||||||
|
sort.Slice(allTransactions, func(i, j int) bool {
|
||||||
|
return allTransactions[i].Time.Before(allTransactions[j].Time)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Calculate running balance
|
||||||
|
balance := uint64(0)
|
||||||
|
for i := range allTransactions {
|
||||||
|
tx := &allTransactions[i]
|
||||||
|
if tx.Type == "received" {
|
||||||
|
balance += tx.Amount
|
||||||
|
} else if tx.Type == "sent" {
|
||||||
|
if balance >= tx.Amount {
|
||||||
|
balance -= tx.Amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.Balance = balance
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.TransactionHistory = allTransactions
|
||||||
|
summary.ReceiveTxCount = len(receiveTxMap)
|
||||||
|
summary.SendTxCount = len(sendTxMap)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterByPath filters addresses by derivation path prefix
|
||||||
|
func filterByPath(addresses []types.AddressInfo, pathPrefix string) []types.AddressInfo {
|
||||||
|
var filtered []types.AddressInfo
|
||||||
|
for _, addr := range addresses {
|
||||||
|
if len(addr.Path) >= len(pathPrefix) && addr.Path[:len(pathPrefix)] == pathPrefix {
|
||||||
|
filtered = append(filtered, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
161
btcphrasechecker/bitcoin/api.go
Normal file
161
btcphrasechecker/bitcoin/api.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package bitcoin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"btcphrasechecker/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// API base URL (using blockstream.info)
|
||||||
|
apiBaseURL = "https://blockstream.info/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockstreamTransaction represents a transaction from Blockstream API
|
||||||
|
type BlockstreamTransaction struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Status struct {
|
||||||
|
Confirmed bool `json:"confirmed"`
|
||||||
|
BlockHeight int `json:"block_height"`
|
||||||
|
BlockTime int64 `json:"block_time"`
|
||||||
|
} `json:"status"`
|
||||||
|
Vin []struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout int `json:"vout"`
|
||||||
|
Prevout struct {
|
||||||
|
Scriptpubkey string `json:"scriptpubkey"`
|
||||||
|
ScriptpubkeyAddress string `json:"scriptpubkey_address"`
|
||||||
|
Value uint64 `json:"value"`
|
||||||
|
} `json:"prevout"`
|
||||||
|
} `json:"vin"`
|
||||||
|
Vout []struct {
|
||||||
|
Scriptpubkey string `json:"scriptpubkey"`
|
||||||
|
ScriptpubkeyAddress string `json:"scriptpubkey_address"`
|
||||||
|
Value uint64 `json:"value"`
|
||||||
|
} `json:"vout"`
|
||||||
|
Fee uint64 `json:"fee"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddressInfo fetches address information from Blockstream API
|
||||||
|
func GetAddressInfo(address string) (balance, txCount uint64, received, sent uint64, err error) {
|
||||||
|
url := fmt.Sprintf("%s/address/%s", apiBaseURL, address)
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, 0, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return 0, 0, 0, 0, fmt.Errorf("API returned status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrData struct {
|
||||||
|
ChainStats struct {
|
||||||
|
FundedTxoSum uint64 `json:"funded_txo_sum"`
|
||||||
|
SpentTxoSum uint64 `json:"spent_txo_sum"`
|
||||||
|
TxCount int `json:"tx_count"`
|
||||||
|
} `json:"chain_stats"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &addrData); err != nil {
|
||||||
|
return 0, 0, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
balance = addrData.ChainStats.FundedTxoSum - addrData.ChainStats.SpentTxoSum
|
||||||
|
txCount = uint64(addrData.ChainStats.TxCount)
|
||||||
|
received = addrData.ChainStats.FundedTxoSum
|
||||||
|
sent = addrData.ChainStats.SpentTxoSum
|
||||||
|
|
||||||
|
return balance, txCount, received, sent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactions fetches all transactions for an address
|
||||||
|
func GetTransactions(address string) ([]types.Transaction, error) {
|
||||||
|
url := fmt.Sprintf("%s/address/%s/txs", apiBaseURL, address)
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bsTxs []BlockstreamTransaction
|
||||||
|
if err := json.Unmarshal(body, &bsTxs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to common transaction format
|
||||||
|
var transactions []types.Transaction
|
||||||
|
for _, bsTx := range bsTxs {
|
||||||
|
tx := convertTransaction(bsTx, address)
|
||||||
|
transactions = append(transactions, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertTransaction converts Blockstream transaction to common format
|
||||||
|
func convertTransaction(bsTx BlockstreamTransaction, forAddress string) types.Transaction {
|
||||||
|
tx := types.Transaction{
|
||||||
|
TxID: bsTx.Txid,
|
||||||
|
BlockHeight: bsTx.Status.BlockHeight,
|
||||||
|
Confirmed: bsTx.Status.Confirmed,
|
||||||
|
Fee: bsTx.Fee,
|
||||||
|
}
|
||||||
|
|
||||||
|
if bsTx.Status.BlockTime > 0 {
|
||||||
|
tx.Time = time.Unix(bsTx.Status.BlockTime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate received and sent amounts for this address
|
||||||
|
var received, sent uint64
|
||||||
|
addressSet := make(map[string]bool)
|
||||||
|
|
||||||
|
// Check inputs (sent from address)
|
||||||
|
for _, vin := range bsTx.Vin {
|
||||||
|
if vin.Prevout.ScriptpubkeyAddress == forAddress {
|
||||||
|
sent += vin.Prevout.Value
|
||||||
|
}
|
||||||
|
addressSet[vin.Prevout.ScriptpubkeyAddress] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check outputs (received by address)
|
||||||
|
for _, vout := range bsTx.Vout {
|
||||||
|
if vout.ScriptpubkeyAddress == forAddress {
|
||||||
|
received += vout.Value
|
||||||
|
}
|
||||||
|
addressSet[vout.ScriptpubkeyAddress] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Received = received
|
||||||
|
tx.Sent = sent
|
||||||
|
tx.NetChange = int64(received) - int64(sent)
|
||||||
|
|
||||||
|
// Collect all unique addresses
|
||||||
|
for addr := range addressSet {
|
||||||
|
if addr != "" && addr != forAddress {
|
||||||
|
tx.Addresses = append(tx.Addresses, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx
|
||||||
|
}
|
||||||
72
btcphrasechecker/bitcoin/api_test.go
Normal file
72
btcphrasechecker/bitcoin/api_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package bitcoin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetAddressInfo(t *testing.T) {
|
||||||
|
// Test with a known address from the test mnemonic
|
||||||
|
testAddress := "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"
|
||||||
|
|
||||||
|
balance, txCount, received, sent, err := GetAddressInfo(testAddress)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetAddressInfo failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This address has been used in tests, so it should have transactions
|
||||||
|
if txCount == 0 {
|
||||||
|
t.Log("Warning: Test address has no transactions. This might be expected if blockchain state changed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Balance should equal received - sent
|
||||||
|
expectedBalance := received - sent
|
||||||
|
if balance != expectedBalance {
|
||||||
|
t.Errorf("Balance mismatch: balance=%d, received=%d, sent=%d, expected=%d",
|
||||||
|
balance, received, sent, expectedBalance)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Address: %s", testAddress)
|
||||||
|
t.Logf("Balance: %d satoshis (%.8f BTC)", balance, float64(balance)/100000000.0)
|
||||||
|
t.Logf("Received: %d satoshis", received)
|
||||||
|
t.Logf("Sent: %d satoshis", sent)
|
||||||
|
t.Logf("Transactions: %d", txCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTransactions(t *testing.T) {
|
||||||
|
// Test with a known address from the test mnemonic
|
||||||
|
testAddress := "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
|
||||||
|
|
||||||
|
txs, err := GetTransactions(testAddress)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetTransactions failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Found %d transactions for address %s", len(txs), testAddress)
|
||||||
|
|
||||||
|
// Verify transaction structure
|
||||||
|
for i, tx := range txs {
|
||||||
|
if tx.TxID == "" {
|
||||||
|
t.Errorf("Transaction %d has empty TxID", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tx.Time.IsZero() && tx.Confirmed {
|
||||||
|
t.Errorf("Transaction %d is confirmed but has zero time", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least one of Received or Sent should be non-zero
|
||||||
|
if tx.Received == 0 && tx.Sent == 0 {
|
||||||
|
t.Logf("Warning: Transaction %d has zero received and sent amounts", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf(" Tx %d: %s, Received: %d, Sent: %d, NetChange: %d, Time: %s",
|
||||||
|
i, tx.TxID[:16], tx.Received, tx.Sent, tx.NetChange, tx.Time.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAddressInfoInvalidAddress(t *testing.T) {
|
||||||
|
// Test with an invalid address
|
||||||
|
_, _, _, _, err := GetAddressInfo("invalid_address")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error for invalid address, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
148
btcphrasechecker/bitcoin/derivation.go
Normal file
148
btcphrasechecker/bitcoin/derivation.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package bitcoin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"btcphrasechecker/types"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DerivationPath represents a BIP derivation path configuration
|
||||||
|
type DerivationPath struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
Purpose uint32
|
||||||
|
Desc string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard Bitcoin derivation paths
|
||||||
|
var StandardPaths = []DerivationPath{
|
||||||
|
{
|
||||||
|
Name: "BIP44 (Legacy)",
|
||||||
|
Path: "m/44'/0'/0'/0",
|
||||||
|
Purpose: 44,
|
||||||
|
Desc: "P2PKH addresses (1...)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "BIP49 (SegWit)",
|
||||||
|
Path: "m/49'/0'/0'/0",
|
||||||
|
Purpose: 49,
|
||||||
|
Desc: "P2SH-wrapped SegWit (3...)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "BIP84 (Native SegWit)",
|
||||||
|
Path: "m/84'/0'/0'/0",
|
||||||
|
Purpose: 84,
|
||||||
|
Desc: "Bech32 addresses (bc1...)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveAddresses derives Bitcoin addresses from a seed for all standard paths
|
||||||
|
func DeriveAddresses(seed []byte, addressCount int) ([]types.AddressInfo, error) {
|
||||||
|
masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create master key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var allAddresses []types.AddressInfo
|
||||||
|
|
||||||
|
for _, pathInfo := range StandardPaths {
|
||||||
|
addresses, err := derivePathAddresses(masterKey, pathInfo, addressCount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
allAddresses = append(allAddresses, addresses...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return allAddresses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// derivePathAddresses derives addresses for a specific derivation path
|
||||||
|
func derivePathAddresses(masterKey *hdkeychain.ExtendedKey, pathInfo DerivationPath, count int) ([]types.AddressInfo, error) {
|
||||||
|
var addresses []types.AddressInfo
|
||||||
|
|
||||||
|
// Derive base path: m/purpose'/coin_type'/account'/change
|
||||||
|
// For Bitcoin: coin_type = 0, account = 0, change = 0 (external addresses)
|
||||||
|
key := masterKey
|
||||||
|
key, _ = key.Derive(hdkeychain.HardenedKeyStart + pathInfo.Purpose)
|
||||||
|
key, _ = key.Derive(hdkeychain.HardenedKeyStart + 0) // coin_type = 0 for Bitcoin
|
||||||
|
key, _ = key.Derive(hdkeychain.HardenedKeyStart + 0) // account = 0
|
||||||
|
key, _ = key.Derive(0) // change = 0 (external)
|
||||||
|
|
||||||
|
// Derive individual addresses
|
||||||
|
for i := uint32(0); i < uint32(count); i++ {
|
||||||
|
childKey, err := key.Derive(i)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := childKey.ECPubKey()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKeyBytes := pubKey.SerializeCompressed()
|
||||||
|
address, err := deriveAddress(pubKeyBytes, pathInfo.Purpose)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses = append(addresses, types.AddressInfo{
|
||||||
|
Address: address,
|
||||||
|
Path: fmt.Sprintf("%s/%d", pathInfo.Path, i),
|
||||||
|
Chain: types.ChainBitcoin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deriveAddress creates an address from a public key based on the purpose
|
||||||
|
func deriveAddress(pubKeyBytes []byte, purpose uint32) (string, error) {
|
||||||
|
|
||||||
|
switch purpose {
|
||||||
|
case 44:
|
||||||
|
// Legacy P2PKH
|
||||||
|
addr, err := btcutil.NewAddressPubKey(pubKeyBytes, &chaincfg.MainNetParams)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return addr.EncodeAddress(), nil
|
||||||
|
|
||||||
|
case 49:
|
||||||
|
// P2SH-wrapped SegWit (BIP49)
|
||||||
|
// Create witness program for P2WPKH
|
||||||
|
pubKeyHash := btcutil.Hash160(pubKeyBytes)
|
||||||
|
witnessProgram, err := txscript.NewScriptBuilder().
|
||||||
|
AddOp(txscript.OP_0).
|
||||||
|
AddData(pubKeyHash).
|
||||||
|
Script()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Wrap witness program in P2SH
|
||||||
|
scriptAddr, err := btcutil.NewAddressScriptHash(witnessProgram, &chaincfg.MainNetParams)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return scriptAddr.EncodeAddress(), nil
|
||||||
|
|
||||||
|
case 84:
|
||||||
|
// Native SegWit (bech32)
|
||||||
|
addr, err := btcutil.NewAddressWitnessPubKeyHash(
|
||||||
|
btcutil.Hash160(pubKeyBytes),
|
||||||
|
&chaincfg.MainNetParams,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return addr.EncodeAddress(), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported purpose: %d", purpose)
|
||||||
|
}
|
||||||
|
}
|
||||||
122
btcphrasechecker/bitcoin/derivation_test.go
Normal file
122
btcphrasechecker/bitcoin/derivation_test.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package bitcoin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/tyler-smith/go-bip39"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeriveAddresses(t *testing.T) {
|
||||||
|
seed := bip39.NewSeed(testMnemonic, "")
|
||||||
|
|
||||||
|
addresses, err := DeriveAddresses(seed, 5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeriveAddresses failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect 5 addresses per path * 3 paths = 15 addresses
|
||||||
|
expectedCount := 5 * len(StandardPaths)
|
||||||
|
if len(addresses) != expectedCount {
|
||||||
|
t.Errorf("Expected %d addresses, got %d", expectedCount, len(addresses))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test known addresses for the test mnemonic
|
||||||
|
expectedAddresses := map[string]string{
|
||||||
|
"m/44'/0'/0'/0/0": "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA",
|
||||||
|
"m/84'/0'/0'/0/0": "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",
|
||||||
|
}
|
||||||
|
|
||||||
|
addressMap := make(map[string]string)
|
||||||
|
for _, addr := range addresses {
|
||||||
|
addressMap[addr.Path] = addr.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
for path, expectedAddr := range expectedAddresses {
|
||||||
|
if actualAddr, exists := addressMap[path]; !exists {
|
||||||
|
t.Errorf("Address for path %s not found", path)
|
||||||
|
} else if actualAddr != expectedAddr {
|
||||||
|
t.Errorf("Address mismatch for path %s: expected %s, got %s", path, expectedAddr, actualAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDerivePathAddresses(t *testing.T) {
|
||||||
|
seed := bip39.NewSeed(testMnemonic, "")
|
||||||
|
masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create master key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
path DerivationPath
|
||||||
|
addressIndex int
|
||||||
|
expectedAddress string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
path: StandardPaths[0], // BIP44
|
||||||
|
addressIndex: 0,
|
||||||
|
expectedAddress: "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: StandardPaths[1], // BIP49
|
||||||
|
addressIndex: 0,
|
||||||
|
expectedAddress: "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: StandardPaths[2], // BIP84
|
||||||
|
addressIndex: 0,
|
||||||
|
expectedAddress: "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.path.Name, func(t *testing.T) {
|
||||||
|
addresses, err := derivePathAddresses(masterKey, tt.path, 5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("derivePathAddresses failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addresses) != 5 {
|
||||||
|
t.Errorf("Expected 5 addresses, got %d", len(addresses))
|
||||||
|
}
|
||||||
|
|
||||||
|
if addresses[tt.addressIndex].Address != tt.expectedAddress {
|
||||||
|
t.Errorf("Address mismatch: expected %s, got %s",
|
||||||
|
tt.expectedAddress, addresses[tt.addressIndex].Address)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeriveAddressesWithPassphrase(t *testing.T) {
|
||||||
|
seed := bip39.NewSeed(testMnemonic, "TREZOR")
|
||||||
|
|
||||||
|
addresses, err := DeriveAddresses(seed, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeriveAddresses failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With passphrase, addresses should be different
|
||||||
|
if len(addresses) == 0 {
|
||||||
|
t.Error("Expected addresses to be generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first BIP44 address with "TREZOR" passphrase should be different
|
||||||
|
addressMap := make(map[string]string)
|
||||||
|
for _, addr := range addresses {
|
||||||
|
addressMap[addr.Path] = addr.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should NOT match the address without passphrase
|
||||||
|
if addr, exists := addressMap["m/44'/0'/0'/0/0"]; exists {
|
||||||
|
if addr == "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA" {
|
||||||
|
t.Error("Address should be different with passphrase")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
btcphrasechecker/go.mod
Normal file
19
btcphrasechecker/go.mod
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
module btcphrasechecker
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/btcsuite/btcd v0.24.0
|
||||||
|
github.com/btcsuite/btcd/btcutil v1.1.5
|
||||||
|
github.com/tyler-smith/go-bip39 v1.1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||||
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||||
|
golang.org/x/crypto v0.17.0 // indirect
|
||||||
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
|
)
|
||||||
119
btcphrasechecker/go.sum
Normal file
119
btcphrasechecker/go.sum
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||||
|
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||||
|
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||||
|
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
|
||||||
|
github.com/btcsuite/btcd v0.24.0 h1:gL3uHE/IaFj6fcZSu03SvqPMSx7s/dPzfpG/atRwWdo=
|
||||||
|
github.com/btcsuite/btcd v0.24.0/go.mod h1:K4IDc1593s8jKXIF7yS7yCTSxrknB9z0STzc2j6XgE4=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||||
|
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
||||||
|
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
||||||
|
github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
|
||||||
|
github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||||
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||||
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||||
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||||
|
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||||
|
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
|
||||||
|
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||||
|
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||||
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||||
|
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||||
|
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||||
|
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||||
|
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||||
|
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||||
|
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
|
||||||
|
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
|
||||||
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
143
btcphrasechecker/main.go
Normal file
143
btcphrasechecker/main.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"btcphrasechecker/bitcoin"
|
||||||
|
"btcphrasechecker/types"
|
||||||
|
|
||||||
|
"github.com/tyler-smith/go-bip39"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultAddressCount = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mnemonic := flag.String("mnemonic", "", "BIP39 mnemonic phrase")
|
||||||
|
passphrase := flag.String("passphrase", "", "Optional passphrase")
|
||||||
|
addressCount := flag.Int("count", defaultAddressCount, "Number of addresses to check per derivation path")
|
||||||
|
chain := flag.String("chain", "bitcoin", "Blockchain to analyze (bitcoin)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *mnemonic == "" {
|
||||||
|
fmt.Println("Usage: btcphrasechecker -mnemonic \"your mnemonic phrase\" [-passphrase \"optional passphrase\"] [-count 20] [-chain bitcoin]")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate mnemonic
|
||||||
|
if !bip39.IsMnemonicValid(*mnemonic) {
|
||||||
|
fmt.Println("Error: Invalid mnemonic phrase")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate seed from mnemonic
|
||||||
|
seed := bip39.NewSeed(*mnemonic, *passphrase)
|
||||||
|
|
||||||
|
// Analyze based on chain
|
||||||
|
switch types.Chain(*chain) {
|
||||||
|
case types.ChainBitcoin:
|
||||||
|
if err := analyzeBitcoin(seed, *addressCount); err != nil {
|
||||||
|
fmt.Printf("Error analyzing Bitcoin wallet: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Printf("Error: Unsupported chain '%s'\n", *chain)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func analyzeBitcoin(seed []byte, addressCount int) error {
|
||||||
|
// Perform wallet analysis
|
||||||
|
summary, err := bitcoin.AnalyzeWallet(seed, addressCount, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch transaction history for active addresses
|
||||||
|
if len(summary.ActiveAddresses) > 0 {
|
||||||
|
fmt.Println("Fetching transaction history...")
|
||||||
|
fmt.Println()
|
||||||
|
if err := bitcoin.FetchTransactionHistory(summary); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display summary
|
||||||
|
displaySummary(summary)
|
||||||
|
|
||||||
|
// Display transaction history
|
||||||
|
if len(summary.TransactionHistory) > 0 {
|
||||||
|
displayTransactionHistory(summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func displaySummary(summary *types.WalletSummary) {
|
||||||
|
fmt.Println("======================")
|
||||||
|
fmt.Println("Summary")
|
||||||
|
fmt.Println("======================")
|
||||||
|
fmt.Printf("Total Balance: %.8f BTC\n", float64(summary.TotalBalance)/100000000.0)
|
||||||
|
fmt.Printf("Total Received: %.8f BTC (%d transactions)\n",
|
||||||
|
float64(summary.TotalReceived)/100000000.0,
|
||||||
|
summary.ReceiveTxCount)
|
||||||
|
fmt.Printf("Total Sent: %.8f BTC (%d transactions)\n",
|
||||||
|
float64(summary.TotalSent)/100000000.0,
|
||||||
|
summary.SendTxCount)
|
||||||
|
fmt.Printf("Total Transactions: %d\n", summary.TotalTxCount)
|
||||||
|
fmt.Printf("Active Addresses: %d\n", len(summary.ActiveAddresses))
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
if len(summary.ActiveAddresses) > 0 {
|
||||||
|
fmt.Println("Active Addresses Summary:")
|
||||||
|
fmt.Println("-----------------------------------------------------------")
|
||||||
|
for _, addr := range summary.ActiveAddresses {
|
||||||
|
fmt.Printf(" %s\n", addr.Address)
|
||||||
|
fmt.Printf(" Path: %s\n", addr.Path)
|
||||||
|
fmt.Printf(" Balance: %.8f BTC\n", float64(addr.Balance)/100000000.0)
|
||||||
|
fmt.Printf(" Received: %.8f BTC\n", float64(addr.Received)/100000000.0)
|
||||||
|
fmt.Printf(" Sent: %.8f BTC\n", float64(addr.Sent)/100000000.0)
|
||||||
|
fmt.Printf(" Txs: %d\n", addr.TxCount)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayTransactionHistory(summary *types.WalletSummary) {
|
||||||
|
fmt.Println("======================")
|
||||||
|
fmt.Println("Transaction History")
|
||||||
|
fmt.Println("======================")
|
||||||
|
fmt.Printf("%-20s %-12s %-10s %-15s %-15s %s\n",
|
||||||
|
"Date", "Type", "Confirmed", "Amount (BTC)", "Balance (BTC)", "TxID")
|
||||||
|
fmt.Println("---------------------------------------------------------------------------------------------------------------------------")
|
||||||
|
|
||||||
|
for _, tx := range summary.TransactionHistory {
|
||||||
|
dateStr := tx.Time.Format("2006-01-02 15:04:05")
|
||||||
|
confirmedStr := "No"
|
||||||
|
if tx.Confirmed {
|
||||||
|
confirmedStr = "Yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
typeSymbol := ""
|
||||||
|
switch tx.Type {
|
||||||
|
case "received":
|
||||||
|
typeSymbol = "+ Received"
|
||||||
|
case "sent":
|
||||||
|
typeSymbol = "- Sent"
|
||||||
|
case "self":
|
||||||
|
typeSymbol = "↔ Self"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%-20s %-12s %-10s %14.8f %14.8f %s\n",
|
||||||
|
dateStr,
|
||||||
|
typeSymbol,
|
||||||
|
confirmedStr,
|
||||||
|
float64(tx.Amount)/100000000.0,
|
||||||
|
float64(tx.Balance)/100000000.0,
|
||||||
|
tx.TxID[:16]+"...")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
236
btcphrasechecker/main_test.go
Normal file
236
btcphrasechecker/main_test.go
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"btcphrasechecker/bitcoin"
|
||||||
|
"btcphrasechecker/types"
|
||||||
|
|
||||||
|
"github.com/tyler-smith/go-bip39"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBitcoinWalletAnalysis(t *testing.T) {
|
||||||
|
seed := bip39.NewSeed(testMnemonic, "")
|
||||||
|
|
||||||
|
summary, err := bitcoin.AnalyzeWallet(seed, 20, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AnalyzeWallet failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify summary structure
|
||||||
|
if summary == nil {
|
||||||
|
t.Fatal("Summary is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Total Balance: %.8f BTC", float64(summary.TotalBalance)/100000000.0)
|
||||||
|
t.Logf("Total Received: %.8f BTC", float64(summary.TotalReceived)/100000000.0)
|
||||||
|
t.Logf("Total Sent: %.8f BTC", float64(summary.TotalSent)/100000000.0)
|
||||||
|
t.Logf("Total Transactions: %d", summary.TotalTxCount)
|
||||||
|
t.Logf("Active Addresses: %d", len(summary.ActiveAddresses))
|
||||||
|
|
||||||
|
// Verify that received - sent = balance
|
||||||
|
expectedBalance := summary.TotalReceived - summary.TotalSent
|
||||||
|
if summary.TotalBalance != expectedBalance {
|
||||||
|
t.Errorf("Balance mismatch: balance=%d, received=%d, sent=%d, expected=%d",
|
||||||
|
summary.TotalBalance, summary.TotalReceived, summary.TotalSent, expectedBalance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify active addresses
|
||||||
|
for i, addr := range summary.ActiveAddresses {
|
||||||
|
if addr.Address == "" {
|
||||||
|
t.Errorf("Active address %d has empty address", i)
|
||||||
|
}
|
||||||
|
if addr.Path == "" {
|
||||||
|
t.Errorf("Active address %d has empty path", i)
|
||||||
|
}
|
||||||
|
if addr.Chain != types.ChainBitcoin {
|
||||||
|
t.Errorf("Active address %d has wrong chain: %s", i, addr.Chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify address-level balance
|
||||||
|
addrExpectedBalance := addr.Received - addr.Sent
|
||||||
|
if addr.Balance != addrExpectedBalance {
|
||||||
|
t.Errorf("Address %s balance mismatch: balance=%d, received=%d, sent=%d",
|
||||||
|
addr.Address, addr.Balance, addr.Received, addr.Sent)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf(" Address %d: %s (Path: %s)", i, addr.Address, addr.Path)
|
||||||
|
t.Logf(" Balance: %.8f BTC, Received: %.8f BTC, Sent: %.8f BTC, Txs: %d",
|
||||||
|
float64(addr.Balance)/100000000.0,
|
||||||
|
float64(addr.Received)/100000000.0,
|
||||||
|
float64(addr.Sent)/100000000.0,
|
||||||
|
addr.TxCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransactionHistory(t *testing.T) {
|
||||||
|
seed := bip39.NewSeed(testMnemonic, "")
|
||||||
|
|
||||||
|
summary, err := bitcoin.AnalyzeWallet(seed, 20, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AnalyzeWallet failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(summary.ActiveAddresses) == 0 {
|
||||||
|
t.Skip("No active addresses found, skipping transaction history test")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bitcoin.FetchTransactionHistory(summary)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FetchTransactionHistory failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Total transactions in history: %d", len(summary.TransactionHistory))
|
||||||
|
t.Logf("Receive transactions: %d", summary.ReceiveTxCount)
|
||||||
|
t.Logf("Send transactions: %d", summary.SendTxCount)
|
||||||
|
|
||||||
|
// Verify transaction history
|
||||||
|
var prevTime int64
|
||||||
|
for i, tx := range summary.TransactionHistory {
|
||||||
|
if tx.TxID == "" {
|
||||||
|
t.Errorf("Transaction %d has empty TxID", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tx.Time.IsZero() {
|
||||||
|
t.Logf("Warning: Transaction %d has zero time (might be unconfirmed)", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify chronological order
|
||||||
|
currentTime := tx.Time.Unix()
|
||||||
|
if i > 0 && currentTime < prevTime {
|
||||||
|
t.Errorf("Transactions not in chronological order at index %d", i)
|
||||||
|
}
|
||||||
|
prevTime = currentTime
|
||||||
|
|
||||||
|
// Verify transaction type
|
||||||
|
if tx.Type != "received" && tx.Type != "sent" && tx.Type != "self" {
|
||||||
|
t.Errorf("Transaction %d has invalid type: %s", i, tx.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < 10 { // Log first 10 transactions
|
||||||
|
t.Logf(" Tx %d: %s", i, tx.TxID[:16])
|
||||||
|
t.Logf(" Date: %s", tx.Time.Format("2006-01-02 15:04:05"))
|
||||||
|
t.Logf(" Type: %s, Amount: %.8f BTC, Balance: %.8f BTC",
|
||||||
|
tx.Type,
|
||||||
|
float64(tx.Amount)/100000000.0,
|
||||||
|
float64(tx.Balance)/100000000.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Final balance verification is complex due to self-transactions and
|
||||||
|
// transaction ordering across multiple addresses. The running balance is calculated
|
||||||
|
// per-address chronologically, but may not match the total wallet balance due to
|
||||||
|
// timing and internal transfers.
|
||||||
|
if len(summary.TransactionHistory) > 0 {
|
||||||
|
lastTx := summary.TransactionHistory[len(summary.TransactionHistory)-1]
|
||||||
|
t.Logf("Final tx balance: %.8f BTC, Total wallet balance: %.8f BTC",
|
||||||
|
float64(lastTx.Balance)/100000000.0,
|
||||||
|
float64(summary.TotalBalance)/100000000.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKnownAddresses(t *testing.T) {
|
||||||
|
seed := bip39.NewSeed(testMnemonic, "")
|
||||||
|
|
||||||
|
addresses, err := bitcoin.DeriveAddresses(seed, 20)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeriveAddresses failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map of known addresses for the test mnemonic
|
||||||
|
knownAddresses := map[string]string{
|
||||||
|
"m/44'/0'/0'/0/0": "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA",
|
||||||
|
"m/44'/0'/0'/0/1": "1Ak8PffB2meyfYnbXZR9EGfLfFZVpzJvQP",
|
||||||
|
"m/49'/0'/0'/0/0": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf",
|
||||||
|
"m/84'/0'/0'/0/0": "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",
|
||||||
|
"m/84'/0'/0'/0/1": "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g",
|
||||||
|
}
|
||||||
|
|
||||||
|
addressMap := make(map[string]string)
|
||||||
|
for _, addr := range addresses {
|
||||||
|
addressMap[addr.Path] = addr.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
for path, expectedAddr := range knownAddresses {
|
||||||
|
actualAddr, exists := addressMap[path]
|
||||||
|
if !exists {
|
||||||
|
t.Errorf("Address for path %s not found", path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if actualAddr != expectedAddr {
|
||||||
|
t.Errorf("Address mismatch for path %s:\n expected: %s\n got: %s",
|
||||||
|
path, expectedAddr, actualAddr)
|
||||||
|
} else {
|
||||||
|
t.Logf("✓ %s = %s", path, actualAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMnemonicValidation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mnemonic string
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid 12-word mnemonic",
|
||||||
|
mnemonic: testMnemonic,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid mnemonic",
|
||||||
|
mnemonic: "invalid mnemonic phrase that should not work",
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty mnemonic",
|
||||||
|
mnemonic: "",
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
valid := bip39.IsMnemonicValid(tt.mnemonic)
|
||||||
|
if valid != tt.valid {
|
||||||
|
t.Errorf("Expected mnemonic validity to be %v, got %v", tt.valid, valid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassphraseSupport(t *testing.T) {
|
||||||
|
// Test that different passphrases generate different seeds
|
||||||
|
seed1 := bip39.NewSeed(testMnemonic, "")
|
||||||
|
seed2 := bip39.NewSeed(testMnemonic, "password123")
|
||||||
|
|
||||||
|
if string(seed1) == string(seed2) {
|
||||||
|
t.Error("Seeds with different passphrases should be different")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that addresses are different with different passphrases
|
||||||
|
addresses1, err := bitcoin.DeriveAddresses(seed1, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeriveAddresses failed for seed1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses2, err := bitcoin.DeriveAddresses(seed2, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeriveAddresses failed for seed2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addresses1) == 0 || len(addresses2) == 0 {
|
||||||
|
t.Fatal("No addresses generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
if addresses1[0].Address == addresses2[0].Address {
|
||||||
|
t.Error("Addresses should be different with different passphrases")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Without passphrase: %s", addresses1[0].Address)
|
||||||
|
t.Logf("With passphrase: %s", addresses2[0].Address)
|
||||||
|
}
|
||||||
67
btcphrasechecker/types/types.go
Normal file
67
btcphrasechecker/types/types.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Chain represents a blockchain type
|
||||||
|
type Chain string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChainBitcoin Chain = "bitcoin"
|
||||||
|
ChainEthereum Chain = "ethereum"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddressInfo contains information about a derived address
|
||||||
|
type AddressInfo struct {
|
||||||
|
Address string
|
||||||
|
Path string
|
||||||
|
Balance uint64
|
||||||
|
TxCount int
|
||||||
|
Chain Chain
|
||||||
|
Received uint64
|
||||||
|
Sent uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction represents a blockchain transaction
|
||||||
|
type Transaction struct {
|
||||||
|
TxID string
|
||||||
|
Time time.Time
|
||||||
|
BlockHeight int
|
||||||
|
Confirmed bool
|
||||||
|
Received uint64
|
||||||
|
Sent uint64
|
||||||
|
Fee uint64
|
||||||
|
NetChange int64 // Can be negative
|
||||||
|
Addresses []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionDetail provides detailed transaction information
|
||||||
|
type TransactionDetail struct {
|
||||||
|
TxID string
|
||||||
|
Time time.Time
|
||||||
|
BlockHeight int
|
||||||
|
Confirmed bool
|
||||||
|
Type string // "received", "sent", "self"
|
||||||
|
Amount uint64
|
||||||
|
Balance uint64 // Running balance after this tx
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletSummary contains overall wallet statistics
|
||||||
|
type WalletSummary struct {
|
||||||
|
TotalBalance uint64
|
||||||
|
TotalReceived uint64
|
||||||
|
TotalSent uint64
|
||||||
|
TotalTxCount int
|
||||||
|
ReceiveTxCount int
|
||||||
|
SendTxCount int
|
||||||
|
ActiveAddresses []AddressInfo
|
||||||
|
TransactionHistory []TransactionDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainAnalyzer defines the interface for blockchain analysis
|
||||||
|
type ChainAnalyzer interface {
|
||||||
|
DeriveAddresses(seed []byte, count int) ([]AddressInfo, error)
|
||||||
|
GetAddressInfo(address string) (balance, txCount uint64, received, sent uint64, err error)
|
||||||
|
GetTransactions(address string) ([]Transaction, error)
|
||||||
|
GetChain() Chain
|
||||||
|
}
|
||||||
0
cat-unless-older/cat-unless-older
Executable file → Normal file
0
cat-unless-older/cat-unless-older
Executable file → Normal file
0
checkcert/checkcert
Executable file → Normal file
0
checkcert/checkcert
Executable file → Normal file
24
chromeurls/urls.sh
Normal file
24
chromeurls/urls.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SRC="$HOME/Library/Application Support/Google/Chrome/Default/History"
|
||||||
|
|
||||||
|
Q="SELECT
|
||||||
|
datetime(last_visit_time/1000000-11644473600, \"unixepoch\") as last_visited,
|
||||||
|
last_visit_time as orig,
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
visit_count
|
||||||
|
FROM urls
|
||||||
|
ORDER BY last_visited desc
|
||||||
|
|
||||||
|
LIMIT 1
|
||||||
|
;
|
||||||
|
"
|
||||||
|
|
||||||
|
mkdir -p $HOME/tmp
|
||||||
|
|
||||||
|
cp "$SRC" $HOME/tmp/history
|
||||||
|
|
||||||
|
sqlite3 -readonly $HOME/tmp/history "$Q"
|
||||||
|
|
||||||
|
rm $HOME/tmp/history
|
||||||
0
clean-old-osx-homedir/cleanhomedir
Executable file → Normal file
0
clean-old-osx-homedir/cleanhomedir
Executable file → Normal file
0
coldbackup/dobackup.sh
Executable file → Normal file
0
coldbackup/dobackup.sh
Executable file → Normal file
0
cronify/cronify
Executable file → Normal file
0
cronify/cronify
Executable file → Normal file
0
dump-imessages/dump.sh
Executable file → Normal file
0
dump-imessages/dump.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/build_ramdisk.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/build_ramdisk.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/build_ramdisk_ios6.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/build_ramdisk_ios6.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/build_tools.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/build_tools.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/dump_data_partition.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/dump_data_partition.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/python_scripts/kernel_patcher.py
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/python_scripts/kernel_patcher.py
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/ramdisk_tools/scripts/mount_partitions.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/ramdisk_tools/scripts/mount_partitions.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/tcprelay.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/tcprelay.sh
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/usbmuxd-python-client/tcprelay.py
Executable file → Normal file
0
dump-imessages/iphone-dataprotection/usbmuxd-python-client/tcprelay.py
Executable file → Normal file
0
email-to-webhook/email_to_webhook
Executable file → Normal file
0
email-to-webhook/email_to_webhook
Executable file → Normal file
0
fetchtweets/fetchtweets
Executable file → Normal file
0
fetchtweets/fetchtweets
Executable file → Normal file
0
findmastos/findmastos.py
Executable file → Normal file
0
findmastos/findmastos.py
Executable file → Normal file
0
fix-raspian-defaults/install-to-boot.sh
Executable file → Normal file
0
fix-raspian-defaults/install-to-boot.sh
Executable file → Normal file
0
fix-raspian-defaults/root.overlay/usr/lib/raspi-config/init_resize.sh
Executable file → Normal file
0
fix-raspian-defaults/root.overlay/usr/lib/raspi-config/init_resize.sh
Executable file → Normal file
0
forward-email-to-slack-webhook/email_to_webhook
Executable file → Normal file
0
forward-email-to-slack-webhook/email_to_webhook
Executable file → Normal file
0
forward-email-to-slack-webhook/install.sh
Executable file → Normal file
0
forward-email-to-slack-webhook/install.sh
Executable file → Normal file
0
geolocate/geolocate.py
Executable file → Normal file
0
geolocate/geolocate.py
Executable file → Normal file
0
geolocate/weather.py
Executable file → Normal file
0
geolocate/weather.py
Executable file → Normal file
29
golang-binaries-installer/install.sh
Normal file
29
golang-binaries-installer/install.sh
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# this is designed to be symlinked to ~/.bashrc.d/999.goinstaller.sh
|
||||||
|
|
||||||
|
if ! which mkcert 2>&1 >/dev/null ; then
|
||||||
|
go install -v filippo.io/mkcert@latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! which certstrap 2>&1 >/dev/null ; then
|
||||||
|
go install -v github.com/square/certstrap@latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! which f2 2>&1 >/dev/null ; then
|
||||||
|
go install -v github.com/ayoisaiah/f2/cmd/f2@latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! which pack 2>&1 >/dev/null ; then
|
||||||
|
go install -v github.com/buildpacks/pack/cmd/pack@latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! which golangci-lint 2>&1 >/dev/null ; then
|
||||||
|
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! which gofumpt 2>&1 >/dev/null ; then
|
||||||
|
go install -v mvdan.cc/gofumpt@latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! which countdown 2>&1 >/dev/null ; then
|
||||||
|
go install -v github.com/antonmedv/countdown@latest
|
||||||
|
fi
|
||||||
0
goshebang/test.go
Executable file → Normal file
0
goshebang/test.go
Executable file → Normal file
0
grandcentral-vm-downloader/gc-vm-email-download.pl
Executable file → Normal file
0
grandcentral-vm-downloader/gc-vm-email-download.pl
Executable file → Normal file
@@ -1,92 +0,0 @@
|
|||||||
JUNKFILES = .bash_history .irb_history .pip .ScanSnap .nbems .fldigi .cpan
|
|
||||||
JUNKFILES += .gdb_history .mysql_history .sqlite_history
|
|
||||||
BREWPACKAGES := mosh duplicity pv offlineimap wget nmap tor torsocks
|
|
||||||
YYYYMM := $(shell date +%Y%m)
|
|
||||||
|
|
||||||
HACKSREPO := ~/.paths/sneak-scratch/dev/hacks/
|
|
||||||
|
|
||||||
NO_COLOR = \033[0m
|
|
||||||
O1_COLOR = \033[0;01m
|
|
||||||
O2_COLOR = \033[32;01m
|
|
||||||
|
|
||||||
PREFIX = "$(O2_COLOR)==>$(O1_COLOR)"
|
|
||||||
SUFFIX = "$(NO_COLOR)"
|
|
||||||
|
|
||||||
default: routine
|
|
||||||
|
|
||||||
# FIXME make this do a local imap download too
|
|
||||||
routine: clean databackup
|
|
||||||
|
|
||||||
backup: clean mailoffsite databackup
|
|
||||||
|
|
||||||
dvbackup:
|
|
||||||
@echo $(PREFIX) $@ $(SUFFIX)
|
|
||||||
cd ~/Documents/datavibe/backup && make
|
|
||||||
|
|
||||||
imapbackup:
|
|
||||||
offlineimap
|
|
||||||
rsync -e "ssh -o Compression=no -x" \
|
|
||||||
-avPhzy --delete-after sneak@datavibe.net:.maildir/ \
|
|
||||||
$(HOME)/Documents/Archival/mail/sneak.datavibe.net.maildir/
|
|
||||||
|
|
||||||
mailoffsite: imapbackup
|
|
||||||
rsync -e "ssh -o Compression=no -x" \
|
|
||||||
-avPhzy --delete-after $(HOME)/Documents/Archival/mail/ \
|
|
||||||
sneak@datavibe.net:.mailbackup/
|
|
||||||
tar -xvf $(HOME)/Documents/Archival/mail/jp.eeqj.com | gzip > \
|
|
||||||
$(HOME)/Documents/Dropbox/eeqj/archives/mail.tgz.new && \
|
|
||||||
mv $(HOME)/Documents/Dropbox/eeqj/archives/mail.tgz.new \
|
|
||||||
$(HOME)/Documents/Dropbox/eeqj/archives/mail.tgz
|
|
||||||
|
|
||||||
databackup:
|
|
||||||
mkdir -p $(HOME)/Library/misc
|
|
||||||
#brew list > $(HOME)/Library/misc/brewinstalled.txt
|
|
||||||
#brew cask list > $(HOME)/Library/misc/brew-cask-installed.txt
|
|
||||||
~/dev/hacks/bin/backup.command
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
-mkdir -p $(HOME)/Documents/$(YYYYMM)
|
|
||||||
-mv $(HOME)/Desktop/* $(HOME)/Documents/$(YYYYMM)
|
|
||||||
|
|
||||||
clean: cleanup
|
|
||||||
@echo $(PREFIX) $@ $(SUFFIX)
|
|
||||||
@-rm -rf ~/.tmp/*
|
|
||||||
@-rm -rf ~/.Trash/*
|
|
||||||
@-rm -rf $(JUNKFILES)
|
|
||||||
|
|
||||||
size:
|
|
||||||
du -sh $(HOME)
|
|
||||||
|
|
||||||
lifeboat:
|
|
||||||
mkdir -p $(HOME)/tmp/lifeboat.$(YYYYMM)
|
|
||||||
rsync -avP --exclude='*.pkg' $(HOME)/Documents/Secure/ \
|
|
||||||
$(HOME)/tmp/lifeboat.$(YYYYMM)/Secure/
|
|
||||||
rsync -avP $(HOME)/Library/ApplicationSupport/Bitcoin/wallet.dat \
|
|
||||||
$(HOME)/tmp/lifeboat.$(YYYYMM)/wallet.dat
|
|
||||||
tar -c $(HOME)/tmp/lifeboat.$(YYYYMM) | bzip2 | \
|
|
||||||
gpg --symmetric -a -o $(HOME)/lifeboat.$(YYYYMM).gpg
|
|
||||||
rm -rf $(HOME)/tmp/lifeboat.$(YYYYMM)
|
|
||||||
cp $(HOME)/lifeboat.$(YYYYMM).gpg \
|
|
||||||
$(HOME)/dev/eeqjcdn/sneak.datavibe.net/lifeboat/lifeboat.gpg
|
|
||||||
cd $(HOME)/dev/eeqjcdn && make
|
|
||||||
mv $(HOME)/lifeboat.$(YYYYMM).gpg $(HOME)/Documents/Dropbox/Backups/
|
|
||||||
|
|
||||||
verify:
|
|
||||||
duplicity verify --exclude-globbing-filelist \
|
|
||||||
$(HOME)/.local/etc/duplicity.exclude \
|
|
||||||
file:///Volumes/EXTUSB01/dup/ ~
|
|
||||||
|
|
||||||
remotebackup:
|
|
||||||
RBACKUPDEST="scp://jp.eeqj.de/backup" $(HOME)/.local/bin/backup.command
|
|
||||||
|
|
||||||
packages:
|
|
||||||
brew install $(BREWPACKAGES)
|
|
||||||
|
|
||||||
# this copies instead of linking because the 'hacks' repo is part of the
|
|
||||||
# sneak-sync shared folder which is synced with machines off-prem
|
|
||||||
# and they are not allowed impending RCE on workstations
|
|
||||||
|
|
||||||
update:
|
|
||||||
cat $(HACKSREPO)/homedir.makefile/Makefile > Makefile
|
|
||||||
brew upgrade
|
|
||||||
|
|
||||||
25
libvirt/diskpools.sh
Normal file
25
libvirt/diskpools.sh
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SSH="ssh root@las1"
|
||||||
|
$SSH virsh pool-define-as flashpool-root dir --target /srv/flash/virt/root
|
||||||
|
$SSH virsh pool-start flashpool-root
|
||||||
|
$SSH virsh pool-autostart flashpool-root
|
||||||
|
$SSH virsh pool-define-as flashpool-storage dir --target /srv/flash/virt/storage
|
||||||
|
$SSH virsh pool-start flashpool-storage
|
||||||
|
$SSH virsh pool-autostart flashpool-storage
|
||||||
|
$SSH virsh pool-define-as pool-root dir --target /srv/storage/virt/root
|
||||||
|
$SSH virsh pool-start pool-root
|
||||||
|
$SSH virsh pool-autostart pool-root
|
||||||
|
$SSH virsh pool-define-as pool-storage dir --target /srv/storage/virt/storage
|
||||||
|
$SSH virsh pool-start pool-storage
|
||||||
|
$SSH virsh pool-autostart pool-storage
|
||||||
|
$SSH virsh pool-list
|
||||||
|
|
||||||
|
SSH="ssh root@lstor1"
|
||||||
|
$SSH virsh pool-define-as pool-root dir --target /srv/lstor1/virt/root
|
||||||
|
$SSH virsh pool-start pool-root
|
||||||
|
$SSH virsh pool-autostart pool-root
|
||||||
|
$SSH virsh pool-define-as pool-storage dir --target /srv/lstor1/virt/storage
|
||||||
|
$SSH virsh pool-start pool-storage
|
||||||
|
$SSH virsh pool-autostart pool-storage
|
||||||
|
$SSH virsh pool-list
|
||||||
25
makefiles/2022-12-10-cyberdyne-nnny.makefile
Normal file
25
makefiles/2022-12-10-cyberdyne-nnny.makefile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
TARGET := ./berlin.sneak.fs.NNNY-cyberdyne-backup-01
|
||||||
|
|
||||||
|
default: backup
|
||||||
|
|
||||||
|
backup: do_file_backup write_checksum
|
||||||
|
|
||||||
|
write_checksum:
|
||||||
|
cd $(TARGET)/fs && find . -type f -print0 | xargs -0 sha1sum > ../.SHASUMS.tmp
|
||||||
|
mv ./.SHASUMS.tmp ./$(TARGET)/SHASUMS.txt
|
||||||
|
|
||||||
|
do_file_backup:
|
||||||
|
rsync -avv \
|
||||||
|
--exclude=/tmp \
|
||||||
|
--exclude=/.cache \
|
||||||
|
--exclude=/.nvm \
|
||||||
|
--exclude=/.Trash \
|
||||||
|
--exclude=/Library/Caches \
|
||||||
|
--exclude=/Library/Mail \
|
||||||
|
--exclude=/Library/Developer \
|
||||||
|
--exclude=.DS_Store \
|
||||||
|
--delete-before \
|
||||||
|
--delete-excluded \
|
||||||
|
$(HOME)/ $(TARGET)/fs/ 2>&1 | tee -a $(TARGET)/$(shell date -u +%Y-%m-%d).log
|
||||||
|
echo '# $(shell date -u)' > $(TARGET)/lastbackup.txt
|
||||||
|
date -u '+%s' >> $(TARGET)/lastbackup.txt
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
THIS = $(shell basename $(CURDIR))
|
||||||
|
YYYYMMDD = $(shell date -u +%Y-%m-%d)
|
||||||
|
YYYY = $(shell date +%Y)
|
||||||
|
|
||||||
|
default: save_makefile update-lifeboat update-photos
|
||||||
|
|
||||||
|
save_makefile:
|
||||||
|
cp ./Makefile $(HOME)/dev/hacks/makefiles/$(YYYYMMDD).$(THIS).makefile
|
||||||
|
|
||||||
|
update-lifeboat:
|
||||||
|
rsync -avP --delete $(HOME)/Library/Syncthing/folders/lifeboat/*.zip /Volumes/1tb-lifeboat1G/berlin.sneak.fs.lifeboat/
|
||||||
|
zip -T /Volumes/1tb-lifeboat1G/berlin.sneak.fs.lifeboat/*.zip
|
||||||
|
|
||||||
|
update-photos:
|
||||||
|
rsync -avP --delete $(HOME)/Library/Syncthing/folders/LightroomMasters-CurrentYear/ ./fs/ 2>&1 | tee -a log.txt
|
||||||
|
cd ./fs && find . -type f -print0 | xargs -0 shasum 2&>1 | tee -a ../SHASUMS.txt
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
THIS = $(shell basename $(CURDIR))
|
||||||
|
YYYYMMDD = $(shell date -u +%Y-%m-%d)
|
||||||
|
|
||||||
|
default: backup
|
||||||
|
|
||||||
|
save_makefile:
|
||||||
|
cp ./Makefile $(HOME)/dev/hacks/makefiles/$(YYYYMMDD).$(THIS).makefile
|
||||||
|
|
||||||
|
backup: save_makefile do_file_backup write_checksum
|
||||||
|
|
||||||
|
write_checksum:
|
||||||
|
cd ./fs && find . -type f -print0 | xargs -0 sha1sum | tee -a ../SHASUMS.txt
|
||||||
|
|
||||||
|
do_file_backup:
|
||||||
|
rsync -avv \
|
||||||
|
--exclude=/tmp \
|
||||||
|
--exclude=/.cache \
|
||||||
|
--exclude=/.nvm \
|
||||||
|
--exclude=/.Trash \
|
||||||
|
--exclude=/Library/Caches \
|
||||||
|
--exclude=/Library/Mail \
|
||||||
|
--exclude=/Library/Developer \
|
||||||
|
--exclude=.DS_Store \
|
||||||
|
--delete-before \
|
||||||
|
--delete-excluded \
|
||||||
|
$(HOME)/ ./fs/ 2>&1 | tee -a ./$(shell date -u +%Y-%m-%d).log
|
||||||
|
echo '# $(shell date -u)' > ./lastbackup.txt
|
||||||
|
date -u '+%s' >> ./lastbackup.txt
|
||||||
10
makefiles/2022-12-10.usbroot-makefile.makefile
Normal file
10
makefiles/2022-12-10.usbroot-makefile.makefile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
THIS = usbroot-makefile
|
||||||
|
YYYYMMDD = $(shell date -u +%Y-%m-%d)
|
||||||
|
|
||||||
|
default: save_makefile alldirs
|
||||||
|
|
||||||
|
save_makefile:
|
||||||
|
cp ./Makefile $(HOME)/dev/hacks/makefiles/$(YYYYMMDD).$(THIS).makefile
|
||||||
|
|
||||||
|
alldirs:
|
||||||
|
for D in ./berlin.sneak.fs.* ; do cd $$D && make && cd .. ; done
|
||||||
40
makefiles/nostromo-backup.makefile
Normal file
40
makefiles/nostromo-backup.makefile
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
HOMEEXCLUDE := --exclude /.Trash \
|
||||||
|
--exclude .DS_Store \
|
||||||
|
--exclude /.Spotlight-V100 \
|
||||||
|
--exclude /.cache \
|
||||||
|
--exclude /.fseventsd \
|
||||||
|
--exclude /Library/Caches \
|
||||||
|
--exclude /Library/Mail \
|
||||||
|
--exclude /Library/Metadata/CoreSpotlight \
|
||||||
|
--exclude /tmp
|
||||||
|
|
||||||
|
ROOTEXCLUDE := --exclude /.Trash \
|
||||||
|
--exclude /proc \
|
||||||
|
--exclude /dev \
|
||||||
|
--exclude /.fseventsd \
|
||||||
|
--exclude .DS_Store \
|
||||||
|
--exclude /System/Volumes/Data/Volumes \
|
||||||
|
--exclude /private/var/vm \
|
||||||
|
--exclude /System/Volumes/Data/nix \
|
||||||
|
--exclude /System/Volumes/Data/Users/sneak \
|
||||||
|
--exclude /Users/sneak \
|
||||||
|
--exclude /System/Volumes/Data/.fseventsd \
|
||||||
|
--exclude /System/Volumes/Data/.Spotlight-V100 \
|
||||||
|
--exclude /System/Volumes/Data/private/var/folders \
|
||||||
|
--exclude /private/var/folders \
|
||||||
|
--exclude /Volumes
|
||||||
|
|
||||||
|
OPTS := -avP --delete --delete-excluded --delete-before
|
||||||
|
|
||||||
|
HOMEOPTS := $(OPTS) $(HOMEEXCLUDE)
|
||||||
|
ROOTOPTS := $(OPTS) $(ROOTEXCLUDE)
|
||||||
|
|
||||||
|
default:
|
||||||
|
sudo bash -c 'make synchome; make syncroot'
|
||||||
|
|
||||||
|
synchome:
|
||||||
|
rsync $(HOMEOPTS) $(HOME)/ ./2021-01-12.nostromo.sneakhome/ | tee -a $(shell date +%Y-%m-%d).homesync.log
|
||||||
|
|
||||||
|
syncroot:
|
||||||
|
rsync $(ROOTOPTS) / ./2021-01-12.nostromo.root/ | tee -a $(shell date +%Y-%m-%d).rootsync.log
|
||||||
|
|
||||||
11
makefiles/photobackup-ext.Makefile
Normal file
11
makefiles/photobackup-ext.Makefile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
default: sync
|
||||||
|
|
||||||
|
sync:
|
||||||
|
rsync -avP \
|
||||||
|
--delete \
|
||||||
|
--delete-before \
|
||||||
|
--delete-excluded \
|
||||||
|
--exclude '.sync-conflict*DS_Store' \
|
||||||
|
--exclude '.DS_Store' \
|
||||||
|
$(HOME)/Library/Syncthing/folders/LightroomMasters-CurrentYear/ \
|
||||||
|
./LightroomMasters-CurrentYear/
|
||||||
48
mastodon-s3-move/Makefile
Normal file
48
mastodon-s3-move/Makefile
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
PAR := 50
|
||||||
|
|
||||||
|
default: dl
|
||||||
|
|
||||||
|
dl:
|
||||||
|
rclone copy \
|
||||||
|
--progress \
|
||||||
|
--transfers $(PAR) \
|
||||||
|
--stats-unit bits \
|
||||||
|
--retries 10 \
|
||||||
|
--retries-sleep 60s \
|
||||||
|
--checkers $(PAR) \
|
||||||
|
--fast-list \
|
||||||
|
--s3-list-chunk 5000 \
|
||||||
|
--s3-disable-http2 \
|
||||||
|
--s3-sdk-log-mode Request,Response \
|
||||||
|
--no-traverse \
|
||||||
|
--check-first \
|
||||||
|
-vv --log-file=rclone-log-202505.txt \
|
||||||
|
mastodon-nbg1-s3:mastodon/ las1stor1-files:/srv/berlin.sneak.fs.mastodon-media/mastodon-bucket/
|
||||||
|
|
||||||
|
downloadold:
|
||||||
|
rclone copy \
|
||||||
|
--progress \
|
||||||
|
--transfers $(PAR) \
|
||||||
|
--stats-unit bits \
|
||||||
|
--retries 10 \
|
||||||
|
--retries-sleep 60s \
|
||||||
|
--fast-list \
|
||||||
|
--checkers=$(PAR) \
|
||||||
|
--check-first \
|
||||||
|
-vv --log-file=rclone-log.txt \
|
||||||
|
mastodon-nbg1-s3:mastodon/ ./mastodon-bucket/
|
||||||
|
|
||||||
|
copy:
|
||||||
|
rclone copy \
|
||||||
|
-v -v -v \
|
||||||
|
--progress \
|
||||||
|
--transfers $(PAR) \
|
||||||
|
--stats-unit bits \
|
||||||
|
--retries 10 \
|
||||||
|
--retries-sleep 60s \
|
||||||
|
--fast-list \
|
||||||
|
--checkers $(PAR) \
|
||||||
|
--check-first \
|
||||||
|
--log-file=rclone-copy-las1stor1.txt \
|
||||||
|
./ \
|
||||||
|
las1stor1-files:/srv/berlin.sneak.fs.mastodon-media/
|
||||||
0
misc/startsession
Executable file → Normal file
0
misc/startsession
Executable file → Normal file
0
mtgox.tradescraper/dailyprice.pl
Executable file → Normal file
0
mtgox.tradescraper/dailyprice.pl
Executable file → Normal file
0
mtgox.tradescraper/scrape.py
Executable file → Normal file
0
mtgox.tradescraper/scrape.py
Executable file → Normal file
102
nixpkgs/config.nix
Normal file
102
nixpkgs/config.nix
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
allowUnsupportedSystem = true;
|
||||||
|
packageOverrides = pkgs: with pkgs; rec {
|
||||||
|
myProfile = writeText "my-profile" ''
|
||||||
|
export PATH=$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||||||
|
export MANPATH=$HOME/.nix-profile/share/man:/nix/var/nix/profiles/default/share/man:/usr/share/man
|
||||||
|
'';
|
||||||
|
nostromo = pkgs.buildEnv {
|
||||||
|
name = "nostromopackages";
|
||||||
|
paths = [
|
||||||
|
basePackages
|
||||||
|
macPackages
|
||||||
|
];
|
||||||
|
pathsToLink = [ "/Applications" "/share/man" "/share/doc" "/bin" "/etc" ];
|
||||||
|
extraOutputsToInstall = [ "man" "doc" ];
|
||||||
|
};
|
||||||
|
secondmillion = pkgs.buildEnv {
|
||||||
|
name = "secondmillion";
|
||||||
|
paths = [
|
||||||
|
basePackages
|
||||||
|
macPackages
|
||||||
|
];
|
||||||
|
pathsToLink = [ "/Applications" "/share/man" "/share/doc" "/bin" "/etc" ];
|
||||||
|
extraOutputsToInstall = [ "man" "doc" ];
|
||||||
|
};
|
||||||
|
macPackages = pkgs.buildEnv {
|
||||||
|
name = "macPackages";
|
||||||
|
paths = [
|
||||||
|
pinentry_mac
|
||||||
|
];
|
||||||
|
pathsToLink = [ "/Applications" "/share/man" "/share/doc" "/bin" "/etc" ];
|
||||||
|
extraOutputsToInstall = [ "man" "doc" ];
|
||||||
|
};
|
||||||
|
adminPackages = pkgs.buildEnv {
|
||||||
|
name = "adminPackages";
|
||||||
|
paths = [
|
||||||
|
(runCommand "profile" {} ''
|
||||||
|
mkdir -p $out/etc/profile.d
|
||||||
|
cp ${myProfile} $out/etc/profile.d/my-profile.sh
|
||||||
|
'')
|
||||||
|
byobu
|
||||||
|
envdir
|
||||||
|
httpie
|
||||||
|
jq
|
||||||
|
mosh
|
||||||
|
nmap
|
||||||
|
openssl
|
||||||
|
pv
|
||||||
|
rsync
|
||||||
|
runit
|
||||||
|
terraform
|
||||||
|
tmux
|
||||||
|
wget
|
||||||
|
xz
|
||||||
|
];
|
||||||
|
pathsToLink = [ "/share/man" "/share/doc" "/bin" "/etc" ];
|
||||||
|
extraOutputsToInstall = [ "man" "doc" ];
|
||||||
|
|
||||||
|
};
|
||||||
|
basePackages = pkgs.buildEnv {
|
||||||
|
name = "my-packages";
|
||||||
|
paths = [
|
||||||
|
(runCommand "profile" {} ''
|
||||||
|
mkdir -p $out/etc/profile.d
|
||||||
|
cp ${myProfile} $out/etc/profile.d/my-profile.sh
|
||||||
|
'')
|
||||||
|
adminPackages
|
||||||
|
aria2
|
||||||
|
byobu
|
||||||
|
cmus
|
||||||
|
coreutils-prefixed
|
||||||
|
envdir
|
||||||
|
ffmpeg
|
||||||
|
gnupg
|
||||||
|
go
|
||||||
|
httpie
|
||||||
|
jq
|
||||||
|
neofetch
|
||||||
|
neovim
|
||||||
|
nmap
|
||||||
|
nodejs
|
||||||
|
openssl
|
||||||
|
par2cmdline
|
||||||
|
pv
|
||||||
|
pwgen
|
||||||
|
rsync
|
||||||
|
runit
|
||||||
|
tmux
|
||||||
|
tor
|
||||||
|
vim
|
||||||
|
vimpager
|
||||||
|
weechat
|
||||||
|
wget
|
||||||
|
xz
|
||||||
|
yarn
|
||||||
|
];
|
||||||
|
pathsToLink = [ "/share/man" "/share/doc" "/bin" "/etc" ];
|
||||||
|
extraOutputsToInstall = [ "man" "doc" ];
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
8
nmeastream/main.py
Normal file
8
nmeastream/main.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from serial import Serial
|
||||||
|
from pynmeagps import NMEAReader
|
||||||
|
with Serial('/dev/tty.usbmodem314101', 9600, timeout=3) as stream:
|
||||||
|
nmr = NMEAReader(stream)
|
||||||
|
raw_data, parsed_data = nmr.read()
|
||||||
|
if parsed_data is not None:
|
||||||
|
print(parsed_data)
|
||||||
|
|
||||||
156
nmeastream/nmeapoller.py
Normal file
156
nmeastream/nmeapoller.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
"""
|
||||||
|
nmeapoller.py
|
||||||
|
|
||||||
|
This example illustrates how to read, write and display NMEA messages
|
||||||
|
"concurrently" using threads and queues. This represents a useful
|
||||||
|
generic pattern for many end user applications.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
python3 nmeapoller.py port=/dev/ttyACM0 baudrate=38400 timeout=3
|
||||||
|
|
||||||
|
It implements two threads which run concurrently:
|
||||||
|
1) an I/O thread which continuously reads NMEA data from the
|
||||||
|
receiver and sends any queued outbound command or poll messages.
|
||||||
|
2) a process thread which processes parsed NMEA data - in this example
|
||||||
|
it simply prints the parsed data to the terminal.
|
||||||
|
NMEA data is passed between threads using queues.
|
||||||
|
|
||||||
|
Press CTRL-C to terminate.
|
||||||
|
|
||||||
|
FYI: Since Python implements a Global Interpreter Lock (GIL),
|
||||||
|
threads are not strictly concurrent, though this is of minor
|
||||||
|
practical consequence here.
|
||||||
|
|
||||||
|
Created on 07 Aug 2021
|
||||||
|
|
||||||
|
:author: semuadmin
|
||||||
|
:copyright: SEMU Consulting © 2021
|
||||||
|
:license: BSD 3-Clause
|
||||||
|
"""
|
||||||
|
|
||||||
|
from queue import Queue
|
||||||
|
from sys import argv
|
||||||
|
from threading import Event, Thread
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from serial import Serial
|
||||||
|
|
||||||
|
from pynmeagps import NMEA_MSGIDS, POLL, NMEAMessage, NMEAReader
|
||||||
|
|
||||||
|
|
||||||
|
def io_data(
|
||||||
|
stream: object,
|
||||||
|
nmr: NMEAReader,
|
||||||
|
readqueue: Queue,
|
||||||
|
sendqueue: Queue,
|
||||||
|
stop: Event,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
THREADED
|
||||||
|
Read and parse inbound NMEA data and place
|
||||||
|
raw and parsed data on queue.
|
||||||
|
|
||||||
|
Send any queued outbound messages to receiver.
|
||||||
|
"""
|
||||||
|
# pylint: disable=broad-exception-caught
|
||||||
|
|
||||||
|
while not stop.is_set():
|
||||||
|
try:
|
||||||
|
if stream.in_waiting:
|
||||||
|
(raw_data, parsed_data) = nmr.read()
|
||||||
|
if parsed_data:
|
||||||
|
readqueue.put((raw_data, parsed_data))
|
||||||
|
|
||||||
|
if not sendqueue.empty():
|
||||||
|
data = sendqueue.get(False)
|
||||||
|
if data is not None:
|
||||||
|
nmr.datastream.write(data.serialize())
|
||||||
|
sendqueue.task_done()
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
print(f"\n\nSomething went wrong {err}\n\n")
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
def process_data(queue: Queue, stop: Event):
|
||||||
|
"""
|
||||||
|
THREADED
|
||||||
|
Get NMEA data from queue and display.
|
||||||
|
"""
|
||||||
|
|
||||||
|
while not stop.is_set():
|
||||||
|
if queue.empty() is False:
|
||||||
|
(_, parsed) = queue.get()
|
||||||
|
print(parsed)
|
||||||
|
queue.task_done()
|
||||||
|
|
||||||
|
|
||||||
|
def main(**kwargs):
|
||||||
|
"""
|
||||||
|
Main routine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
port = kwargs.get("serport", "/dev/ttyACM0")
|
||||||
|
baudrate = int(kwargs.get("baudrate", 38400))
|
||||||
|
timeout = float(kwargs.get("timeout", 3))
|
||||||
|
|
||||||
|
with Serial(port, baudrate, timeout=timeout) as serial_stream:
|
||||||
|
nmeareader = NMEAReader(serial_stream)
|
||||||
|
|
||||||
|
read_queue = Queue()
|
||||||
|
send_queue = Queue()
|
||||||
|
stop_event = Event()
|
||||||
|
|
||||||
|
io_thread = Thread(
|
||||||
|
target=io_data,
|
||||||
|
args=(
|
||||||
|
serial_stream,
|
||||||
|
nmeareader,
|
||||||
|
read_queue,
|
||||||
|
send_queue,
|
||||||
|
stop_event,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
process_thread = Thread(
|
||||||
|
target=process_data,
|
||||||
|
args=(
|
||||||
|
read_queue,
|
||||||
|
stop_event,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\nStarting handler threads. Press Ctrl-C to terminate...")
|
||||||
|
io_thread.start()
|
||||||
|
process_thread.start()
|
||||||
|
|
||||||
|
# loop until user presses Ctrl-C
|
||||||
|
while not stop_event.is_set():
|
||||||
|
try:
|
||||||
|
# DO STUFF IN THE BACKGROUND...
|
||||||
|
# Poll for each NMEA sentence type.
|
||||||
|
# NB: Your receiver may not support all types. It will return a
|
||||||
|
# GNTXT "NMEA unknown msg" response for any types it doesn't support.
|
||||||
|
for msgid in NMEA_MSGIDS:
|
||||||
|
print(
|
||||||
|
f"\nSending a GNQ message to poll for an {msgid} response...\n"
|
||||||
|
)
|
||||||
|
msg = NMEAMessage("EI", "GNQ", POLL, msgId=msgid)
|
||||||
|
send_queue.put(msg)
|
||||||
|
sleep(1)
|
||||||
|
sleep(3)
|
||||||
|
stop_event.set()
|
||||||
|
|
||||||
|
except KeyboardInterrupt: # capture Ctrl-C
|
||||||
|
print("\n\nTerminated by user.")
|
||||||
|
stop_event.set()
|
||||||
|
|
||||||
|
print("\nStop signal set. Waiting for threads to complete...")
|
||||||
|
io_thread.join()
|
||||||
|
process_thread.join()
|
||||||
|
print("\nProcessing complete")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
main(**dict(arg.split("=") for arg in argv[1:]))
|
||||||
16
osmand-maps/Dockerfile
Normal file
16
osmand-maps/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM ubuntu as builder
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y nginx unzip zip curl wget
|
||||||
|
|
||||||
|
RUN mkdir -p /work/webroot && mkdir -p /work/download && mkdir -p /work/bin
|
||||||
|
|
||||||
|
VOLUME /work/webroot
|
||||||
|
VOLUME /work/download
|
||||||
|
|
||||||
|
ADD gen.sh /work/bin
|
||||||
|
ADD run.sh /work/bin
|
||||||
|
|
||||||
|
CMD ["/bin/bash", "/work/bin/run.sh" ]
|
||||||
17
osmand-maps/Makefile
Normal file
17
osmand-maps/Makefile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
IMAGE := osmand-maps
|
||||||
|
|
||||||
|
default: build-and-run
|
||||||
|
|
||||||
|
build-and-run: build run
|
||||||
|
|
||||||
|
build:
|
||||||
|
script -a log.txt docker build -t $(IMAGE) .
|
||||||
|
|
||||||
|
run:
|
||||||
|
docker rm -f osmand-maps
|
||||||
|
script -a log.txt docker run \
|
||||||
|
-v /webroot:/work/webroot \
|
||||||
|
-v /download:/work/download \
|
||||||
|
-p 80:80 \
|
||||||
|
--name osmand-maps \
|
||||||
|
$(IMAGE)
|
||||||
114
osmand-maps/gen.sh
Normal file
114
osmand-maps/gen.sh
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
export YYYYMMDD="$(date -u +%Y-%m-%d)"
|
||||||
|
export CONTINENTS="
|
||||||
|
europe
|
||||||
|
northamerica
|
||||||
|
southamerica
|
||||||
|
centralamerica
|
||||||
|
asia
|
||||||
|
africa
|
||||||
|
oceania
|
||||||
|
antarctica
|
||||||
|
world
|
||||||
|
voice
|
||||||
|
"
|
||||||
|
|
||||||
|
#https://download.osmand.net/download?standard=yes&file=$FN
|
||||||
|
|
||||||
|
#https://download.osmand.net/indexes.php
|
||||||
|
|
||||||
|
function fetchContinent() {
|
||||||
|
CONTINENT="$1"
|
||||||
|
cd /work/download
|
||||||
|
if [[ ! -d ./$CONTINENT ]]; then
|
||||||
|
mkdir $CONTINENT
|
||||||
|
fi
|
||||||
|
cd $CONTINENT
|
||||||
|
FILES="$(grep -i $CONTINENT ../listing.txt)"
|
||||||
|
|
||||||
|
for FILE in $FILES ; do
|
||||||
|
URL="https://download.osmand.net/download?standard=yes&file=$FILE"
|
||||||
|
if [[ ! -e "$FILE" ]]; then
|
||||||
|
echo "file $FILE is missing, downloading"
|
||||||
|
wget --progress=dot:giga --report-speed=bits \
|
||||||
|
-O "$FILE.tmp" \
|
||||||
|
-c \
|
||||||
|
"$URL" && mv "$FILE.tmp" "$FILE"
|
||||||
|
rm *.tmp
|
||||||
|
fi
|
||||||
|
ls -tla
|
||||||
|
du -sh .
|
||||||
|
df -h .
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function zipContinent() {
|
||||||
|
CONTINENT="$1"
|
||||||
|
cd /work/download
|
||||||
|
du -sh $CONTINENT
|
||||||
|
df -h .
|
||||||
|
cd $CONTINENT
|
||||||
|
find . -type f -iname '*.zip' -print0 | xargs -0 -n 1 -P 8 unzip
|
||||||
|
rm -fv *.zip *.tmp
|
||||||
|
cd ..
|
||||||
|
zip -9r $YYYYMMDD.$CONTINENT.zip $CONTINENT
|
||||||
|
rm -rfv $CONTINENT
|
||||||
|
mv $YYYYMMDD.$CONTINENT.zip /work/webroot
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch() {
|
||||||
|
|
||||||
|
cd /work/download
|
||||||
|
|
||||||
|
# srtmf files are 404
|
||||||
|
curl -sf https://download.osmand.net/indexes.php | tr "\"" "\n" |
|
||||||
|
tr ">" "\n" | tr "<" "\n" | grep obf.zip |
|
||||||
|
grep -v "srtmf" | sort > listing.txt
|
||||||
|
|
||||||
|
cat > /work/webroot/index.html.new <<EOF
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>files</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>files</h1>
|
||||||
|
<ul>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
for CONTINENT in $CONTINENTS; do
|
||||||
|
if [[ ! -e /work/webroot/$YYYYMMDD.$CONTINENT.zip ]]; then
|
||||||
|
fetchContinent "$CONTINENT"
|
||||||
|
zipContinent "$CONTINENT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >> /work/webroot/index.html.new <<EOF
|
||||||
|
<li>
|
||||||
|
<a href="/$YYYYMMDD.$CONTINENT.zip">$YYYYMMDD.$CONTINENT.zip</a>
|
||||||
|
</li>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
cat >> /work/webroot/index.html.new <<EOF
|
||||||
|
</ul>
|
||||||
|
<pre>
|
||||||
|
$(date -u)
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mv /work/webroot/index.html.new /work/webroot/index.html
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
fetch
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
10
osmand-maps/run.sh
Normal file
10
osmand-maps/run.sh
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
chmod a+rx /work/bin/*
|
||||||
|
|
||||||
|
bash -c "/work/bin/gen.sh" &
|
||||||
|
|
||||||
|
rm -rfv /var/www/html
|
||||||
|
ln -s /work/webroot /var/www/html
|
||||||
|
chmod -Rv a+rx /work/webroot/*
|
||||||
|
exec nginx -g "daemon off;"
|
||||||
0
osxbackup/appbackup.command
Executable file → Normal file
0
osxbackup/appbackup.command
Executable file → Normal file
0
osxbackup/backup.command
Executable file → Normal file
0
osxbackup/backup.command
Executable file → Normal file
23
osxbackup/rsyncbackup.command
Executable file → Normal file
23
osxbackup/rsyncbackup.command
Executable file → Normal file
@@ -42,13 +42,12 @@ RE+=" --exclude=.cache/"
|
|||||||
RE+=" --exclude=/.cpan/build/"
|
RE+=" --exclude=/.cpan/build/"
|
||||||
RE+=" --exclude=/.cpan/sources/"
|
RE+=" --exclude=/.cpan/sources/"
|
||||||
RE+=" --exclude=/.docker/"
|
RE+=" --exclude=/.docker/"
|
||||||
|
RE+=" --exclude=/.local/share/containers/podman/machine/"
|
||||||
RE+=" --exclude=/.dropbox/"
|
RE+=" --exclude=/.dropbox/"
|
||||||
RE+=" --exclude=/.minikube/cache/"
|
RE+=" --exclude=/.minikube/cache/"
|
||||||
RE+=" --exclude=/Desktop/" # desktop is like a visible tempdir.
|
RE+=" --exclude=/Applications/Fortnite/"
|
||||||
RE+=" --exclude=/Library/VoiceTrigger/SAT"
|
|
||||||
RE+=" --exclude=/Documents/Dropbox/.dropbox.cache/"
|
RE+=" --exclude=/Documents/Dropbox/.dropbox.cache/"
|
||||||
RE+=" --exclude=/Documents/Steam?Content/"
|
RE+=" --exclude=/Documents/Steam?Content/"
|
||||||
RE+=" --exclude=/Downloads/"
|
|
||||||
RE+=" --exclude=/Library/Application?Support/Ableton/"
|
RE+=" --exclude=/Library/Application?Support/Ableton/"
|
||||||
RE+=" --exclude=/Library/Application?Support/Adobe/Adobe?Device?Central?CS4/"
|
RE+=" --exclude=/Library/Application?Support/Adobe/Adobe?Device?Central?CS4/"
|
||||||
RE+=" --exclude=/Library/Application?Support/CrossOver?Games/"
|
RE+=" --exclude=/Library/Application?Support/CrossOver?Games/"
|
||||||
@@ -58,29 +57,36 @@ RE+=" --exclude=/Library/Application?Support/MobileSync/"
|
|||||||
RE+=" --exclude=/Library/Application?Support/SecondLife/cache/"
|
RE+=" --exclude=/Library/Application?Support/SecondLife/cache/"
|
||||||
RE+=" --exclude=/Library/Application?Support/Steam/SteamApps/"
|
RE+=" --exclude=/Library/Application?Support/Steam/SteamApps/"
|
||||||
RE+=" --exclude=/Library/Application?Support/SyncServices/"
|
RE+=" --exclude=/Library/Application?Support/SyncServices/"
|
||||||
|
RE+=" --exclude=/Library/Application?Support/protonmail/bridge/cache"
|
||||||
|
RE+=' --exclude=/Library/Application?Support/Syncthing/index-*'
|
||||||
|
RE+=" --exclude=/Library/Metadata"
|
||||||
RE+=" --exclude=/Library/Caches/"
|
RE+=" --exclude=/Library/Caches/"
|
||||||
RE+=" --exclude=/Library/Containers/com.docker.docker/"
|
RE+=" --exclude=/Library/Containers/com.docker.docker/"
|
||||||
|
RE+=" --exclude=/Library/Group?Containers/group.com.apple.secure-control-center-preferences"
|
||||||
RE+=" --exclude=/Library/Cookies/"
|
RE+=" --exclude=/Library/Cookies/"
|
||||||
RE+=" --exclude=/Library/Developer/"
|
RE+=" --exclude=/Library/Developer/"
|
||||||
|
RE+=" --exclude=/Library/Google/GoogleSoftwareUpdate/"
|
||||||
RE+=" --exclude=/Library/Homebrew/"
|
RE+=" --exclude=/Library/Homebrew/"
|
||||||
RE+=" --exclude=/Library/Syncthing/folders/" # syncthing is its own backup
|
|
||||||
RE+=" --exclude=/Library/Logs/"
|
RE+=" --exclude=/Library/Logs/"
|
||||||
RE+=" --exclude=/Library/Mail/" # keep your mail on the server!
|
RE+=" --exclude=/Library/Mail/" # keep your mail on the server!
|
||||||
RE+=" --exclude=/Library/Mail?Downloads/"
|
RE+=" --exclude=/Library/Mail?Downloads/"
|
||||||
|
RE+=" --exclude=/Library/Parallels/"
|
||||||
|
RE+=" --exclude=/Library/Suggestions/"
|
||||||
RE+=" --exclude=/Library/Preferences/Macromedia/Flash?Player/"
|
RE+=" --exclude=/Library/Preferences/Macromedia/Flash?Player/"
|
||||||
RE+=" --exclude=/Library/Preferences/SDMHelpData/"
|
RE+=" --exclude=/Library/Preferences/SDMHelpData/"
|
||||||
RE+=" --exclude=/Library/PubSub/"
|
RE+=" --exclude=/Library/PubSub/"
|
||||||
RE+=" --exclude=/Library/Safari/"
|
RE+=" --exclude=/Library/Safari/"
|
||||||
RE+=" --exclude=/Library/Safari/HistoryIndex.sk"
|
RE+=" --exclude=/Library/Safari/HistoryIndex.sk"
|
||||||
|
RE+=" --exclude=/Library/Syncthing/folders/" # syncthing is its own backup
|
||||||
|
RE+=" --exclude=/Library/VoiceTrigger/SAT"
|
||||||
RE+=" --exclude=/Library/iTunes/iPad?Software?Updates/"
|
RE+=" --exclude=/Library/iTunes/iPad?Software?Updates/"
|
||||||
RE+=" --exclude=/Library/iTunes/iPhone?Software?Updates/"
|
RE+=" --exclude=/Library/iTunes/iPhone?Software?Updates/"
|
||||||
|
RE+=" --exclude=/Movies/CacheClip/"
|
||||||
|
RE+=" --exclude=/Movies/ProxyMedia/"
|
||||||
RE+=" --exclude=/Music/iTunes/Album?Artwork/"
|
RE+=" --exclude=/Music/iTunes/Album?Artwork/"
|
||||||
RE+=" --exclude=/Pictures/iPod?Photo?Cache/"
|
RE+=" --exclude=/Pictures/iPod?Photo?Cache/"
|
||||||
RE+=" --exclude=/Receivd/"
|
RE+=" --exclude=/Receivd/"
|
||||||
RE+=" --exclude=/VirtualBox?VMs/"
|
RE+=" --exclude=/.persepolis/"
|
||||||
|
|
||||||
# apps in homedir
|
|
||||||
RE+=" --exclude=/Applications/Fortnite/"
|
|
||||||
|
|
||||||
MINRE=""
|
MINRE=""
|
||||||
MINRE+=" --exclude=/.fseventsd/"
|
MINRE+=" --exclude=/.fseventsd/"
|
||||||
@@ -148,6 +154,7 @@ RE+=" --exclude=/Stickies.app"
|
|||||||
RE+=" --exclude=/System?Preferences.app"
|
RE+=" --exclude=/System?Preferences.app"
|
||||||
RE+=" --exclude=/TextEdit.app"
|
RE+=" --exclude=/TextEdit.app"
|
||||||
RE+=" --exclude=/Time?Machine.app"
|
RE+=" --exclude=/Time?Machine.app"
|
||||||
|
RE+=" --exclude=/Yubikey?Manager.app"
|
||||||
RE+=" --exclude=/Utilities/Activity?Monitor.app"
|
RE+=" --exclude=/Utilities/Activity?Monitor.app"
|
||||||
RE+=" --exclude=/Utilities/AirPort?Utility.app"
|
RE+=" --exclude=/Utilities/AirPort?Utility.app"
|
||||||
RE+=" --exclude=/Utilities/AppleScript?Editor.app"
|
RE+=" --exclude=/Utilities/AppleScript?Editor.app"
|
||||||
|
|||||||
0
osxubuntumirror/debmirror.sh
Executable file → Normal file
0
osxubuntumirror/debmirror.sh
Executable file → Normal file
0
osxubuntumirror/syncubuntu.sh
Executable file → Normal file
0
osxubuntumirror/syncubuntu.sh
Executable file → Normal file
0
pgoatp/pgoatp.pl
Executable file → Normal file
0
pgoatp/pgoatp.pl
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user