commit 05b1ea59fec1a8124e304116c73ad7ea9a41da0d
parent 323ef8f4a478bab0da5bc08f29d62338ab4ee621
Author: Florian Dold <dold@taler.net>
Date: Sat, 5 Sep 2026 22:38:49 +0200
helpers: forward Ansible arguments and drop .sh suffixes
Diffstat:
27 files changed, 314 insertions(+), 242 deletions(-)
diff --git a/README b/README
@@ -22,9 +22,26 @@ $ gpg -d vault_pass.txt.gpg > vault_pass.txt
The canonical playbooks are run via shell scripts in the top-level
directory.
+The operational helpers pass arguments after their positional arguments
+directly to `ansible-playbook`. For example:
+
+```
+$ ./deploy spec --check --diff
+$ ./sanction-check spec sanctions.txt --diff
+$ BORG_PASSPHRASE=... ./restore spec archive-name --verbose
+```
+
+Put the host/group first, followed by the sanction list or restore archive
+where required, then any Ansible options. `extract-borg-key` and
+`setup-pixel-borg` default to `spec` when called without arguments; supply
+an explicit target when passing options. Quote option values containing
+spaces as usual. Check-mode support depends on the playbook; restore
+explicitly rejects check mode. The `test` runner and `stage-activate` do
+not forward Ansible options.
+
### Local Podman deployment test
-Run `./test.sh` to build a Debian Trixie container, provision the complete test
+Run `./test` to build a Debian Trixie container, provision the complete test
deployment, repeat `setup.yml` with `--check --diff`, and exercise the main
systemd units and HTTP APIs. The script creates a disposable SSH key and
removes the container when it exits.
@@ -35,16 +52,16 @@ pregenerated DH parameters and a container-only Certbot substitute which
creates short-lived self-signed certificates: public ACME validation cannot
reach the local test domains. Neither shortcut is used by real deployments.
-### Main setup (restore.sh, deploy.sh)
+### Main setup (restore, deploy)
Database restore is deliberately separate from normal deployment. It is a
-fresh-host recovery operation, not an idempotent part of `deploy.sh`, and it
+fresh-host recovery operation, not an idempotent part of `deploy`, and it
never chooses an archive implicitly. First list the Borg repository and
select the exact archive to restore:
```
$ BORG_PASSPHRASE=... borg list ssh://borg@pixel.taler-systems.com/~/spec-backup
-$ BORG_PASSPHRASE=... ./restore.sh spec 'archive-name-copied-from-list'
+$ BORG_PASSPHRASE=... ./restore spec 'archive-name-copied-from-list'
```
The first argument must be one exact inventory hostname (not a group or host
@@ -53,18 +70,18 @@ pattern). The destination PostgreSQL cluster must contain only the standard
`root/postgres-backup.sql.gz` artifact, rejects dumps made by a newer
PostgreSQL major version, restores all databases and roles, and verifies the
exchange database before reporting success. It does not deploy or start the
-application; inspect the recovered host and then run `deploy.sh` separately.
+application; inspect the recovered host and then run `deploy` separately.
If SQL restore or verification fails, the partially restored cluster is
considered tainted and must be reinitialized before another attempt. The
temporary database dump is removed, while a private error log is retained in
`/var/tmp/taler-postgres-restore-*.log` on the target for diagnosis.
-The "deploy.sh" script deploys the latest version of a system on a host.
+The "deploy" script deploys the latest version of a system on a host.
If you are root@rusty.taler-ops.ch, you may be able to:
```
-$ ./deploy.sh rusty
+$ ./deploy rusty
```
For TOPS production, replace the "rusty" with "spec" to use the actual secrets
@@ -89,12 +106,12 @@ for the staging exchange, where allowing repository users to sign staging
configuration is intentional. It must never be used for production, for an
exchange holding funds of value, or as the basis for any security assumption.
-### sanction-check.sh
+### sanction-check
This command imports and checks the latest sanction lists:
```
-$ ./sanction-check.sh $TARGET $LIST
+$ ./sanction-check $TARGET $LIST
```
where "$TARGET" is a host or group from "inventories/default" and $LIST
@@ -108,14 +125,14 @@ host and referenced from the exchange configuration.
NOTE: this should still be further automated.
-### remove-monitoring.sh
+### remove-monitoring
The legacy monitoring stack is no longer deployed by this repository. To
permanently remove its services, packages, configuration, local metrics data,
nginx site, certificate, and database user from an existing deployment, run:
```
-$ ./remove-monitoring.sh $DEPLOYMENT
+$ ./remove-monitoring $DEPLOYMENT
```
This is destructive. It does not back up the local Prometheus or Alloy data.
@@ -126,7 +143,7 @@ This is destructive. It does not back up the local Prometheus or Alloy data.
First run:
```
-$ ./extract-borg-key.sh $DEPLOYMENT
+$ ./extract-borg-key $DEPLOYMENT
```
The resulting SSH public key should be added to the borg-account
@@ -138,7 +155,7 @@ server-side (see admin-logs/pixel/03-borg.txt), start the daily
backups via:
```
-$ ./start-borg-backups.sh $DEPLOYMENT
+$ ./start-borg-backups $DEPLOYMENT
```
This will make a backup basically everything relevant to the
@@ -158,7 +175,7 @@ To run a backup "immediately" (instead of the daily regular
backups), use:
```
-$ ./backup.sh $DEPLOYMENT
+$ ./backup $DEPLOYMENT
```
@@ -168,7 +185,7 @@ This should be done via the 'reboot' playbook which can
be invoked via the
```
-$ ./reboot.sh $DEPLOYMENT
+$ ./reboot $DEPLOYMENT
```
script. The reboot playbook first stops all Taler services,
@@ -182,7 +199,7 @@ online cleanly.
With podman and ansible installed locally one can run:
```
-$ ./test.sh
+$ ./test
```
This will begin building the Containerfile in this repo, which is a Debian
@@ -274,7 +291,7 @@ never restores a backup.
### database_restore
Implements the guarded fresh-cluster restore used only by `playbooks/restore.yml`
-and `restore.sh`.
+and `restore`.
### devtesting
@@ -346,7 +363,7 @@ post_deployment_http_retries defaults to 60 (with a five-second request timeout
and one-second retry delay); systemd unit checks retain their shorter retry limit.
A failed deployment leaves applications stopped without removing their boot
-enablement. Inspect the failed task, correct the problem, then rerun deploy.sh.
+enablement. Inspect the failed task, correct the problem, then rerun deploy.
Do not reboot or manually start applications to bypass an unfinished migration.
Optional services whose provisioning is skipped are restored if they were active
when that deployment began. After a failed deployment, also inspect any optional
@@ -371,7 +388,7 @@ remain supported. No archives are moved automatically.
The backup script uses /root/.ssh/borg-config and leaves /root/.ssh/config intact.
A lock protects the entire backup operation. Install the updated backup script
-between backup runs: an already running older script does not acquire this lock. If cron, backup.sh or reboot.sh
+between backup runs: an already running older script does not acquire this lock. If cron, backup or reboot
encounters another backup, it exits nonzero (script status 75) without touching
that run's snapshot. Retry after the existing backup finishes; a reboot playbook
aborts before rebooting on this failure. Pruning and compaction are skipped after
@@ -381,7 +398,7 @@ an unsuccessful or warning-producing archive creation.
Run python3 contrib/tests/test_backup.py for isolated concurrency, failure,
cleanup and archive round-trip checks (requires Borg and Python Jinja2).
-The extended ./test.sh also exercises real systemd shutdown, package-start
+The extended ./test also exercises real systemd shutdown, package-start
suppression, a pre-2.20 Ansible dependency bootstrap, secret permissions,
devtesting revocation, preservation of unrelated configuration, failed deployment
and recovery. All fixtures run in the disposable container, never an inventory
diff --git a/backup b/backup
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+set -eu
+
+if [ -z "${1:-}" ]
+then
+ echo "Usage: $0 <host/group> [ansible-playbook options...]" >&2
+ exit 1
+fi
+
+target=$1
+shift
+
+ansible-playbook \
+ --inventory inventories/default \
+ --limit "$target" \
+ playbooks/backup.yml \
+ "$@"
+
+exit 0
diff --git a/backup.sh b/backup.sh
@@ -1,16 +0,0 @@
-#!/bin/sh
-
-set -eu
-
-if [ -z "${1:-}" ]
-then
- echo "Call with 'spec' or another host/group to select target"
- exit 1
-fi
-
-ansible-playbook \
- --inventory inventories/default \
- --limit "$1" \
- playbooks/backup.yml
-
-exit 0
diff --git a/contrib/ci/jobs/001-build/build.sh b/contrib/ci/jobs/001-build/build.sh
@@ -4,4 +4,4 @@ set -euo pipefail
# This job runs inside the disposable CI container. The shared runner provisions
# its nested container over the published SSH port and executes all regressions.
rm -f /etc/containers/storage.conf
-exec ./test.sh
+exec ./test
diff --git a/contrib/tests/test_deployment.py b/contrib/tests/test_deployment.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-"""Integration regressions for the disposable container created by test.sh."""
+"""Integration regressions for the disposable container created by test."""
import argparse
import json
import os
diff --git a/contrib/tests/test_upgrade_policy.py b/contrib/tests/test_upgrade_policy.py
@@ -12,7 +12,7 @@ TEMPLATE = Path(__file__).resolve().parents[2] / 'roles/common_packages/template
def verify_upgrade_policy_restoration(container, play):
- # This callback is only invoked in test.sh's disposable container.
+ # This callback is only invoked in test's disposable container.
container('sh', '-eu', '-c', '''
rm -f /usr/sbin/policy-rc.d
printf '#!/bin/sh\\nexit 0\\n' > /usr/sbin/taler-test-original-policy
diff --git a/deploy b/deploy
@@ -0,0 +1,19 @@
+#!/bin/sh
+set -eu
+
+if [ -z "${1:-}" ]
+then
+ echo "Usage: $0 <host/group> [ansible-playbook options...]" >&2
+ exit 1
+fi
+
+target=$1
+shift
+
+ansible-playbook -v \
+ --inventory inventories/default \
+ --limit "$target" \
+ playbooks/setup.yml \
+ "$@"
+
+exit 0
diff --git a/deploy.sh b/deploy.sh
@@ -1,15 +0,0 @@
-#!/bin/sh
-set -eu
-
-if [ -z "${1:-}" ]
-then
- echo "Call with 'spec' or another host/group to select target"
- exit 1
-fi
-
-ansible-playbook -v \
- --inventory inventories/default \
- --limit "$1" \
- playbooks/setup.yml
-
-exit 0
diff --git a/extract-borg-key b/extract-borg-key
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -eu
+
+case "${1:-}" in
+ -*)
+ echo "Usage: $0 [host/group [ansible-playbook options...]]" >&2
+ exit 1
+ ;;
+esac
+
+target=${1:-spec}
+if [ "$#" -gt 0 ]; then
+ shift
+fi
+
+ansible-playbook \
+ --inventory inventories/default \
+ --limit "$target" \
+ --user root \
+ playbooks/borg-ssh-export.yml \
+ "$@"
+cat borg.pub/*/root/.ssh/borg.pub
+rm -rf borg.pub/
+exit 0
diff --git a/extract-borg-key.sh b/extract-borg-key.sh
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-set -eu
-
-ansible-playbook \
- --inventory inventories/default \
- --limit "${1:-spec}" \
- --user root \
- playbooks/borg-ssh-export.yml
-cat borg.pub/*/root/.ssh/borg.pub
-rm -rf borg.pub/
-exit 0
diff --git a/playbooks/restore.yml b/playbooks/restore.yml
@@ -40,7 +40,7 @@
- database_restore_archive is match('^[A-Za-z0-9][A-Za-z0-9._:+-]*$')
- database_restore_controller_directory | length > 0
- lookup('ansible.builtin.env', 'BORG_PASSPHRASE') | length > 0
- fail_msg: Run restore.sh with a passphrase, host, and exact archive.
+ fail_msg: Run restore with a passphrase, host, and exact archive.
quiet: true
roles:
diff --git a/playbooks/setup.yml b/playbooks/setup.yml
@@ -13,7 +13,7 @@
that: not (enable_restore_backup | default(false) | bool)
fail_msg: >-
enable_restore_backup is no longer supported. Restore a fresh host
- with restore.sh before running the normal deployment.
+ with restore before running the normal deployment.
quiet: true
- name: "Fail if the deployment kind is not defined"
diff --git a/reboot b/reboot
@@ -0,0 +1,19 @@
+#!/bin/sh
+set -eu
+
+if [ -z "${1:-}" ]
+then
+ echo "Usage: $0 <host/group> [ansible-playbook options...]" >&2
+ exit 1
+fi
+
+target=$1
+shift
+
+ansible-playbook \
+ --inventory inventories/default \
+ --limit "$target" \
+ playbooks/reboot.yml \
+ "$@"
+
+exit 0
diff --git a/reboot.sh b/reboot.sh
@@ -1,15 +0,0 @@
-#!/bin/sh
-set -eu
-
-if [ -z "${1:-}" ]
-then
- echo "Call with 'spec' or another host/group to select target"
- exit 1
-fi
-
-ansible-playbook \
- --inventory inventories/default \
- --limit "$1" \
- playbooks/reboot.yml
-
-exit 0
diff --git a/remove-monitoring b/remove-monitoring
@@ -0,0 +1,19 @@
+#!/bin/sh
+set -eu
+
+if [ -z "${1:-}" ]
+then
+ echo "Usage: $0 <host/group> [ansible-playbook options...]" >&2
+ exit 1
+fi
+
+target=$1
+shift
+
+ansible-playbook \
+ --inventory inventories/default \
+ --limit "$target" \
+ playbooks/remove-monitoring.yml \
+ "$@"
+
+exit 0
diff --git a/remove-monitoring.sh b/remove-monitoring.sh
@@ -1,15 +0,0 @@
-#!/bin/sh
-set -eu
-
-if [ -z "${1:-}" ]
-then
- echo "Call with 'spec' or another host/group to select target"
- exit 1
-fi
-
-ansible-playbook \
- --inventory inventories/default \
- --limit "$1" \
- playbooks/remove-monitoring.yml
-
-exit 0
diff --git a/restore b/restore
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+set -eu
+
+usage()
+{
+ echo "Usage: BORG_PASSPHRASE=... $0 <inventory-host> <archive-name> [ansible-playbook options...]" >&2
+}
+
+if [ -z "${BORG_PASSPHRASE:-}" ]
+then
+ echo "BORG_PASSPHRASE must be set." >&2
+ echo "It can be found encrypted in admin-log.git under the target host." >&2
+ exit 1
+fi
+
+if [ "$#" -lt 2 ]
+then
+ usage
+ exit 1
+fi
+
+restore_target=$1
+restore_archive=$2
+shift 2
+
+case "$restore_target" in
+ ''|*[!A-Za-z0-9._-]*)
+ echo "The target must be one exact inventory hostname." >&2
+ exit 1
+ ;;
+esac
+
+case "$restore_archive" in
+ latest|LATEST)
+ echo "Select an exact Borg archive; 'latest' is not accepted." >&2
+ exit 1
+ ;;
+ ''|*[!A-Za-z0-9._:+-]*)
+ echo "Invalid Borg archive name: $restore_archive" >&2
+ exit 1
+ ;;
+esac
+
+for restore_command in ansible-playbook borg gzip zgrep
+do
+ if ! command -v "$restore_command" >/dev/null 2>&1
+ then
+ echo "Required command not found: $restore_command" >&2
+ exit 1
+ fi
+done
+
+restore_workdir=$(mktemp -d "${TMPDIR:-/tmp}/taler-database-restore.XXXXXX")
+chmod 700 "$restore_workdir"
+
+cleanup()
+{
+ rm -f -- "$restore_workdir/postgres-backup.sql.gz"
+ rmdir -- "$restore_workdir" 2>/dev/null || true
+}
+trap cleanup EXIT
+trap 'exit 1' HUP INT TERM
+
+export TALER_RESTORE_TARGET="$restore_target"
+export TALER_RESTORE_ARCHIVE="$restore_archive"
+export TALER_RESTORE_CONTROLLER_DIRECTORY="$restore_workdir"
+
+echo "Restoring $restore_target from exact archive $restore_archive"
+
+ansible-playbook -v \
+ --inventory inventories/default \
+ --limit "$restore_target" \
+ playbooks/restore.yml \
+ "$@"
+
+echo "Database restore and verification completed successfully."
+echo "Inspect the host, then deploy the application with: ./deploy $restore_target"
diff --git a/restore.sh b/restore.sh
@@ -1,76 +0,0 @@
-#!/bin/sh
-
-set -eu
-
-usage()
-{
- echo "Usage: BORG_PASSPHRASE=... $0 <inventory-host> <archive-name>" >&2
-}
-
-if [ -z "${BORG_PASSPHRASE:-}" ]
-then
- echo "BORG_PASSPHRASE must be set." >&2
- echo "It can be found encrypted in admin-log.git under the target host." >&2
- exit 1
-fi
-
-if [ "$#" -ne 2 ]
-then
- usage
- exit 1
-fi
-
-restore_target=$1
-restore_archive=$2
-
-case "$restore_target" in
- ''|*[!A-Za-z0-9._-]*)
- echo "The target must be one exact inventory hostname." >&2
- exit 1
- ;;
-esac
-
-case "$restore_archive" in
- latest|LATEST)
- echo "Select an exact Borg archive; 'latest' is not accepted." >&2
- exit 1
- ;;
- ''|*[!A-Za-z0-9._:+-]*)
- echo "Invalid Borg archive name: $restore_archive" >&2
- exit 1
- ;;
-esac
-
-for restore_command in ansible-playbook borg gzip zgrep
-do
- if ! command -v "$restore_command" >/dev/null 2>&1
- then
- echo "Required command not found: $restore_command" >&2
- exit 1
- fi
-done
-
-restore_workdir=$(mktemp -d "${TMPDIR:-/tmp}/taler-database-restore.XXXXXX")
-chmod 700 "$restore_workdir"
-
-cleanup()
-{
- rm -f -- "$restore_workdir/postgres-backup.sql.gz"
- rmdir -- "$restore_workdir" 2>/dev/null || true
-}
-trap cleanup EXIT
-trap 'exit 1' HUP INT TERM
-
-export TALER_RESTORE_TARGET="$restore_target"
-export TALER_RESTORE_ARCHIVE="$restore_archive"
-export TALER_RESTORE_CONTROLLER_DIRECTORY="$restore_workdir"
-
-echo "Restoring $restore_target from exact archive $restore_archive"
-
-ansible-playbook -v \
- --inventory inventories/default \
- --limit "$restore_target" \
- playbooks/restore.yml
-
-echo "Database restore and verification completed successfully."
-echo "Inspect the host, then deploy the application with: ./deploy.sh $restore_target"
diff --git a/roles/borg-start/tasks/main.yml b/roles/borg-start/tasks/main.yml
@@ -21,7 +21,7 @@
- name: Fail if we do not have an SSH key for the backup server
fail:
- msg: "You need to first run extract-borg-key.sh"
+ msg: "You need to first run extract-borg-key"
when: not have_ssh_key.stat.exists
- name: Ensure /root/bin/ directory exists
diff --git a/sanction-check b/sanction-check
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -eu
+
+if [ -z "${1:-}" ]; then
+ echo "Usage: $0 <host/group> <sanction-list> [ansible-playbook options...]" >&2
+ exit 1
+fi
+
+if [ -z "${2:-}" ]; then
+ echo "Pass sanction list as 2nd argument"
+ exit 1
+fi
+
+if [ ! -f "$2" ]; then
+ echo "Sanction list '$2' not found"
+ exit 1
+fi
+
+target=$1
+sanction_list=$2
+shift 2
+
+ansible-playbook \
+ --extra-vars "sanction_list=$sanction_list" \
+ --verbose \
+ --limit "$target" \
+ --inventory inventories/default \
+ playbooks/sanctionlist-check.yml \
+ "$@"
diff --git a/sanction-check.sh b/sanction-check.sh
@@ -1,25 +0,0 @@
-#!/bin/bash
-
-set -eu
-
-if [ -z "${1:-}" ]; then
- echo "Call with 'spec' or other host/group as target"
- exit 1
-fi
-
-if [ -z "${2:-}" ]; then
- echo "Pass sanction list as 2nd argument"
- exit 1
-fi
-
-if [ ! -f "$2" ]; then
- echo "Sanction list '$2' not found"
- exit 1
-fi
-
-ansible-playbook \
- --extra-vars "sanction_list=$2" \
- --verbose \
- --limit "$1" \
- --inventory inventories/default \
- playbooks/sanctionlist-check.yml
diff --git a/setup-pixel-borg b/setup-pixel-borg
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+set -eu
+
+if [ -z "${PIXEL_BORG_KEY:-}" ]
+then
+ echo "You need to set the PIXEL_BORG_KEY in your environment before running this script (see admin-log/pixel/03-borg.txt)"
+ exit 1
+fi
+
+case "${1:-}" in
+ -*)
+ echo "Usage: $0 [host/group [ansible-playbook options...]]" >&2
+ exit 1
+ ;;
+esac
+
+target=${1:-spec}
+if [ "$#" -gt 0 ]; then
+ shift
+fi
+
+ansible-playbook \
+ --extra-vars "pixel_borg_key={{ lookup('env', 'PIXEL_BORG_KEY') }}" \
+ --inventory inventories/default \
+ --limit "$target" \
+ --user root \
+ playbooks/pixel-borg.yml \
+ "$@"
+mv borg-repokey/*/home/borg/borg-repo.key .
+rm -rf borg-repokey/
+echo "Make sure to back up the borg-repo.key to admin-log/pixel/borg-repo.key"
+exit 0
diff --git a/setup-pixel-borg.sh b/setup-pixel-borg.sh
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-set -eu
-
-if [ -z "${PIXEL_BORG_KEY:-}" ]
-then
- echo "You need to set the PIXEL_BORG_KEY in your environment before running this script (see admin-log/pixel/03-borg.txt)"
- exit 1
-fi
-ansible-playbook \
- --extra-vars "pixel_borg_key={{ lookup('env', 'PIXEL_BORG_KEY') }}" \
- --inventory inventories/default \
- --limit "${1:-spec}" \
- --user root \
- playbooks/pixel-borg.yml
-mv borg-repokey/*/home/borg/borg-repo.key .
-rm -rf borg-repokey/
-echo "Make sure to back up the borg-repo.key to admin-log/pixel/borg-repo.key"
-exit 0
diff --git a/stage-activate.sh b/stage-activate
diff --git a/start-borg-backups b/start-borg-backups
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+set -eu
+
+if [ -z "${BORG_PASSPHRASE:-}" ]
+then
+ echo "You need to set the BORG_PASSPHRASE in your environment before running this script!"
+ exit 1
+fi
+
+if [ -z "${1:-}" ]
+then
+ echo "Usage: $0 <host/group> [ansible-playbook options...]" >&2
+ exit 1
+fi
+
+target=$1
+shift
+
+ansible-playbook \
+ --verbose \
+ --extra-vars "borg_passphrase={{ lookup('env', 'BORG_PASSPHRASE') }}" \
+ --inventory inventories/default \
+ --limit "$target" \
+ playbooks/borg-start.yml \
+ "$@"
+
+exit 0
diff --git a/start-borg-backups.sh b/start-borg-backups.sh
@@ -1,23 +0,0 @@
-#!/bin/bash
-
-set -eu
-
-if [ -z "${BORG_PASSPHRASE:-}" ]
-then
- echo "You need to set the BORG_PASSPHRASE in your environment before running this script!"
- exit 1
-fi
-
-if [ -z "${1:-}" ]
-then
- echo "Call with 'spec' or another host/group to select target"
- exit 1
-fi
-ansible-playbook \
- --verbose \
- --extra-vars "borg_passphrase={{ lookup('env', 'BORG_PASSPHRASE') }}" \
- --inventory inventories/default \
- --limit "${1:-spec}" \
- playbooks/borg-start.yml
-
-exit 0
diff --git a/test.sh b/test