taler-deployment

Deployment scripts and configuration files
Log | Files | Refs | README

commit ddcd2db9a580dfbe9f1da76a8d683144ff76a1a5
parent 2f9e597398dc78519401c365051415b3217d2f5b
Author: Florian Dold <dold@taler.net>
Date:   Thu,  3 Sep 2026 00:28:16 +0200

regional-currency-ng: add destructive exchange reset playbook

Diffstat:
Mregional-currency-ng/ChangeLog | 4++++
Mregional-currency-ng/README.md | 31+++++++++++++++++++++++++++++++
Aregional-currency-ng/dangerously-reset-exchange.yml | 16++++++++++++++++
Aregional-currency-ng/roles/regional_currency/tasks/dangerously-reset-exchange.yml | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 232 insertions(+), 0 deletions(-)

diff --git a/regional-currency-ng/ChangeLog b/regional-currency-ng/ChangeLog @@ -1,3 +1,7 @@ +Thu Sep 3 2026 + Added a guarded playbook that completely resets and reprovisions an + exchange on testing deployments. + Wed Sep 2 2026 Added an optional, default-enabled landing page on the deployment's main domain with links to the bank, merchant backend, and exchange. diff --git a/regional-currency-ng/README.md b/regional-currency-ng/README.md @@ -143,6 +143,37 @@ tune `regional_currency_bank_token_rate`, Merchant instances and their credentials must be created separately. +Dangerously reset a test exchange +--------------------------------- + +The destructive reset playbook deletes the exchange database, all offline and +online exchange keys, and the generated denomination configuration. It then +uses the normal regional-currency role to provision a new exchange and update +the merchant with the new exchange master public key. Existing coins, reserves, +and exchange-related payment state become permanently unusable. + +The playbook refuses inventories where +`regional_currency_testing_deployment` is not `true`. Limit each invocation to +the intended target: + +```console +$ ansible-playbook -i inventory dangerously-reset-exchange.yml --limit HOST +``` + +For each host, enter the displayed domain-specific confirmation phrase. The +playbook processes hosts one at a time so one confirmation cannot authorize a +different target accidentally. + +The reset preserves the LibEuFin bank database, the exchange bank account and +token, the merchant database, TLS state, and the recorded deployment identity. +Because the fresh exchange database has no previous wirewatch position, it may +observe historical incoming transfers from the preserved bank account again. +This playbook is therefore suitable only for disposable testing deployments. + +If normal reprovisioning fails after deletion, correct the reported problem and +run `site.yml` to finish reconciling the deployment. The deleted test data and +keys cannot be recovered by the playbooks. + The `diagnose.sh` and `list-incoming.sh` scripts are server-side diagnostic utilities, not deployment or upgrade entry points. All deployment and package upgrade work is performed by `ansible-playbook` from the operator machine. diff --git a/regional-currency-ng/dangerously-reset-exchange.yml b/regional-currency-ng/dangerously-reset-exchange.yml @@ -0,0 +1,16 @@ +--- +- name: Dangerously reset and reprovision a test exchange + hosts: regional_currency + become: true + gather_facts: true + serial: 1 + + tasks: + - name: Dangerously reset the exchange + ansible.builtin.import_role: + name: regional_currency + tasks_from: dangerously-reset-exchange.yml + + - name: Reprovision the regional currency + ansible.builtin.import_role: + name: regional_currency diff --git a/regional-currency-ng/roles/regional_currency/tasks/dangerously-reset-exchange.yml b/regional-currency-ng/roles/regional_currency/tasks/dangerously-reset-exchange.yml @@ -0,0 +1,181 @@ +--- +- name: Require a testing deployment for exchange reset + ansible.builtin.assert: + that: + - regional_currency_testing_deployment | bool + fail_msg: >- + Refusing to reset the exchange because this inventory is not marked as a + testing deployment. + +- name: Read the configured exchange database + ansible.builtin.command: + argv: + - taler-exchange-config + - -s + - exchangedb-postgres + - -o + - CONFIG + register: regional_currency_exchange_reset_database + changed_when: false + +- name: Read the configured offline key paths + become: true + become_user: taler-exchange-offline + ansible.builtin.command: + argv: + - taler-exchange-config + - -s + - exchange-offline + - -f + - -o + - "{{ item.option }}" + loop: + - option: MASTER_PRIV_FILE + expected: /home/taler-exchange-offline/.local/share/taler/exchange/offline-keys/master.priv + - option: SECM_TOFU_FILE + expected: /home/taler-exchange-offline/.local/share/taler/exchange/offline-keys/secm_tofus.pub + register: regional_currency_exchange_reset_offline_paths + changed_when: false + +- name: Read the configured online key paths + ansible.builtin.command: + argv: + - taler-exchange-config + - -s + - "{{ item.section }}" + - -f + - -o + - "{{ item.option }}" + loop: + - section: taler-exchange-secmod-rsa + option: KEY_DIR + expected: /var/lib/taler-exchange/secmod-rsa/keys + - section: taler-exchange-secmod-rsa + option: SM_PRIV_KEY + expected: /var/lib/taler-exchange/secmod-rsa/secmod-private-key + - section: taler-exchange-secmod-cs + option: KEY_DIR + expected: /var/lib/taler-exchange/secmod-cs/keys + - section: taler-exchange-secmod-cs + option: SM_PRIV_KEY + expected: /var/lib/taler-exchange/secmod-cs/secmod-private-key + - section: taler-exchange-secmod-eddsa + option: KEY_DIR + expected: /var/lib/taler-exchange/secmod-eddsa/keys + - section: taler-exchange-secmod-eddsa + option: SM_PRIV_KEY + expected: /var/lib/taler-exchange/secmod-eddsa/secmod-private-key + register: regional_currency_exchange_reset_online_paths + changed_when: false + +- name: Validate the exchange database reset target + ansible.builtin.assert: + that: + - (regional_currency_exchange_reset_database.stdout | trim) == "postgres:///exchange" + fail_msg: >- + Refusing to reset an exchange database outside the role-managed + postgres:///exchange configuration. + +- name: Validate the offline key reset targets + ansible.builtin.assert: + that: + - (item.stdout | trim) == item.item.expected + fail_msg: >- + Refusing to delete unexpected offline key path {{ item.stdout | trim }}. + loop: "{{ regional_currency_exchange_reset_offline_paths.results }}" + loop_control: + label: "{{ item.item.option }}" + +- name: Validate the online key reset targets + ansible.builtin.assert: + that: + - (item.stdout | trim) == item.item.expected + fail_msg: >- + Refusing to delete unexpected online key path {{ item.stdout | trim }}. + loop: "{{ regional_currency_exchange_reset_online_paths.results }}" + loop_control: + label: "{{ item.item.section }} {{ item.item.option }}" + +- name: Confirm the destructive exchange reset + ansible.builtin.pause: + prompt: >- + This permanently deletes the exchange database, all exchange keys, and + all existing coins and reserves. Type + "DANGEROUSLY RESET EXCHANGE {{ regional_currency_domain }}" to continue + echo: true + register: regional_currency_exchange_reset_confirmation + +- name: Require the exact exchange reset confirmation + ansible.builtin.assert: + that: + - >- + regional_currency_exchange_reset_confirmation.user_input + == "DANGEROUSLY RESET EXCHANGE " ~ regional_currency_domain + fail_msg: Exchange reset cancelled because the confirmation did not match. + +- name: Disable and stop exchange offline maintenance + ansible.builtin.systemd_service: + name: taler-exchange-offline.timer + enabled: false + state: stopped + +- name: Stop exchange offline maintenance + ansible.builtin.systemd_service: + name: taler-exchange-offline.service + state: stopped + +- name: Stop the merchant during exchange reset + ansible.builtin.systemd_service: + name: taler-merchant.target + state: stopped + +- name: Stop the exchange + ansible.builtin.systemd_service: + name: taler-exchange.target + state: stopped + +- name: Drop the exchange database + become: true + become_user: postgres + ansible.builtin.command: + argv: + - dropdb + - --if-exists + - --force + - exchange + changed_when: true + +- name: Delete offline exchange key material + ansible.builtin.file: + path: "{{ item.stdout | trim }}" + state: absent + loop: "{{ regional_currency_exchange_reset_offline_paths.results }}" + loop_control: + label: "{{ item.item.option }}" + +- name: Delete online exchange key material + ansible.builtin.file: + path: "{{ item.stdout | trim }}" + state: absent + loop: "{{ regional_currency_exchange_reset_online_paths.results }}" + loop_control: + label: "{{ item.item.section }} {{ item.item.option }}" + +- name: Delete generated exchange configuration + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - /etc/taler-exchange/conf.d/setup.conf + - "/etc/taler-exchange/conf.d/{{ regional_currency_currency }}-coins.conf" + - /etc/taler-merchant/conf.d/setup.conf + +- name: Delete exchange provisioning markers + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - "{{ regional_currency_state_directory }}/exchange-account-setup" + - "{{ regional_currency_state_directory }}/conversion-account-setup" + - "{{ regional_currency_state_directory }}/exchange-signed-coin-config" + - /var/lib/taler-exchange-offline-maintenance/fee-year