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?