summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-02-22 17:09:34 +0100
committerAntoine A <>2024-02-24 18:28:36 +0100
commit31a7de33b12e5e941a9025de86a66d67a7e27f61 (patch)
tree2e89d14755b8d61179917854c649bb8ed702d1d9
parentc9ccf8d118c45d5c38cb1a7301f3a5aec12ec807 (diff)
downloaddeployment-31a7de33b12e5e941a9025de86a66d67a7e27f61.tar.gz
deployment-31a7de33b12e5e941a9025de86a66d67a7e27f61.tar.bz2
deployment-31a7de33b12e5e941a9025de86a66d67a7e27f61.zip
Better questions using a python script
-rwxr-xr-xregional-currency/ask_questions.py238
-rwxr-xr-xregional-currency/functions.sh100
-rwxr-xr-xregional-currency/main.sh17
3 files changed, 241 insertions, 114 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()),
+)
diff --git a/regional-currency/functions.sh b/regional-currency/functions.sh
index 77616ba..749d1bd 100755
--- a/regional-currency/functions.sh
+++ b/regional-currency/functions.sh
@@ -22,106 +22,6 @@ function check_user() {
fi
}
-function ask_questions() {
- if test -z "${CURRENCY:-}"; then
- read -r -p "1. Enter the name of the regional currency (e.g. 'NETZBON'): " CURRENCY
- CURRENCY=$(normalize_currency "${CURRENCY}")
- echo "CURRENCY=${CURRENCY}" >>config/user.conf
- fi
- if test -z "${FIAT_CURRENCY:-}"; then
- read -r -p "2. Enter the name of the fiat currency (e.g. 'CHF'): " FIAT_CURRENCY
- FIAT_CURRENCY=$(normalize_currency "${FIAT_CURRENCY}")
- echo "FIAT_CURRENCY=${FIAT_CURRENCY}" >>config/user.conf
- fi
- if test -z "${BANK_NAME:-}"; then
- read -r -p "3. Enter the human-readable name of the bank (e.g. 'Taler Bank'): " BANK_NAME
- echo "BANK_NAME=\"${BANK_NAME}\"" >>config/user.conf
- fi
- if test -z "${DOMAIN_NAME:-}"; then
- read -r -p "4. Enter the domain name: " DOMAIN_NAME
- # convert to lower-case
- DOMAIN_NAME=$(echo "${DOMAIN_NAME}" | tr A-Z a-z)
- check_dns
- echo "DOMAIN_NAME=${DOMAIN_NAME}" >>config/user.conf
- fi
- if test -z "${ENABLE_TLS:-}"; then
- read -r -p "5. Setup TLS using Let's Encrypt? (y/n): " ENABLE_TLS
- echo "ENABLE_TLS=${ENABLE_TLS}" >>config/user.conf
- fi
- if test -z "${TLS_EMAIL:-}"; then
- if test "${ENABLE_TLS:-}" == y; then
- read -r -p "5.1. Enter an email address for Let's Encrypt: " TLS_EMAIL
- echo "TLS_EMAIL=${TLS_EMAIL}" >>config/user.conf
- fi
- fi
- if test -z "${TLS_TOS:-}"; then
- if test "${ENABLE_TLS:-}" == y; then
- echo "5.2. Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf."
- read -r -p "5.2. You must agree in order to register with the ACME server. Do you agree? (y/n): " TLS_TOS
- if test "${TLS_TOS:-y}" != y; then
- say "You must agree in order to register with the ACME server"
- exit 1
- fi
- echo "TLS_TOS=${TLS_TOS}" >>config/user.conf
- fi
- fi
- if test -z "${DO_OFFLINE:-}"; then
- read -r -p "6. Do you want Taler Exchange keys on this server (y) or externally on another server (n): " DO_OFFLINE
- echo "DO_OFFLINE=${DO_OFFLINE}" >>config/user.conf
- fi
- if test -z "${MASTER_PUBLIC_KEY:-}"; then
- if test "${DO_OFFLINE:-y}" == n; then
- read -r -p "6.1. Enter the exchange-offline master public key: " MASTER_PUBLIC_KEY
- echo "MASTER_PUBLIC_KEY=${MASTER_PUBLIC_KEY}" >>config/user.conf
- fi
- fi
- if test -z "${DO_TELESIGN:-}"; then
- read -r -p "7. Setup sms two-factor authentication using Telesign https://www.telesign.com? (y/n): " DO_TELESIGN
- if test "${DO_TELESIGN:-y}" != n; then
- read -r -p "7.1. Enter your Telesign Customer ID: " TELESIGN_CUSTOMER_ID
- read -r -s -p "7.2. Enter your Telesign API Key: " TELESIGN_API_KEY
- echo "" # force new line
- read -r -p "7.3. Enter a phone number to test your API key (e.g. '+447911123456'): " TELESIGN_PHONE
- TELESIGN_AUTH_TOKEN=$(echo "$TELESIGN_CUSTOMER_ID:$TELESIGN_API_KEY" | base64 -w 0)
- export AUTH_TOKEN=$TELESIGN_AUTH_TOKEN
- echo "12345" | libeufin-tan-sms.sh $TELESIGN_PHONE
- read -r -p "7.4. Enter the code received by $TELESIGN_PHONE : " TELESIGN_TEST_CODE
- if test "${TELESIGN_TEST_CODE:-y}" != "12345"; then
- say "Wrong code got '$TELESIGN_TEST_CODE' expected '12345', rerun this script to enter the right Telesign auth info"
- exit 1
- fi
- echo "TELESIGN_AUTH_TOKEN=\"${TELESIGN_AUTH_TOKEN}\"" >>config/user.conf
- fi
- echo "DO_TELESIGN=${DO_TELESIGN}" >>config/user.conf
- fi
- if test -z "${BANK_ADMIN_PASSWORD:-}"; then
- read -r -s -p "8. Enter the admin password for the bank (or press enter to autogenerate password): " BANK_ADMIN_PASSWORD
- echo "BANK_ADMIN_PASSWORD=$(printf '%q' "${BANK_ADMIN_PASSWORD}")" >>config/user.conf
- echo "" # force new line
- fi
-}
-
-function normalize_currency() {
- # convert to all-caps
- local CURRENCY=$(echo "$1" | tr a-z A-Z)
- # libeufin currenly doesn't like currency names less than 3 letters.
- if [[ ${#CURRENCY} -lt 3 || ${#CURRENCY} -gt 11 ]]; then
- say "Currency name must be between 3 and 10 letters"
- exit 1
- fi
- echo "${CURRENCY}"
-}
-
-function check_dns() {
- for prefix in "exchange" "bank" "backend"; do
- if ! ping -c1 "${prefix}.${DOMAIN_NAME}" &>>setup.log; then
- say "Could not ping ${prefix}.${DOMAIN_NAME}."
- say "Please make sure your DNS/network are working."
- exit 1
- fi
- done
-}
-
# Set DISTRO to the detected distro or return non-zero
# status if distro not supported.
function detect_distro() {
diff --git a/regional-currency/main.sh b/regional-currency/main.sh
index 144fe5a..f7bc9e3 100755
--- a/regional-currency/main.sh
+++ b/regional-currency/main.sh
@@ -44,9 +44,9 @@ say "Installing packages (step 1 of 6)"
say ""
say "Interactive configuration (step 2 of 6)"
-ask_questions
+./ask_questions.py
+source config/user.conf
-check_dns
# Remove when libeufin currencies.conf is in sync with exchange
cat >>/usr/share/libeufin/config.d/netzbon.conf <<EOF
[CURRENCY-NETZBON]
@@ -64,22 +64,10 @@ if test -z "${BANK_EXCHANGE_PASSWORD:-}"; then
echo "BANK_EXCHANGE_PASSWORD=\"${BANK_EXCHANGE_PASSWORD}\"" >>config/internal.conf
fi
-if test -z "${BANK_ADMIN_PASSWORD:-}"; then
- BANK_ADMIN_PASSWORD=$(uuidgen)
- echo "BANK_ADMIN_PASSWORD=\"${BANK_ADMIN_PASSWORD}\"" >>config/user.conf
-fi
-
if test -z "${BANK_PORT:-}"; then
echo "BANK_PORT=8080" >>config/user.conf
fi
-if test "${ENABLE_TLS:-}" == "y"; then
- PROTO="https"
-else
- PROTO="http"
-fi
-echo "PROTO=${PROTO}" >>config/internal.conf
-
say ""
say "Configuring nginx (step 3 of 6)"
./config_nginx.sh
@@ -100,6 +88,7 @@ say "Setting up merchant (step 6 of 6)"
say ""
say "Congratulations, you have successfully installed GNU Taler"
say "Your bank is at ${PROTO}://bank.${DOMAIN_NAME}/"
+say "You can connect to the bank web UI as 'admin' using '${BANK_ADMIN_PASSWORD}'"
say "A merchant is at ${PROTO}://backend.${DOMAIN_NAME}/"
say "You should set credentials for the merchant soon."