#!/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 < -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