sandcastle-ng

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

setup-sandcastle.sh (52176B)


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