Files
nix-config/scripts/helpers.sh
2025-05-12 23:25:39 -03:00

180 lines
5.6 KiB
Bash

#!/usr/bin/env bash
set -eo pipefail
### UX helpers
function red() {
echo -e "\x1B[31m[!] $1 \x1B[0m"
if [ -n "${2-}" ]; then
echo -e "\x1B[31m[!] $($2) \x1B[0m"
fi
}
function green() {
echo -e "\x1B[32m[+] $1 \x1B[0m"
if [ -n "${2-}" ]; then
echo -e "\x1B[32m[+] $($2) \x1B[0m"
fi
}
function blue() {
echo -e "\x1B[34m[*] $1 \x1B[0m"
if [ -n "${2-}" ]; then
echo -e "\x1B[34m[*] $($2) \x1B[0m"
fi
}
function yellow() {
echo -e "\x1B[33m[*] $1 \x1B[0m"
if [ -n "${2-}" ]; then
echo -e "\x1B[33m[*] $($2) \x1B[0m"
fi
}
# Ask yes or no, with yes being the default
function yes_or_no() {
echo -en "\x1B[34m[?] $* [y/n] (default: y): \x1B[0m"
while true; do
read -rp "" yn
yn=${yn:-y}
case $yn in
[Yy]*) return 0 ;;
[Nn]*) return 1 ;;
esac
done
}
# Ask yes or no, with no being the default
function no_or_yes() {
echo -en "\x1B[34m[?] $* [y/n] (default: n): \x1B[0m"
while true; do
read -rp "" yn
yn=${yn:-n}
case $yn in
[Yy]*) return 0 ;;
[Nn]*) return 1 ;;
esac
done
}
### SOPS helpers
nix_secrets_dir=${NIX_SECRETS_DIR:-"$(dirname "${BASH_SOURCE[0]}")/../../nix-secrets"}
SOPS_FILE="${nix_secrets_dir}/.sops.yaml"
# Updates the .sops.yaml file with a new host or user age key.
function sops_update_age_key() {
field="$1"
keyname="$2"
key="$3"
if [ ! "$field" == "hosts" ] && [ ! "$field" == "users" ]; then
red "Invalid field passed to sops_update_age_key. Must be either 'hosts' or 'users'."
exit 1
fi
if [[ -n $(yq ".keys.${field}[] | select(anchor == \"$keyname\")" "${SOPS_FILE}") ]]; then
green "Updating existing ${keyname} key"
yq -i "(.keys.${field}[] | select(anchor == \"$keyname\")) = \"$key\"" "$SOPS_FILE"
else
green "Adding new ${keyname} key"
yq -i ".keys.$field += [\"$key\"] | .keys.${field}[-1] anchor = \"$keyname\"" "$SOPS_FILE"
fi
}
# Adds the user and host to the shared.yaml creation rules
function sops_add_shared_creation_rules() {
u="\"$1_$2\"" # quoted user_host for yaml
h="\"$2\"" # quoted hostname for yaml
shared_selector='.creation_rules[] | select(.path_regex == "shared\.yaml$")'
if [[ -n $(yq "$shared_selector" "${SOPS_FILE}") ]]; then
echo "BEFORE"
cat "${SOPS_FILE}"
if [[ -z $(yq "$shared_selector.key_groups[].age[] | select(alias == $h)" "${SOPS_FILE}") ]]; then
green "Adding $u and $h to shared.yaml rule"
# NOTE: Split on purpose to avoid weird file corruption
yq -i "($shared_selector).key_groups[].age += [$u, $h]" "$SOPS_FILE"
yq -i "($shared_selector).key_groups[].age[-2] alias = $u" "$SOPS_FILE"
yq -i "($shared_selector).key_groups[].age[-1] alias = $h" "$SOPS_FILE"
fi
else
red "shared.yaml rule not found"
fi
}
# Adds the user and host to the host.yaml creation rules
function sops_add_host_creation_rules() {
host="$2" # hostname for selector
h="\"$2\"" # quoted hostname for yaml
u="\"$1_$2\"" # quoted user_host for yaml
w="\"$(whoami)_$(hostname)\"" # quoted whoami_hostname for yaml
n="\"$(hostname)\"" # quoted hostname for yaml
host_selector=".creation_rules[] | select(.path_regex | contains(\"${host}\.yaml\"))"
if [[ -z $(yq "$host_selector" "${SOPS_FILE}") ]]; then
green "Adding new host file creation rule"
yq -i ".creation_rules += {\"path_regex\": \"${host}\\.yaml$\", \"key_groups\": [{\"age\": [$u, $h]}]}" "$SOPS_FILE"
# Add aliases one by one
yq -i "($host_selector).key_groups[].age[0] alias = $u" "$SOPS_FILE"
yq -i "($host_selector).key_groups[].age[1] alias = $h" "$SOPS_FILE"
yq -i "($host_selector).key_groups[].age[2] alias = $w" "$SOPS_FILE"
yq -i "($host_selector).key_groups[].age[3] alias = $n" "$SOPS_FILE"
fi
}
# Adds the user and host to the shared.yaml and host.yaml creation rules
function sops_add_creation_rules() {
user="$1"
host="$2"
sops_add_shared_creation_rules "$user" "$host"
sops_add_host_creation_rules "$user" "$host"
}
age_secret_key=""
# Generate a user age key, update the .sops.yaml entries, and return the key in age_secret_key
# args: user, hostname
function sops_generate_user_age_key() {
target_user="$1"
target_hostname="$2"
key_name="${target_user}_${target_hostname}"
green "Age key does not exist. Generating."
user_age_key=$(age-keygen)
readarray -t entries <<<"$user_age_key"
age_secret_key=${entries[2]}
public_key=$(echo "${entries[1]}" | rg key: | cut -f2 -d: | xargs)
green "Generated age key for ${key_name}"
# Place the anchors into .sops.yaml so other commands can reference them
sops_update_age_key "users" "$key_name" "$public_key"
sops_add_creation_rules "${target_user}" "${target_hostname}"
# "return" key so it can be used by caller
export age_secret_key
}
function sops_setup_user_age_key() {
target_user="$1"
target_hostname="$2"
secret_file="${nix_secrets_dir}/sops/${target_hostname}.yaml"
config="${nix_secrets_dir}/.sops.yaml"
# If the secret file doesn't exist, it means we're generating a new user key as well
if [ ! -f "$secret_file" ]; then
green "Host secret file does not exist. Creating $secret_file"
sops_generate_user_age_key "${target_user}" "${target_hostname}"
mkdir -p "$(dirname "$secret_file")"
echo "{}" >"$secret_file"
sops --config "$config" -e "$secret_file" >"$secret_file.enc"
mv "$secret_file.enc" "$secret_file"
fi
if ! sops --config "$config" -d --extract '["keys]["age"]' "$secret_file" >/dev/null 2>&1; then
if [ -z "$age_secret_key" ]; then
sops_generate_user_age_key "${target_user}" "${target_hostname}"
fi
# shellcheck disable=SC2116,SC2086
sops --config "$config" --set "$(echo '["keys"]["age"] "'$age_secret_key'"')" "$secret_file"
else
green "Age key already exists for ${target_hostname}"
fi
}