Skip to content

Instantly share code, notes, and snippets.

@Shados
Last active January 26, 2022 04:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shados/19c34d5e92218f444f98b51a7cf5446d to your computer and use it in GitHub Desktop.
Save Shados/19c34d5e92218f444f98b51a7cf5446d to your computer and use it in GitHub Desktop.
Script-directory watcher/pusher for the Steam version of Bitburner
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash gnused jq coreutils curl inotify-tools
# vim: set ft=sh :
set -eEo pipefail
shopt -s inherit_errexit nullglob
set -u
SCRIPTNAME=$(basename "$0")
# Set the DRY_RUN env var when running the command if you just want it to print
# what it will do, instead of actually doing anything
if [[ -v DRY_RUN ]]; then
DRY_RUN_CMD="dry_run"
else
DRY_RUN_CMD=""
fi
BB_PORT=9990
BB_URL=localhost
# Oddly, .txt files aren't allowed, even though you can create them in game.
ALLOWED_FILETYPES=("js" "ns" "script")
ALLOWED_FILETYPES_PATTERN=""
declare -A ALLOWED_FILETYPES_HASH
for ft in "${ALLOWED_FILETYPES[@]}"; do
ALLOWED_FILETYPES_HASH["$ft"]=1
ALLOWED_FILETYPES_PATTERN="${ALLOWED_FILETYPES_PATTERN:+${ALLOWED_FILETYPES_PATTERN}|}$ft"
done
ALLOWED_FILETYPES_PATTERN=".*?\.($ALLOWED_FILETYPES_PATTERN)\$"
function main {
check_parameters "$@"
local script_dir="$(realpath "$2")"
# Do an initial sync of all files in the script directory
push_script_dir "$script_dir"
# Watch the script directory, and push files on writes
watch_script_dir "$script_dir"
}
function check_parameters {
if [ $# -ne 2 ]; then
usage
exit 1
fi
AUTH_TOKEN=$(cat "$1")
if [[ "${#AUTH_TOKEN}" -ne 64 ]]; then
usage
printf "\n\nAUTH_TOKEN_FILE does not appear to be contain the token (does not contain a 64-character string)!\n"
exit 1
fi
}
function usage {
printf "Usage: %s AUTH_TOKEN_FILE SCRIPT_DIRECTORY\n" "$SCRIPTNAME"
printf "\tAUTH_TOKEN_FILE is the path containing your bitburner API Server auth token\n"
printf "\tSCRIPT_DIRECTORY is the directory of files you want to watch and push to bitburner\n"
}
function push_script_dir {
local script_dir="$1"
log "Doing initial push of %q\n" "$script_dir..."
find "$script_dir" -type f -regextype egrep -regex "$ALLOWED_FILETYPES_PATTERN" -print0 | while IFS= read -r -d $'\0' file_path; do
push_file "$script_dir" "$file_path"
done
log "Initial push complete\n"
}
function watch_script_dir {
log "Starting to watch %q for changes...\n" "$script_dir"
local script_dir="$1"
inotifywait --monitor --event close_write --format '%w%f%0' --no-newline -r "$script_dir" | while IFS= read -r -d $'\0' file_path; do
if file_is_allowed "$file_path"; then
push_file "$script_dir" "$file_path"
fi
done
}
function file_is_allowed {
local file_path="$1"
local ext="${file_path##*.}"
return $([[ -v ALLOWED_FILETYPES_HASH["$ext"] ]])
}
function push_file {
local script_dir="$1"
local file_path="$2"
# Strip the base directory from the file path
local filename="${file_path#"$script_dir/"}"
log "Pushing file %q\n" "$filename"
# Prepare the payload
if [[ $(dirname "$filename") != "." ]]; then
# If the file is going to be in a subdirectory, it NEEDS the leading `/`,
# i.e. `/my-dir/file.js`.
# If the file is standalone, it CAN NOT HAVE a leading slash, i.e.
# `file.js`.
filename="/$filename"
fi
# base64 encode the file and chuck it into a temporary file, as a JSON string
local code_tmpfile="$SCRIPT_TMPDIR/code_b64.json"
printf '"' >"$code_tmpfile"
base64 -w 0 "$file_path" >>"$code_tmpfile"
printf '"\n' >>"$code_tmpfile"
# Prepare the JSON payload
local payload_tmpfile="$SCRIPT_TMPDIR/payload.json"
jq -c --null-input \
--exit-status \
--arg filename "$(sanitise_path "$filename")" \
--argfile code "$code_tmpfile" \
'{ filename: $filename, code: $code }' >"$payload_tmpfile"
# Push the file
$DRY_RUN_CMD curl \
--request POST \
--header "Content-Type: application/json" \
--header "Authorization: Bearer $AUTH_TOKEN" \
--data "@$payload_tmpfile" \
--verbose \
"http://$BB_URL:$BB_PORT/" >/dev/null 2>/dev/null
# TODO handle HTTP return codes
rm "$code_tmpfile" "$payload_tmpfile"
}
function sanitise_path {
# NOTE: You can upload files with spaces in the path, but you can't actually
# `vim` or `rm` them in-game, as there's no way to escape or quote the ' '
# character in the game's very limited terminal. So instead we replace spaces
# with -.
printf "%s" "$1" | sed -e 's| |-|g'
}
function dry_run {
printf "%s" "$1"
shift
for arg in "$@"; do
printf " %q" "$arg"
done
printf "\n"
}
function log {
printf "[%s] " "$(date -Ins)"
printf "$@"
}
function cleanup {
rm -rf "$1"
}
SCRIPT_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/$SCRIPTNAME.XXXXXXXX")
trap "cleanup $SCRIPT_TMPDIR" SIGINT SIGTERM ERR EXIT
main "$@"
@Shados
Copy link
Author

Shados commented Jan 22, 2022

If you don't have Nix, you'll need to replace the shebang lines with #!/usr/bin/env bash and then ensure all packages listed after the -p in the second shebang line are available on $PATH. Most likely this will just mean installing the jq and inotify-tools packages for your distro.

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

@Shados
Copy link
Author

Shados commented Jan 26, 2022

A word to anyone using this: bitburner's file-upload API is pretty glitchy. Under some circumstances it will just kinda crap out and valid attempts to push files over it will just hang, until you restart bitburner outright.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment