#!/bin/sh set -eu umask 077 if [ "$#" -ne 2 ]; then echo "Usage: $0 ENV_FILE VAPID_SUBJECT" >&2 exit 1 fi ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) env_file=$1 subject=$2 if [ ! -f "$env_file" ]; then echo "Environment file does not exist: $env_file" >&2 exit 1 fi if [ "$(stat -c '%a' "$env_file")" != 600 ]; then echo "Environment file must have mode 0600: $env_file" >&2 exit 1 fi case "$subject" in mailto:?* | https://?*) ;; *) echo "VAPID_SUBJECT must be a non-empty mailto: or https:// URI without whitespace." >&2 exit 1 ;; esac case "$subject" in *' '*) echo "VAPID_SUBJECT must be a non-empty mailto: or https:// URI without whitespace." >&2 exit 1 ;; esac if printf '%s' "$subject" | LC_ALL=C grep -q '[[:space:]]'; then echo "VAPID_SUBJECT must be a non-empty mailto: or https:// URI without whitespace." >&2 exit 1 fi for command in awk chmod date docker grep mktemp rm stat; do if ! command -v "$command" >/dev/null 2>&1; then echo "Required command is unavailable: $command" >&2 exit 1 fi done for key in \ WEB_PUSH_VAPID_PUBLIC_KEY \ WEB_PUSH_VAPID_PRIVATE_KEY \ WEB_PUSH_VAPID_SUBJECT; do key_count=$( awk -F= -v key="$key" '$1 == key { count++ } END { print count + 0 }' \ "$env_file" ) if [ "$key_count" -ne 1 ]; then echo "Environment must contain exactly one $key entry before VAPID generation." >&2 exit 1 fi existing_value=$( awk -F= -v key="$key" ' $1 == key { print substr($0, index($0, "=") + 1) exit } ' "$env_file" ) if [ -n "$existing_value" ]; then echo "Refusing to rotate an existing VAPID identity: $key is already configured." >&2 exit 1 fi done unset existing_value env_dir=$(CDPATH='' cd -- "$(dirname -- "$env_file")" && pwd) temporary_dir=$(mktemp -d "$env_dir/.vapid-generation.XXXXXX") chmod 700 "$temporary_dir" raw_keys="$temporary_dir/generated.txt" values_file="$temporary_dir/values.env" generator_image=${WNH_VAPID_GENERATOR_IMAGE:-} owned_image=false run_id="$(date -u +%Y%m%d%H%M%S)-$$" generator_container="wnh-vapid-tool-$run_id" cleanup() { trap - EXIT HUP INT TERM rm -rf "$temporary_dir" docker rm --force "$generator_container" >/dev/null 2>&1 || true if [ "$owned_image" = true ]; then docker image rm "$generator_image" >/dev/null 2>&1 || true fi } cleanup_on_exit() { exit_status=$? cleanup exit "$exit_status" } trap cleanup_on_exit EXIT trap 'cleanup; exit 129' HUP trap 'cleanup; exit 130' INT trap 'cleanup; exit 143' TERM if [ -z "$generator_image" ]; then generator_image="who-need-help:vapid-tool-$run_id" owned_image=true docker build --quiet --target test --tag "$generator_image" "$ROOT" >/dev/null fi docker image inspect "$generator_image" >/dev/null docker run --rm \ --name "$generator_container" \ --network none \ --read-only \ --tmpfs /tmp:rw,noexec,nosuid,size=16m \ --entrypoint mix \ "$generator_image" \ generate.vapid.keys >"$raw_keys" chmod 600 "$raw_keys" if ! awk -F'"' -v subject="$subject" ' /^[[:space:]]*vapid_private_key:/ { private_count++ private_key = $2 } /^[[:space:]]*vapid_public_key:/ { public_count++ public_key = $2 } END { valid_private = private_key ~ /^[A-Za-z0-9_-]+$/ valid_public = public_key ~ /^[A-Za-z0-9_-]+$/ if (private_count != 1 || public_count != 1 || !valid_private || !valid_public || private_key == public_key) { exit 40 } print "WEB_PUSH_VAPID_PUBLIC_KEY=" public_key print "WEB_PUSH_VAPID_PRIVATE_KEY=" private_key print "WEB_PUSH_VAPID_SUBJECT=" subject } ' "$raw_keys" >"$values_file"; then echo "The pinned web_push_elixir generator returned an unexpected key format." >&2 exit 1 fi chmod 600 "$values_file" "$ROOT/scripts/set-env-values.sh" "$env_file" "$values_file" >/dev/null echo "Generated a new environment-specific VAPID identity without printing its keys." echo "Updated the single mode-0600 environment file: $env_file"