summaryrefslogtreecommitdiff
path: root/regional-currency/ask_questions.py
blob: 8c3631181705a1d0139619dd599bc37fb4d43c5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
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()),
)