#!/bin/bash # Grant a named admin user passwordless sudo for Proxmox management tools # (pvesh, qm, pct) and the Nix package manager so that scripts in the # nixos flake repo can run these over non-interactive SSH without a TTY. # # Nix is included because single-user Nix installations (common on PVE # hosts bootstrapped via codex-setup.sh) are owned by root; non-root # users can't touch the Nix store lock without sudo. # # Idempotent - safe to re-run (rewrites if paths have changed). Run as # root on the PVE host. # # Usage: ./setup-admin-sudo.sh set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=lib/common.sh source "${SCRIPT_DIR}/lib/common.sh" require_root USERNAME="${1:-}" if [ -z "$USERNAME" ]; then echo "Usage: $0 " >&2 exit 1 fi # Resolve actual binary paths at script time -- they differ across Proxmox # versions (pvesh moved from /usr/sbin to /usr/bin in PVE 8.x) and the # sudoers rule must match the real path or sudo will fall back to # prompting for a password. resolve_bin() { command -v "$1" 2>/dev/null || { echo "ERROR: $1 not found on PATH" >&2; exit 1; } } PVESH="$(resolve_bin pvesh)" QM="$(resolve_bin qm)" PCT="$(resolve_bin pct)" # Nix installs to a fixed path regardless of which user bootstrapped it. NIX_BIN="/nix/var/nix/profiles/default/bin/nix" if [ ! -x "$NIX_BIN" ]; then echo "WARNING: $NIX_BIN not found -- Nix may not be installed yet." >&2 echo " Re-run this script after running codex-setup.sh on the node." >&2 NIX_BIN="" fi SUDOERS_FILE="/etc/sudoers.d/${USERNAME}-proxmox" NIX_ENTRY="${NIX_BIN:+, ${NIX_BIN}}" CONTENT="${USERNAME} ALL=(root) NOPASSWD: ${PVESH}, ${QM}, ${PCT}${NIX_ENTRY}" write_if_changed "$SUDOERS_FILE" "$CONTENT" # visudo -c validates the file we just wrote before we walk away. if visudo -c -f "$SUDOERS_FILE" >/dev/null 2>&1; then chmod 0440 "$SUDOERS_FILE" echo "Sudoers rule for ${USERNAME} is valid and in place." echo " ${CONTENT}" else echo "ERROR: sudoers validation failed -- removing bad file." >&2 rm -f "$SUDOERS_FILE" exit 1 fi