summaryrefslogtreecommitdiff
path: root/regional-currency/ask_questions.py
diff options
context:
space:
mode:
Diffstat (limited to 'regional-currency/ask_questions.py')
-rwxr-xr-xregional-currency/ask_questions.py238
1 files changed, 238 insertions, 0 deletions
diff --git a/regional-currency/ask_questions.py b/regional-currency/ask_questions.py
new file mode 100755
index 0000000..8c36311
--- /dev/null
+++ b/regional-currency/ask_questions.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python3
+
+import base64
+import os
+import subprocess
+import uuid
+from typing import Callable, Dict, TypeVar
+
+log = open("setup.log", "ab", buffering=0)
+CONFIG_FILE = "config/user.conf"
+
+
+def load_conf() -> Dict[str, str]:
+ conf = {}
+ with open(CONFIG_FILE, "r") as f:
+ for kv in f.read().splitlines():
+ if len(kv) != 0:
+ [k, v] = [part.strip() for part in kv.split("=", 1)]
+ conf[k] = v.strip('"').replace('\\"', '"')
+ return conf
+
+
+conf = load_conf()
+
+
+def add_conf(name: str, value: str):
+ conf[name] = value
+ content = ""
+ for key, value in conf.items():
+ escaped = value.replace('"', '\\"')
+ content += f'{key}="{escaped}"\n'
+ with open(CONFIG_FILE, "w") as f:
+ f.write(content)
+
+
+def run_cmd(
+ cmd: list[str], input: str | None = None, env: Dict[str, str] | None = None
+) -> int:
+ result = subprocess.run(
+ cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ input=input.encode() if input is not None else None,
+ stdin=subprocess.DEVNULL if input is None else None,
+ env=env,
+ )
+ log.write(result.stdout)
+ if result.returncode != 0:
+ print(result.stdout.decode("utf-8"), end="")
+ return result.returncode
+
+
+def try_cmd(
+ cmd: list[str], input: str | None = None, env: Dict[str, str] | None = None
+) -> bool:
+ return run_cmd(cmd, input, env) == 0
+
+
+A = TypeVar("A")
+T = TypeVar("T")
+
+
+def custom(
+ name: str | None,
+ action: Callable[[], str | None],
+ default: T | None = None,
+ check: Callable[[str], T | None] = lambda it: it,
+ fmt: Callable[[T], str] = lambda it: str(it),
+):
+ if name is not None:
+ prev = conf.get(name, None)
+ if prev is not None: # If got previous value
+ checked = check(prev)
+ if checked is not None: # And previous value is valid
+ add_conf(name, fmt(checked)) # Add again in case normalization changed
+ return checked
+
+ while True:
+ raw = action()
+ if raw is None:
+ if default is not None:
+ if name is not None:
+ add_conf(name, fmt(default))
+ return default
+ continue
+ else:
+ checked = check(raw)
+ if checked is not None:
+ if name is not None:
+ add_conf(name, fmt(checked))
+ return checked
+
+
+def ask(
+ name: str | None,
+ msg: str,
+ default: T | None = None,
+ check: Callable[[str], T | None] = lambda it: it,
+ fmt: Callable[[T], str] = lambda it: str(it),
+) -> T:
+ def do_ask():
+ log.write(msg.encode())
+ log.write("\n".encode())
+ raw = input(msg).strip()
+ if raw == "":
+ if default is None:
+ print("You must enter a value")
+ return None
+ return raw
+
+ return custom(name, do_ask, default, check, fmt)
+
+
+def ask_str(name: str | None, msg: str, default: str | None = None) -> str:
+ return ask(name, msg, default)
+
+
+def ask_currency(name: str, msg: str, default: str | None = None) -> str:
+ def check_currency(currency: str) -> str | None:
+ currency = currency.upper()
+ if not currency.isascii() or not currency.isalpha():
+ print("The currency name must be an ASCII alphabetic string")
+ elif len(currency) < 3 or 11 < len(currency):
+ print("The currency name had to be between 3 and 11 characters long")
+ else:
+ return currency
+ return None
+
+ return ask(name, msg, default, check_currency)
+
+
+def ask_host(name: str, msg: str, default: str | None = None) -> str:
+ def check_host(host: str) -> str | None:
+ success = True
+ for subdomain in ["backend", "bank", "exchange"]:
+ success = try_cmd(["ping", "-c", "1", f"{subdomain}.{host}"]) and success
+ if success:
+ return host
+ else:
+ return None
+
+ return ask(name, msg, default, check_host)
+
+
+def ask_yes_no(name: str | None, msg: str, default: bool | None = None) -> bool:
+ def check_yes_no(raw: str) -> bool | None:
+ raw = raw.lower()
+ if raw == "y" or raw == "yes":
+ return True
+ elif raw == "n" or raw == "no":
+ return False
+ else:
+ print("Expected 'y' or 'n'")
+ return None
+
+ return ask(name, msg, default, check_yes_no, lambda it: "y" if it else "n")
+
+
+ask_currency(
+ "CURRENCY",
+ "1. Enter the name of the regional currency (e.g. 'NETZBON'): ",
+ "NETZBON",
+)
+ask_currency(
+ "FIAT_CURRENCY", "2. Enter the name of the fiat currency (e.g. 'CHF'): ", "CHF"
+)
+ask_str(
+ "BANK_NAME",
+ "3. Enter the human-readable name of the bank (e.g. 'Taler Bank'): ",
+ "Taler Bank",
+)
+ask_host("DOMAIN_NAME", "4. Enter the domain name (e.g. 'example.com'): ")
+if ask_yes_no("ENABLE_TLS", "5. Setup TLS using Let's Encrypt? (Y/n): ", True):
+ ask_str("TLS_EMAIL", "5.1. Enter an email address for Let's Encrypt: ")
+
+ def ask_tos():
+ print(
+ "5.2. Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf."
+ )
+ if not ask_yes_no(
+ None,
+ "5.2. You must agree in order to register with the ACME server. Do you agree? (y/n): ",
+ False,
+ ):
+ print("You must agree in order to register with the ACME server")
+ return None
+ else:
+ return "y"
+
+ custom("TLS_TOS", ask_tos)
+ add_conf("PROTO", "https")
+else:
+ add_conf("PROTO", "http")
+
+if not ask_yes_no(
+ "DO_OFFLINE",
+ "6. Do you want Taler Exchange keys on this server (Y) or externally on another server (n): ",
+ True,
+):
+ ask_str("MASTER_PUBLIC_KEY", "6.1. Enter the exchange-offline master public key: ")
+
+if ask_yes_no(
+ "DO_TELESIGN",
+ "7. Setup sms two-factor authentication using Telesign https://www.telesign.com? (Y/n): ",
+ True,
+):
+
+ def ask_telesign():
+ customer_id = ask_str(None, "7.1. Enter your Telesign Customer ID: ")
+ api_key = ask_str(None, "7.2. Enter your Telesign API Key: ")
+ phone_number = ask_str(
+ None,
+ "7.3. Enter a phone number to test your API key (e.g. '+447911123456'): ",
+ )
+ auth_token = base64.b64encode(f"{customer_id}:{api_key}".encode()).decode()
+ if not try_cmd(
+ ["libeufin-tan-sms.sh", phone_number],
+ "12345",
+ {**os.environ, "AUTH_TOKEN": auth_token},
+ ):
+ print(
+ "Failed to send an sms using Telesign API, check your credentials and phone number"
+ )
+ return None
+ code = ask_str(None, f"7.4. Enter the code received by {phone_number} : ")
+ if code != "12345":
+ print(
+ f"Wrong code got '{code}' expected '12345', check your credentials and phone number"
+ )
+ return None
+ return auth_token
+
+ custom("TELESIGN_AUTH_TOKEN", ask_telesign)
+ask_str(
+ "BANK_ADMIN_PASSWORD",
+ "8. Enter the admin password for the bank (or press enter to autogenerate password): ",
+ str(uuid.uuid4()),
+)