sandcastle-ng

Scripts for the deployment of Sandcastle (GNU Taler)
Log | Files | Refs | README

setup-sandcastle.sh (47723B)


      1 #!/usr/bin/env bash
      2 
      3 # This scripts provisions all configuration and
      4 # services for the Taler sandcastle container.
      5 #
      6 # Important: This script needs to be completely
      7 # idempotent, nothing must break if it is executed
      8 # multiple times.
      9 
     10 set -eu
     11 set -x
     12 export LC_ALL="C.UTF-8"
     13 
     14 if [[ -n ${SANDCASTLE_SKIP_SETUP:-} ]]; then
     15   echo "skipping sandcastle setup, requested by environment var SANDCASTLE_SKIP_SETUP"
     16   exit 1
     17 fi
     18 
     19 
     20 # Helper to replace a comment-delimited block of lines in a config file with
     21 # the desired content. If the block doesn't exist yet, append it.
     22 update_config_block() {
     23   local config_file="$1"
     24   local marker_tag="$2"
     25   local new_content="$3"
     26   local begin_marker="# begin ${marker_tag}"
     27   local end_marker="# end ${marker_tag}"
     28   if [[ ! -f "$config_file" ]]; then
     29     echo "Error: Config file '$config_file' not found." >&2
     30     return 1
     31   fi
     32   if grep -qF "$begin_marker" "$config_file"; then
     33     # Markers exist. Replace the block.
     34     # Escape newlines in the content so sed processes it as a single block
     35     local escaped_content="${new_content//$'\n'/\\n}"
     36     sed -i "/$begin_marker/,/$end_marker/c\\$begin_marker\n$escaped_content\n$end_marker" "$config_file"
     37   else
     38     # Markers do not exist. Append to the end.
     39     printf "\n%s\n%s\n%s\n" "$begin_marker" "$new_content" "$end_marker" >> "$config_file"
     40   fi
     41 }
     42 
     43 echo "Provisioning sandcastle"
     44 
     45 # General configuration.
     46 # Might eventually be moved to an external file.
     47 
     48 # Source any overrides from external file
     49 if [[ -e /overrides ]]; then
     50   source /overrides
     51 fi
     52 
     53 # When serving on an external port (for localhost deployments),
     54 # we use http.
     55 if [[ ${EXTERNAL_PORT:-} =~ ^[0-9]+$ ]]; then
     56   PROTO=http
     57   PORT_SUFFIX=:$EXTERNAL_PORT
     58 else
     59   PROTO=https
     60   PORT_SUFFIX=
     61 fi
     62 
     63 : ${CURRENCY:="KUDOS"}
     64 : ${WIRE_METHOD:=x-taler-bank}
     65 
     66 
     67 if [[ $WIRE_METHOD = iban ]]; then
     68   EXCHANGE_IBAN=DE159593
     69   EXCHANGE_PAYTO="payto://iban/$EXCHANGE_IBAN?receiver-name=Sandcastle+Echange+Inc"
     70 
     71   # Randomly generated IBANs for the merchants
     72   MERCHANT_IBAN_ADMIN=DE85500105175178585583
     73   MERCHANT_IBAN_POS=DE4218710
     74   MERCHANT_IBAN_BLOG=DE8292195
     75   MERCHANT_IBAN_GNUNET=DE9709960
     76   MERCHANT_IBAN_TALER=DE1740597
     77   MERCHANT_IBAN_TOR=DE2648777
     78   MERCHANT_IBAN_SANDBOX=DE949115029592
     79   MERCHANT_IBAN_UMAMI=DE358841382499
     80   MERCHANT_IBAN_PAIVANA=DE5183926
     81   MERCHANT_IBAN_TESTING=DE38936850270207298566
     82 
     83   MERCHANT_PAYTO_ADMIN="payto://iban/$MERCHANT_IBAN_ADMIN?receiver-name=Default+Merchant"
     84   MERCHANT_PAYTO_TESTING="payto://iban/$MERCHANT_IBAN_TESTING?receiver-name=Testing+Merchant"
     85   MERCHANT_PAYTO_POS="payto://iban/$MERCHANT_IBAN_POS?receiver-name=PoS+Merchant"
     86   MERCHANT_PAYTO_BLOG="payto://iban/$MERCHANT_IBAN_BLOG?receiver-name=Blog+Merchant"
     87   MERCHANT_PAYTO_GNUNET="payto://iban/$MERCHANT_IBAN_GNUNET?receiver-name=GNUnet+Merchant"
     88   MERCHANT_PAYTO_TALER="payto://iban/$MERCHANT_IBAN_TALER?receiver-name=Taler+Merchant"
     89   MERCHANT_PAYTO_TOR="payto://iban/$MERCHANT_IBAN_TOR?receiver-name=Tor+Merchant"
     90   MERCHANT_PAYTO_UMAMI="payto://iban/$MERCHANT_IBAN_UMAMI?receiver-name=Umami"
     91   MERCHANT_PAYTO_PAIVANA="payto://iban/$MERCHANT_IBAN_PAIVANA?receiver-name=Paivana+Merchant"
     92   MERCHANT_PAYTO_SANDBOX="payto://iban/$MERCHANT_IBAN_SANDBOX?receiver-name=Sandbox+Merchant"
     93 elif [[ $WIRE_METHOD = x-taler-bank ]]; then
     94   XTBHOST=sandcastle
     95   EXCHANGE_PAYTO="payto://x-taler-bank/$XTBHOST/exchange?receiver-name=Sandcastle+Exchange+Inc"
     96   MERCHANT_PAYTO_ADMIN="payto://x-taler-bank/$XTBHOST/merchant-admin?receiver-name=Admin+Merchant"
     97   MERCHANT_PAYTO_TESTING="payto://x-taler-bank/$XTBHOST/merchant-testing?receiver-name=Default+Merchant"
     98   MERCHANT_PAYTO_POS="payto://x-taler-bank/$XTBHOST/merchant-pos?receiver-name=PoS+Merchant"
     99   MERCHANT_PAYTO_BLOG="payto://x-taler-bank/$XTBHOST/merchant-blog?receiver-name=Blog+Merchant"
    100   MERCHANT_PAYTO_GNUNET="payto://x-taler-bank/$XTBHOST/merchant-gnunet?receiver-name=GNUnet+Merchant"
    101   MERCHANT_PAYTO_TALER="payto://x-taler-bank/$XTBHOST/merchant-taler?receiver-name=Taler+Merchant"
    102   MERCHANT_PAYTO_TOR="payto://x-taler-bank/$XTBHOST/merchant-tor?receiver-name=Tor+Merchant"
    103   MERCHANT_PAYTO_UMAMI="payto://x-taler-bank/$XTBHOST/merchant-umami?receiver-name=Umami"
    104   MERCHANT_PAYTO_PAIVANA="payto://x-taler-bank/$XTBHOST/merchant-paivana?receiver-name=Paivana+Merchant"
    105   MERCHANT_PAYTO_SANDBOX="payto://x-taler-bank/$XTBHOST/merchant-sandbox?receiver-name=Sandbox+Merchant"
    106 else
    107   echo "wire method $WIRE_METHOD not supported"
    108   exit 1
    109 fi
    110 
    111 MYDOMAIN=${MYDOMAIN:="demo.taler.net"}
    112 LANDING_DOMAIN=$MYDOMAIN
    113 BANK_DOMAIN=bank.$MYDOMAIN
    114 EXCHANGE_DOMAIN=exchange.$MYDOMAIN
    115 MERCHANT_DOMAIN=backend.$MYDOMAIN
    116 DONAU_DOMAIN=donau.$MYDOMAIN
    117 BLOG_DOMAIN=shop.$MYDOMAIN
    118 DONATIONS_DOMAIN=donations.$MYDOMAIN
    119 DRUPAL_DOMAIN=drupal.$MYDOMAIN
    120 CHALLENGER_DOMAIN=challenger.$MYDOMAIN
    121 AUDITOR_DOMAIN=auditor.$MYDOMAIN
    122 PAIVANA_DOMAIN=paivana.$MYDOMAIN
    123 
    124 # Website that is put behind the Paivana paywall.
    125 # Note that this is an *upstream* server, it is not
    126 # served by the sandcastle itself.
    127 PAIVANA_DESTINATION=${PAIVANA_DESTINATION:-https://docs.taler.net/}
    128 
    129 # Ports of the services running inside the container.
    130 # Should be synchronized with the sandcastle-run script.
    131 PORT_INTERNAL_EXCHANGE=8201
    132 PORT_INTERNAL_MERCHANT=8301
    133 PORT_INTERNAL_LIBEUFIN_BANK=8080
    134 PORT_INTERNAL_LANDING=8501
    135 PORT_INTERNAL_BLOG=8502
    136 PORT_INTERNAL_DONATIONS=8503
    137 PORT_INTERNAL_BANK_SPA=8505
    138 PORT_INTERNAL_CHALLENGER=8506
    139 PORT_INTERNAL_AUDITOR=8507
    140 PORT_INTERNAL_DONAU=8508
    141 PORT_INTERNAL_DRUPAL=8509
    142 PORT_INTERNAL_PAIVANA=8510
    143 
    144 
    145 ENABLE_AUDITOR=0
    146 
    147 # Just make sure the services are stopped
    148 systemctl stop postgresql.service
    149 systemctl stop taler-auditor.target
    150 systemctl stop taler-exchange.target
    151 systemctl stop taler-exchange-offline.timer
    152 systemctl stop taler-merchant-httpd.service
    153 systemctl stop taler-merchant.target
    154 systemctl stop taler-demo-landing.service
    155 systemctl stop taler-demo-blog.service
    156 systemctl stop taler-demo-donations.service
    157 systemctl stop libeufin-bank.service
    158 systemctl stop donau-httpd.service
    159 systemctl stop paivana-httpd.service
    160 systemctl stop paivana-httpd.socket
    161 
    162 # libeufin-nexus is not used
    163 systemctl stop libeufin-nexus-ebics-fetch.service
    164 systemctl disable libeufin-nexus-ebics-fetch.service
    165 systemctl stop libeufin-nexus-ebics-submit.service
    166 systemctl disable libeufin-nexus-ebics-submit.service
    167 
    168 systemctl reset-failed
    169 
    170 # We now make sure that some important locations are symlinked to
    171 # the persistent storage volume.
    172 # Files that already exist in this location are moved to the storage volume
    173 # and then symlinked.
    174 # These locations are:
    175 # /etc/taler
    176 # /etc/libeufin
    177 # /var/lib/taler
    178 # postgres DB directory
    179 
    180 function lift_dir() {
    181   where=$1
    182   src=$2
    183   target=$3
    184   if [[ -L $src ]]; then
    185     # be idempotent
    186     echo "$src is already a symlink"
    187   elif [[ -d /$where/$target ]]; then
    188     echo "symlinking existing /$where/$target"
    189     rm -rf "$src"
    190     ln -s "/$where/$target" "$src"
    191   else
    192     echo "symlinking new /$where/$target"
    193     mv "$src" "/$where/$target"
    194     ln -s "/$where/$target" "$src"
    195   fi
    196 }
    197 
    198 lift_dir talerdata /var/lib/taler-exchange var-lib-taler-exchange
    199 lift_dir talerdata /etc/taler-merchant etc-taler-merchant
    200 lift_dir talerdata /etc/taler-exchange etc-taler-exchange
    201 lift_dir talerdata /etc/taler-exchange etc-taler-auditor
    202 lift_dir talerdata /etc/donau etc-donau
    203 lift_dir talerdata /etc/libeufin etc-libeufin
    204 # lift both config and data
    205 lift_dir talerdata /etc/postgresql etc-postgresql
    206 lift_dir talerdata /var/lib/postgresql var-lib-postgresql
    207 # offline keys are in a separate volume
    208 lift_dir talerdata_persistent /var/lib/taler-exchange/offline exchange-offline
    209 
    210 # Usage: get_credential_pw COMPONENT/ACCOUNT
    211 function get_credential_pw() {
    212   if [[ ${USE_INSECURE_SANDBOX_PASSWORDS:-0} = 1 ]]; then
    213     echo "sandbox"
    214     return
    215   fi
    216   p=/credentials/$1
    217   if [[ ! -f $p ]]; then
    218     mkdir -p $(dirname "$p")
    219     uuidgen -r >$p
    220   fi
    221   cat "$p"
    222 }
    223 
    224 import_instr=none
    225 if [[ -d /exported && -e /exported/import-request ]]; then
    226   import_instr=$(cat /exported/import-request)
    227 fi
    228 
    229 # If necessary, import the offline key.
    230 # Done before everything else, as we need the key
    231 # to generate the config.
    232 
    233 if [[ $import_instr = all ]]; then
    234   echo "Importing exchange offline key"
    235   rm -rf /var/lib/taler-exchange/offline/*
    236   cp -r /exported/taler-exchange/offline/* /var/lib/taler-exchange/offline/
    237 fi
    238 
    239 # Adjust ownership.
    240 # Necessary when the container is rebuilt with different user IDs.
    241 chown --recursive taler-exchange-offline:taler-exchange-offline /var/lib/taler-exchange/offline/. || true
    242 chown root:taler-exchange-db /etc/taler-exchange/secrets/exchange-db.secret.conf
    243 chown taler-exchange-wire:root /etc/taler-exchange/secrets/exchange-accountcredentials-*.conf
    244 chown taler-merchant-httpd:root /etc/taler-merchant/secrets/merchant-db.secret.conf
    245 chown root:donau-db /etc/donau/secrets/donau-db.secret.conf
    246 
    247 
    248 MASTER_PUBLIC_KEY=$(sudo -u taler-exchange-offline taler-exchange-offline -LDEBUG setup)
    249 
    250 
    251 #
    252 # Create the basic configuration files
    253 #
    254 
    255 mkdir -p /etc/challenger/conf.d
    256 cat <<EOF >/etc/challenger/conf.d/setup-sandcastle.conf
    257 [challenger]
    258 BASE_URL = $PROTO://$CHALLENGER_DOMAIN$PORT_SUFFIX/
    259 ADDRESS_TYPE = email
    260 AUTH_COMMAND = /data/sandcastle-challenger-auth
    261 ADDRESS_RESTRICTIONS = {"email":{"hint":"not an e-mail address","regex":"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$"}}
    262 EOF
    263 
    264 mkdir -p /etc/paivana
    265 cat <<EOF >/etc/paivana/paivana.conf
    266 [paivana]
    267 # Serve on the socket provided by paivana-httpd.socket.
    268 SERVE = systemd
    269 
    270 # Public base URL of the paywall.
    271 BASE_URL = $PROTO://$PAIVANA_DOMAIN$PORT_SUFFIX/
    272 
    273 # Upstream site that is protected by the paywall.
    274 DESTINATION_BASE_URL = $PAIVANA_DESTINATION
    275 
    276 # Merchant instance that sells access to the site.
    277 MERCHANT_BACKEND_URL = $PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/instances/paivana/
    278 MERCHANT_ACCESS_TOKEN = secret-token:$(get_credential_pw merchant/paivana)
    279 
    280 # Stable key for the access cookie MAC, so that paid clients
    281 # survive a restart of paivana-httpd.
    282 SECRET = $(get_credential_pw paivana/secret)
    283 
    284 # Static resources are served without payment, as the
    285 # protected site is otherwise unusable after paying.
    286 WHITELIST = ^/(favicon\.ico|robots\.txt|.*\.(css|js|png|jpe?g|gif|svg|ico|webp|woff2?|ttf|eot))\$
    287 EOF
    288 
    289 # The config contains the merchant access token and the cookie secret.
    290 chown paivana-httpd:root /etc/paivana/paivana.conf
    291 chmod 640 /etc/paivana/paivana.conf
    292 
    293 # The paywall runs behind the caddy reverse proxy, so we need
    294 # '-f' to get the client address from X-Forwarded-For.  With '-g'
    295 # a single payment unlocks the entire site instead of just the
    296 # URL that was paid for.
    297 mkdir -p /etc/systemd/system/paivana-httpd.service.d
    298 cat <<EOF >/etc/systemd/system/paivana-httpd.service.d/sandcastle.conf
    299 [Unit]
    300 # Paywall templates are fetched from the merchant backend on
    301 # startup, so keep retrying until the merchant is up.
    302 After=taler-merchant-httpd.service
    303 StartLimitIntervalSec=0
    304 
    305 [Service]
    306 ExecStart=
    307 ExecStart=/usr/bin/paivana-httpd -c /etc/paivana/paivana.conf -f -g -L INFO
    308 EOF
    309 
    310 systemctl daemon-reload
    311 
    312 cat <<EOF >/etc/libeufin/libeufin-bank.conf
    313 [libeufin-bank]
    314 BASE_URL = $PROTO://$BANK_DOMAIN$PORT_SUFFIX/
    315 CURRENCY = $CURRENCY
    316 DEFAULT_DEBT_LIMIT = $CURRENCY:500
    317 REGISTRATION_BONUS = $CURRENCY:100
    318 SPA_CAPTCHA_URL = $PROTO://$BANK_DOMAIN$PORT_SUFFIX/webui/#/operation/{woid}
    319 SUGGESTED_WITHDRAWAL_EXCHANGE = $PROTO://$EXCHANGE_DOMAIN$PORT_SUFFIX/
    320 ALLOW_REGISTRATION = yes
    321 SERVE = tcp
    322 PORT = 8080
    323 # Bind address.
    324 # Option soon to be deprecated!
    325 ADDRESS = 0.0.0.0
    326 WIRE_TYPE = $WIRE_METHOD
    327 
    328 # Compat mode for now
    329 PWD_CHECK = no
    330 PWD_AUTH_COMPAT = yes
    331 
    332 [currency-$CURRENCY]
    333 ENABLED = YES
    334 name = "${NAME:=Kudos}"
    335 code = "$CURRENCY"
    336 decimal_separator = "."
    337 fractional_input_digits = ${FRACTIONALS:=2}
    338 fractional_normal_digits = ${FRACTIONALS:=2}
    339 fractional_trailing_zero_digits = ${FRACTIONALS:=2}
    340 is_currency_name_leading = NO
    341 alt_unit_names = {"0":"${ALT_UNIT_NAME:=ク}"}
    342 EOF
    343 
    344 cat <<EOF >/etc/libeufin/settings.json
    345 {
    346   "topNavSites": {
    347     "Landing": "$PROTO://$LANDING_DOMAIN$PORT_SUFFIX/",
    348     "Bank": "$PROTO://$BANK_DOMAIN$PORT_SUFFIX",
    349     "Essay Shop": "$PROTO://$BLOG_DOMAIN$PORT_SUFFIX",
    350     "Donations": "$PROTO://$DONATIONS_DOMAIN$PORT_SUFFIX",
    351   }
    352 }
    353 EOF
    354 
    355 # Generate /tmp/sandcastle-setup.conf
    356 cat <<EOF >/tmp/sandcastle-setup.conf
    357 [currency-$CURRENCY]
    358 ENABLED = YES
    359 name = "${NAME:=Kudos}"
    360 code = "$CURRENCY"
    361 decimal_separator = "."
    362 fractional_input_digits = ${FRACTIONALS:=2}
    363 fractional_normal_digits = ${FRACTIONALS:=2}
    364 fractional_trailing_zero_digits = ${FRACTIONALS:=2}
    365 is_currency_name_leading = NO
    366 alt_unit_names = {"0":"${ALT_UNIT_NAME:=ク}"}
    367 EOF
    368 
    369 cp /tmp/sandcastle-setup.conf /etc/taler-exchange/conf.d/sandcastle-setup.conf
    370 cp /tmp/sandcastle-setup.conf /etc/taler-merchant/conf.d/sandcastle-setup.conf
    371 
    372 
    373 cat <<EOF >/etc/taler-exchange/conf.d/sandcastle-exchange.conf
    374 [exchange]
    375 CURRENCY = $CURRENCY
    376 CURRENCY_ROUND_UNIT = $CURRENCY:0.01
    377 TINY_AMOUNT = $CURRENCY:0.01
    378 AML_THRESHOLD = $CURRENCY:1000000
    379 MASTER_PUBLIC_KEY = $MASTER_PUBLIC_KEY
    380 BASE_URL = $PROTO://$EXCHANGE_DOMAIN$PORT_SUFFIX/
    381 
    382 [taler-exchange-secmod-rsa]
    383 LOOKAHEAD_SIGN = 4 weeks
    384 
    385 [taler-exchange-secmod-eddsa]
    386 LOOKAHEAD_SIGN = 4 weeks
    387 
    388 [taler-exchange-secmod-cs]
    389 LOOKAHEAD_SIGN = 4 weeks
    390 
    391 [exchange-account-default]
    392 PAYTO_URI = $EXCHANGE_PAYTO
    393 ENABLE_DEBIT = YES
    394 ENABLE_CREDIT = YES
    395 @inline-secret@ exchange-accountcredentials-default ../secrets/exchange-accountcredentials-default.secret.conf
    396 EOF
    397 
    398 
    399 cat <<EOF >/etc/taler-exchange/secrets/exchange-accountcredentials-default.secret.conf
    400 [exchange-accountcredentials-default]
    401 WIRE_GATEWAY_URL = $PROTO://$BANK_DOMAIN$PORT_SUFFIX/accounts/exchange/taler-wire-gateway/
    402 WIRE_GATEWAY_AUTH_METHOD = basic
    403 USERNAME = exchange
    404 PASSWORD = $(get_credential_pw bank/exchange)
    405 EOF
    406 
    407 if [[ $ENABLE_AUDITOR = 1 ]]; then
    408   # Make sandcastle exchange config available to auditor
    409   cp /etc/taler-exchange/conf.d/sandcastle-exchange.conf /etc/taler-auditor/conf.d/sandcastle-exchange.conf
    410 
    411   # We run the offline tooling as root, maybe in the future there should be
    412   # a separate user created by the Debian package for that.
    413   AUDITOR_PUB=$(taler-auditor-offline setup)
    414 
    415   cat <<EOF >/etc/taler-auditor/conf.d/sandcastle-auditor.conf
    416 [auditor]
    417 PUBLIC_KEY = $AUDITOR_PUB
    418 
    419 [exchangedb]
    420 
    421 $(dup_exchange_opt exchangedb IDLE_RESERVE_EXPIRATION_TIME)
    422 $(dup_exchange_opt exchangedb LEGAL_RESERVE_EXPIRATION_TIME)
    423 $(dup_exchange_opt exchangedb AGGREGATOR_SHIFT)
    424 $(dup_exchange_opt exchangedb DEFAULT_PURSE_LIMIT)
    425 
    426 [exchangedb-postgres]
    427 $(dup_exchange_opt exchangedb-postgres CONFIG)
    428 
    429 [exchange]
    430 $(dup_exchange_opt exchange CURRENCY)
    431 $(dup_exchange_opt exchange CURRENCY_ROUND_UNIT)
    432 $(dup_exchange_opt exchange DB)
    433 
    434 
    435 EOF
    436 fi
    437 
    438 # The config shipped with the package can conflict with the
    439 # trusted sandcastle exchange if the currency is KUDOS.
    440 rm -f /usr/share/taler-exchange/config.d/kudos.conf
    441 rm -f /usr/share/taler-merchant/config.d/kudos.conf
    442 
    443 MY_HELPER_EMAIL=${OVERRIDE_MERCHANT_HELPER_EMAIL:-/data/sandcastle-merchant-email-helper}
    444 
    445 # We need to define the default currency for the UI.
    446 cat <<EOF >/etc/taler-merchant/conf.d/sandcastle-merchant.conf
    447 [merchant]
    448 # Default currency
    449 CURRENCY = $CURRENCY
    450 ENABLE_SELF_PROVISIONING = YES
    451 MANDATORY_TAN_CHANNELS = email
    452 HELPER_EMAIL = $MY_HELPER_EMAIL
    453 
    454 BASE_URL = $PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/
    455 
    456 # Will be the default in future merchant versions
    457 # => Can be removed after 1.6 release
    458 BACKOFFICE_SPA_DIR = /usr/share/taler-merchant-webui/
    459 
    460 EOF
    461 
    462 cat <<EOF >/etc/taler-merchant/conf.d/sandcastle-merchant-exchanges.conf
    463 [merchant-exchange-sandcastle]
    464 EXCHANGE_BASE_URL = $PROTO://$EXCHANGE_DOMAIN$PORT_SUFFIX/
    465 MASTER_KEY = $MASTER_PUBLIC_KEY
    466 CURRENCY = $CURRENCY
    467 EOF
    468 
    469 # Allow overrides to modify merchant config
    470 [[ $(type -t hook_merchant_config) == function ]] && hook_merchant_config
    471 
    472 # FIXME: This is a workaround, fix the packaging of taler-merchant-frontends here!
    473 mkdir -p /etc/taler
    474 
    475 cat <<EOF >/etc/taler/taler-merchant-frontends.conf
    476 # Different entry point, we need to repeat some settings.
    477 # In the future, taler-merchant-demos should become
    478 # robust enough to read from the main config.
    479 [taler]
    480 CURRENCY = $CURRENCY
    481 
    482 [frontend-demo-landing]
    483 SERVE = http
    484 HTTP_PORT = $PORT_INTERNAL_LANDING
    485 
    486 [frontend-demo-blog]
    487 SERVE = http
    488 HTTP_PORT = $PORT_INTERNAL_BLOG
    489 BACKEND_URL = $PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/instances/blog/
    490 BACKEND_APIKEY = secret-token:$(get_credential_pw merchant/blog)
    491 ENABLE_TOKENS = ${ENABLE_SUBSCRIPTIONS:-no}
    492 
    493 [frontend-demo-donations]
    494 DONAU_URL = $PROTO://$DONAU_DOMAIN$PORT_SUFFIX/
    495 SERVE = http
    496 HTTP_PORT = $PORT_INTERNAL_DONATIONS
    497 BACKEND_URL_TOR = $PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/instances/tor/
    498 BACKEND_APIKEY_TOR = secret-token:$(get_credential_pw merchant/tor)
    499 BACKEND_URL_TALER = $PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/instances/taler/
    500 BACKEND_APIKEY_TALER = secret-token:$(get_credential_pw merchant/taler)
    501 BACKEND_URL_GNUNET = $PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/instances/gnunet/
    502 BACKEND_APIKEY_GNUNET = secret-token:$(get_credential_pw merchant/gnunet)
    503 EOF
    504 
    505 # This really should not exist, the taler-merchant-frontends
    506 # should be easier to configure!
    507 cat <<EOF >/etc/taler/taler-merchant-frontends.env
    508 TALER_ENV_URL_INTRO=$PROTO://$LANDING_DOMAIN$PORT_SUFFIX/
    509 TALER_ENV_URL_LANDING=$PROTO://$LANDING_DOMAIN$PORT_SUFFIX/
    510 TALER_ENV_URL_BANK=$PROTO://$BANK_DOMAIN$PORT_SUFFIX/
    511 TALER_ENV_URL_MERCHANT_BLOG=$PROTO://$BLOG_DOMAIN$PORT_SUFFIX/
    512 TALER_ENV_URL_MERCHANT_DONATIONS=$PROTO://$DONATIONS_DOMAIN$PORT_SUFFIX/
    513 EOF
    514 
    515 #
    516 # Create databases
    517 #
    518 
    519 function wait_pg_ready() {
    520   while true; do
    521     ret=0
    522     pg_isready || ret=$?
    523     case "$ret" in
    524       0)
    525         echo "Postgres is ready" >&2
    526         break
    527         ;;
    528       1|2)
    529         echo "pg_isready returned status $ret, waiting" >&2
    530         sleep 1
    531         ;;
    532       3)
    533         echo "pg_isready returned status $ret, giving up" >&2
    534         exit 3
    535         ;;
    536     esac
    537   done
    538 }
    539 
    540 PG_VERSION="17"
    541 
    542 # Since the sandcastle is a test system, we turn fsync off for performance
    543 # reasons (especially with the drupal setup).
    544 # CAUTION: You do not want to set this in production,
    545 # especially not for the taler-exchange.
    546 pg_conftool $PG_VERSION main set fsync off
    547 # Higher limits needed for migration
    548 pg_conftool $PG_VERSION main set max_locks_per_transaction 512
    549 pg_conftool $PG_VERSION main set max_pred_locks_per_transaction 512
    550 
    551 backup_file=/exported/postgres-backup.sql
    552 if [[ $import_instr = singledump ]]; then
    553   echo "Importing database dump"
    554   if [[ ! -e "$backup_file" ]]; then
    555     echo "Requested import, but backup file does not exist" >&2
    556     exit 1
    557   fi
    558   pg_dropcluster --stop 17 main || true
    559   pg_createcluster 17 main
    560   systemctl start postgresql.service
    561   wait_pg_ready
    562   sudo -u postgres psql postgres -f "$backup_file"
    563 else
    564   systemctl start postgresql.service
    565   wait_pg_ready
    566 fi
    567 
    568 # Set up databases.
    569 # Do that *before* we potentially do a per-service restore-from-backup.
    570 
    571 challenger-dbconfig
    572 
    573 # Sets up the database for both libeufin-bank and libeufin-nexus.  We only need
    574 # the libeufin-bank DB though.
    575 libeufin-dbconfig
    576 
    577 if [[ $ENABLE_AUDITOR = 1 ]]; then
    578   # Add auditor user to DB group *before* running taler-exchange-dbconfig,
    579   # so that DB permissions are adjusted accordingly.
    580   usermod taler-auditor-httpd -aG taler-exchange-db
    581   taler-auditor-dbconfig
    582 fi
    583 
    584 taler-exchange-dbconfig
    585 
    586 taler-merchant-dbconfig
    587 
    588 
    589 #
    590 # Import backup if necessary.
    591 #
    592 
    593 if [[ $import_instr = all ]]; then
    594   echo "Importing databases"
    595 
    596   # FIXME: Consider backing up old DB before importing new one
    597   # FIXME: This is rather hacky, it would be better to use "pg_dump -Fc" and "pg_restore"
    598   sudo -u postgres dropdb taler-exchange
    599   sudo -u postgres dropdb taler-merchant
    600   sudo -u postgres dropdb libeufin
    601 
    602   sudo -u postgres createdb taler-exchange
    603   sudo -u postgres createdb taler-merchant
    604   sudo -u postgres createdb libeufin
    605 
    606   sudo -u postgres psql taler-exchange -f /exported/taler-exchange/taler-exchange.sql
    607   sudo -u postgres psql taler-merchant -f /exported/taler-merchant/taler-merchant.sql
    608   sudo -u postgres psql libeufin -f /exported/libeufin/libeufin.sql
    609 
    610   libeufin-dbconfig
    611   taler-exchange-dbconfig
    612   taler-merchant-dbconfig
    613 
    614   rm -rf /var/lib/taler-exchange/secmod-eddsa/*
    615   cp -r /exported/taler-exchange/secmod-eddsa/* /var/lib/taler-exchange/secmod-eddsa/
    616 
    617   rm -rf /var/lib/taler-exchange/secmod-rsa/*
    618   cp -r /exported/taler-exchange/secmod-rsa/* /var/lib/taler-exchange/secmod-rsa/
    619 
    620   rm -rf /var/lib/taler-exchange/secmod-cs/*
    621   cp -r /exported/taler-exchange/secmod-cs/* /var/lib/taler-exchange/secmod-cs/
    622 fi
    623 
    624 if [[ $import_instr != none ]]; then
    625   echo "Marking import as done"
    626   rm /exported/import-request
    627 fi
    628 
    629 # We need to adjust file ownership, as the container might have different user and group
    630 # IDs than the volume. That can happen when the packages in the container are installed
    631 # in a different order.
    632 # This is only relevant for non-root ownership.
    633 chown --recursive taler-exchange-offline:taler-exchange-offline /var/lib/taler-exchange/offline/* || true
    634 chown --recursive taler-exchange-secmod-cs:taler-exchange-secmod /var/lib/taler-exchange/secmod-cs
    635 chown --recursive taler-exchange-secmod-rsa:taler-exchange-secmod /var/lib/taler-exchange/secmod-rsa
    636 chown --recursive taler-exchange-secmod-eddsa:taler-exchange-secmod /var/lib/taler-exchange/secmod-eddsa
    637 chown root:taler-exchange-db /etc/taler-exchange/secrets/exchange-db.secret.conf
    638 chown root:taler-auditor-httpd /etc/taler-auditor/secrets/auditor-db.secret.conf
    639 chmod 440 /etc/taler-merchant/secrets/merchant-db.secret.conf
    640 chown taler-merchant-httpd:root /etc/taler-merchant/secrets/merchant-db.secret.conf
    641 chown root:taler-exchange-db /etc/taler-exchange/secrets/exchange-db.secret.conf
    642 chown taler-exchange-wire:taler-exchange-db /etc/taler-exchange/secrets/exchange-accountcredentials-default.secret.conf
    643 
    644 
    645 # Caddy configuration.
    646 # We use the caddy reverse proxy with automatic
    647 # internal TLS setup to ensure that the services are
    648 # reachable inside the container without any external
    649 # DNS setup under the same domain name and with TLS
    650 # from inside the container.
    651 
    652 systemctl stop caddy.service
    653 
    654 cat <<EOF >/etc/caddy/Caddyfile
    655 {
    656   servers {
    657       trusted_proxies static private_ranges
    658   }
    659 }
    660 
    661 # Services that only listen on unix domain sockets
    662 # are reverse-proxied to serve on a TCP port.
    663 
    664 :$PORT_INTERNAL_EXCHANGE {
    665   reverse_proxy unix//run/taler-exchange/httpd/exchange-http.sock
    666 }
    667 
    668 :$PORT_INTERNAL_MERCHANT {
    669   reverse_proxy unix//run/taler-merchant/httpd/merchant-http.sock {
    670     # Set this, or otherwise wrong taler://pay URIs will be generated.
    671     header_up X-Forwarded-Proto "https"
    672   }
    673 }
    674 
    675 :$PORT_INTERNAL_DONAU {
    676   reverse_proxy unix//run/donau/httpd/http.sock {
    677     header_up X-Forwarded-Proto "https"
    678   }
    679 }
    680 
    681 :$PORT_INTERNAL_BANK_SPA {
    682   root * /usr/share/libeufin/spa
    683   root /settings.json /etc/libeufin/
    684   file_server
    685 }
    686 
    687 :$PORT_INTERNAL_DRUPAL {
    688   root * /talerdata/sandcastle-drupal/web/
    689   php_fastcgi unix/var/run/php/php8.4-fpm.sock
    690   file_server
    691 }
    692 
    693 :$PORT_INTERNAL_AUDITOR {
    694   reverse_proxy unix//run/taler-auditor/httpd/auditor-http.sock
    695 }
    696 
    697 :$PORT_INTERNAL_PAIVANA {
    698   reverse_proxy unix//run/paivana/httpd/paivana-http.sock
    699 }
    700 
    701 :$PORT_INTERNAL_CHALLENGER {
    702   handle {
    703     reverse_proxy unix//run/challenger/httpd/challenger.http {
    704       # Set this, or otherwise wrong taler://pay URIs will be generated.
    705       header_up X-Forwarded-Proto "https"
    706     }
    707   }
    708 
    709   # Serve challenges via HTTP.
    710   # This is obviously completely insecure, but fine
    711   # for the demo sandcastle.
    712   handle_path /challenges/* {
    713     root * /tmp/challenges/
    714     file_server {
    715       browse
    716     }
    717   }
    718 }
    719 EOF
    720 
    721 if [[ $PROTO = https ]]; then
    722   cat <<EOF >>/etc/caddy/Caddyfile
    723 
    724 # Internally reverse-proxy https://,
    725 # so that service can talk to each other via
    726 # https:// inside the container.
    727 
    728 https://$BANK_DOMAIN {
    729   tls internal
    730   reverse_proxy :8080 {
    731     # libeufin-bank should eventually not require this anymore,
    732     # but currently doesn't work without this header.
    733     header_up X-Forwarded-Prefix ""
    734   }
    735 }
    736 
    737 https://$EXCHANGE_DOMAIN {
    738   tls internal
    739   reverse_proxy unix//run/taler-exchange/httpd/exchange-http.sock
    740 }
    741 
    742 https://$MERCHANT_DOMAIN {
    743   tls internal
    744   reverse_proxy unix//run/taler-merchant/httpd/merchant-http.sock {
    745     # Set this, or otherwise wrong taler://pay URIs will be generated.
    746     header_up X-Forwarded-Proto "https"
    747   }
    748 }
    749 
    750 https://$DONAU_DOMAIN {
    751   tls internal
    752   reverse_proxy unix//run/donau/httpd/http.sock {
    753     header_up X-Forwarded-Proto "https"
    754   }
    755 }
    756 
    757 https://$AUDITOR_DOMAIN {
    758   tls internal
    759   reverse_proxy unix//run/taler-auditor/httpd/auditor-http.sock
    760 }
    761 
    762 https://$CHALLENGER_DOMAIN {
    763   tls internal
    764   reverse_proxy unix//run/challenger/httpd/challenger.http
    765 }
    766 
    767 https://$PAIVANA_DOMAIN {
    768   tls internal
    769   reverse_proxy unix//run/paivana/httpd/paivana-http.sock
    770 }
    771 
    772 EOF
    773 
    774 else
    775   # Config for HTTP without TLS.
    776 
    777   cat <<EOF >>/etc/caddy/Caddyfile
    778 
    779 http://$BANK_DOMAIN$PORT_SUFFIX {
    780   reverse_proxy :8080 {
    781     # libeufin-bank should eventually not require this anymore,
    782     # but currently doesn't work without this header.
    783     header_up X-Forwarded-Prefix ""
    784   }
    785 }
    786 
    787 http://$EXCHANGE_DOMAIN$PORT_SUFFIX {
    788   reverse_proxy unix//run/taler-exchange/httpd/exchange-http.sock
    789 }
    790 
    791 http://$MERCHANT_DOMAIN$PORT_SUFFIX {
    792   reverse_proxy unix//run/taler-merchant/httpd/merchant-http.sock
    793 }
    794 
    795 http://$DONAU_DOMAIN$PORT_SUFFIX {
    796   reverse_proxy unix//run/donau/httpd/http.sock
    797 }
    798 
    799 http://$AUDITOR_DOMAIN$PORT_SUFFIX {
    800   reverse_proxy unix//run/taler-auditor/httpd/auditor-http.sock
    801 }
    802 
    803 http://$CHALLENGER_DOMAIN$PORT_SUFFIX {
    804   reverse_proxy unix//run/challenger/httpd/challenger.http
    805 }
    806 
    807 http://$LANDING_DOMAIN$PORT_SUFFIX {
    808   reverse_proxy :$PORT_INTERNAL_LANDING
    809 }
    810 
    811 http://$BLOG_DOMAIN$PORT_SUFFIX {
    812   reverse_proxy :$PORT_INTERNAL_BLOG
    813 }
    814 
    815 http://$DONATIONS_DOMAIN$PORT_SUFFIX {
    816   reverse_proxy :$PORT_INTERNAL_DONATIONS
    817 }
    818 
    819 http://$DRUPAL_DOMAIN$PORT_SUFFIX {
    820   reverse_proxy :$PORT_INTERNAL_DRUPAL
    821 }
    822 
    823 http://$PAIVANA_DOMAIN$PORT_SUFFIX {
    824   reverse_proxy unix//run/paivana/httpd/paivana-http.sock
    825 }
    826 
    827 EOF
    828 
    829 fi
    830 
    831 cat <<EOF >>/etc/hosts
    832 # Start of Taler Sandcastle Domains
    833 127.0.0.1 $LANDING_DOMAIN
    834 127.0.0.1 $BANK_DOMAIN
    835 127.0.0.1 $EXCHANGE_DOMAIN
    836 127.0.0.1 $MERCHANT_DOMAIN
    837 127.0.0.1 $BLOG_DOMAIN
    838 127.0.0.1 $DONATIONS_DOMAIN
    839 127.0.0.1 $DONAU_DOMAIN
    840 127.0.0.1 $CHALLENGER_DOMAIN
    841 127.0.0.1 $DRUPAL_DOMAIN
    842 127.0.0.1 $PAIVANA_DOMAIN
    843 # End of Taler Sandcastle Domains
    844 EOF
    845 
    846 systemctl start caddy.service
    847 
    848 # Install local, internal CA certs for caddy
    849 caddy trust
    850 
    851 # Set up challenger
    852 
    853 CHALLENGER_CLIENT_SECRET=secret-token:sandbox
    854 CHALLENGER_CLIENT_ID=$(sudo -u challenger-httpd challenger-admin -q --add="$CHALLENGER_CLIENT_SECRET" https://$EXCHANGE_DOMAIN/kyc-proof/mychallenger)
    855 echo Challenger client ID: $CHALLENGER_CLIENT_ID
    856 
    857 systemctl enable --now challenger-httpd.service
    858 
    859 # Set up bank
    860 
    861 sudo -u libeufin-bank libeufin-bank edit-account admin --debit_threshold=$CURRENCY:10000000
    862 sudo -u libeufin-bank libeufin-bank passwd admin $(get_credential_pw bank/admin)
    863 
    864 systemctl enable --now libeufin-bank.service
    865 
    866 BANK_BASEURL=$PROTO://$BANK_DOMAIN$PORT_SUFFIX/
    867 
    868 taler-harness deployment wait-taler-service taler-corebank ${BANK_BASEURL}config
    869 
    870 sudo -u libeufin-bank libeufin-bank passwd exchange $(get_credential_pw bank/exchange) || true
    871 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    872   --login exchange --exchange --public \
    873   --payto $EXCHANGE_PAYTO \
    874   --name Exchange \
    875   --password $(get_credential_pw bank/exchange)
    876 
    877 sudo -u libeufin-bank libeufin-bank passwd merchant-admin $(get_credential_pw bank/merchant-admin) || true
    878 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    879   --login merchant-admin --public \
    880   --payto $MERCHANT_PAYTO_ADMIN \
    881   --name "Default Demo Merchant" \
    882   --password $(get_credential_pw bank/merchant-admin)
    883 
    884 sudo -u libeufin-bank libeufin-bank passwd merchant-testing $(get_credential_pw bank/merchant-testing) || true
    885 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    886   --login merchant-testing --public \
    887   --payto $MERCHANT_PAYTO_TESTING \
    888   --name "Testing Merchant" \
    889   --password $(get_credential_pw bank/merchant-testing)
    890 
    891 sudo -u libeufin-bank libeufin-bank passwd merchant-pos $(get_credential_pw bank/merchant-pos) || true
    892 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    893   --login merchant-pos --public \
    894   --payto $MERCHANT_PAYTO_POS \
    895   --name "PoS Merchant" \
    896   --password $(get_credential_pw bank/merchant-pos)
    897 
    898 sudo -u libeufin-bank libeufin-bank passwd merchant-blog $(get_credential_pw bank/merchant-blog) || true
    899 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    900   --login merchant-blog --public \
    901   --payto $MERCHANT_PAYTO_BLOG \
    902   --name "Blog Merchant" \
    903   --password $(get_credential_pw bank/merchant-blog)
    904 
    905 sudo -u libeufin-bank libeufin-bank passwd merchant-gnunet $(get_credential_pw bank/merchant-gnunet) || true
    906 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    907   --login merchant-gnunet --public \
    908   --payto "$MERCHANT_PAYTO_GNUNET" \
    909   --name "GNUnet Donations Merchant" \
    910   --password $(get_credential_pw bank/merchant-gnunet)
    911 
    912 sudo -u libeufin-bank libeufin-bank passwd merchant-taler $(get_credential_pw bank/merchant-taler) || true
    913 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    914   --login merchant-taler --public \
    915   --payto "$MERCHANT_PAYTO_TALER" \
    916   --name "Taler Donations Merchant" \
    917   --password $(get_credential_pw bank/merchant-taler)
    918 
    919 sudo -u libeufin-bank libeufin-bank passwd merchant-tor $(get_credential_pw bank/merchant-tor) || true
    920 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    921   --login merchant-tor --public \
    922   --payto "$MERCHANT_PAYTO_TOR" \
    923   --name "Tor Donations Merchant" \
    924   --password $(get_credential_pw bank/merchant-tor)
    925 
    926 sudo -u libeufin-bank libeufin-bank passwd merchant-umami $(get_credential_pw bank/merchant-umami) || true
    927 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    928   --login merchant-umami --public \
    929   --payto "$MERCHANT_PAYTO_UMAMI" \
    930   --name "Umami Merchant" \
    931   --password $(get_credential_pw bank/merchant-umami)
    932 
    933 sudo -u libeufin-bank libeufin-bank passwd merchant-paivana $(get_credential_pw bank/merchant-paivana) || true
    934 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    935   --login merchant-paivana --public \
    936   --payto "$MERCHANT_PAYTO_PAIVANA" \
    937   --name "Paivana Merchant" \
    938   --password $(get_credential_pw bank/merchant-paivana)
    939 
    940 # Special bank account without a secure password
    941 sudo -u libeufin-bank libeufin-bank passwd merchant-sandbox sandbox || true
    942 taler-harness deployment provision-bank-account "${BANK_BASEURL}" \
    943   --login merchant-sandbox --public \
    944   --payto "$MERCHANT_PAYTO_SANDBOX" \
    945   --name "Sandbox Merchant" \
    946   --password sandbox
    947 
    948 # Set up exchange
    949 
    950 ##
    951 ## Configure KYC if enabled
    952 ##
    953 
    954 if [[ ${ENABLE_KYC:-0} = 1 ]]; then
    955   # KYC config
    956   if [[ ${KYC_DIALECT:-simple} = simple ]]; then
    957     source /data/setup-kyc-simple.sh
    958   elif [[ ${KYC_DIALECT:-simple} = tops ]]; then
    959     source /data/setup-kyc-tops.sh
    960   fi
    961 else
    962   rm -f /etc/taler-exchange/conf.d/sandcastle-kyc.conf
    963 fi
    964 
    965 
    966 if [[ ! -e /etc/taler-exchange/conf.d/sandcastle-$CURRENCY-coins.conf ]]; then
    967   # Only create if necessary, as each [COIN-...] section
    968   # has a unique name with a timestamp.
    969   taler-harness deployment gen-coin-config \
    970     --min-amount "${CURRENCY}:0.01" \
    971     --max-amount "${CURRENCY}:100" \
    972     --no-fees \
    973     >"/etc/taler-exchange/conf.d/sandcastle-$CURRENCY-coins.conf"
    974 else
    975   # Exchange broke backwards compatibility, fix up existing config file.
    976   sed -i 's/COIN-/COIN_/gI' "/etc/taler-exchange/conf.d/sandcastle-$CURRENCY-coins.conf"
    977 fi
    978 
    979 taler-terms-generator -i /usr/share/taler-exchange/terms/exchange-tos-v0
    980 taler-terms-generator -i /usr/share/taler-exchange/terms/exchange-pp-v0
    981 
    982 systemctl enable --now taler-exchange.target
    983 
    984 taler-harness deployment wait-taler-service taler-exchange $PROTO://$EXCHANGE_DOMAIN$PORT_SUFFIX/config
    985 taler-harness deployment wait-endpoint $PROTO://$EXCHANGE_DOMAIN$PORT_SUFFIX/management/keys
    986 
    987 sudo -u taler-exchange-offline \
    988   taler-exchange-offline \
    989   -c /etc/taler-exchange/taler-exchange.conf \
    990   download \
    991   sign \
    992   upload
    993 
    994 sudo -u taler-exchange-offline \
    995   taler-exchange-offline \
    996   enable-account "${EXCHANGE_PAYTO}" \
    997   wire-fee now "$WIRE_METHOD" "${CURRENCY}":0 "${CURRENCY}":0 \
    998   global-fee now "${CURRENCY}":0 "${CURRENCY}":0 "${CURRENCY}":0 1h 6a 0 \
    999   upload
   1000 
   1001 systemctl enable --now taler-exchange-offline.timer
   1002 
   1003 function dup_exchange_opt() {
   1004   echo "$2 = $(taler-exchange-config -c /etc/taler-exchange/taler-exchange.conf -s $1 -o $2)"
   1005 }
   1006 
   1007 #
   1008 # Set up exchange auditor
   1009 #
   1010 
   1011 if [[ $ENABLE_AUDITOR = 1 ]]; then
   1012   systemctl enable --now taler-auditor.target
   1013 fi
   1014 
   1015 # Set up merchant backend
   1016 
   1017 MERCHANT_BASEURL=$PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/
   1018 
   1019 cat <<EOF >/etc/taler-merchant/conf.d/sandcastle-merchant-terms.conf
   1020 [merchant]
   1021 TERMS_ETAG = merchant-tos-demo-v0
   1022 TERMS_DIR = \${TALER_DATA_HOME}terms/
   1023 EOF
   1024 
   1025 taler-terms-generator -i /usr/share/taler-merchant/terms/merchant-tos-demo-v0.en.rst -o "$(taler-merchant-config -f -s merchant -o terms_dir)"
   1026 
   1027 systemctl enable --now taler-merchant.target
   1028 taler-harness deployment wait-taler-service taler-merchant ${MERCHANT_BASEURL}config
   1029 
   1030 function reset_merchant_pw() {
   1031   pw=$(get_credential_pw merchant/$1)
   1032   sudo -u taler-merchant-httpd taler-merchant-passwd --instance "$1" "$pw"
   1033   if [[ $? -eq 2 ]]; then
   1034     echo "Instance $1 does not exist" >&2
   1035     return 2
   1036   fi
   1037   if [[ $? -ne 0 ]]; then
   1038     echo "Failed to reset password for merchant instance $1" >&2
   1039     exit 1
   1040   fi
   1041 }
   1042 
   1043 # FIXME: Move this into a harness tool (that just reads a config file)?
   1044 
   1045 instance_missing=no
   1046 reset_merchant_pw admin || instance_missing=yes
   1047 if [[ $instance_missing = yes ]]; then
   1048   taler-harness deployment provision-merchant-instance \
   1049     ${MERCHANT_BASEURL} \
   1050     --management-token "secret-token:none" \
   1051     --instance-password $(get_credential_pw merchant/admin) \
   1052     --name Merchant \
   1053     --id admin \
   1054     --payto "$MERCHANT_PAYTO_ADMIN"
   1055 fi
   1056 
   1057 ADMIN_TOK=$(taler-harness merchant token ${MERCHANT_BASEURL} admin --password $(get_credential_pw merchant/admin))
   1058 
   1059 instance_missing=no
   1060 reset_merchant_pw pos || instance_missing=yes
   1061 if [[ $instance_missing = yes ]]; then
   1062   taler-harness deployment provision-merchant-instance \
   1063     ${MERCHANT_BASEURL} \
   1064     --management-token $ADMIN_TOK \
   1065     --instance-password $(get_credential_pw merchant/pos) \
   1066     --name "POS Merchant" \
   1067     --id pos \
   1068     --payto "$MERCHANT_PAYTO_POS"
   1069 fi
   1070 
   1071 instance_missing=no
   1072 reset_merchant_pw testing || instance_missing=yes
   1073 if [[ $instance_missing = yes ]]; then
   1074   taler-harness deployment provision-merchant-instance \
   1075     ${MERCHANT_BASEURL} \
   1076     --management-token $ADMIN_TOK \
   1077     --instance-password $(get_credential_pw merchant/testing) \
   1078     --name "Testing Merchant" \
   1079     --id testing \
   1080     --payto "$MERCHANT_PAYTO_TESTING"
   1081 fi
   1082 
   1083 instance_missing=no
   1084 reset_merchant_pw blog || instance_missing=yes
   1085 if [[ $instance_missing = yes ]]; then
   1086   taler-harness deployment provision-merchant-instance \
   1087     ${MERCHANT_BASEURL} \
   1088     --management-token $ADMIN_TOK \
   1089     --instance-password $(get_credential_pw merchant/blog) \
   1090     --name "Blog Merchant" \
   1091     --id blog \
   1092     --payto "$MERCHANT_PAYTO_BLOG"
   1093 fi
   1094 
   1095 instance_missing=no
   1096 reset_merchant_pw gnunet || instance_missing=yes
   1097 if [[ $instance_missing = yes ]]; then
   1098   taler-harness deployment provision-merchant-instance \
   1099     ${MERCHANT_BASEURL} \
   1100     --management-token $ADMIN_TOK \
   1101     --instance-password $(get_credential_pw merchant/gnunet) \
   1102     --name "GNUnet Merchant" \
   1103     --id gnunet \
   1104     --payto "$MERCHANT_PAYTO_GNUNET"
   1105 fi
   1106 
   1107 instance_missing=no
   1108 reset_merchant_pw taler || instance_missing=yes
   1109 if [[ $instance_missing = yes ]]; then
   1110   taler-harness deployment provision-merchant-instance \
   1111     ${MERCHANT_BASEURL} \
   1112     --management-token $ADMIN_TOK \
   1113     --instance-password $(get_credential_pw merchant/taler) \
   1114     --name "Taler Merchant" \
   1115     --id taler \
   1116     --payto "$MERCHANT_PAYTO_TALER"
   1117 fi
   1118 
   1119 instance_missing=no
   1120 reset_merchant_pw tor || instance_missing=yes
   1121 if [[ $instance_missing = yes ]]; then
   1122   taler-harness deployment provision-merchant-instance \
   1123     ${MERCHANT_BASEURL} \
   1124     --management-token $ADMIN_TOK \
   1125     --instance-password $(get_credential_pw merchant/tor) \
   1126     --name "Tor Merchant" \
   1127     --id tor \
   1128     --payto "$MERCHANT_PAYTO_TOR"
   1129 fi
   1130 
   1131 instance_missing=no
   1132 reset_merchant_pw umami || instance_missing=yes
   1133 if [[ $instance_missing = yes ]]; then
   1134   taler-harness deployment provision-merchant-instance \
   1135     ${MERCHANT_BASEURL} \
   1136     --management-token $ADMIN_TOK \
   1137     --instance-password $(get_credential_pw merchant/umami) \
   1138     --name "Umami Merchant" \
   1139     --id umami \
   1140     --payto "$MERCHANT_PAYTO_UMAMI"
   1141 fi
   1142 
   1143 instance_missing=no
   1144 reset_merchant_pw paivana || instance_missing=yes
   1145 if [[ $instance_missing = yes ]]; then
   1146   taler-harness deployment provision-merchant-instance \
   1147     ${MERCHANT_BASEURL} \
   1148     --management-token $ADMIN_TOK \
   1149     --instance-password $(get_credential_pw merchant/paivana) \
   1150     --name "Paivana Merchant" \
   1151     --id paivana \
   1152     --payto "$MERCHANT_PAYTO_PAIVANA"
   1153 fi
   1154 
   1155 # Special instance with fixed "sandbox" password
   1156 sudo -u taler-merchant-httpd taler-merchant-passwd --instance sandbox sandbox || true
   1157 taler-harness deployment provision-merchant-instance \
   1158   ${MERCHANT_BASEURL} \
   1159   --management-token $ADMIN_TOK \
   1160   --instance-password sandbox \
   1161   --name "sandbox merchant" \
   1162   --id sandbox \
   1163   --payto "$MERCHANT_PAYTO_SANDBOX"
   1164 
   1165 # token families needed by demo blog
   1166 
   1167 langs=(de en ar zh fr hi it ja ko pt pt_BR ru es sv tr uk)
   1168 valid_before_ts=$(date -u +%s -d '+1 year') # one year later
   1169 duration_us=$((30 * 24 * 60 * 60 * 1000000)) # 30 days
   1170 validity_granularity_us=$((24 * 60 * 60 * 1000000)) # 1 day
   1171 
   1172 # FIXME: Move this into a harness tool?
   1173 for lang in "${langs[@]}"; do
   1174     curl -X POST "${MERCHANT_BASEURL}instances/blog/private/tokenfamilies" \
   1175          -H "Authorization: Bearer secret-token:$(get_credential_pw merchant/blog)" \
   1176          -H "Content-Type: application/json" \
   1177          --data-raw "{
   1178   \"kind\": \"subscription\",
   1179   \"slug\": \"blog_abo_${lang}\",
   1180   \"name\": \"One month of access (${lang})\",
   1181   \"description\": \"One month of access (${lang})\",
   1182   \"description_i18n\": {
   1183     \"de\": \"Ein monat lang Zugang zu den Artikeln\",
   1184     \"en\": \"One month of access to articles\",
   1185     \"fr\": \"Un mois d'accès aux articles\",
   1186     \"es\": \"Un mes de acceso a los artículos\"
   1187   },
   1188   \"valid_before\": { \"t_s\": ${valid_before_ts} },
   1189   \"duration\": { \"d_us\": ${duration_us} },
   1190   \"validity_granularity\": { \"d_us\": ${validity_granularity_us} }
   1191 }"
   1192 done
   1193 
   1194 
   1195 # Set up the Paivana paywall.
   1196 # The paywall itself is configured entirely via merchant templates,
   1197 # see /etc/paivana/paivana.conf for the daemon configuration.
   1198 
   1199 PAIVANA_INSTANCE_URL=${MERCHANT_BASEURL}instances/paivana/
   1200 PAIVANA_TOKEN=secret-token:$(get_credential_pw merchant/paivana)
   1201 
   1202 # Subscription offered as an alternative to paying per visit.
   1203 # Reuses the validity settings of the blog subscriptions above.
   1204 curl -X POST "${PAIVANA_INSTANCE_URL}private/tokenfamilies" \
   1205      -H "Authorization: Bearer $PAIVANA_TOKEN" \
   1206      -H "Content-Type: application/json" \
   1207      --data-raw "{
   1208   \"kind\": \"subscription\",
   1209   \"slug\": \"paivana_abo\",
   1210   \"name\": \"One month of access\",
   1211   \"description\": \"One month of access to $PAIVANA_DESTINATION\",
   1212   \"valid_before\": { \"t_s\": ${valid_before_ts} },
   1213   \"duration\": { \"d_us\": ${duration_us} },
   1214   \"validity_granularity\": { \"d_us\": ${validity_granularity_us} }
   1215 }"
   1216 
   1217 # paivana-httpd learns which URLs cost how much from the templates
   1218 # of its merchant instance.  A single template covering the whole
   1219 # site is enough here, as paivana-httpd runs with '-g'.
   1220 paivana_template=$(cat <<EOF
   1221 {
   1222   "template_id": "paivana",
   1223   "template_description": "Paywall for $PAIVANA_DESTINATION",
   1224   "template_contract": {
   1225     "template_type": "paivana",
   1226     "summary": "Access to $PAIVANA_DESTINATION",
   1227     "website_regex": ".*",
   1228     "max_pickup_duration": { "d_us": $((24 * 60 * 60 * 1000000)) },
   1229     "choices": [
   1230       {
   1231         "amount": "$CURRENCY:1",
   1232         "description": "Pay for one day of access"
   1233       },
   1234       {
   1235         "amount": "$CURRENCY:10",
   1236         "description": "Buy a one month subscription",
   1237         "outputs": [ { "type": "token", "token_family_slug": "paivana_abo" } ]
   1238       },
   1239       {
   1240         "amount": "$CURRENCY:0",
   1241         "description": "Use my subscription",
   1242         "inputs": [ { "type": "token", "token_family_slug": "paivana_abo" } ],
   1243         "outputs": [ { "type": "token", "token_family_slug": "paivana_abo" } ]
   1244       }
   1245     ]
   1246   }
   1247 }
   1248 EOF
   1249 )
   1250 
   1251 paivana_response=$(mktemp)
   1252 paivana_status=$(curl -X POST "${PAIVANA_INSTANCE_URL}private/templates" \
   1253   -H "Authorization: Bearer $PAIVANA_TOKEN" \
   1254   -H "Content-Type: application/json" \
   1255   --data-raw "$paivana_template" \
   1256   -s -o "$paivana_response" -w '%{http_code}')
   1257 
   1258 case "$paivana_status" in
   1259   204)
   1260     ;;
   1261   409)
   1262     # Template exists already, update it so that changes take effect.
   1263     paivana_status=$(curl -X PATCH "${PAIVANA_INSTANCE_URL}private/templates/paivana" \
   1264       -H "Authorization: Bearer $PAIVANA_TOKEN" \
   1265       -H "Content-Type: application/json" \
   1266       --data-raw "$(jq 'del(.template_id)' <<<"$paivana_template")" \
   1267       -s -o "$paivana_response" -w '%{http_code}')
   1268     if [[ $paivana_status != 204 ]]; then
   1269       echo "Failed to update Paivana template (HTTP $paivana_status)" >&2
   1270       cat "$paivana_response" >&2
   1271       exit 1
   1272     fi
   1273     ;;
   1274   *)
   1275     echo "Failed to create Paivana template (HTTP $paivana_status)" >&2
   1276     cat "$paivana_response" >&2
   1277     exit 1
   1278     ;;
   1279 esac
   1280 rm -f "$paivana_response"
   1281 
   1282 systemctl enable --now paivana-httpd.socket
   1283 systemctl enable --now paivana-httpd.service
   1284 
   1285 
   1286 # Set up Donau
   1287 
   1288 cat <<EOF >/etc/donau/conf.d/sandcastle.conf
   1289 [donau]
   1290 CURRENCY = $CURRENCY
   1291 LEGAL_DOMAIN = Gnuland
   1292 EXPIRE_LEGAL_YEARS = 3
   1293 # We don't do the token yet, as the merchant doesn't support
   1294 # authenticating with donau.
   1295 # ADMIN_BEARER_TOKEN = secret-token:secret
   1296 EOF
   1297 
   1298 donau-dbconfig
   1299 
   1300 if [[ ! -e /etc/donau/conf.d/sandcastle-$CURRENCY-units.conf ]]; then
   1301   # Only create if necessary
   1302   taler-harness deployment gen-doco-config \
   1303     --min-amount "${CURRENCY}:0.01" \
   1304     --max-amount "${CURRENCY}:100" \
   1305     >"/etc/donau/conf.d/sandcastle-$CURRENCY-units.conf"
   1306 fi
   1307 
   1308 systemctl enable --now donau.target
   1309 
   1310 DONAU_BASE_URL=$PROTO://$DONAU_DOMAIN$PORT_SUFFIX/
   1311 
   1312 taler-harness deployment wait-taler-service donau ${DONAU_BASE_URL}config
   1313 
   1314 if [[ ${ENABLE_TALDIR:-0} == 1 ]]; then
   1315   # Mailbox and Directory
   1316   mkdir -p /etc/taler-directory
   1317   cp /usr/share/taler-directory/taldir.conf.example /etc/taler-directory/taler-directory.conf
   1318   taler-directory-dbconfig
   1319   systemctl enable --now taler-directory.service
   1320 fi
   1321 
   1322 if [[ ${ENABLE_MAILBOX:-0} == 1 ]]; then
   1323   mkdir -p /etc/taler-mailbox
   1324   cp /usr/share/taler-mailbox/mailbox.conf.example /etc/taler-mailbox/taler-mailbox.conf
   1325   sed -i 's/localhost:11000/localhost:12000/' /etc/taler-mailbox/taler-mailbox.conf
   1326   taler-mailbox-dbconfig
   1327   systemctl enable --now taler-mailbox.service
   1328 fi
   1329 
   1330 GNUNET_TOK=$(taler-harness merchant token ${MERCHANT_BASEURL}instances/gnunet/ gnunet --password $(get_credential_pw merchant/gnunet))
   1331 taler-harness deployment provision-merchant-donau \
   1332   --merchant-auth-token $GNUNET_TOK \
   1333   --merchant-base-url $PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/instances/gnunet/ \
   1334   --donau-base-url $DONAU_BASE_URL \
   1335   --donau-auth-token secret-token:secret \
   1336   --currency $CURRENCY
   1337 
   1338 TALER_TOK=$(taler-harness merchant token ${MERCHANT_BASEURL}instances/taler/ taler --password $(get_credential_pw merchant/taler))
   1339 taler-harness deployment provision-merchant-donau \
   1340   --merchant-auth-token $TALER_TOK \
   1341   --merchant-base-url $PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/instances/taler/ \
   1342   --donau-base-url $DONAU_BASE_URL \
   1343   --donau-auth-token secret-token:secret \
   1344   --currency $CURRENCY
   1345 
   1346 TOR_TOK=$(taler-harness merchant token ${MERCHANT_BASEURL}instances/tor/ tor --password $(get_credential_pw merchant/tor))
   1347 taler-harness deployment provision-merchant-donau \
   1348   --merchant-auth-token $TOR_TOK \
   1349   --merchant-base-url $PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/instances/tor/ \
   1350   --donau-base-url $DONAU_BASE_URL \
   1351   --donau-auth-token secret-token:secret \
   1352   --currency $CURRENCY
   1353 
   1354 UMAMI_TOK=$(taler-harness merchant token ${MERCHANT_BASEURL}instances/umami/ umami --password $(get_credential_pw merchant/umami))
   1355 
   1356 # Now we set up the taler-merchant-demos
   1357 
   1358 systemctl enable --now taler-demo-landing
   1359 systemctl enable --now taler-demo-blog
   1360 systemctl enable --now taler-demo-donations
   1361 
   1362 # Turnstile (drupal/php)
   1363 if [[ ${ENABLE_TURNSTILE:-1} = 1 ]]; then
   1364 
   1365 systemctl enable --now php8.4-fpm
   1366 
   1367 DRUPAL_DB_PW=$(get_credential_pw db/drupal)
   1368 DRUPAL_ADMIN_PW=$(get_credential_pw drupal/admin)
   1369 
   1370 sudo -i -u postgres psql postgres -c "CREATE ROLE drupal WITH login;" || true
   1371 sudo -i -u postgres psql postgres -c "ALTER ROLE drupal password '$DRUPAL_DB_PW';"
   1372 sudo -u postgres createdb drupal --owner=drupal || true
   1373 
   1374 # Needed by PHP's composer
   1375 export HOME=/root
   1376 
   1377 # FIXME: Would probably be better to checkout output of
   1378 # drush status --fields=bootstrap --format=string
   1379 
   1380 cd /talerdata/
   1381 if [[ ! -e /talerdata/sandcastle-drupal ]]; then
   1382   composer create-project drupal/recommended-project:^10 sandcastle-drupal
   1383   cd /talerdata/sandcastle-drupal
   1384   composer require drush/drush
   1385   # This can take a ridiculous amount of time!
   1386   COMPOSER_PROCESS_TIMEOUT=0 composer exec -- drush site-install demo_umami --account-name=admin --account-pass=$DRUPAL_ADMIN_PW --account-mail=admin@localhost --db-url=pgsql://drupal:$DRUPAL_DB_PW@localhost/drupal --site-name=SandcastleUmami --yes
   1387 fi
   1388 
   1389 chown -R www-data:www-data /talerdata/sandcastle-drupal/
   1390 
   1391 ln -sf /opt/turnstile /talerdata/sandcastle-drupal/web/modules/taler_turnstile
   1392 
   1393 snip=$(cat <<'EOF'
   1394 $settings['reverse_proxy'] = TRUE;
   1395 $settings['reverse_proxy_addresses'] = ['127.0.0.1', '10.0.0.0/8'];
   1396 $settings['trusted_host_patterns'] = ['.*'];
   1397 EOF
   1398 )
   1399 
   1400 update_config_block /talerdata/sandcastle-drupal/web/sites/default/settings.php SANDCASTLE "$snip"
   1401 
   1402 cd /talerdata/sandcastle-drupal
   1403 composer exec -- drush upwd admin $DRUPAL_ADMIN_PW
   1404 composer exec -- drush en taler_turnstile
   1405 composer exec -- drush config:set taler_turnstile.settings access_token "$UMAMI_TOK" --yes
   1406 composer exec -- drush config:set taler_turnstile.settings payment_backend_url "$PROTO://$MERCHANT_DOMAIN$PORT_SUFFIX/instances/umami/" --yes
   1407 composer exec -- drush config:set --input-format=yaml taler_turnstile.settings enabled_content_types '["article", "recipe"]' --yes
   1408 
   1409 snip=$(cat <<'EOF' | sed "s/@CURRENCY@/$CURRENCY/"
   1410 $storage = \Drupal::entityTypeManager()->getStorage('taler_turnstile_price_category');
   1411 $e = $storage->create(['id'=>"normal", 'label'=>'Normal', 'description' => "Normal Article Price", 'prices' => ['%none%' => ['@CURRENCY@' => '0.3']]]);
   1412 try {
   1413   $e->save();
   1414 } catch (Exception $ex) {
   1415   echo $ex->getMessage();
   1416   echo "\n";
   1417 }
   1418 EOF
   1419 )
   1420 composer exec -- drush php:eval "$snip"
   1421 
   1422 snip=$(cat <<'EOF'
   1423 $prcat_storage = \Drupal::entityTypeManager()->getStorage('taler_turnstile_price_category');
   1424 $node_storage = \Drupal::entityTypeManager()->getStorage('node');
   1425 $prcat = $prcat_storage->load('normal');
   1426 $nodes = $node_storage->loadByProperties(['type'=> ['article', 'recipe']]);
   1427 foreach ($nodes as $k => $node) {
   1428   echo 'updating node ' . $k . "\n";
   1429   if (!$node->hasField('field_taler_turnstile_prcat')) {
   1430     echo 'prcat missing' . "\n";
   1431     continue;
   1432   }
   1433   $node->set('field_taler_turnstile_prcat', $prcat);
   1434   $node->save();
   1435 }
   1436 EOF
   1437 )
   1438 composer exec -- drush php:eval "$snip"
   1439 
   1440 composer exec -- drush cr
   1441 
   1442 fi
   1443 
   1444 cd /
   1445 
   1446 # FIXME: Maybe do some taler-wallet-cli test?
   1447 # FIXME: How do we report errors occurring during the setup script?