this_dir="$(cd "$(dirname "$0")" && pwd)" exists() { command -v "$1" >/dev/null 2>&1; } # Check if command exists (is in path). if [[ "$(whoami)" == "root" ]]; then # find out who the normal user is based on $this_dir, # since $this_dir will be somewhere within /Users//... or /home//... # and then switch to that user, using exec to avoid forking # Cross-platform user detection user="" # Use uname to detect OS and choose appropriate stat command if command -v stat >/dev/null 2>&1; then if [[ "$(uname)" == "Darwin" ]]; then user="$(stat -f '%Su' "${this_dir}" 2>/dev/null || true)" elif [[ "$(uname)" == "Linux" ]]; then user="$(stat -c '%U' "${this_dir}" 2>/dev/null || true)" else >&2 echo "ERROR: Unsupported OS: $(uname)" exit 1 fi fi # Fallback method: Parse user from directory path if [[ -z "${user}" ]]; then # Extract username from path like /Users/username/... or /home/username/... if [[ "$(uname)" == "Darwin" ]] && [[ "${this_dir}" =~ ^/Users/([^/]+) ]]; then user="${BASH_REMATCH[1]}" elif [[ "$(uname)" == "Linux" ]] && [[ "${this_dir}" =~ ^/home/([^/]+) ]]; then user="${BASH_REMATCH[1]}" fi fi if [[ -n "${user}" ]]; then if id "${user}" >/dev/null 2>&1; then # exec to avoid forking, # and make sure to use a login shell # propagate all arguments using "$@" # The command structure: su - user -c "command" -- args # - su - user: switch to user with login shell # - -c "command": execute the quoted command # - -- args: separator, everything after -- goes to the command # - "$@": all original script arguments # - \"\$@\": escaped version for the inner command string exec su - "${user}" -c "exec bash -l $0 \"\$@\"" -- "$@" >&2 echo "ERROR: Failed to switch to user ${user}" else >&2 echo "ERROR: Detected user ${user} does not exist" fi else >&2 echo "WARN: Failed to determine user from szecret dir: ${this_dir}" fi >&2 echo "ERROR: Refusing to run szecret as root" exit 1 fi if ! exists age ; then >&2 cat <&2 cat < ${szecret_data_dir}/id.pub mv private "${private_key_dir}/\${pubkey}" EOF exit 1 fi key_id="$(cat "${public_key}" 2>/dev/null || true)" if [[ -z "$key_id" ]]; then >&2 echo "WARN: Invalid public key: ${public_key}" exit 1 fi private_key="${SZECRET_PRIVATE_KEY:-${private_key_dir}/${key_id}}" file_for() { local name="$1" echo "${store}/${name}.age" } local_key_name() { local local_store_name="$1" echo "local-key-${local_store_name}" } ensure_local_store() { local local_store_name="$1" local local_key_name local local_key_file local_key_name="$(local_key_name "${local_store_name}")" local_key_file="$(file_for "${local_key_name}")" if ! [[ -r "${local_key_file}" ]]; then >&2 echo "ERROR: Local store '${local_store_name}' does not exist (key: ${local_key_name})" exit 1 fi } get_local_private_key() { local local_store_name="$1" local local_key_name local_key_name="$(local_key_name "${local_store_name}")" "${this_dir}/get.sh" "${local_key_name}" } exists() { command -v "$1" >/dev/null 2>&1; } # Check if command exists (is in path). pathadd() { [[ -d "$1" ]] && PATH="$1"$(echo ":$PATH:" | sed "s|:$1:|:|" | sed 's|:$||'); } ensure_age() { if exists age ; then return fi local bin="${this_dir}/.bin" pathadd "${bin}" if [[ -x "${bin}/age" ]]; then return fi local os local arch os="$(uname)" os=${os,,} arch="$(uname -m)" if [[ "$arch" == "aarch64" ]]; then arch="arm64" elif [[ "$arch" == "x86_64" ]]; then arch="amd64" fi tar="$bin/age.tar.gz" curl -LSsfo "$tar" "https://dl.filippo.io/age/latest?for=${os}/${arch}" tar xf "$tar" -C "$bin" --strip-components=1 } private_key_is_setup() { check_legacy_private_key_migration if ! [[ -n "${private_key}" && -r "${private_key}" ]]; then >&2 echo "WARN: Missing age key: ${private_key}" fi [[ -r "${private_key}" ]] } ensure_private_key() { mkdir -p "${private_key_dir}" if private_key_is_setup; then return fi if [ ! -t 0 ]; then >&2 echo "ERROR: szecret private key not installed: ${public_key}" exit 1 fi ensure_age echo -n "Enter age private key ${public_key}: " read -s private_key_value echo local derived derived="$(age-keygen -y <<<"${private_key_value}" 2>/dev/null || true)" if [[ "$derived" != "$(cat "${public_key}")" ]]; then >&2 echo "ERROR: Invalid private key (does not match ${public_key})" exit 1 fi mkdir -p "${private_key_dir}" local target="${private_key_dir}/${derived}" echo "${private_key_value}" > "${target}" chmod 600 "${target}" 2>/dev/null || true } check_legacy_private_key_migration() { local f="${private_key_dir}/.id" if [[ ! -r "${f}" ]]; then return fi local pub pub="$(age-keygen -y "$f" 2>/dev/null || true)" if [[ -z "$pub" || "$pub" != age1* ]]; then >&2 echo "WARN: stray file in ${private_key_dir} that does not belong there: $f" return fi local target="${private_key_dir}/${pub}" mv "$f" "$target" } age_encrypt() { local pubkeyfile="$1" local outputfile="$2" cat | age --encrypt --recipients-file "${pubkeyfile}" --output "${outputfile}" } age_decrypt() { local privatekeyfile="$1" local inputfile="$2" age --decrypt --identity "${privatekeyfile}" "${inputfile}" } # Local store metadata helpers write_local_metadata() { local metadata_file="$1" local store_name="$2" local pubkey="$3" cat > "${metadata_file}" <&2 echo "ERROR: Invalid metadata format in ${metadata_file}" >&2 echo "Run 'szecret create-local ' to recreate" return 1 fi } get_local_public_key() { local local_store_name="$1" local local_private_key local_private_key="$(get_local_private_key "${local_store_name}")" age-keygen -y <<<"${local_private_key}" } verify_local_pubkey() { local expected_pubkey="$1" local actual_pubkey="$2" local store_name="$3" if [[ "${expected_pubkey}" != "${actual_pubkey}" ]]; then >&2 echo "ERROR: Public key mismatch for store '${store_name}'" >&2 echo " Expected: ${expected_pubkey}" >&2 echo " Actual: ${actual_pubkey}" return 1 fi }