Files
athenix/sw/updater.nix
2025-12-29 17:26:26 -05:00

241 lines
7.2 KiB
Nix

{
config,
lib,
pkgs,
...
}:
with lib;
{
options.athenix.sw.remoteBuild = lib.mkOption {
type = types.submodule {
options = {
hosts = mkOption {
type = types.listOf types.str;
default = [ "engr-ugaif@192.168.11.133 x86_64-linux" ];
description = "List of remote build hosts for system rebuilding.";
};
enable = mkOption {
type = types.bool;
default = false;
description = "Whether to enable remote build for 'update-system' command.";
};
};
};
default = { };
description = "Remote build configuration";
};
config = {
athenix.sw.remoteBuild.enable = lib.mkDefault (config.athenix.sw.type == "tablet-kiosk");
environment.systemPackages = [
(pkgs.writeShellScriptBin "update-system" ''
set -euo pipefail
RED='\033[31m'; NC='\033[0m'
is_root() { [ "''${EUID:-$(id -u)}" -eq 0 ]; }
in_wheel() { id -nG 2>/dev/null | tr ' ' '\n' | grep -qx wheel; }
# Service path for unprivileged (no flags)
UNIT="update-system.service"
# Figure out the "real" invoking user, even under sudo.
INVOKER_USER="''${SUDO_USER:-$(id -un)}"
INVOKER_HOME="$(getent passwd "$INVOKER_USER" | cut -d: -f6)"
if [ -z "$INVOKER_HOME" ]; then
# fallback if getent is weird in some containers
INVOKER_HOME="''${HOME:-/home/$INVOKER_USER}"
fi
# Defaults for flagged mode
DEFAULT_REMOTE_URL="https://git.factory.uga.edu/UGA-Innovation-Factory/athenix"
REPO_MODE="default" # default | local | remote
LOCAL_PATH=""
REMOTE_URL=""
BRANCH=""
IMPURE=0
usage() {
cat >&2 <<'EOF'
usage:
update-system
update-system [--local-repo[=PATH]] [--remote-repo=URL] [--branch=BRANCH] [--impure]
notes:
- No flags: runs the systemd service (works for unprivileged users via polkit).
- Any flags: only allowed for root or wheel (runs nixos-rebuild directly).
EOF
exit 2
}
# No flags -> polkit-friendly systemd service route
if [ "$#" -eq 0 ]; then
journalctl -fu "$UNIT" -n 0 --output=cat &
JPID=$!
if systemctl start --wait --no-ask-password "$UNIT"; then
STATUS=$?
else
STATUS=$?
fi
sleep 2
kill "$JPID" 2>/dev/null || true
exit "$STATUS"
fi
# Flags -> require root or wheel
if ! is_root && ! in_wheel; then
printf "''${RED}error:''${NC} flags are only allowed for root or wheel. Run without flags (service path), or use sudo / add yourself to wheel.\n" >&2
exit 2
fi
# Parse flags
while [ "$#" -gt 0 ]; do
case "$1" in
--local-repo)
REPO_MODE="local"
LOCAL_PATH="$INVOKER_HOME/athenix"
shift
;;
--local-repo=*)
REPO_MODE="local"
LOCAL_PATH="''${1#*=}"
shift
;;
--remote-repo=*)
REPO_MODE="remote"
REMOTE_URL="''${1#*=}"
shift
;;
--branch)
[ "$#" -ge 2 ] || usage
BRANCH="$2"
shift 2
;;
--branch=*)
BRANCH="''${1#*=}"
shift
;;
--impure)
IMPURE=1
shift
;;
-h|--help) usage ;;
*)
printf "''${RED}error:''${NC} unknown argument: %s\n" "$1" >&2
usage
;;
esac
done
if [ "$REPO_MODE" = "local" ] && [ -n "$REMOTE_URL" ]; then
printf "''${RED}error:''${NC} can't use --local-repo and --remote-repo together.\n" >&2
exit 2
fi
host="''${HOSTNAME:-$(hostname)}"
# Build flake ref
if [ "$REPO_MODE" = "local" ]; then
[ -n "$LOCAL_PATH" ] || LOCAL_PATH="$INVOKER_HOME/athenix"
# Clone default repo if missing
if [ ! -d "$LOCAL_PATH" ]; then
printf "local repo not found at %s, cloning %s...\n" "$LOCAL_PATH" "$DEFAULT_REMOTE_URL" >&2
if [ -n "$BRANCH" ]; then
git clone --branch "$BRANCH" "$DEFAULT_REMOTE_URL" "$LOCAL_PATH"
else
git clone "$DEFAULT_REMOTE_URL" "$LOCAL_PATH"
fi
fi
flakeRef="''${LOCAL_PATH}#''${host}"
else
url="''${REMOTE_URL:-$DEFAULT_REMOTE_URL}"
if echo "$url" | grep -qE '^(https?|ssh)://'; then
base="git+''${url}"
elif echo "$url" | grep -qE '^[^/@:]+@[^/:]+:'; then
# scp-style: git@host:owner/repo(.git)
userhost="''${url%%:*}"
path="''${url#*:}"
base="git+ssh://''${userhost}/''${path}"
else
base="''${url}"
fi
if [ -n "$BRANCH" ]; then
if echo "$base" | grep -q '?'; then
base="''${base}&ref=''${BRANCH}"
else
base="''${base}?ref=''${BRANCH}"
fi
fi
flakeRef="''${base}#''${host}"
fi
impureFlag=""
if [ "$IMPURE" -eq 1 ]; then
impureFlag="--impure"
fi
# If not root, re-exec via sudo to do the actual switch.
# Preserve our computed invoker context so sudo doesn't "helpfully" change it.
if ! is_root; then
exec sudo --preserve-env=HOME,USER,LOGNAME \
nixos-rebuild switch --refresh --print-build-logs $impureFlag --flake "$flakeRef"
else
exec nixos-rebuild switch --refresh --print-build-logs $impureFlag --flake "$flakeRef"
fi
'')
];
systemd.services.update-system = {
enable = true;
description = "System daemon to one-shot run the Nix updater from fleet flake as root";
path = with pkgs; [
git
nixos-rebuild
nix
];
serviceConfig = {
Type = "oneshot";
ExecStart =
let
hosts = config.athenix.sw.remoteBuild.hosts;
builders = lib.strings.concatMapStringsSep ";" (x: x) hosts;
rebuildCmd = "${pkgs.nixos-rebuild}/bin/nixos-rebuild switch --refresh";
source = "--flake git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix";
remoteBuildFlags =
if config.athenix.sw.remoteBuild.enable then ''--builders "${builders}"'' else "";
in
"${rebuildCmd} ${remoteBuildFlags} --print-build-logs ${source}#${config.networking.hostName}";
User = "root";
Group = "root";
TimeoutStartSec = "0";
};
};
security.polkit = {
debug = true;
enable = true;
extraConfig = ''
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "update-system.service" &&
action.lookup("verb") == "start" &&
subject.isInGroup("users")) {
return polkit.Result.YES;
}
});
'';
};
};
}