#!/usr/bin/env bash #==============================# # Global # #==============================# DEBUG=${DEBUG:-"false"} #==============================# # Injected # #==============================# hosts="@hosts@" help_root="@help@" #==============================# # Logging # #==============================# text_reset="\e[m" text_bold="\e[1m" text_dim="\e[2m" text_italic="\e[3m" text_underline="\e[4m" text_blink="\e[5m" text_highlight="\e[7m" text_hidden="\e[8m" text_strike="\e[9m" text_fg_red="\e[38;5;1m" text_fg_green="\e[38;5;2m" text_fg_yellow="\e[38;5;3m" text_fg_blue="\e[38;5;4m" text_fg_magenta="\e[38;5;5m" text_fg_cyan="\e[38;5;6m" text_fg_white="\e[38;5;7m" text_fg_dim="\e[38;5;8m" text_bg_red="\e[48;5;1m" text_bg_green="\e[48;5;2m" text_bg_yellow="\e[48;5;3m" text_bg_blue="\e[48;5;4m" text_bg_magenta="\e[48;5;5m" text_bg_cyan="\e[48;5;6m" text_bg_white="\e[48;5;7m" text_bg_dim="\e[48;5;8m" # Usage: log_info <message> log_info() { echo -e "${text_fg_blue}info${text_reset} $1" } # Usage: log_todo <message> log_todo() { echo -e "${text_bg_magenta}${text_fg_white}todo${text_reset} $1" } # Usage: log_debug <message> log_debug() { if [[ $DEBUG == true ]]; then echo -e "${text_fg_dim}debug${text_reset} $1" fi } # Usage: log_warn <message> log_warn() { echo -e "${text_fg_yellow}warn${text_reset} $1" } # Usage: log_error <message> log_error() { echo -e "${text_fg_red}error${text_reset} $1" } # Usage: log_fatal <message> [exit-code] log_fatal() { echo -e "${text_fg_white}${text_bg_red}fatal${text_reset} $1" if [ -z ${2:-} ]; then exit 1 else exit $2 fi } # Usage: clear_previous_line [number] clear_line() { echo -e "\e[${1:-"1"}A\e[2K" } # Usage: # rewrite_line <message> # rewrite_line <number> <message> rewrite_line() { if [[ $# == 1 ]]; then echo -e "\e[1A\e[2K${1}" else echo -e "\e[${1}A\e[2K${2}" fi } #==============================# # Options # #==============================# positional_args=() opt_help=false opt_pick=false opt_list=false # Usage: missing_value <option> missing_opt_value() { log_fatal "Option $1 requires a value" } # shellcheck disable=SC2154 while [[ $# > 0 ]]; do case "$1" in -h|--help) opt_help=true shift ;; -p|--pick) opt_pick=true shift ;; -l|--list) opt_list=true shift ;; --show-trace) opt_show_trace=true shift ;; --debug) DEBUG=true shift ;; --) shift break ;; -*|--*) echo "Unknown option $1" exit 1 ;; *) positional_args+=("$1") shift ;; esac done passthrough_args=($@) show_trace="" #==============================# # Helpers # #==============================# # Usage: split <string> <delimiter> split() { IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}" printf '%s\n' "${arr[@]}" } # Usage: lstrip <string> <pattern> lstrip() { printf '%s\n' "${1##$2}" } # Usage: show_help <path> show_help() { log_debug "Showing help: ${text_bold}$1${text_reset}" source "${help_root}/$1.sh" } # Usage: require_flake_nix require_flake_nix() { if ! [ -f ./flake.nix ]; then log_fatal "This command requires a flake.nix file, but one was not found in the current directory." fi } # Usage: get_flake_attributes <output> <flake-uri> get_flake_attributes() { local outputs="" # NOTE: Some flakes may not contain the values we're looking for. In # which case, we swallow errors here to keep the output clean. set +e outputs=$(nix eval --impure --raw --expr "\ let flake = builtins.getFlake \"$2\"; outputs = if builtins.elem \"$1\" [ \"packages\" \"devShells\" \"apps\" ] then builtins.attrNames (flake.\"$1\".\${builtins.currentSystem} or {}) else builtins.attrNames (flake.\"$1\" or {}); names = builtins.map (output: \"$2#\" + output) outputs; in builtins.concatStringsSep \" \" names " 2>/dev/null) set -e echo "$outputs" } # Usage: replace_each <pattern> <text> <data> replace_each() { local results=() for item in $3; do if [[ "$item" == $1* ]]; then results+=("$2$(lstrip $item $1)") else results+=("$item") fi done echo "${results[*]}" } # Usage: prefix_each <prefix> <data> prefix_each() { local results=() for item in $2; do results+=("$1 $item") done echo "${results[*]}" } # Usage: join_by <delimiter> <data> <data> [...<data>] join_by() { local d=${1-} f=${2-} if shift 2; then printf %s "$f" "${@/#/$d}" fi } # Usage: privileged <command> privileged() { cmd="$@" if command -v sudo >/dev/null; then log_debug "sudo $cmd" sudo $cmd elif command -v doas >/dev/null; then log_debug "doas $cmd" doas $cmd else log_warn "Could not find ${text_bold}sudo${text_reset} or ${text_bold}doas${text_reset}. Executing without privileges." log_debug "$cmd" eval "$cmd" fi } #==============================# # Commands # #==============================# nixos_hosts_all() { if [[ $opt_help == true ]]; then show_help nixos-hosts exit 0 fi if [[ -z "${hosts}" ]]; then log_error "To use this program, override it with your nixosConfigurations:" log_error "" log_error " nixos-hosts.override {" log_error " inherit (self) nixosConfigurations;" log_error " }" log_error "" log_fatal "No hosts configured." fi if [ "${opt_list}" == "true" ]; then local csv=$(cat "${hosts}") local csv_data=$(echo -n "$csv" | tail +2) while read -r line; do local name=$(split "${line}" "," | head -n 1) echo "${name}" done <<< "${csv_data}" exit 0 fi host=$(gum table --selected.foreground="4" --widths=32,20 < "${hosts}") if [ -z "${host}" ]; then exit 0 else local name=$(split "${host}" "," | head -n 1) echo $name fi } #==============================# # Execute # #==============================# if [ -z "${positional_args:-}" ]; then if [[ $opt_help == true ]]; then show_help "nixos-hosts" exit 0 else log_debug "Running subcommand: ${text_bold}nixos_hosts_all${text_reset}" nixos_hosts_all exit 0 fi fi case ${positional_args[0]} in *) log_fatal "Unknown subcommand: ${text_bold}${positional_args[0]}${text_reset}" ;; esac