diff --git a/WalletRestoreManager.py b/WalletRestoreManager.py index 2c7d2e6..21eb0e3 100755 --- a/WalletRestoreManager.py +++ b/WalletRestoreManager.py @@ -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" diff --git a/bin/backup.sh b/bin/backup.sh index d720bd8..f5755db 100755 --- a/bin/backup.sh +++ b/bin/backup.sh @@ -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: diff --git a/maintain.py b/maintain.py index f38e176..056c277 100755 --- a/maintain.py +++ b/maintain.py @@ -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: @@ -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): @@ -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() @@ -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) @@ -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. @@ -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") @@ -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")) diff --git a/maintain.sh b/maintain.sh index afe1e5d..1a11953 100644 --- a/maintain.sh +++ b/maintain.sh @@ -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 diff --git a/shutdown-if-inactive.sh b/shutdown-if-inactive.sh index 4587072..2cea888 100755 --- a/shutdown-if-inactive.sh +++ b/shutdown-if-inactive.sh @@ -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 diff --git a/upgraders/6.py b/upgraders/6.py index 671dbdb..56cda3f 100644 --- a/upgraders/6.py +++ b/upgraders/6.py @@ -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') \ No newline at end of file +os.system('sudo cp /home/ubuntu/pcw/10-help-text /etc/update-motd.d/ && sudo chmod +x /etc/update-motd.d/10-help-text') \ No newline at end of file diff --git a/upgraders/7.py b/upgraders/7.py index 3e9f08b..3046567 100644 --- a/upgraders/7.py +++ b/upgraders/7.py @@ -1,4 +1,5 @@ import os -# Make sure wallet is using production branch of keripy. -os.system('cd ~/keripy && git checkout production') \ No newline at end of file +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') \ No newline at end of file diff --git a/util.py b/util.py index abd6ae9..9349503 100644 --- a/util.py +++ b/util.py @@ -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() @@ -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 @@ -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() @@ -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$') @@ -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