#!/bin/sh set -e set -a # automatically export all variables . ./.env.development if [ -f .env.local ]; then . ./.env.local fi logFilter() { grep -v --line-buffered --color=never 'POST /api/graphql .*200' } docker__compose() { if [ ! -x "$(command -v docker)" ]; then echo "docker compose is not installed" echo "installation instructions are here: https://docs.docker.com/desktop/" exit 0 fi ENV_LOCAL= if [ -f .env.local ]; then ENV_LOCAL='--env-file .env.local' fi CURRENT_UID=$(id -u) CURRENT_GID=$(id -g) command docker compose --env-file .env.development $ENV_LOCAL "$@" } docker__exec() { if [ ! -x "$(command -v docker)" ]; then echo "docker is not installed" echo "installation instructions are here: https://docs.docker.com/desktop/" exit 0 fi DOCKER_CLI_HINTS=false command docker exec -i "$@" } sndev__start() { shift if [ $# -eq 0 ]; then docker__compose up --build exit 0 fi docker__compose up "$@" } sndev__help_start() { help=" start the sndev env USAGE $ sndev start [OPTIONS] [SERVICE...] OPTIONS" echo "$help" docker__compose up --help | awk '/Options:/{y=1;next}y' } sndev__stop() { shift docker__compose down "$@" } sndev__help_stop() { help=" stop the sndev env USAGE $ sndev stop [OPTIONS] [SERVICE...] OPTIONS" echo "$help" docker__compose down --help | awk '/Options:/{y=1;next}y' } sndev__open() { shift service=$(docker__compose ps $1 --format '{{.Label "CONNECT"}}') if [ -z "$service" ]; then echo "no url found for $1" exit 1 fi service="http://$service" echo "opening $1 ... $service" if [ "$(uname)" = "Darwin" ]; then open $service elif [ "$(uname)" = "Linux" ]; then xdg-open $service elif [ "$(uname)" = "Windows_NT" ]; then start $service fi } sndev__help_open() { help=" open a container's url if it has one USAGE $ sndev open SERVICE OPTIONS no options currently exist " echo "$help" } sndev__restart() { shift docker__compose restart "$@" } sndev__help_restart() { help=" restart the sndev env USAGE $ sndev restart [OPTIONS] [SERVICE...] OPTIONS" echo "$help" docker__compose restart --help | awk '/Options:/{y=1;next}y' } sndev__logs() { shift if [ $# -eq 1 ]; then docker__compose logs -t --tail=1000 -f "$@" | logFilter exit 0 fi docker__compose logs "$@" } sndev__help_logs() { help=" get logs from sndev env USAGE $ sndev logs [OPTIONS] [SERVICE...] OPTIONS" echo "$help" docker__compose logs --help | awk '/Options:/{y=1;next}y' } sndev__status() { shift if [ $# -eq 0 ]; then docker__compose ps -a --format 'table {{.Service}}\t{{.State}}\t{{.Status}}\t{{.Label "CONNECT"}}' exit 0 fi docker__compose ps "$@" } sndev__help_status() { help=" show container status of sndev env USAGE $ sndev status [OPTIONS] [SERVICE...] OPTIONS" echo "$help" docker__compose ps --help | awk '/Options:/{y=1;next}y' } sndev__delete() { printf "this deletes containers, volumes, and orphans - are you sure? [y/N] " read -r answer if [ "$answer" = "y" ]; then docker__compose down --volumes --remove-orphans else echo "delete cancelled" fi } sndev__help_delete() { help=" remove orphans and volumes from sndev env equivalent to sndev stop --volumes --remove-orphans USAGE $ sndev delete " echo "$help" } sndev__set_balance() { shift if [ -z "$1" ]; then echo "NYM argument required" sndev__help_set_balance exit 1 fi if [ -z "$2" ]; then echo "MSATS argument required" sndev__help_set_balance exit 2 fi if ! echo "$2" | grep -qE "^[0-9]+$"; then echo "MSATS argument is not a positive integer" sndev__help_set_balance exit 3 fi docker__exec db psql -U sn -d stackernews -q <<EOF UPDATE users set msats = $2 where name = '$1'; EOF } sndev__help_set_balance() { help=" set the balance of a nym USAGE $ sndev set_balance NYM MSATS NYM - the name of the user you want to set the balance of MSATS - the amount of millisatoshis to set the account to. Must be >= 0 " echo "$help" } sndev__fund() { shift if [ "$1" = "--cln" ]; then shift sndev__cli -t cln pay "$@" else sndev__cli -t lnd payinvoice "$@" fi } sndev__help_fund() { help=" pay a bolt11 for funding USAGE $ sndev fund BOLT11 [OPTIONS] $ sndev fund --cln BOLT11 OPTIONS" echo "$help" sndev__cli lnd payinvoice -h | awk '/OPTIONS:/{y=1;next}y' | awk '!/^[\t ]+--pay_req value/' } sndev__withdraw() { shift if [ "$1" = "--cln" ]; then shift label=$(date +%s) sndev__cli -t cln invoice "$1"sats "$label" sndev | jq -j '.bolt11'; echo else sndev__cli lnd addinvoice --amt "$@" | jq -j '.payment_request'; echo fi } sndev__help_withdraw() { help=" create a bolt11 for withdrawal USAGE $ sndev withdraw SATS [OPTIONS] $ sndev withdraw --cln SATS OPTIONS" echo "$help" sndev__cli lnd addinvoice -h | awk '/OPTIONS:/{y=1;next}y' | awk '!/^[\t ]+(--amt|--amt_msat) value/' } sndev__psql() { shift docker__exec -t db psql "$@" -U sn -d stackernews } sndev__help_psql() { help=" open psql on db USAGE $ sndev psql [OPTIONS] OPTIONS" echo "$help" docker__exec db psql --help | awk '/General options:/{y=1;next}y' | sed -n '/Connection options:/q;p' | awk '!/^([\t ]+-l, --list)|([\t ]+-d, --dbname)|([\t ]+-\?, --help)|([\t ]--help=)/' } sndev__prisma() { shift docker__exec -t -u apprunner app npx prisma "$@" } sndev__help_prisma() { help=" run prisma commands USAGE $ sndev prisma [COMMAND] COMMANDS" echo "$help" sndev__prisma --help | awk '/Commands/{y=1;next}y' | awk '!/^([\t ]+init)|([\t ]+studio)/' | sed -n '/Flags/q;p' } sndev__lint() { shift docker__exec -t -u apprunner app npm run lint } sndev__help_lint() { help=" run linters USAGE $ sndev lint " echo "$help" } sndev__test() { shift args="" if [ $# -gt 0 ]; then args="-- $@" fi docker__exec -t -u apprunner app npm run test $args } sndev__help_test() { help=" run tests USAGE $ sndev test [OPTIONS] OPTIONS" echo "$help" docker__exec -u apprunner app npm run test -- --help | awk '/Options:/{y=1;next}y' } sndev__compose() { shift docker__compose "$@" } sndev__help_compose() { docker__compose --help } __sndev__pr_track() { json=$(curl -fsSH "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/stackernews/stacker.news/pulls/$1") case $(git config --get remote.origin.url) in "http"*) url=$(echo "$json" | grep -e '"clone_url"' | head -n1 | sed -e 's/^.*"clone_url":[[:space:]]*"//; s/",[[:space:]]*$//') ;; *) url=$(echo "$json" | grep -e '"ssh_url"' | head -n1 | sed -e 's/^.*"ssh_url":[[:space:]]*"//; s/",[[:space:]]*$//') ;; esac push=$(git remote -v | grep -e "$url .*push" | head -n1) || true if [ -n "$push" ]; then remote=$(echo "$push" | cut -f 1) else remote=$(echo "$json" | grep -e '"login"' | head -n1 | sed -e 's/^.*"login":[[:space:]]*"//; s/",[[:space:]]*$//') git remote remove "$remote" 1>/dev/null 2>&1 || true git remote add "$remote" "$url" fi ref=$(echo "$json" | grep -e '"ref"' | head -n1 | sed -e 's/^.*"ref":[[:space:]]*"//; s/",[[:space:]]*$//') git fetch "$remote" "$ref" git checkout -t -b "pr/$1" "$remote/$ref" git config --local "remote.$remote.push" pr/$1:$ref exit 0 } __sndev__pr_detach() { refspec="+refs/pull/$1/head:refs/remotes/pr/$1" case $(git config --get remote.origin.url) in "http"*) git fetch https://github.com/stackernews/stacker.news.git "$refspec" ;; *) git fetch git@github.com:stackernews/stacker.news.git "$refspec" ;; esac git checkout "pr/$1" exit 0 } sndev__pr() { shift case $1 in -t|--track) call "__sndev__pr_track" "$2" ;; *) call "__sndev__pr_detach" "$1" ;; esac } sndev__help_pr() { help=" fetch and checkout a pr USAGE $ sndev pr [OPTIONS] PR_NUMBER OPTIONS -t, --track track the pr in a new branch, creating a remote if necessary defaults to checking out the pr in a detached state " echo "$help" } sndev__login() { shift if [ -z "$1" ]; then echo "NYM argument required" sndev__help_login exit 1 fi # hardcode token for which is the hex digest of the sha256 of # "SNDEV-TOKEN3_0W_PhDRZVanbeJsZZGIEljexkKoGbL6qGIqSwTjjI" # next-auth concats the token with the secret from env and then sha256's it token="d5fce54babffcb070c39f78d947761fd9ec37647fafcecb9734a3085a78e5c5e" salt="202c90943c313b829e65e3f29164fb5dd7ea3370d7262c4159691c2f6493bb8b" # upsert user with nym and nym@sndev.team email="$1@sndev.team" docker__exec db psql -U sn -d stackernews -q <<EOF INSERT INTO users (name) VALUES ('$1') ON CONFLICT DO NOTHING; UPDATE users SET email = '$email', "emailHash" = encode(digest(LOWER('$email')||'$salt', 'sha256'), 'hex') WHERE name = '$1'; INSERT INTO verification_requests (identifier, token, expires) VALUES ('$email', '$token', NOW() + INTERVAL '1 day') ON CONFLICT (token) DO UPDATE SET identifier = '$email', expires = NOW() + INTERVAL '1 day'; EOF echo echo "open url in browser" echo "http://localhost:3000/api/auth/callback/email?token=SNDEV-TOKEN&email=$1%40sndev.team" echo } sndev__help_login() { help=" login as a nym USAGE $ sndev login NYM " echo "$help" } sndev__onion() { shift tordir=$(docker__compose ps $1 --format '{{.Label "TORDIR"}}') if [ -z "$tordir" ]; then echo "no TORDIR label found for $1" exit 1 fi onion=$(docker__exec $1 cat $tordir/hidden_service/hostname | tr -d '[:space:]') echo "$onion" } sndev__help_onion() { help=" get the onion address of a service USAGE $ sndev onion SERVICE " echo "$help" } sndev__cert() { shift certdir=$(docker__compose ps $1 --format '{{.Label "CERTDIR"}}') if [ -z "$certdir" ]; then echo "no CERTDIR label found for $1" exit 1 fi docker__exec $1 cat $certdir/tls.cert | base64 } sndev__help_cert() { help=" get the tls cert of a service USAGE $ sndev cert SERVICE " echo "$help" } sndev__cli() { t=$1 if [ "$t" = "-t" ]; then shift else t="" fi if [ "$1" = "cli" ]; then shift fi if [ -z "$1" ]; then echo "SERVICE required" sndev__help_cli exit 1 fi service=$1 cli=$(docker__compose ps $service --format '{{.Label "CLI"}}') cli_user=$(docker__compose ps $service --format '{{.Label "CLI_USER"}}') cli_args=$(docker__compose ps $service --format '{{.Label "CLI_ARGS"}}') if [ -z "$cli" ]; then echo "no CLI label found for $service" exit 1 fi shift if [ -n "$cli_user" ]; then cli_user="-u $cli_user" fi docker__exec $t $cli_user $service $cli $cli_args "$@" } sndev__help_cli() { help=" run a cli command on a service USAGE $ sndev cli SERVICE [COMMAND [ARGS]] " echo "$help" } sndev__help() { if [ $# -eq 2 ]; then call "sndev__$1_$2" "$@" exit 0 fi help=" 888 888 888 .d8888b 88888b. .d88888 .d88b. 888 888 88K 888 '88b d88' 888 d8P Y8b 888 888 'Y8888b. 888 888 888 888 88888888 Y88 88P X88 888 888 Y88b 888 Y8b. Y8bd8P 88888P' 888 888 'Y88888 'Y8888 Y88P manages a docker based stacker news development environment USAGE $ sndev [COMMAND] $ sndev help [COMMAND] COMMANDS help show help env: start start env stop stop env restart restart env status status of env logs logs from env delete delete env sn: login login as a nym set_balance set the balance of a nym lightning: fund pay a bolt11 for funding withdraw create a bolt11 for withdrawal db: psql open psql on db prisma run prisma commands dev: pr fetch and checkout a pr lint run linters test run tests other: cli service cli passthrough open open service GUI in browser onion service onion address cert service tls cert compose docker compose passthrough " echo "$help" } call() { func=$1 if type "$func" 1>/dev/null 2>&1; then # if it's sndev COMMAND help, then call help for that command case $3 in -h|--help|help) call "sndev__help_$2" exit 0 ;; esac shift # remove func from args "$func" "$@" # invoke our named function w/ all remaining arguments else # if it's sndev -h COMMAND, then call help for that command case $2 in -h|--help) call "sndev__help_$3" exit 0 ;; esac sndev__help exit 1 fi } call "sndev__$1" "$@"