Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions WalletRestoreManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

BUCKET_NAME = 'pcw-backups'

AWS_ACCESS_KEY_ID=os.environ['PCW_SHARE_S3_ACCESS_KEY_ID']
AWS_ACCESS_KEY=os.environ['PCW_SHARE_S3_ACCESS_KEY']
AWS_ACCESS_KEY_ID=os.environ['PCW_SHARE_S3_ACCESS_KEY_ID2']
AWS_ACCESS_KEY=os.environ['PCW_SHARE_S3_ACCESS_KEY2']
AWS_REGION_NAME="us-east-1"


Expand Down
4 changes: 2 additions & 2 deletions bin/backup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import boto3

BUCKET_NAME = 'pcw-backups'

AWS_ACCESS_KEY_ID=os.environ['PCW_SHARE_S3_ACCESS_KEY_ID']
AWS_ACCESS_KEY=os.environ['PCW_SHARE_S3_ACCESS_KEY']
AWS_ACCESS_KEY_ID=os.environ['PCW_SHARE_S3_ACCESS_KEY_ID2']
AWS_ACCESS_KEY=os.environ['PCW_SHARE_S3_ACCESS_KEY2']
AWS_REGION_NAME="us-east-1"

class WalletBackupManager:
Expand Down
43 changes: 29 additions & 14 deletions maintain.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,27 @@ def personalize():
with open(bashrc, 'rt') as f:
script = f.read()
s = script
is_guest = guest_mode_is_active()

def get_var(name, prompt, sh, no_prompt=None):
def get_var(name, prompt, sh, default_value=None, force_for_guest=True):
val, exported, start, end = get_shell_variable(name, sh)
if (not val) or (not exported):
if not val:
val = ask(prompt).strip() if prompt else no_prompt
# If there's any reason that the shell script isn't currently what it should be...
if (not val) or (not exported) or (force_for_guest and val != default_value):
if is_guest and force_for_guest and default_value:
val = default_value
elif not val:
val = ask(prompt).strip() if prompt else default_value
else: # variable exists but was not exported; rewrite
sh = sh[:start] + sh[end:]
sh = "export " + name + '="' + val + '"\n\n' + sh
return val, sh

# Set this variable once and don't let user have input on it.
_, s = get_var("WALLET_DB_NAME", None, s, "XAR")
owner, s = get_var("OWNER", "What is your first name?", s)
org, s = get_var("ORG", "What org do you represent (one word)?", s)
ctx, s = get_var("CTX", "Is this wallet for use in dev, stage, or production contexts?", s)
# Find out who is using this wallet -- but use default values and skip the questions for guests.
owner, s = get_var("OWNER", "What is your first name?", s, "guest")
org, s = get_var("ORG", "What org do you represent (one word)?", s, "provenant")
ctx, s, = get_var("CTX", "Is this wallet for use in dev, stage, or production contexts?", s, "stage")
ctx = ctx.lower()[0]
ctx = 'dev' if ctx == 'd' else 'stage' if ctx == 's' else 'prod'
if s != script:
Expand All @@ -105,11 +111,12 @@ def get_var(name, prompt, sh, no_prompt=None):
f.write(s)
run(f"touch {semaphore}")
if not is_protected_by_passcode():
protect_by_passcode(ctx != "prod")
protect_by_passcode(ctx != "prod", is_guest)

return owner, org, ctx


def patch_os(cache_secs=86400):
def patch_os(cache_secs=86400*3):
if time_since(PATCH_CHECK_FILE) > cache_secs:
cout("Making sure your wallet OS is fully patched.\n")
if os.path.isfile(PATCH_CHECK_FILE):
Expand Down Expand Up @@ -137,15 +144,16 @@ def reset_wallet():
run("mv ~/.bashrc.bak ~/.bashrc")


def reset_after_confirm():
def reset_after_confirm(preconfirm = False):
cout("Wallet reset requested.\n" + term.normal)
prompt = RESET_PROMPT
confirm = "yes"
if os.getenv("CTX") not in ["dev", "stage"]:
preconfirm = False
cout(term.red("\n\nTHIS IS A PRODUCTION WALLET. BE VERY, VERY CAREFUL!\n\n"))
confirm = str(time.time())[0:6]
prompt = prompt.replace('"yes"', f'"{confirm}"')
should_proceed = ask(prompt).lower() == confirm
should_proceed = True if preconfirm else (ask(prompt).lower() == confirm)
if should_proceed:
cout("\nResetting wallet. This will destroy all saved state.\n")
reset_wallet()
Expand Down Expand Up @@ -213,7 +221,7 @@ def patch_config(ctx):
def update_pcw_code():
updated = refresh_repo("https://github.com/provenant-dev/pcw.git")
if updated:
cout("Wallet software updated. Requesting re-launch of maintenance script with latest code.\n")
cout("Wallet software updated. Restarting maintenance script with latest code.\n")
run(f"touch {RERUNNER}")
# Give file buffers time to flush.
time.sleep(1)
Expand Down Expand Up @@ -245,8 +253,8 @@ def do_maintenance():
with TempColor(MAINTENANCE_COLOR):
if os.path.exists(RERUNNER):
break_rerun_cycle()
if len(sys.argv) == 2 and sys.argv[1] == '--reset':
reset_after_confirm()
if len(sys.argv) >= 2 and '--reset' in sys.argv:
reset_after_confirm('--noprompt' in sys.argv)
else:
if update_pcw_code():
# Script will be re-launched, doing remaining maintenance with new code.
Expand All @@ -257,6 +265,10 @@ def do_maintenance():
# wallet is built.
run_upgrade_scripts()

if guest_mode_is_active():
if not enforce_guest_checkout():
sys.exit(111)

owner, org, ctx = personalize()
patch_os()
refresh_repo("https://github.com/provenant-dev/keripy.git")
Expand All @@ -273,6 +285,9 @@ def do_maintenance():
except KeyboardInterrupt:
cout(term.red("--- Exited script early. Wallet may not be fully functional.\n"))
sys.exit(1)
except SystemExit:
cout(term.red("--- Maintenance stopped.\n"))
raise
except:
cout(term.red("--- Error.\n" + traceback.format_exc() + "---\n"))

Expand Down
10 changes: 9 additions & 1 deletion maintain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ alias lock='unset TYPED_PASSCODE && printf "Wallet locked. Run \"unlock\" to mak
alias set-person='source ~/pcw/set-person.sh'
alias set-org='source ~/pcw/set-org.sh'
alias whatsnew='head -n 10 ~/pcw/whatsnew.md && printf "\n\n(Use less ~/pcw/whatsnew.md for full info.)\n"'
alias guest-checkin='rm /tmp/guest.txt && maintain --reset --noprompt && exit'

# Run the wallet maintenance script at least once, and
# up to 3 times to account for relaunches for self patching.
for i in 1 2 3; do
python3 ~/pcw/maintain.py
maintain_err=$?
if test $maintain_err -ne 0; then break; fi
if test "$maintain_err" -eq "111"; then
printf "Logging off.\n"
sleep 5
exit
fi
if test $maintain_err -ne 0; then
break
fi
if ! test -f ".rerun"; then break; fi
done

Expand Down
7 changes: 6 additions & 1 deletion shutdown-if-inactive.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ fi
if [ "$STATUS" == "inactive" ]; then
if [ -f "$MARKER_FILE" ]; then

# If someone logged in within the last 30 minutes, don't shut down.
# If someone logged in within the last 30 minutes, don't shut down. Otherwise...
if ! last --since "30 minutes ago" | grep -q "pts/"; then
echo $(date) ": Shutting down due to no ssh activity for 30 minutes."
# If this is a guest wallet, reset who has it checked out, plus all other wallet state.
if test -f "/tmp/guest.txt"; then
rm "/tmp/guest.txt"
sudo runuser ubuntu -c '/usr/bin/env python3 /home/ubuntu/pcw/maintain.py --reset --noprompt'
fi
poweroff
fi

Expand Down
2 changes: 1 addition & 1 deletion upgraders/6.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os

# Change URL for PCW help.
os.system('sudo cp /home/ubuntu/pcw/10-help-text /etc/update-motd.d/ && chmod +x /etc/update-motd.d/10-help-text')
os.system('sudo cp /home/ubuntu/pcw/10-help-text /etc/update-motd.d/ && sudo chmod +x /etc/update-motd.d/10-help-text')
5 changes: 3 additions & 2 deletions upgraders/7.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os

# Make sure wallet is using production branch of keripy.
os.system('cd ~/keripy && git checkout production')
if os.path.isdir(os.path.join(os.path.expanduser('~/'), 'keripy')):
# Make sure wallet is using production branch of keripy.
os.system('cd ~/keripy && git checkout production')
102 changes: 96 additions & 6 deletions util.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ def sys_call_with_output_or_die(cmd):
return output


_guest_mode_active = None
def guest_mode_is_active():
global _guest_mode_active
if _guest_mode_active is None:
exit_code, hostname = sys_call_with_output('hostname')
_guest_mode_active = bool('guest' in hostname.lower() if hostname else False)
return _guest_mode_active


def cout(txt):
sys.stdout.write(txt)
sys.stdout.flush()
Expand Down Expand Up @@ -210,7 +219,7 @@ def get_passcode():
return "".join(code)


def protect_by_passcode(hardcode=False):
def protect_by_passcode(hardcode=False, quiet=False):
# In this function, we switch between sys.stdout and cout very deliberately.
# cout() writes to the log, whereas sys.stdout only writes to the screen.
# We want the log to contain almost, but not quite, what we write to the
Expand All @@ -220,8 +229,9 @@ def protect_by_passcode(hardcode=False):
with TempColor(term.normal, MAINTENANCE_COLOR):
if hardcode:
passcode = HARDCODED_PASSCODE
cout(term.yellow(HARDCODED_PROTECT_PROMPT))
sys.stdout.write(term.red(passcode) + "\n")
if not quiet:
cout(term.yellow(HARDCODED_PROTECT_PROMPT))
sys.stdout.write(term.red(passcode) + "\n")
else:
cout(term.yellow(PROTECT_PROMPT))
passcode = get_passcode()
Expand Down Expand Up @@ -314,9 +324,10 @@ def configure_auto_shutdown():
else:
with open(restart_url, "rt") as f:
restart_url = f.read().strip()
with TempColor(term.dim_white, MAINTENANCE_COLOR):
advice = AUTO_SHUTDOWN_EXPLANATION % restart_url if restart_url else NO_AUTO_SHUTDOWN_EXPLANATION
print(advice)
if not guest_mode_is_active():
with TempColor(term.dim_white, MAINTENANCE_COLOR):
advice = AUTO_SHUTDOWN_EXPLANATION % restart_url if restart_url else NO_AUTO_SHUTDOWN_EXPLANATION
print(advice)


UPGRADER_PAT = re.compile(r'^\d+[.]py$')
Expand Down Expand Up @@ -405,3 +416,82 @@ def mention_whats_new():
f.write(new_hash)
with TempColor(term.white, MAINTENANCE_COLOR):
cout("\033[00mThe wallet has new features. Run the \033[0;34mwhatsnew\033[0;37m command to learn more.\n\n")



GUESTFILE="/tmp/guest.txt"
EMAIL_REGEX = re.compile("^[a-z0-9.-]+@[a-z0-9-]+[.][a-z0-9-.]+$")


def enforce_guest_checkout():
# Undo dimness of maintenance text.
sys.stdout.write(term.normal)

with TempColor(term.white, MAINTENANCE_COLOR):
try:
if os.path.isfile(GUESTFILE):
with open(GUESTFILE, 'rt') as f:
email = f.read().strip().lower()
print("This wallet is currently in use by a guest.")
answer = get_email()
if not answer:
return False
# Undo dimness of maintenance text again.
sys.stdout.write(term.normal)
if email != answer:
print(term.red("""\
Someone else has this wallet checked out. Please try a different guest wallet,
or check back in an hour to see if this one frees up."""))
return False
else:
print("Welcome back to your checked out guest wallet.")
else:
print("""
Welcome. You can use this guest wallet to do KERI experiments with low risk.
Feel free to create and connect AIDs, issue credentials, try various commands
with the KERI kli tool, and so forth. All operations use stage witnesses
rather than production ones. Any data you create is temporary.

Guest wallets are checked out for the duration of a single SSH session plus a
few minutes (so you can log back in quickly if you get disconnected).
""")
print(term.red("TERMS OF USE") + """ -- If you continue to use this wallet, you agree that:

1. You'll only use the wallet for KERI experiments, not for hacking, random
downloads, DOS attacks, SSH tunnels, etc. You won't install new stuff or
break stuff. Provenant may monitor your behavior to hold you accountable.

2. We offer no warranties or guarantees, and make no commitment to provide
support. Use at your own risk. However, if something breaks or you have a
burning question, please email pcw-guest@provenant.net.

3. You may use the code on this machine ONLY on this machine, and only while
you are in the current SSH session. You may not copy it elsewhere.
""")
print(term.red("""IF YOU DON'T AGREE, LOG OFF NOW. OTHERWISE, CHECK OUT THE WALLET BY
PROVIDING YOUR EMAIL ADDRESS AS THE RESPONSIBLE PARTY.
"""))
email = get_email()
if not email:
return False
else:
# Undo dimness of maintenance text again.
sys.stdout.write(term.normal)
print(term.normal + term.white(f"\nThis guest wallet now checked out to {email}."))
print(term.white("To relinquish, run:\n ") + term.blue("guest-checkin") + "\n")
with open(GUESTFILE, "wt") as f:
f.write(email)
return True
except KeyboardInterrupt:
return False


def get_email():
i = 5
while i > 0:
email = ask("Your email?").strip().lower()
if EMAIL_REGEX.match(email):
return email
else:
print("Bad email address.")
i -= 1