bootstrap.yml prepares a host as an admin via -u; new-deploy-key.sh makes the key pair and refuses to write inside the repo. CI deploys refuse the bootstrap playbook, since the dispatch API ignores the dropdown. Co-Authored-By: Claude Opus 5.5 <[email protected]>
98 lines
3.2 KiB
Bash
Executable File
98 lines
3.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Generate the Ansible deploy key pair.
|
|
#
|
|
# scripts/new-deploy-key.sh # ~/.ssh/deploy_key
|
|
# scripts/new-deploy-key.sh ~/.ssh/other # somewhere else
|
|
# scripts/new-deploy-key.sh --force # replace an existing key
|
|
# scripts/new-deploy-key.sh --print-secret # also print the value for CI
|
|
#
|
|
# Prints the paths and the public key. It does not print the private key unless
|
|
# you ask for it: load-ssh-key.sh keeps key material off stdout and out of logs
|
|
# and the process list, and this script holds the same line.
|
|
#
|
|
# The public key belongs in the deploy user's authorized_keys, which
|
|
# ansible/playbooks/bootstrap.yml does for you. The private key belongs in the
|
|
# CI secret SSH_PRIVATE_KEY_B64. See docs/bootstrap.md and docs/secrets.md.
|
|
set -euo pipefail
|
|
umask 077
|
|
|
|
log() { printf 'new-deploy-key: %s\n' "$*" >&2; }
|
|
die() { log "$*"; exit 1; }
|
|
|
|
force=0
|
|
print_secret=0
|
|
key_path=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--force) force=1 ;;
|
|
--print-secret) print_secret=1 ;;
|
|
-h|--help) sed -n '3,9p' "$0" | sed 's/^#[[:space:]]\{0,1\}//'; exit 0 ;;
|
|
-*) die "unknown option: $1" ;;
|
|
*)
|
|
[ -z "$key_path" ] || die "only one key path may be given"
|
|
key_path="$1"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
key_path="${key_path:-$HOME/.ssh/deploy_key}"
|
|
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
key_dir="$(dirname "$key_path")"
|
|
[ -d "$key_dir" ] || die "no such directory: $key_dir"
|
|
key_dir="$(cd "$key_dir" && pwd)"
|
|
|
|
# gitleaks scans the working tree *and* the full history, and a key committed
|
|
# once stays leaked after the file is deleted. Refuse before that can happen.
|
|
case "$key_dir/" in
|
|
"$repo_root"/*) die "refusing to write a private key inside the repository: $repo_root" ;;
|
|
esac
|
|
|
|
if [ -e "$key_path" ] && [ "$force" -ne 1 ]; then
|
|
die "$key_path already exists. Replacing it locks out every host that still
|
|
trusts the old key, so pass --force if that is what you want."
|
|
fi
|
|
# An `if`, not `[ ... ] && rm ...`: an AND-list whose test fails returns
|
|
# non-zero, which under `set -e` is a trap this repository has been bitten by
|
|
# before -- see the same note in .gitea/workflows/deploy.yml.
|
|
if [ "$force" -eq 1 ]; then
|
|
rm -f "$key_path" "$key_path.pub"
|
|
fi
|
|
|
|
# ed25519, and no passphrase: nothing in CI or in a playbook run can type one,
|
|
# and an encrypted key fails later with an authentication error that says
|
|
# nothing about the passphrase.
|
|
ssh-keygen -t ed25519 -f "$key_path" -N "" -C "ansible-deploy@$(basename "$repo_root")" >/dev/null
|
|
|
|
printf 'private key %s\n' "$key_path"
|
|
printf 'public key %s\n\n' "$key_path.pub"
|
|
cat "$key_path.pub"
|
|
|
|
cat <<INSTRUCTIONS
|
|
|
|
Next:
|
|
|
|
1. Put it on the host, and create everything else it needs:
|
|
|
|
cd ansible
|
|
ansible-playbook playbooks/bootstrap.yml -u <admin-user> -k -K \\
|
|
-e deploy_pubkey_file=$key_path.pub
|
|
|
|
2. Store the private key as the CI secret SSH_PRIVATE_KEY_B64 (where, per
|
|
platform, is in docs/secrets.md):
|
|
|
|
base64 -w0 < $key_path
|
|
|
|
3. Confirm it works before any deploy: the connectivity workflow on GitHub
|
|
or Gitea, the ansible:ping job on GitLab.
|
|
INSTRUCTIONS
|
|
|
|
if [ "$print_secret" -eq 1 ]; then
|
|
printf '\nSSH_PRIVATE_KEY_B64:\n'
|
|
base64 -w0 < "$key_path"
|
|
printf '\n'
|
|
fi
|