mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-06-05 21:03:40 +00:00
staging-nixos merge for 2026-06-01 (#526774)
This commit is contained in:
@@ -19,6 +19,12 @@
|
||||
|
||||
- `uhttpmock` providing 0.0 ABI was removed. `uhttpmock_1_0` providing 1.0 ABI was renamed to `uhttpmock` and `uhttpmock_1_0` was kept as an alias.
|
||||
|
||||
- The ARMv5 Linux kernel build now uses a standard configuration and generates a standard compressed image instead of the deprecated legacy U‐Boot image format.
|
||||
`lib.systems.{examples,platforms}.{sheevaplug,pogoplug4}` have been unified into `lib.systems.examples.armv5tel-multiplatform`.
|
||||
Note that there is no official support for ARMv5 and it is not possible to build even a simple NixOS configuration out of the box.
|
||||
|
||||
- Support for the legacy U‐Boot image format has been removed from the Linux kernel builders, as it is deprecated upstream and no longer used by any platform in Nixpkgs.
|
||||
|
||||
- `requireFile` now sets `meta.license = lib.licenses.unfree` by default. Users of `requireFile`-based derivations that preserve this default will need to explicitly allow their evaluation as described in [](#sec-allow-unfree).
|
||||
|
||||
- `librest` providing 0.7 ABI was removed. `librest_1_0` providing 1.0 ABI was renamed to `librest` and `librest_1_0` was kept as an alias.
|
||||
|
||||
@@ -40,10 +40,9 @@ rec {
|
||||
rust.rustcTarget = "powerpc-unknown-linux-gnu";
|
||||
};
|
||||
|
||||
sheevaplug = {
|
||||
armv5tel-multiplatform = {
|
||||
config = "armv5tel-unknown-linux-gnueabi";
|
||||
}
|
||||
// platforms.sheevaplug;
|
||||
};
|
||||
|
||||
raspberryPi = {
|
||||
config = "armv6l-unknown-linux-gnueabihf";
|
||||
@@ -99,11 +98,6 @@ rec {
|
||||
useLLVM = true;
|
||||
};
|
||||
|
||||
pogoplug4 = {
|
||||
config = "armv5tel-unknown-linux-gnueabi";
|
||||
}
|
||||
// platforms.pogoplug4;
|
||||
|
||||
ben-nanonote = {
|
||||
config = "mipsel-unknown-linux-uclibc";
|
||||
}
|
||||
|
||||
@@ -46,138 +46,15 @@ rec {
|
||||
## ARM
|
||||
##
|
||||
|
||||
pogoplug4 = {
|
||||
armv5tel-multiplatform = {
|
||||
linux-kernel = {
|
||||
name = "pogoplug4";
|
||||
name = "armv5tel-multiplatform";
|
||||
|
||||
baseConfig = "multi_v5_defconfig";
|
||||
autoModules = false;
|
||||
extraConfig = ''
|
||||
# Ubi for the mtd
|
||||
MTD_UBI y
|
||||
UBIFS_FS y
|
||||
UBIFS_FS_XATTR y
|
||||
UBIFS_FS_ADVANCED_COMPR y
|
||||
UBIFS_FS_LZO y
|
||||
UBIFS_FS_ZLIB y
|
||||
UBIFS_FS_DEBUG n
|
||||
'';
|
||||
makeFlags = [ "LOADADDR=0x8000" ];
|
||||
target = "uImage";
|
||||
# TODO reenable once manual-config's config actually builds a .dtb and this is checked to be working
|
||||
#DTB = true;
|
||||
};
|
||||
gcc = {
|
||||
arch = "armv5te";
|
||||
};
|
||||
};
|
||||
|
||||
sheevaplug = {
|
||||
linux-kernel = {
|
||||
name = "sheevaplug";
|
||||
|
||||
baseConfig = "multi_v5_defconfig";
|
||||
autoModules = false;
|
||||
extraConfig = ''
|
||||
BLK_DEV_RAM y
|
||||
BLK_DEV_INITRD y
|
||||
BLK_DEV_CRYPTOLOOP m
|
||||
BLK_DEV_DM m
|
||||
DM_CRYPT m
|
||||
MD y
|
||||
BTRFS_FS m
|
||||
XFS_FS m
|
||||
JFS_FS m
|
||||
EXT4_FS m
|
||||
USB_STORAGE_CYPRESS_ATACB m
|
||||
|
||||
# mv cesa requires this sw fallback, for mv-sha1
|
||||
CRYPTO_SHA1 y
|
||||
# Fast crypto
|
||||
CRYPTO_TWOFISH y
|
||||
CRYPTO_TWOFISH_COMMON y
|
||||
CRYPTO_BLOWFISH y
|
||||
CRYPTO_BLOWFISH_COMMON y
|
||||
|
||||
IP_PNP y
|
||||
IP_PNP_DHCP y
|
||||
NFS_FS y
|
||||
ROOT_NFS y
|
||||
TUN m
|
||||
NFS_V4 y
|
||||
NFS_V4_1 y
|
||||
NFS_FSCACHE y
|
||||
NFSD m
|
||||
NFSD_V2_ACL y
|
||||
NFSD_V3 y
|
||||
NFSD_V3_ACL y
|
||||
NFSD_V4 y
|
||||
NETFILTER y
|
||||
IP_NF_IPTABLES y
|
||||
IP_NF_FILTER y
|
||||
IP_NF_MATCH_ADDRTYPE y
|
||||
IP_NF_TARGET_LOG y
|
||||
IP_NF_MANGLE y
|
||||
IPV6 m
|
||||
VLAN_8021Q m
|
||||
|
||||
CIFS y
|
||||
CIFS_XATTR y
|
||||
CIFS_POSIX y
|
||||
CIFS_FSCACHE y
|
||||
CIFS_ACL y
|
||||
|
||||
WATCHDOG y
|
||||
WATCHDOG_CORE y
|
||||
ORION_WATCHDOG m
|
||||
|
||||
ZRAM m
|
||||
NETCONSOLE m
|
||||
|
||||
# Disable OABI to have seccomp_filter (required for systemd)
|
||||
# https://github.com/raspberrypi/firmware/issues/651
|
||||
OABI_COMPAT n
|
||||
|
||||
# Fail to build
|
||||
DRM n
|
||||
SCSI_ADVANSYS n
|
||||
USB_ISP1362_HCD n
|
||||
SND_SOC n
|
||||
SND_ALI5451 n
|
||||
FB_SAVAGE n
|
||||
SCSI_NSP32 n
|
||||
ATA_SFF n
|
||||
SUNGEM n
|
||||
IRDA n
|
||||
ATM_HE n
|
||||
SCSI_ACARD n
|
||||
BLK_DEV_CMD640_ENHANCED n
|
||||
|
||||
FUSE_FS m
|
||||
|
||||
# systemd uses cgroups
|
||||
CGROUPS y
|
||||
|
||||
# Latencytop
|
||||
LATENCYTOP y
|
||||
|
||||
# Ubi for the mtd
|
||||
MTD_UBI y
|
||||
UBIFS_FS y
|
||||
UBIFS_FS_XATTR y
|
||||
UBIFS_FS_ADVANCED_COMPR y
|
||||
UBIFS_FS_LZO y
|
||||
UBIFS_FS_ZLIB y
|
||||
UBIFS_FS_DEBUG n
|
||||
|
||||
# Kdb, for kernel troubles
|
||||
KGDB y
|
||||
KGDB_SERIAL_CONSOLE y
|
||||
KGDB_KDB y
|
||||
'';
|
||||
makeFlags = [ "LOADADDR=0x0200000" ];
|
||||
target = "uImage";
|
||||
DTB = true; # Beyond 3.10
|
||||
DTB = true;
|
||||
autoModules = true;
|
||||
preferBuiltin = true;
|
||||
target = "zImage";
|
||||
};
|
||||
gcc = {
|
||||
arch = "armv5te";
|
||||
@@ -192,11 +69,6 @@ rec {
|
||||
DTB = true;
|
||||
autoModules = true;
|
||||
preferBuiltin = true;
|
||||
extraConfig = ''
|
||||
# Disable OABI to have seccomp_filter (required for systemd)
|
||||
# https://github.com/raspberrypi/firmware/issues/651
|
||||
OABI_COMPAT n
|
||||
'';
|
||||
target = "zImage";
|
||||
};
|
||||
gcc = {
|
||||
@@ -217,15 +89,6 @@ rec {
|
||||
};
|
||||
|
||||
zero-gravitas = {
|
||||
linux-kernel = {
|
||||
name = "zero-gravitas";
|
||||
|
||||
baseConfig = "zero-gravitas_defconfig";
|
||||
# Target verified by checking /boot on reMarkable 1 device
|
||||
target = "zImage";
|
||||
autoModules = false;
|
||||
DTB = true;
|
||||
};
|
||||
gcc = {
|
||||
fpu = "neon";
|
||||
cpu = "cortex-a9";
|
||||
@@ -233,15 +96,6 @@ rec {
|
||||
};
|
||||
|
||||
zero-sugar = {
|
||||
linux-kernel = {
|
||||
name = "zero-sugar";
|
||||
|
||||
baseConfig = "zero-sugar_defconfig";
|
||||
DTB = true;
|
||||
autoModules = false;
|
||||
preferBuiltin = true;
|
||||
target = "zImage";
|
||||
};
|
||||
gcc = {
|
||||
cpu = "cortex-a7";
|
||||
fpu = "neon-vfpv4";
|
||||
@@ -249,49 +103,6 @@ rec {
|
||||
};
|
||||
};
|
||||
|
||||
utilite = {
|
||||
linux-kernel = {
|
||||
name = "utilite";
|
||||
maseConfig = "multi_v7_defconfig";
|
||||
autoModules = false;
|
||||
extraConfig = ''
|
||||
# Ubi for the mtd
|
||||
MTD_UBI y
|
||||
UBIFS_FS y
|
||||
UBIFS_FS_XATTR y
|
||||
UBIFS_FS_ADVANCED_COMPR y
|
||||
UBIFS_FS_LZO y
|
||||
UBIFS_FS_ZLIB y
|
||||
UBIFS_FS_DEBUG n
|
||||
'';
|
||||
makeFlags = [ "LOADADDR=0x10800000" ];
|
||||
target = "uImage";
|
||||
DTB = true;
|
||||
};
|
||||
gcc = {
|
||||
cpu = "cortex-a9";
|
||||
fpu = "neon";
|
||||
};
|
||||
};
|
||||
|
||||
guruplug = lib.recursiveUpdate sheevaplug {
|
||||
# Define `CONFIG_MACH_GURUPLUG' (see
|
||||
# <http://kerneltrap.org/mailarchive/git-commits-head/2010/5/19/33618>)
|
||||
# and other GuruPlug-specific things. Requires the `guruplug-defconfig'
|
||||
# patch.
|
||||
linux-kernel.baseConfig = "guruplug_defconfig";
|
||||
};
|
||||
|
||||
beaglebone = lib.recursiveUpdate armv7l-hf-multiplatform {
|
||||
linux-kernel = {
|
||||
name = "beaglebone";
|
||||
baseConfig = "bb.org_defconfig";
|
||||
autoModules = false;
|
||||
extraConfig = ""; # TBD kernel config
|
||||
target = "zImage";
|
||||
};
|
||||
};
|
||||
|
||||
# https://developer.android.com/ndk/guides/abis#v7a
|
||||
armv7a-android = {
|
||||
linux-kernel.name = "armeabi-v7a";
|
||||
@@ -305,32 +116,11 @@ rec {
|
||||
armv7l-hf-multiplatform = {
|
||||
linux-kernel = {
|
||||
name = "armv7l-hf-multiplatform";
|
||||
Major = "2.6"; # Using "2.6" enables 2.6 kernel syscalls in glibc.
|
||||
baseConfig = "multi_v7_defconfig";
|
||||
baseConfig = "defconfig";
|
||||
DTB = true;
|
||||
autoModules = true;
|
||||
preferBuiltin = true;
|
||||
target = "zImage";
|
||||
extraConfig = ''
|
||||
# Serial port for Raspberry Pi 3. Wasn't included in ARMv7 defconfig
|
||||
# until 4.17.
|
||||
SERIAL_8250_BCM2835AUX y
|
||||
SERIAL_8250_EXTENDED y
|
||||
SERIAL_8250_SHARE_IRQ y
|
||||
|
||||
# Hangs ODROID-XU4
|
||||
ARM_BIG_LITTLE_CPUIDLE n
|
||||
|
||||
# Disable OABI to have seccomp_filter (required for systemd)
|
||||
# https://github.com/raspberrypi/firmware/issues/651
|
||||
OABI_COMPAT n
|
||||
|
||||
# >=5.12 fails with:
|
||||
# drivers/net/ethernet/micrel/ks8851_common.o: in function `ks8851_probe_common':
|
||||
# ks8851_common.c:(.text+0x179c): undefined reference to `__this_module'
|
||||
# See: https://lore.kernel.org/netdev/20210116164828.40545-1-marex@denx.de/T/
|
||||
KS8851_MLL y
|
||||
'';
|
||||
};
|
||||
gcc = {
|
||||
# Some table about fpu flags:
|
||||
@@ -363,22 +153,6 @@ rec {
|
||||
autoModules = true;
|
||||
preferBuiltin = true;
|
||||
extraConfig = ''
|
||||
# Raspberry Pi 3 stuff. Not needed for s >= 4.10.
|
||||
ARCH_BCM2835 y
|
||||
BCM2835_MBOX y
|
||||
BCM2835_WDT y
|
||||
RASPBERRYPI_FIRMWARE y
|
||||
RASPBERRYPI_POWER y
|
||||
SERIAL_8250_BCM2835AUX y
|
||||
SERIAL_8250_EXTENDED y
|
||||
SERIAL_8250_SHARE_IRQ y
|
||||
|
||||
# Cavium ThunderX stuff.
|
||||
PCI_HOST_THUNDER_ECAM y
|
||||
|
||||
# Nvidia Tegra stuff.
|
||||
PCI_TEGRA y
|
||||
|
||||
# The default (=y) forces us to have the XHCI firmware available in initrd,
|
||||
# which our initrd builder can't currently do easily.
|
||||
USB_XHCI_TEGRA m
|
||||
@@ -412,74 +186,6 @@ rec {
|
||||
};
|
||||
|
||||
fuloong2f_n32 = {
|
||||
linux-kernel = {
|
||||
name = "fuloong2f_n32";
|
||||
baseConfig = "lemote2f_defconfig";
|
||||
autoModules = false;
|
||||
extraConfig = ''
|
||||
MIGRATION n
|
||||
COMPACTION n
|
||||
|
||||
# nixos mounts some cgroup
|
||||
CGROUPS y
|
||||
|
||||
BLK_DEV_RAM y
|
||||
BLK_DEV_INITRD y
|
||||
BLK_DEV_CRYPTOLOOP m
|
||||
BLK_DEV_DM m
|
||||
DM_CRYPT m
|
||||
MD y
|
||||
EXT4_FS m
|
||||
USB_STORAGE_CYPRESS_ATACB m
|
||||
|
||||
IP_PNP y
|
||||
IP_PNP_DHCP y
|
||||
IP_PNP_BOOTP y
|
||||
NFS_FS y
|
||||
ROOT_NFS y
|
||||
TUN m
|
||||
NFS_V4 y
|
||||
NFS_V4_1 y
|
||||
NFS_FSCACHE y
|
||||
NFSD m
|
||||
NFSD_V2_ACL y
|
||||
NFSD_V3 y
|
||||
NFSD_V3_ACL y
|
||||
NFSD_V4 y
|
||||
|
||||
# Fail to build
|
||||
DRM n
|
||||
SCSI_ADVANSYS n
|
||||
USB_ISP1362_HCD n
|
||||
SND_SOC n
|
||||
SND_ALI5451 n
|
||||
FB_SAVAGE n
|
||||
SCSI_NSP32 n
|
||||
ATA_SFF n
|
||||
SUNGEM n
|
||||
IRDA n
|
||||
ATM_HE n
|
||||
SCSI_ACARD n
|
||||
BLK_DEV_CMD640_ENHANCED n
|
||||
|
||||
FUSE_FS m
|
||||
|
||||
# Needed for udev >= 150
|
||||
SYSFS_DEPRECATED_V2 n
|
||||
|
||||
VGA_CONSOLE n
|
||||
VT_HW_CONSOLE_BINDING y
|
||||
SERIAL_8250_CONSOLE y
|
||||
FRAMEBUFFER_CONSOLE y
|
||||
EXT2_FS y
|
||||
EXT3_FS y
|
||||
MAGIC_SYSRQ y
|
||||
|
||||
# The kernel doesn't boot at all, with FTRACE
|
||||
FTRACE n
|
||||
'';
|
||||
target = "vmlinux";
|
||||
};
|
||||
gcc = {
|
||||
arch = "loongson2f";
|
||||
float = "hard";
|
||||
@@ -525,35 +231,6 @@ rec {
|
||||
};
|
||||
};
|
||||
|
||||
# based on:
|
||||
# https://www.mail-archive.com/qemu-discuss@nongnu.org/msg05179.html
|
||||
# https://gmplib.org/~tege/qemu.html#mips64-debian
|
||||
mips64el-qemu-linux-gnuabi64 = {
|
||||
linux-kernel = {
|
||||
name = "mips64el";
|
||||
baseConfig = "64r2el_defconfig";
|
||||
target = "vmlinuz";
|
||||
autoModules = false;
|
||||
DTB = true;
|
||||
# for qemu 9p passthrough filesystem
|
||||
extraConfig = ''
|
||||
MIPS_MALTA y
|
||||
PAGE_SIZE_4KB y
|
||||
CPU_LITTLE_ENDIAN y
|
||||
CPU_MIPS64_R2 y
|
||||
64BIT y
|
||||
CPU_MIPS64_R2 y
|
||||
|
||||
NET_9P y
|
||||
NET_9P_VIRTIO y
|
||||
9P_FS y
|
||||
9P_FS_POSIX_ACL y
|
||||
PCI y
|
||||
VIRTIO_PCI y
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
##
|
||||
## Other
|
||||
##
|
||||
@@ -607,7 +284,7 @@ rec {
|
||||
if version == null then
|
||||
pc
|
||||
else if lib.versionOlder version "6" then
|
||||
sheevaplug
|
||||
armv5tel-multiplatform
|
||||
else if lib.versionOlder version "7" then
|
||||
raspberrypi
|
||||
else
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
|
||||
- `boot.vesa` has been removed. It was deprecated in 2020 because Xorg now works better with kernel modesetting. If you still need the legacy VESA 800x600 fallback, set `boot.kernelParams = [ "vga=0x317" "nomodeset" ];` directly.
|
||||
|
||||
- Support for the legacy U‐Boot image format has been removed from the initrd generators, as it is deprecated upstream and no longer used by any platform in Nixpkgs.
|
||||
|
||||
- Python 2 has been removed from the top-level package set, as it is long past end-of-life. The `python2`, `python27`, `python2Full`, `python27Full`, `python2Packages`, and `python27Packages` attributes, along with the legacy `python`, `pythonFull`, and `pythonPackages` aliases, now throw an error directing you to `python3`. The `isPy2` and `isPy27` package flags have been removed accordingly. The only remaining Python 2 interpreter is vendored inside the `resholve` package for its `oil` dependency and is not exposed for general use.
|
||||
|
||||
## Other Notable Changes {#sec-release-26.11-notable-changes}
|
||||
|
||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||
|
||||
- Create the first release note entry in this section!
|
||||
- The `newuidmap` and `newgidmap` security wrappers are now installed with `cap_setuid`/`cap_setgid` file capabilities instead of the setuid-root bit, matching shadow's `--with-fcaps` install mode and other major distributions. Rootless containers (podman, docker-rootless, unprivileged user namespaces) are unaffected. The only behavioural change is that mapping host uid 0 via `/etc/subuid` (which NixOS never configures by default) additionally requires `cap_setfcap`; users who explicitly grant uid 0 in a subuid range can restore the previous behaviour with `security.wrappers.newuidmap.capabilities = lib.mkForce "cap_setuid,cap_setfcap+ep";`.
|
||||
|
||||
@@ -1031,6 +1031,7 @@ class QemuMachine(BaseMachine):
|
||||
As soon as we read some data from the socket here, we assume that
|
||||
our root shell is operational.
|
||||
"""
|
||||
assert self.shell
|
||||
(ready, _, _) = select.select([self.shell], [], [], timeout_secs)
|
||||
return bool(ready)
|
||||
|
||||
|
||||
@@ -314,6 +314,27 @@ in
|
||||
name = "nixos-rebuild";
|
||||
package = config.system.build.nixos-rebuild;
|
||||
})
|
||||
(
|
||||
{ config, ... }:
|
||||
{
|
||||
options.system.tools.nixos-rebuild.enableRun0Elevation = lib.mkEnableOption ''
|
||||
support for being targeted by `nixos-rebuild --elevate=run0
|
||||
--ask-elevate-password`.
|
||||
|
||||
This enables polkit and adds {command}`polkit-stdin-agent` to
|
||||
{option}`environment.systemPackages` so that a deploying host
|
||||
can find a target-architecture agent at
|
||||
{file}`<toplevel>/sw/bin/polkit-stdin-agent` after copying the
|
||||
closure (which is required for cross-architecture deploys and
|
||||
mismatched nixpkgs revisions to work).
|
||||
'';
|
||||
|
||||
config = lib.mkIf config.system.tools.nixos-rebuild.enableRun0Elevation {
|
||||
security.polkit.enable = lib.mkDefault true;
|
||||
environment.systemPackages = [ pkgs.polkit-stdin-agent ];
|
||||
};
|
||||
}
|
||||
)
|
||||
(mkToolModule {
|
||||
name = "nixos-version";
|
||||
package = nixos-version;
|
||||
|
||||
@@ -267,13 +267,22 @@ in
|
||||
group = "root";
|
||||
inherit source;
|
||||
};
|
||||
mkCapRoot = capabilities: source: {
|
||||
inherit capabilities source;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
};
|
||||
in
|
||||
{
|
||||
su = mkSetuidRoot "${config.security.shadow.su.package}/bin/su";
|
||||
sg = mkSetuidRoot "${cfg.package.out}/bin/sg";
|
||||
newgrp = mkSetuidRoot "${cfg.package.out}/bin/newgrp";
|
||||
newuidmap = mkSetuidRoot "${cfg.package.out}/bin/newuidmap";
|
||||
newgidmap = mkSetuidRoot "${cfg.package.out}/bin/newgidmap";
|
||||
# File capabilities instead of setuid root, mirroring shadow's
|
||||
# own --with-fcaps install mode and what Arch/Fedora/Debian ship.
|
||||
# The kernel only requires CAP_SETUID/CAP_SETGID over the parent
|
||||
# userns to write a multi-line /proc/<pid>/[ug]id_map.
|
||||
newuidmap = mkCapRoot "cap_setuid+ep" "${cfg.package.out}/bin/newuidmap";
|
||||
newgidmap = mkCapRoot "cap_setgid+ep" "${cfg.package.out}/bin/newgidmap";
|
||||
}
|
||||
// lib.optionalAttrs config.users.mutableUsers {
|
||||
chsh = mkSetuidRoot "${cfg.package.out}/bin/chsh";
|
||||
|
||||
@@ -322,6 +322,9 @@ in
|
||||
description = "Run user-specific NixOS activation";
|
||||
script = config.system.userActivationScripts.script;
|
||||
unitConfig.ConditionUser = "!@system";
|
||||
# switch-to-configuration restarts this explicitly on every switch.
|
||||
restartIfChanged = false;
|
||||
serviceConfig.RemainAfterExit = true;
|
||||
serviceConfig.Type = "oneshot";
|
||||
wantedBy = [ "default.target" ];
|
||||
};
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
e2fsprogs,
|
||||
iproute2,
|
||||
lib,
|
||||
mypy,
|
||||
ruff,
|
||||
setuptools,
|
||||
systemd,
|
||||
ty,
|
||||
}:
|
||||
|
||||
buildPythonApplication {
|
||||
@@ -35,13 +35,13 @@ buildPythonApplication {
|
||||
doCheck = true;
|
||||
|
||||
nativeCheckInputs = [
|
||||
mypy
|
||||
ruff
|
||||
ty
|
||||
];
|
||||
|
||||
checkPhase = ''
|
||||
echo -e "\x1b[32m## run mypy\x1b[0m"
|
||||
mypy run_nspawn
|
||||
echo -e "\x1b[32m## run ty\x1b[0m"
|
||||
ty check --error-on-warning run_nspawn
|
||||
echo -e "\x1b[32m## run ruff check\x1b[0m"
|
||||
ruff check .
|
||||
echo -e "\x1b[32m## run ruff format\x1b[0m"
|
||||
|
||||
@@ -551,7 +551,7 @@ in
|
||||
y = 768;
|
||||
};
|
||||
description = ''
|
||||
The resolution of the virtual machine display.
|
||||
The resolution of the virtual machine display (relevant only if virtualised machine uses grub bootloader).
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -1379,7 +1379,6 @@ in
|
||||
"-device usb-tablet,bus=usb-bus.0"
|
||||
])
|
||||
(mkIf pkgs.stdenv.hostPlatform.isAarch [
|
||||
"-device virtio-gpu-pci"
|
||||
"-device usb-ehci,id=usb0"
|
||||
"-device usb-kbd"
|
||||
"-device usb-tablet"
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
};
|
||||
|
||||
system.includeBuildDependencies = true;
|
||||
# Needed so the offline build of the target config succeeds.
|
||||
system.extraDependencies = [ pkgs.polkit-stdin-agent ];
|
||||
|
||||
virtualisation = {
|
||||
cores = 2;
|
||||
@@ -49,6 +51,11 @@
|
||||
users.users.alice.extraGroups = [ "wheel" ];
|
||||
users.users.bob.extraGroups = [ "wheel" ];
|
||||
|
||||
# Needed for --elevate=run0. NixOS's default polkit admin rule is
|
||||
# `unix-group:wheel`, so bob (in wheel) can authenticate with his
|
||||
# own password via polkit-stdin-agent.
|
||||
system.tools.nixos-rebuild.enableRun0Elevation = true;
|
||||
|
||||
# Disable sudo for root to ensure sudo isn't called without `--sudo`
|
||||
security.sudo.extraRules = lib.mkForce [
|
||||
{
|
||||
@@ -142,6 +149,7 @@
|
||||
deployer.copy_from_host("${configFile "config-1-deployed"}", "/root/configuration-1.nix")
|
||||
deployer.copy_from_host("${configFile "config-2-deployed"}", "/root/configuration-2.nix")
|
||||
deployer.copy_from_host("${configFile "config-3-deployed"}", "/root/configuration-3.nix")
|
||||
deployer.copy_from_host("${configFile "config-4-deployed"}", "/root/configuration-4.nix")
|
||||
deployer.copy_from_host("${targetNetworkJSON}", "/root/target-network.json")
|
||||
deployer.copy_from_host("${targetConfigJSON}", "/root/target-configuration.json")
|
||||
|
||||
@@ -168,6 +176,20 @@
|
||||
target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname").rstrip()
|
||||
assert target_hostname == "config-3-deployed", f"{target_hostname=}"
|
||||
|
||||
with subtest("Deploy to bob@target with run0 and password"):
|
||||
# polkit-stdin-agent registers an agent for systemd-run on the
|
||||
# target and answers the PAM conversation with the password we
|
||||
# supply locally. The agent is resolved on the target from
|
||||
# <toplevel>/sw/bin (see Run0Elevator._remote_agent_argv).
|
||||
deployer.send_chars("nixos-rebuild switch -I nixos-config=/root/configuration-4.nix --target-host bob@target --elevate=run0 --ask-elevate-password\n")
|
||||
deployer.wait_until_tty_matches("1", "\\[run0\\] password for bob@target")
|
||||
deployer.send_chars("${nodes.target.users.users.bob.password}\n")
|
||||
deployer.wait_until_tty_matches("1", "Done. The new configuration is /nix/store/.*config-4-deployed")
|
||||
target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname").rstrip()
|
||||
assert target_hostname == "config-4-deployed", f"{target_hostname=}"
|
||||
# The target-arch agent is reachable at the stable sw/bin path.
|
||||
target.succeed("test -x /run/current-system/sw/bin/polkit-stdin-agent")
|
||||
|
||||
with subtest("Deploy works with very long TMPDIR"):
|
||||
tmp_dir = "/var/folder/veryveryveryveryverylongpathnamethatdoesnotworkwithcontrolpath"
|
||||
deployer.succeed(f"mkdir -p {tmp_dir}")
|
||||
|
||||
@@ -739,6 +739,22 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
# As above, but with reloadIfChanged: pass 2 must reload, not
|
||||
# restart.
|
||||
userServiceMigratedToNixosReloadOnly.configuration = {
|
||||
imports = [ userServiceMigratedToNixosNoStop.configuration ];
|
||||
systemd.user.services.migrated = {
|
||||
reloadIfChanged = true;
|
||||
serviceConfig.ExecReload = "${pkgs.coreutils}/bin/true";
|
||||
};
|
||||
};
|
||||
|
||||
# As above, but with restartIfChanged = false: pass 2 must skip it.
|
||||
userServiceMigratedToNixosNoRestart.configuration = {
|
||||
imports = [ userServiceMigratedToNixosNoStop.configuration ];
|
||||
systemd.user.services.migrated.restartIfChanged = false;
|
||||
};
|
||||
|
||||
no_inhibitors.configuration.system.switch.inhibitors = lib.mkForce { };
|
||||
|
||||
inhibitors.configuration.system.switch.inhibitors = lib.mkForce {
|
||||
@@ -810,6 +826,15 @@ in
|
||||
RemainAfterExit=true
|
||||
ExecStart=${pkgs.runtimeShell} -c 'echo home > %t/migrated-owner'
|
||||
'';
|
||||
|
||||
# Unit file placed in ~/.local/share/systemd/user (lower priority than
|
||||
# /etc) to simulate a package-shipped unit.
|
||||
dataMigratedUnit = pkgs.writeText "migrated.service" ''
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=true
|
||||
ExecStart=${pkgs.runtimeShell} -c 'echo data > %t/migrated-owner'
|
||||
'';
|
||||
in
|
||||
# python
|
||||
''
|
||||
@@ -1729,9 +1754,10 @@ in
|
||||
out = switch_to_specialisation("${machine}", "simpleUserService")
|
||||
user_systemctl("is-active usertest.service")
|
||||
|
||||
# No-op switch does nothing
|
||||
# No-op switch leaves the test unit alone.
|
||||
out = switch_to_specialisation("${machine}", "simpleUserService")
|
||||
assert_lacks(out, "user units:")
|
||||
assert_lacks(out, "usertest.service")
|
||||
assert_contains(out, "restarting the following user units: nixos-activation.service")
|
||||
|
||||
# Modifying the unit stop-starts it (default stopIfChanged=true)
|
||||
out = switch_to_specialisation("${machine}", "simpleUserServiceModified")
|
||||
@@ -1748,7 +1774,7 @@ in
|
||||
# reloadIfChanged=true reloads instead
|
||||
out = switch_to_specialisation("${machine}", "simpleUserServiceReload")
|
||||
assert_lacks(out, "stopping the following user units:")
|
||||
assert_lacks(out, "restarting the following user units:")
|
||||
assert_lacks(out, "restarting the following user units: usertest.service")
|
||||
assert_contains(out, "reloading the following user units: usertest.service")
|
||||
user_systemctl("is-active usertest.service")
|
||||
|
||||
@@ -1818,6 +1844,59 @@ in
|
||||
out = machine.succeed(f"sudo -u usertest {user_env} cat /run/user/1001/migrated-owner")
|
||||
assert_contains(out, "nixos")
|
||||
|
||||
# Pass 2 must honour reloadIfChanged.
|
||||
switch_to_specialisation("${machine}", "")
|
||||
machine.fail(f"sudo -u usertest {user_env} systemctl --user is-active migrated.service")
|
||||
seed_home_unit()
|
||||
out = switch_to_specialisation("${machine}", "userServiceMigratedToNixosReloadOnly")
|
||||
assert_lacks(out, "restarting (post-activation) the following user units: migrated.service")
|
||||
assert_contains(out, "reloading (post-activation) the following user units: migrated.service")
|
||||
user_systemctl("is-active migrated.service")
|
||||
# Reloaded only, so the home ExecStart never re-ran.
|
||||
out = machine.succeed(f"sudo -u usertest {user_env} cat /run/user/1001/migrated-owner")
|
||||
assert_contains(out, "home")
|
||||
|
||||
# Pass 2 must honour restartIfChanged = false.
|
||||
switch_to_specialisation("${machine}", "")
|
||||
machine.fail(f"sudo -u usertest {user_env} systemctl --user is-active migrated.service")
|
||||
seed_home_unit()
|
||||
out = switch_to_specialisation("${machine}", "userServiceMigratedToNixosNoRestart")
|
||||
assert_lacks(out, "\nrestarting (post-activation) the following user units: migrated.service")
|
||||
assert_contains(out, "NOT restarting (post-activation) the following user units: migrated.service")
|
||||
user_systemctl("is-active migrated.service")
|
||||
out = machine.succeed(f"sudo -u usertest {user_env} cat /run/user/1001/migrated-owner")
|
||||
assert_contains(out, "home")
|
||||
|
||||
# Migration from a lower-priority search-path entry ($XDG_DATA_HOME
|
||||
# here, standing in for ~/.nix-profile/share etc.). /etc outranks
|
||||
# these, so pass 2 must restart onto the /etc definition.
|
||||
switch_to_specialisation("${machine}", "")
|
||||
machine.fail(f"sudo -u usertest {user_env} systemctl --user is-active migrated.service")
|
||||
machine.succeed(
|
||||
"sudo -u usertest mkdir -p ~usertest/.local/share/systemd/user",
|
||||
"sudo -u usertest cp ${dataMigratedUnit} ~usertest/.local/share/systemd/user/migrated.service",
|
||||
)
|
||||
user_systemctl("daemon-reload")
|
||||
user_systemctl("start migrated.service")
|
||||
user_systemctl("is-active migrated.service")
|
||||
out = machine.succeed(f"sudo -u usertest {user_env} cat /run/user/1001/migrated-owner")
|
||||
assert_contains(out, "data")
|
||||
out = user_systemctl("show -p FragmentPath migrated.service")
|
||||
assert_contains(out, "/.local/share/systemd/user/migrated.service")
|
||||
out = switch_to_specialisation("${machine}", "userServiceMigratedShadowed")
|
||||
assert_contains(out, "restarting (post-activation) the following user units: migrated.service")
|
||||
user_systemctl("is-active migrated.service")
|
||||
out = user_systemctl("show -p FragmentPath migrated.service")
|
||||
assert_contains(out, "/etc/systemd/user/migrated.service")
|
||||
out = machine.succeed(f"sudo -u usertest {user_env} cat /run/user/1001/migrated-owner")
|
||||
assert_contains(out, "nixos")
|
||||
# Switching again must NOT touch it: /etc already had it, so it is
|
||||
# not a candidate even though the lower-priority copy is still there.
|
||||
out = switch_to_specialisation("${machine}", "userServiceMigratedShadowed")
|
||||
assert_lacks(out, "migrated.service")
|
||||
machine.succeed("sudo -u usertest rm -rf ~usertest/.local/share/systemd")
|
||||
user_systemctl("daemon-reload")
|
||||
|
||||
# Units that remain shadowed by ~/.config must be left alone in both
|
||||
# passes even though /etc now also defines them.
|
||||
switch_to_specialisation("${machine}", "")
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
isNormalUser = true;
|
||||
};
|
||||
systemd.user.tmpfiles.users.alice.rules = [ "r %h/file-to-remove" ];
|
||||
specialisation.changed.configuration.system.userActivationScripts.bar = "true";
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
def verify_user_activation_run_count(n):
|
||||
machine.succeed(
|
||||
'[[ "$(find /home/alice/ -name user-activation-ran.\\* | wc -l)" == %s ]]' % n
|
||||
t.assertEqual(
|
||||
n,
|
||||
int(machine.succeed('find /home/alice/ -name user-activation-ran.\\* | wc -l').rstrip())
|
||||
)
|
||||
|
||||
|
||||
@@ -36,5 +38,12 @@
|
||||
machine.succeed("/run/current-system/bin/switch-to-configuration test")
|
||||
verify_user_activation_run_count(2)
|
||||
machine.succeed("[[ ! -f /home/alice/file-to-remove ]] || false")
|
||||
# Activation must not be killed while running.
|
||||
machine.fail("journalctl -b _SYSTEMD_USER_UNIT=nixos-activation.service | grep -q 'code=killed'")
|
||||
|
||||
# Changed activation script: still exactly one run.
|
||||
machine.succeed("/run/current-system/specialisation/changed/bin/switch-to-configuration test")
|
||||
verify_user_activation_run_count(3)
|
||||
machine.fail("journalctl -b _SYSTEMD_USER_UNIT=nixos-activation.service | grep -q 'code=killed'")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
rec {
|
||||
cat = {
|
||||
executable = pkgs: "cat";
|
||||
ubootName = "none";
|
||||
extension = ".cpio";
|
||||
};
|
||||
gzip = {
|
||||
executable = pkgs: "${pkgs.gzip}/bin/gzip";
|
||||
defaultArgs = [ "-9n" ];
|
||||
ubootName = "gzip";
|
||||
extension = ".gz";
|
||||
};
|
||||
bzip2 = {
|
||||
executable = pkgs: "${pkgs.bzip2}/bin/bzip2";
|
||||
ubootName = "bzip2";
|
||||
extension = ".bz2";
|
||||
};
|
||||
xz = {
|
||||
@@ -29,24 +26,20 @@ rec {
|
||||
"--check=crc32"
|
||||
"--lzma1=dict=512KiB"
|
||||
];
|
||||
ubootName = "lzma";
|
||||
extension = ".lzma";
|
||||
};
|
||||
lz4 = {
|
||||
executable = pkgs: "${pkgs.lz4}/bin/lz4";
|
||||
defaultArgs = [ "-l" ];
|
||||
ubootName = "lz4";
|
||||
extension = ".lz4";
|
||||
};
|
||||
lzop = {
|
||||
executable = pkgs: "${pkgs.lzop}/bin/lzop";
|
||||
ubootName = "lzo";
|
||||
extension = ".lzo";
|
||||
};
|
||||
zstd = {
|
||||
executable = pkgs: "${pkgs.zstd}/bin/zstd";
|
||||
defaultArgs = [ "-10" ];
|
||||
ubootName = "zstd";
|
||||
extension = ".zst";
|
||||
};
|
||||
pigz = gzip // {
|
||||
|
||||
@@ -4,14 +4,13 @@ let
|
||||
# from it.
|
||||
compressors = import ./initrd-compressor-meta.nix;
|
||||
# Get the basename of the actual compression program from the whole
|
||||
# compression command, for the purpose of guessing the u-boot
|
||||
# compression command, for the purpose of guessing the
|
||||
# compression type and filename extension.
|
||||
compressorName = fullCommand: builtins.elemAt (builtins.match "([^ ]*/)?([^ ]+).*" fullCommand) 1;
|
||||
in
|
||||
{
|
||||
stdenvNoCC,
|
||||
cpio,
|
||||
ubootTools,
|
||||
lib,
|
||||
pkgsBuildHost,
|
||||
makeInitrdNGTool,
|
||||
@@ -57,22 +56,13 @@ in
|
||||
# symlinks to store paths.
|
||||
prepend ? [ ],
|
||||
|
||||
# Whether to wrap the initramfs in a u-boot image.
|
||||
makeUInitrd ? stdenvNoCC.hostPlatform.linux-kernel.target == "uImage",
|
||||
|
||||
# If generating a u-boot image, the architecture to use. The default
|
||||
# guess may not align with u-boot's nomenclature correctly, so it can
|
||||
# be overridden.
|
||||
# See https://gitlab.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106 for a list.
|
||||
uInitrdArch ? stdenvNoCC.hostPlatform.ubootArch,
|
||||
|
||||
# The name of the compression, as recognised by u-boot.
|
||||
# See https://gitlab.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L195-204 for a list.
|
||||
# If this isn't guessed, you may want to complete the metadata above and send a PR :)
|
||||
uInitrdCompression ?
|
||||
_compressorMeta.ubootName
|
||||
or (throw "Unrecognised compressor ${_compressorName}, please specify uInitrdCompression"),
|
||||
# Deprecated; remove in 27.05.
|
||||
makeUInitrd ? null,
|
||||
uInitrdArch ? null,
|
||||
uInitrdCompression ? null,
|
||||
}:
|
||||
assert lib.assertMsg (makeUInitrd == null && uInitrdArch == null && uInitrdCompression == null)
|
||||
"makeInitrdNg: U‐Boot legacy image support has been removed as it is deprecated upstream and ARMv5 kernels no longer default to uImage";
|
||||
stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
__structuredAttrs = true;
|
||||
|
||||
@@ -83,11 +73,8 @@ stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
inherit
|
||||
name
|
||||
extension
|
||||
makeUInitrd
|
||||
uInitrdArch
|
||||
prepend
|
||||
;
|
||||
${if makeUInitrd then "uInitrdCompression" else null} = uInitrdCompression;
|
||||
|
||||
compress = "${_compressorExecutable} ${lib.escapeShellArgs _compressorArgsReal}";
|
||||
contentsJSON = builtins.toJSON contents;
|
||||
@@ -95,8 +82,7 @@ stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
nativeBuildInputs = [
|
||||
makeInitrdNGTool
|
||||
cpio
|
||||
]
|
||||
++ lib.optional makeUInitrd ubootTools;
|
||||
];
|
||||
|
||||
buildCommand = ''
|
||||
mkdir -p ./root/{run,tmp,var/empty}
|
||||
@@ -109,13 +95,7 @@ stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
done
|
||||
(cd root && find . -print0 | sort -z | cpio --quiet -o -H newc -R +0:+0 --reproducible --null | eval -- $compress >> "$out/initrd")
|
||||
|
||||
if [ -n "$makeUInitrd" ]; then
|
||||
mkimage -A "$uInitrdArch" -O linux -T ramdisk -C "$uInitrdCompression" -d "$out/initrd" $out/initrd.img
|
||||
# Compatibility symlink
|
||||
ln -sf "initrd.img" "$out/initrd"
|
||||
else
|
||||
ln -s "initrd" "$out/initrd$extension"
|
||||
fi
|
||||
ln -s "initrd" "$out/initrd$extension"
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
|
||||
@@ -10,18 +10,16 @@
|
||||
# of algorithms.
|
||||
let
|
||||
# Some metadata on various compression programs, relevant to naming
|
||||
# the initramfs file and, if applicable, generating a u-boot image
|
||||
# from it.
|
||||
# the initramfs file.
|
||||
compressors = import ./initrd-compressor-meta.nix;
|
||||
# Get the basename of the actual compression program from the whole
|
||||
# compression command, for the purpose of guessing the u-boot
|
||||
# compression command, for the purpose of guessing the
|
||||
# compression type and filename extension.
|
||||
compressorName = fullCommand: builtins.elemAt (builtins.match "([^ ]*/)?([^ ]+).*" fullCommand) 1;
|
||||
in
|
||||
{
|
||||
stdenvNoCC,
|
||||
cpio,
|
||||
ubootTools,
|
||||
lib,
|
||||
pkgsBuildHost,
|
||||
# Name of the derivation (not of the resulting file!)
|
||||
@@ -65,22 +63,13 @@ in
|
||||
# symlinks to store paths.
|
||||
prepend ? [ ],
|
||||
|
||||
# Whether to wrap the initramfs in a u-boot image.
|
||||
makeUInitrd ? stdenvNoCC.hostPlatform.linux-kernel.target or "dummy" == "uImage",
|
||||
|
||||
# If generating a u-boot image, the architecture to use. The default
|
||||
# guess may not align with u-boot's nomenclature correctly, so it can
|
||||
# be overridden.
|
||||
# See https://gitlab.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106 for a list.
|
||||
uInitrdArch ? stdenvNoCC.hostPlatform.linuxArch,
|
||||
|
||||
# The name of the compression, as recognised by u-boot.
|
||||
# See https://gitlab.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L195-204 for a list.
|
||||
# If this isn't guessed, you may want to complete the metadata above and send a PR :)
|
||||
uInitrdCompression ?
|
||||
_compressorMeta.ubootName
|
||||
or (throw "Unrecognised compressor ${_compressorName}, please specify uInitrdCompression"),
|
||||
# Deprecated; remove in 27.05.
|
||||
makeUInitrd ? null,
|
||||
uInitrdArch ? null,
|
||||
uInitrdCompression ? null,
|
||||
}:
|
||||
assert lib.assertMsg (makeUInitrd == null && uInitrdArch == null && uInitrdCompression == null)
|
||||
"makeInitrd: U‐Boot legacy image support has been removed as it is deprecated upstream and ARMv5 kernels no longer default to uImage";
|
||||
stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
__structuredAttrs = true;
|
||||
|
||||
@@ -91,18 +80,14 @@ stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
inherit
|
||||
name
|
||||
extension
|
||||
makeUInitrd
|
||||
uInitrdArch
|
||||
prepend
|
||||
;
|
||||
${if makeUInitrd then "uInitrdCompression" else null} = uInitrdCompression;
|
||||
|
||||
builder = ./make-initrd.sh;
|
||||
|
||||
nativeBuildInputs = [
|
||||
cpio
|
||||
]
|
||||
++ lib.optional makeUInitrd ubootTools;
|
||||
];
|
||||
|
||||
compress = "${_compressorExecutable} ${lib.escapeShellArgs _compressorArgsReal}";
|
||||
|
||||
|
||||
@@ -36,10 +36,4 @@ done
|
||||
(cd root && find * .[^.*] -exec touch -h -d '@1' '{}' +)
|
||||
(cd root && find * .[^.*] -print0 | sort -z | cpio --quiet -o -H newc -R +0:+0 --reproducible --null | eval -- $compress >> "$out/initrd")
|
||||
|
||||
if [ -n "$makeUInitrd" ]; then
|
||||
mkimage -A "$uInitrdArch" -O linux -T ramdisk -C "$uInitrdCompression" -d "$out/initrd" $out/initrd.img
|
||||
# Compatibility symlink
|
||||
ln -sf "initrd.img" "$out/initrd"
|
||||
else
|
||||
ln -s "initrd" "$out/initrd$extension"
|
||||
fi
|
||||
ln -s "initrd" "$out/initrd$extension"
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "btrfs-progs";
|
||||
version = "6.19.1";
|
||||
version = "7.0";
|
||||
|
||||
src = fetchurl {
|
||||
url = "mirror://kernel/linux/kernel/people/kdave/btrfs-progs/btrfs-progs-v${finalAttrs.version}.tar.xz";
|
||||
hash = "sha256-uyfh7FTnw8C3suWW+FOnPAej1y8hvJQEIHPCTb8EV5Y=";
|
||||
hash = "sha256-wobWh2y81yMnoLQX5M/SgDU+wj43tUn9vNeACoMtmpk=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "capstone";
|
||||
version = "5.0.7";
|
||||
version = "5.0.9";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "capstone-engine";
|
||||
repo = "capstone";
|
||||
rev = finalAttrs.version;
|
||||
hash = "sha256-+6QReHZK+iIXspizy6Kvk7cj016HOKgiaKSaP4h7mao=";
|
||||
hash = "sha256-uAiiKWKGjEATPE0Xc3g+aOLCz5ffIlDmf+7jaGwaZ4I=";
|
||||
};
|
||||
|
||||
cmakeFlags = [
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "dhcpcd";
|
||||
version = "10.3.1";
|
||||
version = "10.3.2";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "NetworkConfiguration";
|
||||
repo = "dhcpcd";
|
||||
rev = "v${finalAttrs.version}";
|
||||
sha256 = "sha256-L2rR6/qMHWVth2GR3VAoBZmhA6lmCLddbi0VvEG5r70=";
|
||||
sha256 = "sha256-tJV533j/nQT/PP5KVPJCgTo0Lu8NNMIGnJBvYUG8ufw=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ pkg-config ];
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "libgit2";
|
||||
version = "1.9.3";
|
||||
version = "1.9.4";
|
||||
# also check the following packages for updates: python3Packages.pygit2 and libgit2-glib
|
||||
|
||||
outputs = [
|
||||
@@ -35,7 +35,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
owner = "libgit2";
|
||||
repo = "libgit2";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-nJrRdPs86oGNL4W2CJb16oSUgfzYr9A2i5sw9BAehME=";
|
||||
hash = "sha256-ZKUiz3pdFE2SKxh53X2oyr7hs32Njj5YVA0OXDXz7h0=";
|
||||
};
|
||||
|
||||
cmakeFlags = [
|
||||
|
||||
@@ -21,7 +21,7 @@ nixos-rebuild - reconfigure a NixOS machine
|
||||
_nixos-rebuild_ \[--verbose] [--quiet] [--max-jobs MAX_JOBS] [--cores CORES] [--log-format LOG_FORMAT] [--keep-going] [--keep-failed] [--fallback] [--repair] [--option OPTION OPTION] [--builders BUILDERS] [--include INCLUDE]++
|
||||
\[--print-build-logs] [--show-trace] [--accept-flake-config] [--refresh] [--impure] [--offline] [--no-net] [--recreate-lock-file] [--no-update-lock-file] [--no-write-lock-file] [--no-registries] [--commit-lock-file]++
|
||||
\[--update-input UPDATE_INPUT] [--override-input OVERRIDE_INPUT OVERRIDE_INPUT] [--no-build-output] [--use-substitutes] [--help] [--debug] [--file FILE] [--attr ATTR] [--flake [FLAKE]] [--no-flake] [--install-bootloader]++
|
||||
\[--profile-name PROFILE_NAME] [--specialisation SPECIALISATION] [--rollback] [--store-path STORE_PATH] [--upgrade] [--upgrade-all] [--json] [--ask-sudo-password] [--sudo] [--no-reexec]++
|
||||
\[--profile-name PROFILE_NAME] [--specialisation SPECIALISATION] [--rollback] [--store-path STORE_PATH] [--upgrade] [--upgrade-all] [--json] [--elevate {none,sudo,run0}] [--ask-elevate-password] [--no-reexec]++
|
||||
\[--build-host BUILD_HOST] [--target-host TARGET_HOST] [--no-build-nix] [--image-variant IMAGE_VARIANT]++
|
||||
\[{switch,boot,test,build,edit,repl,dry-build,dry-run,dry-activate,build-image,build-vm,build-vm-with-bootloader,list-generations}]
|
||||
|
||||
@@ -269,17 +269,41 @@ It must be one of the following:
|
||||
target-host connection to cache.nixos.org is faster than the connection
|
||||
between hosts.
|
||||
|
||||
*--elevate* {none,sudo,run0}
|
||||
Privilege-elevation method used for activation commands. Setting this
|
||||
option allows deploying as a non-root user.
|
||||
|
||||
_sudo_ prefixes commands with *sudo*. Additional sudo options can be
|
||||
passed via the NIX_SUDOOPTS environment variable.
|
||||
|
||||
_run0_ uses systemd's polkit-based elevation. Locally this runs *run0*
|
||||
directly so the user's normal polkit agent handles any prompts. With
|
||||
*--target-host* the equivalent _systemd-run --uid=0 --pipe_ form is
|
||||
used (no remote TTY is allocated). Unless *--ask-elevate-password* is
|
||||
also passed, the deploying user must be granted the polkit action
|
||||
_org.freedesktop.systemd1.manage-units_ on the target host without
|
||||
authentication, e.g. via _security.polkit.extraConfig_.
|
||||
|
||||
*--ask-elevate-password*, *-S*
|
||||
Prompt locally for a password and feed it to the elevation method.
|
||||
Implies *--elevate=sudo* if *--elevate* is not given.
|
||||
|
||||
For _sudo_ this uses *sudo --stdin*. For _run0_ the command is wrapped
|
||||
in *polkit-stdin-agent*, which registers a per-process polkit agent
|
||||
and answers the PAM conversation from the supplied password. The
|
||||
machine performing the elevation (the local host, or the target host
|
||||
with *--target-host*) must set
|
||||
_system.tools.nixos-rebuild.enableRun0Elevation = true_.
|
||||
*--elevate=run0 --ask-elevate-password* is not usable otherwise.
|
||||
|
||||
*--sudo*
|
||||
When set, *nixos-rebuild* prefixes activation commands with sudo.
|
||||
Setting this option allows deploying as a non-root user.
|
||||
Alias for *--elevate=sudo*.
|
||||
|
||||
You can set sudo options by defining the NIX_SUDOOPTS environment
|
||||
variable.
|
||||
*--ask-sudo-password*
|
||||
Alias for *--elevate=sudo --ask-elevate-password*.
|
||||
|
||||
*--ask-sudo-password*, *-S*
|
||||
When set, *nixos-rebuild* will ask for sudo password for remote
|
||||
activation (i.e.: on *--target-host*) at the start of the build process.
|
||||
Implies *--sudo*.
|
||||
*--use-remote-sudo*
|
||||
Deprecated, use *--elevate=sudo* instead.
|
||||
|
||||
*--file* _path_, *-f* _path_
|
||||
Build the NixOS system from the specified file. The file must
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
callPackage,
|
||||
installShellFiles,
|
||||
mkShell,
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Final, assert_never
|
||||
|
||||
from . import nix, services
|
||||
from .constants import EXECUTABLE, WITH_SHELL_FILES
|
||||
from .elevate import NO_ELEVATOR, ElevatorKind
|
||||
from .models import Action, BuildAttr, Flake, GroupedNixArgs, Profile
|
||||
from .process import Remote
|
||||
from .utils import LogFormatter
|
||||
@@ -163,18 +164,32 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa
|
||||
help="JSON output, only implemented for 'list-generations' right now",
|
||||
)
|
||||
main_parser.add_argument(
|
||||
"--ask-sudo-password",
|
||||
"-S",
|
||||
action="store_true",
|
||||
help="Asks for sudo password for remote activation, implies --sudo",
|
||||
"--elevate",
|
||||
choices=ElevatorKind.choices(),
|
||||
default=None,
|
||||
help="Privilege-elevation method for activation commands",
|
||||
)
|
||||
main_parser.add_argument(
|
||||
"--sudo", action="store_true", help="Prefixes activation commands with sudo"
|
||||
"--ask-elevate-password",
|
||||
"-S",
|
||||
action="store_true",
|
||||
help="Prompt locally for the password to feed to the elevation "
|
||||
"method, implies --elevate=sudo if --elevate is not given",
|
||||
)
|
||||
main_parser.add_argument(
|
||||
"--sudo",
|
||||
action="store_true",
|
||||
help="Alias for '--elevate=sudo'",
|
||||
)
|
||||
main_parser.add_argument(
|
||||
"--ask-sudo-password",
|
||||
action="store_true",
|
||||
help="Alias for '--elevate=sudo --ask-elevate-password'",
|
||||
)
|
||||
main_parser.add_argument(
|
||||
"--use-remote-sudo",
|
||||
action="store_true",
|
||||
help="Deprecated, use '--sudo' instead",
|
||||
help="Deprecated, use '--elevate=sudo' instead",
|
||||
)
|
||||
main_parser.add_argument("--no-ssh-tty", action="store_true", help="Deprecated")
|
||||
main_parser.add_argument(
|
||||
@@ -245,16 +260,32 @@ def parse_args(
|
||||
args.action = Action.DRY_BUILD.value
|
||||
|
||||
if args.ask_sudo_password:
|
||||
args.sudo = True
|
||||
args.ask_elevate_password = True
|
||||
|
||||
if args.use_remote_sudo:
|
||||
parser_warn("--use-remote-sudo is deprecated, use --elevate=sudo instead")
|
||||
|
||||
# Map the elevate flags onto an Elevator. The password itself is
|
||||
# attached later via Elevator.with_prompted_password() once the
|
||||
# target host (used in the prompt) is known.
|
||||
if args.elevate is not None:
|
||||
args.elevator = ElevatorKind.from_name(args.elevate)
|
||||
elif args.sudo or args.use_remote_sudo or args.ask_sudo_password:
|
||||
args.elevator = ElevatorKind.SUDO.make()
|
||||
elif args.ask_elevate_password:
|
||||
# -S historically implied --sudo. Keep that for muscle memory
|
||||
# but be explicit now that there is more than one backend.
|
||||
parser_warn(
|
||||
"--ask-elevate-password without --elevate, falling back to --elevate=sudo"
|
||||
)
|
||||
args.elevator = ElevatorKind.SUDO.make()
|
||||
else:
|
||||
args.elevator = NO_ELEVATOR
|
||||
|
||||
if args.install_grub:
|
||||
parser_warn("--install-grub is deprecated, use --install-bootloader instead")
|
||||
args.install_bootloader = True
|
||||
|
||||
if args.use_remote_sudo:
|
||||
parser_warn("--use-remote-sudo is deprecated, use --sudo instead")
|
||||
args.sudo = True
|
||||
|
||||
if args.fast:
|
||||
parser_warn("--fast is deprecated, use --no-reexec instead")
|
||||
args.no_reexec = True
|
||||
@@ -321,7 +352,7 @@ def execute(argv: list[str]) -> None:
|
||||
args, grouped_nix_args = parse_args(argv)
|
||||
|
||||
if args.upgrade or args.upgrade_all:
|
||||
nix.upgrade_channels(args.upgrade_all, args.sudo)
|
||||
nix.upgrade_channels(args.upgrade_all, args.elevator)
|
||||
|
||||
action = Action(args.action)
|
||||
# Only run shell scripts from the Nixpkgs tree if the action is
|
||||
@@ -337,8 +368,12 @@ def execute(argv: list[str]) -> None:
|
||||
services.reexec(argv, args, grouped_nix_args)
|
||||
|
||||
profile = Profile.from_arg(args.profile_name)
|
||||
target_host = Remote.from_arg(args.target_host, args.ask_sudo_password)
|
||||
build_host = Remote.from_arg(args.build_host, False, validate_opts=False)
|
||||
target_host = Remote.from_arg(args.target_host)
|
||||
build_host = Remote.from_arg(args.build_host, validate_opts=False)
|
||||
args.elevator = args.elevator.with_prompted_password(
|
||||
ask=args.ask_elevate_password,
|
||||
host_label=target_host.host if target_host else "localhost",
|
||||
)
|
||||
build_attr = BuildAttr.from_arg(args.attr, args.file)
|
||||
flake = Flake.from_arg(args.flake, target_host)
|
||||
|
||||
|
||||
418
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/elevate.py
Normal file
418
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/elevate.py
Normal file
@@ -0,0 +1,418 @@
|
||||
"""Privilege-elevation backends for activation commands.
|
||||
|
||||
An :class:`Elevator` describes how to wrap a command so it runs as root,
|
||||
both on the local machine and on a remote target host over SSH (where no
|
||||
controlling terminal is available), and how to feed it a pre-supplied
|
||||
password when the backend supports that. ``run_wrapper`` and its callers
|
||||
carry a single ``elevate: Elevator`` value and let it produce the command
|
||||
prefix and stdin.
|
||||
|
||||
The remote case has no controlling terminal and the elevated command's
|
||||
environment depends on the backend (``sudo`` inherits the SSH login env,
|
||||
while the run0 backend starts a transient unit with only systemd's
|
||||
default ``PATH``), so each backend builds the full remote argv itself
|
||||
via :meth:`Elevator.wrap_remote`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import getpass
|
||||
import os
|
||||
import shlex
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import dataclass, field, replace
|
||||
from enum import Enum
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import ClassVar, Final, Literal, Self, override
|
||||
|
||||
# Kept here (rather than in process.py) so that elevators can build remote
|
||||
# argvs without a circular import.
|
||||
type Arg = str | bytes | os.PathLike[str] | os.PathLike[bytes]
|
||||
type Args = Sequence[Arg]
|
||||
|
||||
|
||||
class _Env(Enum):
|
||||
PRESERVE_ENV = "PRESERVE"
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
||||
#: Sentinel meaning "copy this variable from the environment the wrapped
|
||||
#: command would naturally see" (``os.environ`` locally, the SSH login
|
||||
#: shell's environment remotely).
|
||||
PRESERVE_ENV: Final = _Env.PRESERVE_ENV
|
||||
|
||||
type EnvValue = str | Literal[_Env.PRESERVE_ENV]
|
||||
|
||||
|
||||
def _remote_env_shell_argv(
|
||||
prefix: Sequence[str],
|
||||
env: Mapping[str, EnvValue],
|
||||
args: Args,
|
||||
) -> list[Arg]:
|
||||
"""Build ``<prefix> /bin/sh -c 'exec /usr/bin/env -i K=V… "$@"' sh <args>``.
|
||||
|
||||
The wrapper runs in the SSH login session, resolves ``PRESERVE_ENV``
|
||||
variables against that session's environment, and re-execs the command
|
||||
with exactly that set. ``/usr/bin/env`` is referenced by absolute path so
|
||||
the wrapper does not depend on ``PATH`` itself (provided on NixOS via
|
||||
``environment.usrbinenv``).
|
||||
"""
|
||||
assigns: list[str] = []
|
||||
for k, v in env.items():
|
||||
if v is PRESERVE_ENV:
|
||||
assigns.append(f'{k}="${{{k}-}}"')
|
||||
else:
|
||||
assigns.append(f"{k}={shlex.quote(v)}")
|
||||
script = f'exec /usr/bin/env -i {" ".join(assigns)} "$@"'
|
||||
return [*prefix, "/bin/sh", "-c", script, "sh", *args]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Wrapped:
|
||||
"""Result of wrapping a command for local elevation."""
|
||||
|
||||
#: Arguments to prepend to the command.
|
||||
prefix: list[str]
|
||||
#: Text to send on the wrapped command's stdin (typically a password
|
||||
#: followed by a newline), or ``None`` to leave stdin alone.
|
||||
stdin: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RemoteWrapped:
|
||||
"""Result of wrapping a command for remote elevation over SSH.
|
||||
|
||||
Unlike :class:`Wrapped` this carries the *full* remote argv: backends
|
||||
differ in where the env-resolution shell wrapper must sit relative to
|
||||
the elevator (inside ``sudo``, but *around* ``systemd-run``), so a
|
||||
plain prefix is not expressive enough.
|
||||
"""
|
||||
|
||||
argv: list[Arg]
|
||||
stdin: str | None = None
|
||||
|
||||
|
||||
class Elevator(ABC):
|
||||
"""How to gain root for activation commands."""
|
||||
|
||||
#: CLI name, e.g. ``sudo`` or ``run0``.
|
||||
name: str
|
||||
|
||||
@property
|
||||
def elevates(self) -> bool:
|
||||
"""Whether this elevator actually changes privileges.
|
||||
|
||||
``run_wrapper`` uses this to decide between passing ``env`` to
|
||||
:func:`subprocess.run` directly (unprivileged local case) and
|
||||
injecting it via ``env -i`` inside the wrapped command (where the
|
||||
elevator may otherwise scrub the environment).
|
||||
"""
|
||||
return True
|
||||
|
||||
@abstractmethod
|
||||
def wrap_local(self) -> Wrapped:
|
||||
"""Wrap a command run on the local machine."""
|
||||
|
||||
@abstractmethod
|
||||
def wrap_remote(self, env: Mapping[str, EnvValue], args: Args) -> RemoteWrapped:
|
||||
"""Wrap a command run on a target host over SSH.
|
||||
|
||||
The remote side has no controlling terminal, so backends that need
|
||||
interactive prompts must either accept a pre-supplied password
|
||||
(see :meth:`with_password`) or rely on a passwordless policy on
|
||||
the target.
|
||||
|
||||
*env* is the environment to establish for the elevated command.
|
||||
:data:`PRESERVE_ENV` values are resolved against the SSH login
|
||||
shell's environment, and the backend must do so before any step
|
||||
that replaces it with a service-style one.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def with_password(self, password: str) -> Self:
|
||||
"""Return a copy that will feed *password* to the backend.
|
||||
|
||||
Backends that have no stdin path for credentials must raise
|
||||
:class:`ElevateError` with a hint pointing at the alternative
|
||||
(e.g. a polkit rule).
|
||||
"""
|
||||
|
||||
def with_prompted_password(self, *, ask: bool, host_label: str) -> Self:
|
||||
"""Prompt locally for a password and return a copy carrying it.
|
||||
|
||||
No-op when *ask* is false. May raise :class:`ElevateError` (e.g.
|
||||
on :class:`NoElevator`).
|
||||
"""
|
||||
if not ask:
|
||||
return self
|
||||
password = getpass.getpass(f"[{self.name}] password for {host_label}: ")
|
||||
return self.with_password(password)
|
||||
|
||||
def for_target_config(self, toplevel: PurePosixPath | Path) -> Self:
|
||||
"""Return a copy bound to the toplevel being activated on the target.
|
||||
|
||||
Backends that need a helper binary on the remote
|
||||
(:class:`Run0Elevator`'s ``polkit-stdin-agent``) use this to find
|
||||
a target-architecture copy inside the just-copied closure. No-op
|
||||
by default.
|
||||
"""
|
||||
del toplevel # unused in the base implementation
|
||||
return self
|
||||
|
||||
def on_remote_failure(self) -> str | None:
|
||||
"""Optional hint to print when a remote elevated command fails."""
|
||||
return None
|
||||
|
||||
|
||||
class ElevateError(Exception):
|
||||
"""Raised for invalid elevator/flag combinations."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NoElevator(Elevator):
|
||||
name: str = "none"
|
||||
|
||||
@property
|
||||
@override
|
||||
def elevates(self) -> bool:
|
||||
return False
|
||||
|
||||
@override
|
||||
def wrap_local(self) -> Wrapped:
|
||||
return Wrapped(prefix=[])
|
||||
|
||||
@override
|
||||
def wrap_remote(self, env: Mapping[str, EnvValue], args: Args) -> RemoteWrapped:
|
||||
return RemoteWrapped(argv=_remote_env_shell_argv([], env, args))
|
||||
|
||||
@override
|
||||
def with_password(self, password: str) -> Self:
|
||||
raise ElevateError(
|
||||
"--ask-elevate-password requires --elevate to select an elevation method"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SudoElevator(Elevator):
|
||||
"""Wrap with ``sudo``, optionally feeding the password on stdin.
|
||||
|
||||
Extra arguments come from ``NIX_SUDOOPTS`` for backwards
|
||||
compatibility with the previous implementation in ``run_wrapper``.
|
||||
"""
|
||||
|
||||
name: str = "sudo"
|
||||
password: str | None = None
|
||||
extra_opts: list[str] = field(
|
||||
default_factory=lambda: shlex.split(os.getenv("NIX_SUDOOPTS", ""))
|
||||
)
|
||||
|
||||
@override
|
||||
def wrap_local(self) -> Wrapped:
|
||||
# Local sudo can prompt on /dev/tty itself, so the password is
|
||||
# only piped when one was supplied explicitly.
|
||||
if self.password is not None:
|
||||
return Wrapped(
|
||||
prefix=["sudo", "--prompt=", "--stdin", *self.extra_opts],
|
||||
stdin=self.password + "\n",
|
||||
)
|
||||
return Wrapped(prefix=["sudo", *self.extra_opts])
|
||||
|
||||
@override
|
||||
def wrap_remote(self, env: Mapping[str, EnvValue], args: Args) -> RemoteWrapped:
|
||||
# sudo runs inside the SSH login session, so the env wrapper can
|
||||
# sit *inside* it and ${VAR-} resolves against the login env.
|
||||
if self.password is not None:
|
||||
prefix = ["sudo", "--prompt=", "--stdin", *self.extra_opts]
|
||||
stdin = self.password + "\n"
|
||||
else:
|
||||
prefix = ["sudo", *self.extra_opts]
|
||||
stdin = None
|
||||
return RemoteWrapped(
|
||||
argv=_remote_env_shell_argv(prefix, env, args),
|
||||
stdin=stdin,
|
||||
)
|
||||
|
||||
@override
|
||||
def with_password(self, password: str) -> Self:
|
||||
return replace(self, password=password)
|
||||
|
||||
@override
|
||||
def on_remote_failure(self) -> str | None:
|
||||
if self.password is None:
|
||||
return (
|
||||
"while running command with remote sudo, did you forget to "
|
||||
"use --ask-elevate-password?"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Run0Elevator(Elevator):
|
||||
"""Wrap with systemd's polkit-based ``run0``.
|
||||
|
||||
Locally, ``run0`` is used directly and the user's polkit agent
|
||||
(graphical or ``pkttyagent``) handles any prompts.
|
||||
|
||||
Remotely we spell out the explicit ``systemd-run --uid=0 --pipe``
|
||||
form instead. ``run0`` would internally do the same thing when stdio
|
||||
is not a TTY (see systemd ``src/run/run.c``), but going through
|
||||
``systemd-run`` directly gives us ``--setenv K=V``, which we need to
|
||||
forward the SSH login environment into the transient unit (whose own
|
||||
``PATH`` on NixOS is just the systemd store path), and keeps the
|
||||
argv independent of whether the SSH session happens to have a TTY.
|
||||
|
||||
Authorisation comes from either a polkit rule on the target granting
|
||||
``org.freedesktop.systemd1.manage-units`` to the deploying user, or
|
||||
from ``polkit-stdin-agent`` for ``--ask-elevate-password``.
|
||||
"""
|
||||
|
||||
name: str = "run0"
|
||||
password: str | None = None
|
||||
#: ``${toplevel}/sw/bin/polkit-stdin-agent`` on the target, set via
|
||||
#: :meth:`for_target_config`. ``None`` falls back to the target's ``PATH``.
|
||||
remote_agent: str | None = None
|
||||
|
||||
#: Non-interactive equivalent of ``run0`` (see the class docstring).
|
||||
REMOTE_BASE: ClassVar[tuple[str, ...]] = (
|
||||
"systemd-run",
|
||||
"--uid=0",
|
||||
"--pipe",
|
||||
"--quiet",
|
||||
"--wait",
|
||||
"--collect",
|
||||
"--service-type=exec",
|
||||
"--send-sighup",
|
||||
)
|
||||
|
||||
@override
|
||||
def wrap_local(self) -> Wrapped:
|
||||
if self.password is not None:
|
||||
# Resolved from PATH, same requirement as the remote case: the
|
||||
# machine doing the elevation needs
|
||||
# system.tools.nixos-rebuild.enableRun0Elevation.
|
||||
return Wrapped(
|
||||
prefix=["polkit-stdin-agent", "--password-fd=0", "--", "run0", "--"],
|
||||
stdin=self.password + "\n",
|
||||
)
|
||||
return Wrapped(prefix=["run0", "--"])
|
||||
|
||||
@override
|
||||
def wrap_remote(self, env: Mapping[str, EnvValue], args: Args) -> RemoteWrapped:
|
||||
# /bin/sh wrapper resolves PRESERVE_ENV in the SSH login session
|
||||
# and forwards the result into the unit via --setenv.
|
||||
setenvs: list[str] = []
|
||||
for k, v in env.items():
|
||||
if v is PRESERVE_ENV:
|
||||
setenvs.append(f'--setenv={k}="${{{k}-}}"')
|
||||
else:
|
||||
setenvs.append(f"--setenv={shlex.quote(f'{k}={v}')}")
|
||||
script = f'exec {shlex.join(self.REMOTE_BASE)} {" ".join(setenvs)} -- "$@"'
|
||||
argv: list[Arg] = ["/bin/sh", "-c", script, "sh", *args]
|
||||
if self.password is not None:
|
||||
# polkit has no `sudo --stdin` equivalent. polkit-stdin-agent
|
||||
# registers a per-process agent for the wrapped command and
|
||||
# answers the PAM conversation from its stdin.
|
||||
argv = self._remote_agent_argv(argv)
|
||||
return RemoteWrapped(argv=argv, stdin=self.password + "\n")
|
||||
return RemoteWrapped(argv=argv)
|
||||
|
||||
#: POSIX sh fragment that picks the first runnable agent from the
|
||||
#: positional parameters up to ``--`` and execs it with the remainder.
|
||||
#: ``command -v`` covers both absolute paths and ``PATH`` lookups.
|
||||
_AGENT_PICKER: ClassVar[str] = (
|
||||
"agent=; "
|
||||
"for a; do "
|
||||
"shift; "
|
||||
'[ "$a" = -- ] && break; '
|
||||
'[ -z "$agent" ] && command -v "$a" >/dev/null 2>&1 && agent="$a"; '
|
||||
"done; "
|
||||
'[ -n "$agent" ] && exec "$agent" --password-fd=0 -- "$@"; '
|
||||
'echo "nixos-rebuild: polkit-stdin-agent not found on target host '
|
||||
'(set system.tools.nixos-rebuild.enableRun0Elevation = true)" >&2; '
|
||||
"exit 127"
|
||||
)
|
||||
|
||||
def _remote_agent_argv(self, inner: list[Arg]) -> list[Arg]:
|
||||
"""Wrap *inner* in a target-side agent lookup.
|
||||
|
||||
The deployer's own agent may be the wrong arch/nixpkgs (cross-arch
|
||||
deploys, Darwin deployers, ``--no-reexec``), so resolve on the
|
||||
target instead: first ``${toplevel}/sw/bin/polkit-stdin-agent``
|
||||
(present when ``system.tools.nixos-rebuild.enableRun0Elevation``
|
||||
is set), then bare ``polkit-stdin-agent`` on the SSH login PATH.
|
||||
:data:`_AGENT_PICKER` exits 127 with a hint if neither is found.
|
||||
"""
|
||||
candidates: list[str] = []
|
||||
if self.remote_agent is not None:
|
||||
candidates.append(self.remote_agent)
|
||||
candidates.append("polkit-stdin-agent")
|
||||
return ["/bin/sh", "-c", self._AGENT_PICKER, "sh", *candidates, "--", *inner]
|
||||
|
||||
@override
|
||||
def with_password(self, password: str) -> Self:
|
||||
return replace(self, password=password)
|
||||
|
||||
@override
|
||||
def for_target_config(self, toplevel: PurePosixPath | Path) -> Self:
|
||||
return replace(
|
||||
self, remote_agent=str(toplevel / "sw" / "bin" / "polkit-stdin-agent")
|
||||
)
|
||||
|
||||
@override
|
||||
def on_remote_failure(self) -> str | None:
|
||||
if self.password is None:
|
||||
return (
|
||||
"while running command with remote run0. Either pass "
|
||||
"--ask-elevate-password, or grant the deploying user the "
|
||||
"polkit action 'org.freedesktop.systemd1.manage-units' on "
|
||||
"the target host (security.polkit.extraConfig)."
|
||||
)
|
||||
return (
|
||||
"while running command with remote run0. If the error above "
|
||||
"mentions polkit-stdin-agent or PolicyKit1, the target host "
|
||||
"needs system.tools.nixos-rebuild.enableRun0Elevation = true."
|
||||
)
|
||||
|
||||
|
||||
class ElevatorKind(Enum):
|
||||
"""CLI-selectable elevation backends.
|
||||
|
||||
The enum *value* is the :class:`Elevator` subclass to instantiate,
|
||||
``str(member)`` is what ``--elevate`` accepts on the command line.
|
||||
"""
|
||||
|
||||
NONE = NoElevator
|
||||
SUDO = SudoElevator
|
||||
RUN0 = Run0Elevator
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
return self.name.lower()
|
||||
|
||||
def make(self) -> Elevator:
|
||||
"""Instantiate a fresh, unparameterised elevator of this kind."""
|
||||
cls: type[Elevator] = self.value
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def choices(cls) -> list[str]:
|
||||
"""All ``--elevate`` values, for argparse ``choices=``."""
|
||||
return [str(m) for m in cls]
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, name: str) -> Elevator:
|
||||
try:
|
||||
return cls[name.upper()].make()
|
||||
except KeyError:
|
||||
raise ElevateError(
|
||||
f"unknown elevation method {name!r}, choose from: "
|
||||
+ ", ".join(cls.choices())
|
||||
) from None
|
||||
|
||||
|
||||
#: Singleton used as the default ``elevate=`` argument throughout.
|
||||
NO_ELEVATOR: Final[Elevator] = NoElevator()
|
||||
@@ -14,6 +14,7 @@ from textwrap import dedent
|
||||
from typing import Final, Literal
|
||||
|
||||
from . import tmpdir
|
||||
from .elevate import NO_ELEVATOR, PRESERVE_ENV, Elevator
|
||||
from .models import (
|
||||
Action,
|
||||
BuildAttr,
|
||||
@@ -25,7 +26,7 @@ from .models import (
|
||||
Profile,
|
||||
Remote,
|
||||
)
|
||||
from .process import PRESERVE_ENV, SSH_DEFAULT_OPTS, run_wrapper
|
||||
from .process import SSH_DEFAULT_OPTS, run_wrapper
|
||||
from .utils import Args, dict_to_flags
|
||||
|
||||
FLAKE_FLAGS: Final = ["--extra-experimental-features", "nix-command flakes"]
|
||||
@@ -453,7 +454,7 @@ def get_generations(profile: Profile) -> list[Generation]:
|
||||
def get_generations_from_nix_env(
|
||||
profile: Profile,
|
||||
target_host: Remote | None = None,
|
||||
sudo: bool = False,
|
||||
elevate: Elevator = NO_ELEVATOR,
|
||||
) -> list[Generation]:
|
||||
"""Get all NixOS generations from profile with nix-env. Needs root.
|
||||
|
||||
@@ -468,7 +469,7 @@ def get_generations_from_nix_env(
|
||||
["nix-env", "-p", profile.path, "--list-generations"],
|
||||
stdout=PIPE,
|
||||
remote=target_host,
|
||||
sudo=sudo,
|
||||
elevate=elevate,
|
||||
)
|
||||
|
||||
def parse_line(line: str) -> Generation:
|
||||
@@ -600,12 +601,12 @@ def repl_flake(flake: Flake, flake_flags: Args | None = None) -> None:
|
||||
)
|
||||
|
||||
|
||||
def rollback(profile: Profile, target_host: Remote | None, sudo: bool) -> Path:
|
||||
def rollback(profile: Profile, target_host: Remote | None, elevate: Elevator) -> Path:
|
||||
"Rollback Nix profile, like one created by `nixos-rebuild switch`."
|
||||
run_wrapper(
|
||||
["nix-env", "--rollback", "-p", profile.path],
|
||||
remote=target_host,
|
||||
sudo=sudo,
|
||||
elevate=elevate,
|
||||
)
|
||||
# Rollback config PATH is the own profile
|
||||
return profile.path
|
||||
@@ -614,11 +615,11 @@ def rollback(profile: Profile, target_host: Remote | None, sudo: bool) -> Path:
|
||||
def rollback_temporary_profile(
|
||||
profile: Profile,
|
||||
target_host: Remote | None,
|
||||
sudo: bool,
|
||||
elevate: Elevator,
|
||||
) -> Path | None:
|
||||
"Rollback a temporary Nix profile, like one created by `nixos-rebuild test`."
|
||||
generations = get_generations_from_nix_env(
|
||||
profile, target_host=target_host, sudo=sudo
|
||||
profile, target_host=target_host, elevate=elevate
|
||||
)
|
||||
previous_gen_id = None
|
||||
for generation in generations:
|
||||
@@ -635,7 +636,7 @@ def set_profile(
|
||||
profile: Profile,
|
||||
path_to_config: Path,
|
||||
target_host: Remote | None,
|
||||
sudo: bool,
|
||||
elevate: Elevator,
|
||||
) -> None:
|
||||
"Set a path as the current active Nix profile."
|
||||
if not os.environ.get(
|
||||
@@ -668,7 +669,7 @@ def set_profile(
|
||||
run_wrapper(
|
||||
["nix-env", "-p", profile.path, "--set", path_to_config],
|
||||
remote=target_host,
|
||||
sudo=sudo,
|
||||
elevate=elevate,
|
||||
)
|
||||
|
||||
|
||||
@@ -676,7 +677,7 @@ def switch_to_configuration(
|
||||
path_to_config: Path,
|
||||
action: Literal[Action.SWITCH, Action.BOOT, Action.TEST, Action.DRY_ACTIVATE],
|
||||
target_host: Remote | None,
|
||||
sudo: bool,
|
||||
elevate: Elevator,
|
||||
install_bootloader: bool = False,
|
||||
specialisation: str | None = None,
|
||||
) -> None:
|
||||
@@ -716,7 +717,7 @@ def switch_to_configuration(
|
||||
"NIXOS_INSTALL_BOOTLOADER": "1" if install_bootloader else "0",
|
||||
},
|
||||
remote=target_host,
|
||||
sudo=sudo,
|
||||
elevate=elevate,
|
||||
# switch-to-configuration is not expected to produce meaningful
|
||||
# stdout, but if it (or any of its children) does, it would leak
|
||||
# into our stdout and break the "only the store path on stdout"
|
||||
@@ -728,7 +729,7 @@ def switch_to_configuration(
|
||||
|
||||
def upgrade_channels(
|
||||
all_channels: bool = False,
|
||||
sudo: bool = False,
|
||||
elevate: Elevator = NO_ELEVATOR,
|
||||
channels_dir: Path = Path("/nix/var/nix/profiles/per-user/root/channels/"),
|
||||
) -> None:
|
||||
"""Upgrade channels for classic Nix.
|
||||
@@ -736,10 +737,10 @@ def upgrade_channels(
|
||||
It will either upgrade just the `nixos` channel (including any channel
|
||||
that has a `.update-on-nixos-rebuild` file) or all.
|
||||
"""
|
||||
if not sudo and os.geteuid() != 0:
|
||||
if not elevate.elevates and os.geteuid() != 0:
|
||||
raise NixOSRebuildError(
|
||||
"if you pass the '--upgrade' or '--upgrade-all' flag, you must "
|
||||
"also pass '--sudo' or run the command as root (e.g., with sudo)"
|
||||
"also pass '--elevate' or run the command as root"
|
||||
)
|
||||
|
||||
channel_updated = False
|
||||
@@ -752,7 +753,7 @@ def upgrade_channels(
|
||||
run_wrapper(
|
||||
["nix-channel", "--update", channel_path.name],
|
||||
check=False,
|
||||
sudo=sudo,
|
||||
elevate=elevate,
|
||||
)
|
||||
channel_updated = True
|
||||
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import atexit
|
||||
import getpass
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
from collections.abc import Mapping, Sequence
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from ipaddress import AddressValueError, IPv6Address
|
||||
from typing import Final, Literal, Self, TextIO, TypedDict, Unpack, override
|
||||
from typing import Final, Self, TextIO, TypedDict, Unpack
|
||||
|
||||
from . import tmpdir
|
||||
from .elevate import (
|
||||
NO_ELEVATOR,
|
||||
PRESERVE_ENV,
|
||||
Arg,
|
||||
Args,
|
||||
Elevator,
|
||||
EnvValue,
|
||||
)
|
||||
|
||||
logger: Final = logging.getLogger(__name__)
|
||||
|
||||
@@ -19,40 +25,22 @@ SSH_DEFAULT_OPTS: Final = [
|
||||
"-o",
|
||||
"ControlMaster=auto",
|
||||
"-o",
|
||||
f"ControlPath={tmpdir.TMPDIR_PATH / 'ssh-%n'}",
|
||||
f"ControlPath={tmpdir.TMPDIR_PATH / 'ssh-%C'}",
|
||||
"-o",
|
||||
"ControlPersist=60",
|
||||
]
|
||||
|
||||
|
||||
class _Env(Enum):
|
||||
PRESERVE_ENV = "PRESERVE"
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
||||
PRESERVE_ENV: Final = _Env.PRESERVE_ENV
|
||||
|
||||
|
||||
type Arg = str | bytes | os.PathLike[str] | os.PathLike[bytes]
|
||||
type Args = Sequence[Arg]
|
||||
type EnvValue = str | Literal[_Env.PRESERVE_ENV]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Remote:
|
||||
host: str
|
||||
opts: list[str]
|
||||
sudo_password: str | None
|
||||
store_type: str
|
||||
|
||||
@classmethod
|
||||
def from_arg(
|
||||
cls,
|
||||
host: str | None,
|
||||
ask_sudo_password: bool | None,
|
||||
validate_opts: bool = True,
|
||||
) -> Self | None:
|
||||
if not host:
|
||||
@@ -65,25 +53,21 @@ class Remote:
|
||||
|
||||
opts = shlex.split(os.getenv("NIX_SSHOPTS", ""))
|
||||
if validate_opts:
|
||||
cls._validate_opts(opts, ask_sudo_password)
|
||||
sudo_password = None
|
||||
if ask_sudo_password:
|
||||
sudo_password = getpass.getpass(f"[sudo] password for {host}: ")
|
||||
return cls(host, opts, sudo_password, store_type)
|
||||
cls._validate_opts(opts)
|
||||
return cls(host, opts, store_type)
|
||||
|
||||
@staticmethod
|
||||
def _validate_opts(opts: list[str], ask_sudo_password: bool | None) -> None:
|
||||
def _validate_opts(opts: list[str]) -> None:
|
||||
for o in opts:
|
||||
if o in ["-t", "-tt", "RequestTTY=yes", "RequestTTY=force"]:
|
||||
logger.warning(
|
||||
f"detected option '{o}' in NIX_SSHOPTS. SSH's TTY may "
|
||||
"cause issues, it is recommended to remove this option"
|
||||
)
|
||||
if not ask_sudo_password:
|
||||
logger.warning(
|
||||
"if you want to prompt for sudo password use "
|
||||
"'--ask-sudo-password' option instead"
|
||||
)
|
||||
logger.warning(
|
||||
"if you want to prompt for a password for remote "
|
||||
"elevation use '--ask-elevate-password' instead"
|
||||
)
|
||||
|
||||
def ssh_host(self) -> str:
|
||||
"""Fix up host string for SSH.
|
||||
@@ -130,7 +114,7 @@ def run_wrapper(
|
||||
env: Mapping[str, EnvValue] | None = None,
|
||||
append_local_env: Mapping[str, str] | None = None,
|
||||
remote: Remote | None = None,
|
||||
sudo: bool = False,
|
||||
elevate: Elevator = NO_ELEVATOR,
|
||||
**kwargs: Unpack[RunKwargs],
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
"Wrapper around `subprocess.run` that supports extra functionality."
|
||||
@@ -142,27 +126,9 @@ def run_wrapper(
|
||||
resolved_env = _resolve_env_local(normalized_env)
|
||||
|
||||
if remote:
|
||||
remote_run_args: list[Arg] = [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
_remote_shell_script(normalized_env),
|
||||
"sh",
|
||||
*run_args,
|
||||
]
|
||||
|
||||
if sudo:
|
||||
sudo_args = shlex.split(os.getenv("NIX_SUDOOPTS", ""))
|
||||
if remote.sudo_password:
|
||||
remote_run_args = [
|
||||
"sudo",
|
||||
"--prompt=",
|
||||
"--stdin",
|
||||
*sudo_args,
|
||||
*remote_run_args,
|
||||
]
|
||||
process_input = remote.sudo_password + "\n"
|
||||
else:
|
||||
remote_run_args = ["sudo", *sudo_args, *remote_run_args]
|
||||
rwrapped = elevate.wrap_remote(normalized_env, run_args)
|
||||
process_input = rwrapped.stdin
|
||||
remote_run_args: list[Arg] = rwrapped.argv
|
||||
|
||||
ssh_args: list[Arg] = [
|
||||
"ssh",
|
||||
@@ -176,22 +142,19 @@ def run_wrapper(
|
||||
popen_env = None # keep ssh's environment normal
|
||||
|
||||
else:
|
||||
if sudo:
|
||||
# subprocess.run(env=...) would affect sudo, but sudo may drop env
|
||||
# for the target command.
|
||||
# So we inject env via `sudo env ... cmd`.
|
||||
wrapped = elevate.wrap_local()
|
||||
process_input = wrapped.stdin
|
||||
if elevate.elevates:
|
||||
# subprocess.run(env=...) would affect the elevator process,
|
||||
# which may then drop env for the target command. Inject env
|
||||
# via `env -i ... cmd` instead so it survives.
|
||||
if env is not None and resolved_env:
|
||||
run_args = _prefix_env_cmd(run_args, resolved_env)
|
||||
|
||||
sudo_args = shlex.split(os.getenv("NIX_SUDOOPTS", ""))
|
||||
final_args = ["sudo", *sudo_args, *run_args]
|
||||
|
||||
# No need to pass env to subprocess.run; keep sudo's own env
|
||||
# default.
|
||||
final_args = [*wrapped.prefix, *run_args]
|
||||
popen_env = None
|
||||
else:
|
||||
# Non-sudo local: we can fully control the environment with
|
||||
# subprocess.run(env=...)
|
||||
# Unprivileged local: we can fully control the environment
|
||||
# with subprocess.run(env=...)
|
||||
final_args = run_args
|
||||
popen_env = None if env is None else resolved_env
|
||||
|
||||
@@ -225,16 +188,14 @@ def run_wrapper(
|
||||
|
||||
return r
|
||||
except KeyboardInterrupt:
|
||||
# sudo commands are activation only and unlikely to be long running
|
||||
if remote and not sudo:
|
||||
# elevated commands are activation only and unlikely to be long
|
||||
# running
|
||||
if remote and not elevate.elevates:
|
||||
_kill_long_running_ssh_process(args, remote)
|
||||
raise
|
||||
except subprocess.CalledProcessError:
|
||||
if sudo and remote and remote.sudo_password is None:
|
||||
logger.error(
|
||||
"while running command with remote sudo, did you forget to use "
|
||||
"--ask-sudo-password?"
|
||||
)
|
||||
if remote and (hint := elevate.on_remote_failure()):
|
||||
logger.error(hint)
|
||||
raise
|
||||
|
||||
|
||||
@@ -268,7 +229,7 @@ def _resolve_env_local(env: dict[str, EnvValue]) -> dict[str, str]:
|
||||
return result
|
||||
|
||||
|
||||
def _prefix_env_cmd(cmd: Sequence[Arg], resolved_env: dict[str, str]) -> list[Arg]:
|
||||
def _prefix_env_cmd(cmd: Args, resolved_env: dict[str, str]) -> list[Arg]:
|
||||
"""
|
||||
Prefix a command with `env -i K=V ... -- <cmd...>` to set vars for the
|
||||
command.
|
||||
@@ -280,24 +241,6 @@ def _prefix_env_cmd(cmd: Sequence[Arg], resolved_env: dict[str, str]) -> list[Ar
|
||||
return ["env", "-i", *assigns, *cmd]
|
||||
|
||||
|
||||
def _remote_shell_script(env: Mapping[str, EnvValue]) -> str:
|
||||
"""
|
||||
Build the POSIX shell wrapper used for remote execution over SSH.
|
||||
|
||||
SSH sends the remote command as a shell-interpreted command line, so we
|
||||
need a wrapper to establish a clean environment before `exec`-ing the real
|
||||
command. This wrapper is always run under `/bin/sh -c` so preserved
|
||||
variables like `${PATH-}` do not depend on the remote user's login shell.
|
||||
"""
|
||||
shell_assigns: list[str] = []
|
||||
for k, v in env.items():
|
||||
if v is PRESERVE_ENV:
|
||||
shell_assigns.append(f'{k}="${{{k}-}}"')
|
||||
else:
|
||||
shell_assigns.append(f"{k}={shlex.quote(v)}")
|
||||
return f'exec env -i {" ".join(shell_assigns)} "$@"'
|
||||
|
||||
|
||||
def _quote_remote_arg(arg: Arg) -> str:
|
||||
return shlex.quote(str(arg))
|
||||
|
||||
|
||||
@@ -35,11 +35,7 @@ def reexec(
|
||||
return
|
||||
|
||||
drv = None
|
||||
# Parsing the args here but ignore ask_sudo_password since it is not
|
||||
# needed and we would end up asking sudo password twice
|
||||
if flake := Flake.from_arg(
|
||||
args.flake, Remote.from_arg(args.target_host, ask_sudo_password=None)
|
||||
):
|
||||
if flake := Flake.from_arg(args.flake, Remote.from_arg(args.target_host)):
|
||||
drv = nix.build_flake(
|
||||
NIXOS_REBUILD_ATTR,
|
||||
flake,
|
||||
@@ -132,12 +128,12 @@ def _rollback_system(
|
||||
) -> Path:
|
||||
match action:
|
||||
case Action.SWITCH | Action.BOOT:
|
||||
path_to_config = nix.rollback(profile, target_host, sudo=args.sudo)
|
||||
path_to_config = nix.rollback(profile, target_host, elevate=args.elevator)
|
||||
case Action.TEST | Action.BUILD:
|
||||
maybe_path_to_config = nix.rollback_temporary_profile(
|
||||
profile,
|
||||
target_host,
|
||||
sudo=args.sudo,
|
||||
elevate=args.elevator,
|
||||
)
|
||||
if maybe_path_to_config:
|
||||
path_to_config = maybe_path_to_config
|
||||
@@ -231,13 +227,13 @@ def _activate_system(
|
||||
profile,
|
||||
path_to_config,
|
||||
target_host=target_host,
|
||||
sudo=args.sudo,
|
||||
elevate=args.elevator,
|
||||
)
|
||||
nix.switch_to_configuration(
|
||||
path_to_config,
|
||||
action,
|
||||
target_host=target_host,
|
||||
sudo=args.sudo,
|
||||
elevate=args.elevator,
|
||||
specialisation=args.specialisation,
|
||||
install_bootloader=args.install_bootloader,
|
||||
)
|
||||
@@ -247,7 +243,7 @@ def _activate_system(
|
||||
path_to_config,
|
||||
action,
|
||||
target_host=target_host,
|
||||
sudo=args.sudo,
|
||||
elevate=args.elevator,
|
||||
specialisation=args.specialisation,
|
||||
install_bootloader=args.install_bootloader,
|
||||
)
|
||||
@@ -302,6 +298,11 @@ def build_and_activate_system(
|
||||
copy_flags=grouped_nix_args.copy_flags,
|
||||
)
|
||||
elif args.rollback:
|
||||
if target_host is not None:
|
||||
# The elevated `nix-env --rollback` runs before path_to_config
|
||||
# is known, so point the elevator at the profile to find a
|
||||
# target-arch helper in the *current* generation's sw/bin.
|
||||
args.elevator = args.elevator.for_target_config(profile.path)
|
||||
path_to_config = _rollback_system(
|
||||
action=action,
|
||||
args=args,
|
||||
@@ -319,6 +320,11 @@ def build_and_activate_system(
|
||||
grouped_nix_args=grouped_nix_args,
|
||||
)
|
||||
|
||||
if target_host is not None and not args.rollback:
|
||||
# Prefer the helper from the toplevel we just copied to the
|
||||
# target (correct arch, independent of re-exec / nixpkgs pin).
|
||||
args.elevator = args.elevator.for_target_config(path_to_config)
|
||||
|
||||
current_config = Path("/run/current-system")
|
||||
if args.diff:
|
||||
if current_config.exists():
|
||||
|
||||
155
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_elevate.py
Normal file
155
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_elevate.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
import pytest
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
import nixos_rebuild.elevate as e
|
||||
|
||||
|
||||
def test_no_elevator() -> None:
|
||||
n = e.NoElevator()
|
||||
assert not n.elevates
|
||||
assert n.wrap_local() == e.Wrapped(prefix=[])
|
||||
rw = n.wrap_remote({"PATH": e.PRESERVE_ENV}, ["cmd"])
|
||||
assert rw.stdin is None
|
||||
assert rw.argv[:2] == ["/bin/sh", "-c"]
|
||||
assert rw.argv[-1] == "cmd"
|
||||
with pytest.raises(e.ElevateError):
|
||||
n.with_password("x")
|
||||
|
||||
|
||||
def test_sudo_elevator(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("NIX_SUDOOPTS", raising=False)
|
||||
|
||||
s = e.SudoElevator()
|
||||
assert s.elevates
|
||||
assert s.wrap_local() == e.Wrapped(prefix=["sudo"])
|
||||
rw = s.wrap_remote({"PATH": e.PRESERVE_ENV}, ["cmd"])
|
||||
assert rw.argv[0] == "sudo"
|
||||
assert rw.argv[1:3] == ["/bin/sh", "-c"]
|
||||
assert rw.stdin is None
|
||||
assert s.on_remote_failure() is not None
|
||||
|
||||
sp = s.with_password("hunter2")
|
||||
rw = sp.wrap_remote({"PATH": e.PRESERVE_ENV}, ["cmd"])
|
||||
assert rw.argv[:3] == ["sudo", "--prompt=", "--stdin"]
|
||||
assert rw.stdin == "hunter2\n"
|
||||
assert sp.on_remote_failure() is None
|
||||
# original unchanged
|
||||
assert s.password is None
|
||||
|
||||
|
||||
def test_sudo_elevator_extra_opts(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("NIX_SUDOOPTS", "--preserve-env=FOO -H")
|
||||
s = e.SudoElevator()
|
||||
assert s.wrap_local() == e.Wrapped(prefix=["sudo", "--preserve-env=FOO", "-H"])
|
||||
rw = s.with_password("p").wrap_remote({"PATH": e.PRESERVE_ENV}, ["cmd"])
|
||||
assert rw.argv[:5] == ["sudo", "--prompt=", "--stdin", "--preserve-env=FOO", "-H"]
|
||||
assert rw.stdin == "p\n"
|
||||
|
||||
|
||||
def test_run0_elevator() -> None:
|
||||
r = e.Run0Elevator()
|
||||
assert r.elevates
|
||||
assert r.wrap_local() == e.Wrapped(prefix=["run0", "--"])
|
||||
assert r.on_remote_failure() is not None
|
||||
|
||||
rp = r.with_password("hunter2")
|
||||
w = rp.wrap_local()
|
||||
assert w.prefix[0] == "polkit-stdin-agent"
|
||||
assert "run0" in w.prefix
|
||||
assert w.stdin == "hunter2\n"
|
||||
# With a password the failure hint points at the agent, not at -S.
|
||||
hint = rp.on_remote_failure()
|
||||
assert hint is not None and "polkit-stdin-agent" in hint
|
||||
|
||||
|
||||
def test_run0_elevator_remote() -> None:
|
||||
r = e.Run0Elevator()
|
||||
|
||||
# No password: /bin/sh wrapper around systemd-run, env passed via
|
||||
# --setenv so it is resolved in the SSH login shell rather than
|
||||
# inside the transient unit (which has a useless PATH on NixOS).
|
||||
rw = r.wrap_remote(
|
||||
{"PATH": e.PRESERVE_ENV, "NIXOS_INSTALL_BOOTLOADER": "1"},
|
||||
["nix-env", "-p", "/profile"],
|
||||
)
|
||||
assert rw.stdin is None
|
||||
assert rw.argv[:2] == ["/bin/sh", "-c"]
|
||||
script = rw.argv[2]
|
||||
assert isinstance(script, str)
|
||||
assert script.startswith("exec systemd-run --uid=0 --pipe ")
|
||||
assert '--setenv=PATH="${PATH-}"' in script
|
||||
assert "--setenv=NIXOS_INSTALL_BOOTLOADER=1" in script
|
||||
assert script.endswith(' -- "$@"')
|
||||
assert rw.argv[3:] == ["sh", "nix-env", "-p", "/profile"]
|
||||
|
||||
# With password: an agent-picker /bin/sh wraps the inner /bin/sh, so
|
||||
# the agent is registered for the inner shell (and the systemd-run it
|
||||
# execs into). With no toplevel bound the only candidate is bare-name
|
||||
# PATH lookup.
|
||||
rw = r.with_password("pw").wrap_remote({"PATH": e.PRESERVE_ENV}, ["cmd"])
|
||||
assert rw.stdin == "pw\n"
|
||||
assert rw.argv[:3] == ["/bin/sh", "-c", e.Run0Elevator._AGENT_PICKER]
|
||||
sep = rw.argv.index("--")
|
||||
assert rw.argv[3:sep] == ["sh", "polkit-stdin-agent"]
|
||||
assert rw.argv[sep + 1 : sep + 3] == ["/bin/sh", "-c"]
|
||||
|
||||
# Explicit values containing spaces are shell-quoted inside the
|
||||
# script (the whole thing is later shlex.quoted again for SSH).
|
||||
rw = r.wrap_remote({"FOO": "a b"}, ["cmd"])
|
||||
script = rw.argv[2]
|
||||
assert isinstance(script, str)
|
||||
assert "--setenv='FOO=a b'" in script
|
||||
|
||||
|
||||
def test_run0_for_target_config() -> None:
|
||||
toplevel = PurePosixPath("/nix/store/aaaa-nixos-system")
|
||||
r = e.Run0Elevator().with_password("pw").for_target_config(toplevel)
|
||||
assert r.remote_agent == f"{toplevel}/sw/bin/polkit-stdin-agent"
|
||||
|
||||
rw = r.wrap_remote({"PATH": e.PRESERVE_ENV}, ["cmd"])
|
||||
sep = rw.argv.index("--")
|
||||
# Order matters: target-arch toplevel first, then PATH.
|
||||
assert rw.argv[4:sep] == [
|
||||
f"{toplevel}/sw/bin/polkit-stdin-agent",
|
||||
"polkit-stdin-agent",
|
||||
]
|
||||
# Inner argv is preserved verbatim after the separator.
|
||||
assert rw.argv[sep + 1 : sep + 3] == ["/bin/sh", "-c"]
|
||||
assert rw.argv[-1] == "cmd"
|
||||
|
||||
# Non-run0 elevators ignore the toplevel.
|
||||
s = e.SudoElevator()
|
||||
assert s.for_target_config(toplevel) is s
|
||||
|
||||
|
||||
def test_elevator_kind() -> None:
|
||||
assert isinstance(e.ElevatorKind.from_name("sudo"), e.SudoElevator)
|
||||
assert isinstance(e.ElevatorKind.from_name("run0"), e.Run0Elevator)
|
||||
assert isinstance(e.ElevatorKind.from_name("none"), e.NoElevator)
|
||||
with pytest.raises(e.ElevateError):
|
||||
e.ElevatorKind.from_name("doas")
|
||||
assert set(e.ElevatorKind.choices()) == {"none", "sudo", "run0"}
|
||||
|
||||
|
||||
def test_with_prompted_password(monkeypatch: MonkeyPatch) -> None:
|
||||
prompts: list[str] = []
|
||||
|
||||
def fake_getpass(prompt: str) -> str:
|
||||
prompts.append(prompt)
|
||||
return "hunter2"
|
||||
|
||||
monkeypatch.setattr(e.getpass, "getpass", fake_getpass)
|
||||
|
||||
s = e.SudoElevator()
|
||||
assert s.with_prompted_password(ask=False, host_label="x") is s
|
||||
assert prompts == []
|
||||
|
||||
sp = s.with_prompted_password(ask=True, host_label="user@host")
|
||||
assert isinstance(sp, e.SudoElevator)
|
||||
assert sp.password == "hunter2"
|
||||
assert prompts == ["[sudo] password for user@host: "]
|
||||
|
||||
with pytest.raises(e.ElevateError):
|
||||
e.NoElevator().with_prompted_password(ask=True, host_label="localhost")
|
||||
@@ -21,6 +21,37 @@ DEFAULT_RUN_KWARGS = {
|
||||
}
|
||||
|
||||
|
||||
def test_parse_args_elevate() -> None:
|
||||
r, _ = nr.parse_args(["nixos-rebuild", "switch"])
|
||||
assert r.elevator is nr.elevate.NO_ELEVATOR
|
||||
|
||||
r, _ = nr.parse_args(["nixos-rebuild", "switch", "--elevate=sudo"])
|
||||
assert isinstance(r.elevator, nr.elevate.SudoElevator)
|
||||
|
||||
r, _ = nr.parse_args(["nixos-rebuild", "switch", "--elevate=run0"])
|
||||
assert isinstance(r.elevator, nr.elevate.Run0Elevator)
|
||||
|
||||
# back-compat aliases
|
||||
for flag in ("--sudo", "--use-remote-sudo"):
|
||||
r, _ = nr.parse_args(["nixos-rebuild", "switch", flag])
|
||||
assert isinstance(r.elevator, nr.elevate.SudoElevator)
|
||||
|
||||
r, _ = nr.parse_args(["nixos-rebuild", "switch", "--ask-sudo-password"])
|
||||
assert isinstance(r.elevator, nr.elevate.SudoElevator)
|
||||
assert r.ask_elevate_password
|
||||
|
||||
# -S without --elevate implies sudo
|
||||
r, _ = nr.parse_args(["nixos-rebuild", "switch", "-S"])
|
||||
assert isinstance(r.elevator, nr.elevate.SudoElevator)
|
||||
|
||||
# explicit --elevate wins over the --sudo alias
|
||||
r, _ = nr.parse_args(["nixos-rebuild", "switch", "--elevate=none", "--sudo"])
|
||||
assert isinstance(r.elevator, nr.elevate.NoElevator)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
nr.parse_args(["nixos-rebuild", "switch", "--elevate=doas"])
|
||||
|
||||
|
||||
def test_parse_args() -> None:
|
||||
with pytest.raises(SystemExit) as e:
|
||||
nr.parse_args(["nixos-rebuild", "unknown-action"])
|
||||
@@ -663,7 +694,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"mktemp",
|
||||
"-d",
|
||||
@@ -682,7 +713,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"nix-store",
|
||||
"--realise",
|
||||
@@ -702,7 +733,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"readlink",
|
||||
"-f",
|
||||
@@ -720,7 +751,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"rm",
|
||||
"-rf",
|
||||
@@ -753,7 +784,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
"sudo",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"nix-env",
|
||||
"-p",
|
||||
@@ -772,7 +803,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"test",
|
||||
"-d",
|
||||
@@ -790,7 +821,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
"sudo",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"sh",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
str(config_path / "bin/switch-to-configuration"),
|
||||
@@ -879,7 +910,7 @@ def test_execute_nix_switch_flake_target_host(
|
||||
"sudo",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"nix-env",
|
||||
"-p",
|
||||
@@ -898,7 +929,7 @@ def test_execute_nix_switch_flake_target_host(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"test",
|
||||
"-d",
|
||||
@@ -916,7 +947,7 @@ def test_execute_nix_switch_flake_target_host(
|
||||
"sudo",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"sh",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
str(config_path / "bin/switch-to-configuration"),
|
||||
@@ -1004,7 +1035,7 @@ def test_execute_nix_switch_flake_build_host(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"nix",
|
||||
"--extra-experimental-features",
|
||||
@@ -1243,7 +1274,7 @@ def test_execute_build_dry_run_build_and_target_remote(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"nix",
|
||||
"--extra-experimental-features",
|
||||
@@ -1498,7 +1529,7 @@ def test_execute_switch_store_path_target_host(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"test",
|
||||
"-f",
|
||||
@@ -1516,7 +1547,7 @@ def test_execute_switch_store_path_target_host(
|
||||
"sudo",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"nix-env",
|
||||
"-p",
|
||||
@@ -1535,7 +1566,7 @@ def test_execute_switch_store_path_target_host(
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"test",
|
||||
"-d",
|
||||
@@ -1553,7 +1584,7 @@ def test_execute_switch_store_path_target_host(
|
||||
"sudo",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"sh",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
str(config_path / "bin/switch-to-configuration"),
|
||||
|
||||
@@ -81,7 +81,7 @@ def test_flake_parse(mock_node: Mock, tmpdir: Path, monkeypatch: MonkeyPatch) ->
|
||||
autospec=True,
|
||||
return_value=subprocess.CompletedProcess([], 0, stdout="remote\n"),
|
||||
):
|
||||
target_host = m.Remote("target@remote", [], None, "ssh")
|
||||
target_host = m.Remote("target@remote", [], "ssh")
|
||||
assert m.Flake.parse("/path/to/flake", target_host) == m.Flake(
|
||||
"/path/to/flake", 'nixosConfigurations."remote"'
|
||||
)
|
||||
@@ -201,7 +201,7 @@ def test_flake_from_arg(
|
||||
),
|
||||
):
|
||||
assert m.Flake.from_arg(
|
||||
"/path/to", m.Remote("user@host", [], None, "ssh")
|
||||
"/path/to", m.Remote("user@host", [], "ssh")
|
||||
) == m.Flake("/path/to", 'nixosConfigurations."remote-hostname"')
|
||||
|
||||
|
||||
|
||||
@@ -9,12 +9,15 @@ from unittest.mock import ANY, Mock, call, patch
|
||||
import pytest
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
import nixos_rebuild.elevate as e
|
||||
import nixos_rebuild.models as m
|
||||
import nixos_rebuild.nix as n
|
||||
import nixos_rebuild.process as p
|
||||
|
||||
from .helpers import get_qualified_name
|
||||
|
||||
SUDO = e.SudoElevator()
|
||||
|
||||
|
||||
@patch(
|
||||
get_qualified_name(n.run_wrapper, n),
|
||||
@@ -83,7 +86,7 @@ def test_build_flake(mock_run: Mock, monkeypatch: MonkeyPatch, tmpdir: Path) ->
|
||||
def test_build_remote(
|
||||
mock_uuid4: Mock, mock_run: Mock, monkeypatch: MonkeyPatch
|
||||
) -> None:
|
||||
build_host = m.Remote("user@host", [], None, "ssh")
|
||||
build_host = m.Remote("user@host", [], "ssh")
|
||||
monkeypatch.setenv("NIX_SSHOPTS", "--ssh opts")
|
||||
|
||||
def run_wrapper_side_effect(
|
||||
@@ -177,7 +180,7 @@ def test_build_remote_flake(
|
||||
) -> None:
|
||||
monkeypatch.chdir(tmpdir)
|
||||
flake = m.Flake.parse("/flake.nix#hostname")
|
||||
build_host = m.Remote("user@host", [], None, "ssh")
|
||||
build_host = m.Remote("user@host", [], "ssh")
|
||||
monkeypatch.setenv("NIX_SSHOPTS", "--ssh opts")
|
||||
|
||||
assert n.build_remote_flake(
|
||||
@@ -237,9 +240,9 @@ def test_copy_closure(monkeypatch: MonkeyPatch) -> None:
|
||||
n.copy_closure(closure, None)
|
||||
mock_run.assert_not_called()
|
||||
|
||||
target_host = m.Remote("user@target.host", [], None, "ssh")
|
||||
build_host = m.Remote("user@build.host", [], None, "ssh")
|
||||
target_host_ng = m.Remote("user@target.host", [], None, "ssh-ng")
|
||||
target_host = m.Remote("user@target.host", [], "ssh")
|
||||
build_host = m.Remote("user@build.host", [], "ssh")
|
||||
target_host_ng = m.Remote("user@target.host", [], "ssh-ng")
|
||||
with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run:
|
||||
n.copy_closure(closure, target_host)
|
||||
mock_run.assert_called_with(
|
||||
@@ -531,15 +534,15 @@ def test_get_generations_from_nix_env(tmp_path: Path) -> None:
|
||||
["nix-env", "-p", path, "--list-generations"],
|
||||
stdout=PIPE,
|
||||
remote=None,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
)
|
||||
|
||||
remote = m.Remote("user@host", [], "password", "ssh")
|
||||
remote = m.Remote("user@host", [], "ssh")
|
||||
with patch(
|
||||
get_qualified_name(n.run_wrapper, n), autospec=True, return_value=return_value
|
||||
) as mock_run:
|
||||
assert n.get_generations_from_nix_env(
|
||||
m.Profile("system", path), remote, True
|
||||
m.Profile("system", path), remote, SUDO
|
||||
) == [
|
||||
m.Generation(id=2082, current=False, timestamp="2024-11-07 22:58:56"),
|
||||
m.Generation(id=2083, current=False, timestamp="2024-11-07 22:59:41"),
|
||||
@@ -549,7 +552,7 @@ def test_get_generations_from_nix_env(tmp_path: Path) -> None:
|
||||
["nix-env", "-p", path, "--list-generations"],
|
||||
stdout=PIPE,
|
||||
remote=remote,
|
||||
sudo=True,
|
||||
elevate=SUDO,
|
||||
)
|
||||
|
||||
|
||||
@@ -640,19 +643,19 @@ def test_rollback(mock_run: Mock, tmp_path: Path) -> None:
|
||||
|
||||
profile = m.Profile("system", path)
|
||||
|
||||
assert n.rollback(profile, None, False) == profile.path
|
||||
assert n.rollback(profile, None, e.NO_ELEVATOR) == profile.path
|
||||
mock_run.assert_called_with(
|
||||
["nix-env", "--rollback", "-p", path],
|
||||
remote=None,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
)
|
||||
|
||||
target_host = m.Remote("user@localhost", [], None, "ssh")
|
||||
assert n.rollback(profile, target_host, True) == profile.path
|
||||
target_host = m.Remote("user@localhost", [], "ssh")
|
||||
assert n.rollback(profile, target_host, SUDO) == profile.path
|
||||
mock_run.assert_called_with(
|
||||
["nix-env", "--rollback", "-p", path],
|
||||
remote=target_host,
|
||||
sudo=True,
|
||||
elevate=SUDO,
|
||||
)
|
||||
|
||||
|
||||
@@ -672,7 +675,7 @@ def test_rollback_temporary_profile(tmp_path: Path) -> None:
|
||||
"""),
|
||||
)
|
||||
assert (
|
||||
n.rollback_temporary_profile(m.Profile("system", path), None, False)
|
||||
n.rollback_temporary_profile(m.Profile("system", path), None, e.NO_ELEVATOR)
|
||||
== path.parent / "system-2083-link"
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
@@ -684,12 +687,12 @@ def test_rollback_temporary_profile(tmp_path: Path) -> None:
|
||||
],
|
||||
stdout=PIPE,
|
||||
remote=None,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
)
|
||||
|
||||
target_host = m.Remote("user@localhost", [], None, "ssh")
|
||||
target_host = m.Remote("user@localhost", [], "ssh")
|
||||
assert (
|
||||
n.rollback_temporary_profile(m.Profile("foo", path), target_host, True)
|
||||
n.rollback_temporary_profile(m.Profile("foo", path), target_host, SUDO)
|
||||
== path.parent / "foo-2083-link"
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
@@ -701,12 +704,12 @@ def test_rollback_temporary_profile(tmp_path: Path) -> None:
|
||||
],
|
||||
stdout=PIPE,
|
||||
remote=target_host,
|
||||
sudo=True,
|
||||
elevate=SUDO,
|
||||
)
|
||||
|
||||
with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run:
|
||||
mock_run.return_value = CompletedProcess([], 0, stdout="")
|
||||
assert n.rollback_temporary_profile(profile, None, False) is None
|
||||
assert n.rollback_temporary_profile(profile, None, e.NO_ELEVATOR) is None
|
||||
|
||||
|
||||
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
|
||||
@@ -719,25 +722,25 @@ def test_set_profile(mock_run: Mock) -> None:
|
||||
m.Profile("system", profile_path),
|
||||
config_path,
|
||||
target_host=None,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
)
|
||||
|
||||
mock_run.assert_called_with(
|
||||
["nix-env", "-p", profile_path, "--set", config_path],
|
||||
remote=None,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
)
|
||||
|
||||
mock_run.return_value = CompletedProcess([], 1)
|
||||
|
||||
with pytest.raises(m.NixOSRebuildError) as e:
|
||||
with pytest.raises(m.NixOSRebuildError) as exc:
|
||||
n.set_profile(
|
||||
m.Profile("system", profile_path),
|
||||
config_path,
|
||||
target_host=None,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
)
|
||||
assert str(e.value).startswith(
|
||||
assert str(exc.value).startswith(
|
||||
"error: your NixOS configuration path seems to be missing essential files."
|
||||
)
|
||||
|
||||
@@ -756,7 +759,7 @@ def test_switch_to_configuration_without_systemd_run(
|
||||
n.switch_to_configuration(
|
||||
profile_path,
|
||||
m.Action.SWITCH,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
target_host=None,
|
||||
specialisation=None,
|
||||
install_bootloader=False,
|
||||
@@ -764,29 +767,29 @@ def test_switch_to_configuration_without_systemd_run(
|
||||
mock_run.assert_called_with(
|
||||
[profile_path / "bin/switch-to-configuration", "switch"],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"LOCALE_ARCHIVE": e.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": e.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "0",
|
||||
},
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
remote=None,
|
||||
stdout=sys.stderr,
|
||||
)
|
||||
|
||||
with pytest.raises(m.NixOSRebuildError) as e:
|
||||
with pytest.raises(m.NixOSRebuildError) as exc:
|
||||
n.switch_to_configuration(
|
||||
config_path,
|
||||
m.Action.BOOT,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
target_host=None,
|
||||
specialisation="special",
|
||||
)
|
||||
assert (
|
||||
str(e.value)
|
||||
str(exc.value)
|
||||
== "error: '--specialisation' can only be used with 'switch' and 'test'"
|
||||
)
|
||||
|
||||
target_host = m.Remote("user@localhost", [], None, "ssh")
|
||||
target_host = m.Remote("user@localhost", [], "ssh")
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "/path/to/locale")
|
||||
mp.setenv("PATH", "/path/to/bin")
|
||||
@@ -795,7 +798,7 @@ def test_switch_to_configuration_without_systemd_run(
|
||||
n.switch_to_configuration(
|
||||
Path("/path/to/config"),
|
||||
m.Action.TEST,
|
||||
sudo=True,
|
||||
elevate=SUDO,
|
||||
target_host=target_host,
|
||||
install_bootloader=True,
|
||||
specialisation="special",
|
||||
@@ -806,11 +809,11 @@ def test_switch_to_configuration_without_systemd_run(
|
||||
"test",
|
||||
],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"LOCALE_ARCHIVE": e.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": e.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "1",
|
||||
},
|
||||
sudo=True,
|
||||
elevate=SUDO,
|
||||
remote=target_host,
|
||||
stdout=sys.stderr,
|
||||
)
|
||||
@@ -830,7 +833,7 @@ def test_switch_to_configuration_with_systemd_run(
|
||||
n.switch_to_configuration(
|
||||
profile_path,
|
||||
m.Action.SWITCH,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
target_host=None,
|
||||
specialisation=None,
|
||||
install_bootloader=False,
|
||||
@@ -842,16 +845,16 @@ def test_switch_to_configuration_with_systemd_run(
|
||||
"switch",
|
||||
],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"LOCALE_ARCHIVE": e.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": e.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "0",
|
||||
},
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
remote=None,
|
||||
stdout=sys.stderr,
|
||||
)
|
||||
|
||||
target_host = m.Remote("user@localhost", [], None, "ssh")
|
||||
target_host = m.Remote("user@localhost", [], "ssh")
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "/path/to/locale")
|
||||
mp.setenv("PATH", "/path/to/bin")
|
||||
@@ -860,7 +863,7 @@ def test_switch_to_configuration_with_systemd_run(
|
||||
n.switch_to_configuration(
|
||||
Path("/path/to/config"),
|
||||
m.Action.TEST,
|
||||
sudo=True,
|
||||
elevate=SUDO,
|
||||
target_host=target_host,
|
||||
install_bootloader=True,
|
||||
specialisation="special",
|
||||
@@ -872,11 +875,11 @@ def test_switch_to_configuration_with_systemd_run(
|
||||
"test",
|
||||
],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"LOCALE_ARCHIVE": e.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": e.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "1",
|
||||
},
|
||||
sudo=True,
|
||||
elevate=SUDO,
|
||||
remote=target_host,
|
||||
stdout=sys.stderr,
|
||||
)
|
||||
@@ -887,11 +890,13 @@ def test_switch_to_configuration_with_systemd_run(
|
||||
def test_upgrade_channels(mock_run: Mock, mock_geteuid: Mock, tmpdir: Path) -> None:
|
||||
tmp_path = Path(tmpdir)
|
||||
|
||||
with pytest.raises(m.NixOSRebuildError) as e:
|
||||
n.upgrade_channels(all_channels=False, sudo=False, channels_dir=tmp_path)
|
||||
assert str(e.value) == (
|
||||
with pytest.raises(m.NixOSRebuildError) as exc:
|
||||
n.upgrade_channels(
|
||||
all_channels=False, elevate=e.NO_ELEVATOR, channels_dir=tmp_path
|
||||
)
|
||||
assert str(exc.value) == (
|
||||
"error: if you pass the '--upgrade' or '--upgrade-all' flag, you must "
|
||||
"also pass '--sudo' or run the command as root (e.g., with sudo)"
|
||||
"also pass '--elevate' or run the command as root"
|
||||
)
|
||||
|
||||
(tmp_path / "nixos").mkdir()
|
||||
@@ -899,19 +904,20 @@ def test_upgrade_channels(mock_run: Mock, mock_geteuid: Mock, tmpdir: Path) -> N
|
||||
(tmp_path / "nixos-hardware" / ".update-on-nixos-rebuild").touch()
|
||||
(tmp_path / "home-manager").mkdir()
|
||||
|
||||
# should work because we are passing sudo=True even with os.geteuid == 1000
|
||||
n.upgrade_channels(all_channels=False, sudo=True, channels_dir=tmp_path)
|
||||
# should work because we are passing an elevator even with os.geteuid == 1000
|
||||
n.upgrade_channels(all_channels=False, elevate=SUDO, channels_dir=tmp_path)
|
||||
# Path.glob order is filesystem-dependent, so don't assert call order.
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
["nix-channel", "--update", "nixos-hardware"],
|
||||
check=False,
|
||||
sudo=True,
|
||||
elevate=SUDO,
|
||||
),
|
||||
call(
|
||||
["nix-channel", "--update", "nixos"],
|
||||
check=False,
|
||||
sudo=True,
|
||||
elevate=SUDO,
|
||||
),
|
||||
],
|
||||
any_order=True,
|
||||
@@ -921,23 +927,23 @@ def test_upgrade_channels(mock_run: Mock, mock_geteuid: Mock, tmpdir: Path) -> N
|
||||
# root check
|
||||
mock_geteuid.return_value = 0
|
||||
|
||||
n.upgrade_channels(all_channels=True, sudo=False, channels_dir=tmp_path)
|
||||
n.upgrade_channels(all_channels=True, elevate=e.NO_ELEVATOR, channels_dir=tmp_path)
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
["nix-channel", "--update", "home-manager"],
|
||||
check=False,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
),
|
||||
call(
|
||||
["nix-channel", "--update", "nixos-hardware"],
|
||||
check=False,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
),
|
||||
call(
|
||||
["nix-channel", "--update", "nixos"],
|
||||
check=False,
|
||||
sudo=False,
|
||||
elevate=e.NO_ELEVATOR,
|
||||
),
|
||||
],
|
||||
any_order=True,
|
||||
|
||||
@@ -3,28 +3,46 @@ from unittest.mock import patch
|
||||
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
import nixos_rebuild.elevate as e
|
||||
import nixos_rebuild.models as m
|
||||
import nixos_rebuild.process as p
|
||||
|
||||
|
||||
def test_remote_shell_script() -> None:
|
||||
assert p._remote_shell_script({"PATH": p.PRESERVE_ENV}) == (
|
||||
'''exec env -i PATH="${PATH-}" "$@"'''
|
||||
)
|
||||
assert p._remote_shell_script(
|
||||
def test_remote_env_shell_argv() -> None:
|
||||
assert e._remote_env_shell_argv([], {"PATH": e.PRESERVE_ENV}, ["cmd"]) == [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
'''exec /usr/bin/env -i PATH="${PATH-}" "$@"''',
|
||||
"sh",
|
||||
"cmd",
|
||||
]
|
||||
assert e._remote_env_shell_argv(
|
||||
["sudo"],
|
||||
{
|
||||
"PATH": p.PRESERVE_ENV,
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"PATH": e.PRESERVE_ENV,
|
||||
"LOCALE_ARCHIVE": e.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": e.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "0",
|
||||
}
|
||||
) == (
|
||||
"""exec env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" """
|
||||
'''NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'''
|
||||
)
|
||||
assert p._remote_shell_script({"PATH": p.PRESERVE_ENV, "FOO": "some value"}) == (
|
||||
'''exec env -i PATH="${PATH-}" FOO='some value' "$@"'''
|
||||
)
|
||||
},
|
||||
["cmd", "arg"],
|
||||
) == [
|
||||
"sudo",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""exec /usr/bin/env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" """
|
||||
'''NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"''',
|
||||
"sh",
|
||||
"cmd",
|
||||
"arg",
|
||||
]
|
||||
assert e._remote_env_shell_argv(
|
||||
[], {"PATH": e.PRESERVE_ENV, "FOO": "some value"}, []
|
||||
) == [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
'''exec /usr/bin/env -i PATH="${PATH-}" FOO='some value' "$@"''',
|
||||
"sh",
|
||||
]
|
||||
|
||||
|
||||
@patch.dict(p.os.environ, {"PATH": "/path/to/bin"}, clear=True)
|
||||
@@ -43,7 +61,7 @@ def test_run_wrapper(mock_run: Any) -> None:
|
||||
p.run_wrapper(
|
||||
["test", "--with", "flags"],
|
||||
check=False,
|
||||
sudo=True,
|
||||
elevate=e.SudoElevator(),
|
||||
env={"FOO": "bar"},
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
@@ -67,7 +85,6 @@ def test_run_wrapper(mock_run: Any) -> None:
|
||||
p.run_wrapper(
|
||||
["test", "--with", "flags"],
|
||||
check=False,
|
||||
sudo=False,
|
||||
append_local_env={"FOO": "bar"},
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
@@ -89,7 +106,6 @@ def test_run_wrapper(mock_run: Any) -> None:
|
||||
p.run_wrapper(
|
||||
["test", "--with", "flags"],
|
||||
check=False,
|
||||
sudo=False,
|
||||
env={"PATH": "/"},
|
||||
append_local_env={"FOO": "bar"},
|
||||
)
|
||||
@@ -112,7 +128,7 @@ def test_run_wrapper(mock_run: Any) -> None:
|
||||
p.run_wrapper(
|
||||
["test", "--with", "some flags"],
|
||||
check=True,
|
||||
remote=m.Remote("user@localhost", ["--ssh", "opt"], "password", "ssh"),
|
||||
remote=m.Remote("user@localhost", ["--ssh", "opt"], "ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@@ -124,7 +140,7 @@ def test_run_wrapper(mock_run: Any) -> None:
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"test",
|
||||
"--with",
|
||||
@@ -140,9 +156,9 @@ def test_run_wrapper(mock_run: Any) -> None:
|
||||
p.run_wrapper(
|
||||
["test", "--with", "flags"],
|
||||
check=True,
|
||||
sudo=True,
|
||||
elevate=e.SudoElevator(password="password"),
|
||||
env={"FOO": "bar"},
|
||||
remote=m.Remote("user@localhost", ["--ssh", "opt"], "password", "ssh"),
|
||||
remote=m.Remote("user@localhost", ["--ssh", "opt"], "ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@@ -157,7 +173,7 @@ def test_run_wrapper(mock_run: Any) -> None:
|
||||
"--stdin",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" FOO=bar "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" FOO=bar "$@"'""",
|
||||
"sh",
|
||||
"test",
|
||||
"--with",
|
||||
@@ -181,7 +197,7 @@ def test__kill_long_running_ssh_process(mock_run: Any) -> None:
|
||||
"build",
|
||||
"/nix/store/la0c8nmpr9xfclla0n4f3qq9iwgdrq4g-nixos-system-sankyuu-nixos-25.05.20250424.f771eb4.drv^*",
|
||||
],
|
||||
m.Remote("user@localhost", opts=[], sudo_password=None, store_type="ssh"),
|
||||
m.Remote("user@localhost", opts=[], store_type="ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@@ -204,21 +220,18 @@ def test__kill_long_running_ssh_process(mock_run: Any) -> None:
|
||||
|
||||
def test_remote_from_name(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("NIX_SSHOPTS", "")
|
||||
assert m.Remote.from_arg("user@localhost", None, False) == m.Remote(
|
||||
assert m.Remote.from_arg("user@localhost", validate_opts=False) == m.Remote(
|
||||
"user@localhost",
|
||||
opts=[],
|
||||
sudo_password=None,
|
||||
store_type="ssh",
|
||||
)
|
||||
|
||||
with patch("getpass.getpass", autospec=True, return_value="password"):
|
||||
monkeypatch.setenv("NIX_SSHOPTS", "-f foo -b bar -t")
|
||||
assert m.Remote.from_arg("user@localhost", True, True) == m.Remote(
|
||||
"user@localhost",
|
||||
opts=["-f", "foo", "-b", "bar", "-t"],
|
||||
sudo_password="password",
|
||||
store_type="ssh",
|
||||
)
|
||||
monkeypatch.setenv("NIX_SSHOPTS", "-f foo -b bar -t")
|
||||
assert m.Remote.from_arg("user@localhost") == m.Remote(
|
||||
"user@localhost",
|
||||
opts=["-f", "foo", "-b", "bar", "-t"],
|
||||
store_type="ssh",
|
||||
)
|
||||
|
||||
|
||||
def test_ssh_host() -> None:
|
||||
@@ -240,18 +253,94 @@ def test_ssh_host() -> None:
|
||||
}
|
||||
|
||||
for host_input, expected in ssh_remotes.items():
|
||||
remote = m.Remote.from_arg(host_input, None, False)
|
||||
remote = m.Remote.from_arg(host_input, validate_opts=False)
|
||||
assert remote is not None
|
||||
assert remote.ssh_host() == expected
|
||||
assert remote.store_type == "ssh"
|
||||
|
||||
for host_input, expected in ssh_ng_remotes.items():
|
||||
remote = m.Remote.from_arg(host_input, None, False)
|
||||
remote = m.Remote.from_arg(host_input, validate_opts=False)
|
||||
assert remote is not None
|
||||
assert remote.ssh_host() == expected
|
||||
assert remote.store_type == "ssh-ng"
|
||||
|
||||
|
||||
@patch.dict(p.os.environ, {"PATH": "/path/to/bin"}, clear=True)
|
||||
@patch("subprocess.run", autospec=True)
|
||||
def test_run_wrapper_run0(mock_run: Any) -> None:
|
||||
p.run_wrapper(["cmd", "arg"], elevate=e.Run0Elevator())
|
||||
mock_run.assert_called_with(
|
||||
["run0", "--", "cmd", "arg"],
|
||||
check=True,
|
||||
text=True,
|
||||
errors="surrogateescape",
|
||||
env=None,
|
||||
input=None,
|
||||
)
|
||||
|
||||
run0_script = (
|
||||
"exec systemd-run --uid=0 --pipe --quiet --wait --collect "
|
||||
"--service-type=exec --send-sighup "
|
||||
'--setenv=PATH="${PATH-}" -- "$@"'
|
||||
)
|
||||
|
||||
p.run_wrapper(
|
||||
["cmd", "arg"],
|
||||
elevate=e.Run0Elevator(),
|
||||
remote=m.Remote("user@host", [], "ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
"ssh",
|
||||
*p.SSH_DEFAULT_OPTS,
|
||||
"user@host",
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
p._quote_remote_arg(run0_script),
|
||||
"sh",
|
||||
"cmd",
|
||||
"arg",
|
||||
],
|
||||
check=True,
|
||||
text=True,
|
||||
errors="surrogateescape",
|
||||
env=None,
|
||||
input=None,
|
||||
)
|
||||
|
||||
p.run_wrapper(
|
||||
["cmd"],
|
||||
elevate=e.Run0Elevator().with_password("pw"),
|
||||
remote=m.Remote("user@host", [], "ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
"ssh",
|
||||
*p.SSH_DEFAULT_OPTS,
|
||||
"user@host",
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
p._quote_remote_arg(e.Run0Elevator._AGENT_PICKER),
|
||||
"sh",
|
||||
# No toplevel bound, so the only candidate is PATH lookup.
|
||||
"polkit-stdin-agent",
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
p._quote_remote_arg(run0_script),
|
||||
"sh",
|
||||
"cmd",
|
||||
],
|
||||
check=True,
|
||||
text=True,
|
||||
errors="surrogateescape",
|
||||
env=None,
|
||||
input="pw\n",
|
||||
)
|
||||
|
||||
|
||||
@patch("subprocess.run", autospec=True)
|
||||
def test_custom_sudo_args(mock_run: Any) -> None:
|
||||
with patch.dict(
|
||||
@@ -262,7 +351,7 @@ def test_custom_sudo_args(mock_run: Any) -> None:
|
||||
p.run_wrapper(
|
||||
["test"],
|
||||
check=False,
|
||||
sudo=True,
|
||||
elevate=e.SudoElevator(),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@@ -287,8 +376,8 @@ def test_custom_sudo_args(mock_run: Any) -> None:
|
||||
p.run_wrapper(
|
||||
["test"],
|
||||
check=False,
|
||||
sudo=True,
|
||||
remote=m.Remote("user@localhost", [], None, "ssh"),
|
||||
elevate=e.SudoElevator(),
|
||||
remote=m.Remote("user@localhost", [], "ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@@ -302,7 +391,7 @@ def test_custom_sudo_args(mock_run: Any) -> None:
|
||||
"--args",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" "$@"'""",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"test",
|
||||
],
|
||||
|
||||
48
pkgs/by-name/po/polkit-stdin-agent/package.nix
Normal file
48
pkgs/by-name/po/polkit-stdin-agent/package.nix
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
lib,
|
||||
rustPlatform,
|
||||
fetchFromGitea,
|
||||
nix-update-script,
|
||||
nixosTests,
|
||||
}:
|
||||
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "polkit-stdin-agent";
|
||||
version = "0.3.0";
|
||||
|
||||
src = fetchFromGitea {
|
||||
domain = "codeberg.org";
|
||||
owner = "r-vdp";
|
||||
repo = "polkit-stdin-agent";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-Nl/+IBbUEsxSKSWLXwUB3mV4iAG0z9mv+Bl6CSeFzR4=";
|
||||
};
|
||||
|
||||
cargoHash = "sha256-Eb/7ejVmtG5FNSh66gZO3337KCPNi+xtYVC5qyFKJzg=";
|
||||
|
||||
strictDeps = true;
|
||||
__structuredAttrs = true;
|
||||
|
||||
passthru = {
|
||||
updateScript = nix-update-script { };
|
||||
tests = { inherit (nixosTests) nixos-rebuild-target-host; };
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Non-interactive polkit authentication agent that answers PAM prompts from a file descriptor";
|
||||
longDescription = ''
|
||||
Registers a per-process polkit authentication agent for a wrapped
|
||||
command and answers the PAM conversation from a file descriptor
|
||||
instead of /dev/tty, giving run0 / systemd-run the same
|
||||
"password on stdin" ergonomics as `sudo --stdin`.
|
||||
|
||||
Used by `nixos-rebuild --elevate=run0 --ask-elevate-password` to
|
||||
authenticate on a target host over SSH without allocating a TTY.
|
||||
'';
|
||||
homepage = "https://codeberg.org/r-vdp/polkit-stdin-agent";
|
||||
license = lib.licenses.mit;
|
||||
maintainers = with lib.maintainers; [ rvdp ];
|
||||
platforms = lib.platforms.linux;
|
||||
mainProgram = "polkit-stdin-agent";
|
||||
};
|
||||
})
|
||||
@@ -140,11 +140,11 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
+ lib.optionalString nixosTestRunner "-for-vm-tests"
|
||||
+ lib.optionalString toolsOnly "-utils"
|
||||
+ lib.optionalString userOnly "-user";
|
||||
version = "10.2.2";
|
||||
version = "11.0.0";
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://download.qemu.org/qemu-${finalAttrs.version}.tar.xz";
|
||||
hash = "sha256-eEspb/KcFBeqcjI6vLLS6pq5dxck9Xfc14XDsE8h4XY=";
|
||||
hash = "sha256-wEyjYBJlPzLRHGdNNwz1KnEOfT8Ywti2PkkyBSpIVNY=";
|
||||
};
|
||||
|
||||
depsBuildBuild = [
|
||||
@@ -165,6 +165,8 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
# For python changes other than simple package additions, ping @dramforever for review.
|
||||
# Don't change `python3Packages` to `python3.pkgs.*`, breaks cross-compilation.
|
||||
python3Packages.distlib
|
||||
python3Packages.setuptools
|
||||
python3Packages.wheel
|
||||
# Hooks from the python package are needed to add `$pythonPath` so
|
||||
# `python/scripts/mkvenv.py` can detect `meson` otherwise the vendored meson without patches will be used.
|
||||
python3Packages.python
|
||||
@@ -374,7 +376,11 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
|
||||
# tests can still timeout on slower systems
|
||||
doCheck = false;
|
||||
nativeCheckInputs = [ socat ];
|
||||
nativeCheckInputs = [
|
||||
python3Packages.pygdbmi
|
||||
python3Packages.qemu-qmp
|
||||
socat
|
||||
];
|
||||
preCheck = ''
|
||||
# time limits are a little meagre for a build machine that's
|
||||
# potentially under load.
|
||||
@@ -444,7 +450,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
license = lib.licenses.gpl2Plus;
|
||||
maintainers = with lib.maintainers; [ qyliss ];
|
||||
teams = lib.optionals xenSupport xen.meta.teams;
|
||||
platforms = lib.platforms.unix;
|
||||
platforms = with lib.systems.inspect; patternLogicalAnd patterns.is64bit patterns.isUnix;
|
||||
}
|
||||
# toolsOnly: Does not have qemu-kvm and there's no main support tool
|
||||
# userOnly: There's one qemu-<arch> for every architecture
|
||||
@@ -453,7 +459,14 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
}
|
||||
# userOnly: https://qemu.readthedocs.io/en/master/user/main.html#supported-operating-systems
|
||||
// lib.optionalAttrs userOnly {
|
||||
platforms = with lib.platforms; (linux ++ freebsd ++ openbsd ++ netbsd);
|
||||
platforms =
|
||||
with lib.systems.inspect;
|
||||
patternLogicalAnd patterns.is64bit [
|
||||
patterns.isLinux
|
||||
patterns.isFreeBSD
|
||||
patterns.isOpenBSD
|
||||
patterns.isNetBSD
|
||||
];
|
||||
description = "QEMU User space emulator - launch executables compiled for one CPU on another CPU";
|
||||
};
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "ruff";
|
||||
version = "0.15.14";
|
||||
version = "0.15.15";
|
||||
|
||||
__structuredAttrs = true;
|
||||
|
||||
@@ -24,12 +24,12 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
owner = "astral-sh";
|
||||
repo = "ruff";
|
||||
tag = finalAttrs.version;
|
||||
hash = "sha256-Z8UhVS+YbYAxVWodU/I+p3Ns5/EpmzBTChcbkvJwe6Y=";
|
||||
hash = "sha256-WpjOOCYLZ1d8XPUx3qNHD+fuK6t65u/1/ZezABWpBD0=";
|
||||
};
|
||||
|
||||
cargoBuildFlags = [ "--package=ruff" ];
|
||||
|
||||
cargoHash = "sha256-GnRC5jXySAna7uAKPDtpPQUJe8AKqVSU+ynmGKZtfTs=";
|
||||
cargoHash = "sha256-SfkoLl43Y1DNqIRW+HljVcEHWhedTS99SGhMvkQ4dmo=";
|
||||
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
|
||||
|
||||
@@ -688,12 +688,7 @@ fn handle_modified_unit(
|
||||
let reload_list = scope.reload_list_file();
|
||||
let use_restart_as_stop_and_start = new_unit_info.is_none();
|
||||
|
||||
if matches!(
|
||||
unit,
|
||||
"sysinit.target" | "basic.target" | "multi-user.target" | "graphical.target"
|
||||
) || unit.ends_with(".unit")
|
||||
|| unit.ends_with(".slice")
|
||||
{
|
||||
if cannot_be_restarted_directly(unit) {
|
||||
// Do nothing. These cannot be restarted directly.
|
||||
|
||||
// Slices and Paths don't have to be restarted since properties (resource limits and
|
||||
@@ -940,6 +935,17 @@ fn parse_fstab(fstab: impl BufRead) -> (HashMap<String, Filesystem>, HashMap<Str
|
||||
(filesystems, swaps)
|
||||
}
|
||||
|
||||
/// Whether a unit cannot be (re)started directly. Special targets are pulled
|
||||
/// in by their dependents; slices and paths get their properties applied on
|
||||
/// daemon-reload.
|
||||
fn cannot_be_restarted_directly(unit: &str) -> bool {
|
||||
matches!(
|
||||
unit,
|
||||
"sysinit.target" | "basic.target" | "multi-user.target" | "graphical.target"
|
||||
) || unit.ends_with(".path")
|
||||
|| unit.ends_with(".slice")
|
||||
}
|
||||
|
||||
// Returns a HashMap containing the same contents as the passed in `units`, minus the units in
|
||||
// `units_to_filter`.
|
||||
fn filter_units(
|
||||
@@ -957,6 +963,50 @@ fn filter_units(
|
||||
res
|
||||
}
|
||||
|
||||
/// Action to take on a unit that migrated to NixOS ownership during the
|
||||
/// post-activation pass. Honours the same X-* directives as
|
||||
/// `handle_modified_unit`.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum MigrationAction {
|
||||
Skip,
|
||||
Reload,
|
||||
Restart,
|
||||
Start,
|
||||
}
|
||||
|
||||
impl MigrationAction {
|
||||
/// Action to take on a migrated unit that is still active.
|
||||
fn for_active_unit(unit: &str, new_unit_info: &UnitInfo) -> Self {
|
||||
if cannot_be_restarted_directly(unit) {
|
||||
return Self::Skip;
|
||||
}
|
||||
|
||||
if parse_systemd_bool(Some(new_unit_info), "Service", "X-ReloadIfChanged", false) {
|
||||
return Self::Reload;
|
||||
}
|
||||
|
||||
if !parse_systemd_bool(Some(new_unit_info), "Service", "X-RestartIfChanged", true)
|
||||
|| parse_systemd_bool(Some(new_unit_info), "Unit", "RefuseManualStop", false)
|
||||
|| parse_systemd_bool(Some(new_unit_info), "Unit", "X-OnlyManualStart", false)
|
||||
{
|
||||
return Self::Skip;
|
||||
}
|
||||
|
||||
Self::Restart
|
||||
}
|
||||
|
||||
/// Action to take on a migrated unit that the previous owner stopped.
|
||||
fn for_stopped_unit(new_unit_info: &UnitInfo) -> Self {
|
||||
if parse_systemd_bool(Some(new_unit_info), "Unit", "RefuseManualStart", false)
|
||||
|| parse_systemd_bool(Some(new_unit_info), "Unit", "X-OnlyManualStart", false)
|
||||
{
|
||||
return Self::Skip;
|
||||
}
|
||||
|
||||
Self::Start
|
||||
}
|
||||
}
|
||||
|
||||
fn unit_is_active(conn: &LocalConnection, unit: &str) -> Result<bool> {
|
||||
let unit_object_path = conn
|
||||
.with_proxy(
|
||||
@@ -1345,26 +1395,79 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
|
||||
let current_active_units = get_active_units(&systemd)?;
|
||||
|
||||
let old_unit_dir = old_toplevel.join(scope.etc_dir());
|
||||
let new_unit_dir = toplevel.join(scope.etc_dir());
|
||||
let fragment_prefix = scope
|
||||
.current_dir()
|
||||
.to_str()
|
||||
.expect("scope dir is valid UTF-8");
|
||||
let fragment_dir = scope.current_dir();
|
||||
|
||||
// Units that are currently running from a non-/etc location (typically
|
||||
// ~/.config/systemd/user, i.e. home-manager) but that the new NixOS
|
||||
// configuration also defines. Pass 1 will skip these because of the
|
||||
// FragmentPath filter; if the per-user activation (sd-switch) later drops
|
||||
// its copy, we need a second pass to bring the NixOS-owned definition up.
|
||||
// Determine $XDG_CONFIG_HOME/systemd/user from the user manager's own
|
||||
// environment (we are spawned with env_clear()).
|
||||
let user_config_unit_dir: Option<PathBuf> = match systemd.environment() {
|
||||
Err(err) => {
|
||||
log::debug!("Failed to read user manager environment: {err}");
|
||||
None
|
||||
}
|
||||
Ok(env) => {
|
||||
let lookup = |key: &str| {
|
||||
env.iter().find_map(|kv| {
|
||||
kv.strip_prefix(key)
|
||||
.and_then(|rest| rest.strip_prefix('='))
|
||||
.filter(|v| Path::new(v).is_absolute())
|
||||
.map(PathBuf::from)
|
||||
})
|
||||
};
|
||||
let config_home =
|
||||
lookup("XDG_CONFIG_HOME").or_else(|| lookup("HOME").map(|h| h.join(".config")));
|
||||
if config_home.is_none() {
|
||||
log::debug!(
|
||||
"Neither $XDG_CONFIG_HOME nor $HOME is set in the user manager's environment"
|
||||
);
|
||||
}
|
||||
config_home.map(|config_home| config_home.join("systemd/user"))
|
||||
}
|
||||
};
|
||||
|
||||
if user_config_unit_dir.is_none() {
|
||||
log::debug!(
|
||||
"Could not determine $XDG_CONFIG_HOME/systemd/user; \
|
||||
units shadowed by ~/.config will not be considered for migration"
|
||||
);
|
||||
}
|
||||
|
||||
// Units active from a non-/etc location that the new generation defines
|
||||
// in /etc/systemd/user. Pass 1 skips these (FragmentPath filter); pass 2
|
||||
// brings the /etc definition into effect once /etc has won. Two cases:
|
||||
// * ~/.config/systemd/user (home-manager): shadows /etc, so wait for
|
||||
// the per-user activation (sd-switch) to remove its copy.
|
||||
// * anywhere else outside /etc ($XDG_DATA_HOME, $XDG_DATA_DIRS, ...):
|
||||
// /etc outranks these, so only act when /etc is gaining the unit;
|
||||
// if the previous generation already had it, leave it alone.
|
||||
// Pass 2's `now_etc` check verifies /etc actually won before acting.
|
||||
let migration_candidates: Vec<String> = current_active_units
|
||||
.iter()
|
||||
.filter(|(unit, _)| new_unit_dir.join(unit).exists())
|
||||
.filter(|(_, unit_state)| {
|
||||
!unit_state
|
||||
.filter(|(unit, unit_state)| {
|
||||
let Ok(fragment_path) = unit_state
|
||||
.proxy
|
||||
.get("org.freedesktop.systemd1.Unit", "FragmentPath")
|
||||
.map(|p: String| p.starts_with(fragment_prefix))
|
||||
.unwrap_or(false)
|
||||
.get::<String>("org.freedesktop.systemd1.Unit", "FragmentPath")
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let fragment_parent = Path::new(&fragment_path).parent();
|
||||
|
||||
// Already in /etc: handled by pass 1.
|
||||
if fragment_parent == Some(fragment_dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loaded from ~/.config/systemd/user, which shadows /etc.
|
||||
if let Some(dir) = &user_config_unit_dir {
|
||||
if fragment_parent == Some(dir.as_path()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Elsewhere: only act if /etc is gaining the unit this switch.
|
||||
!old_unit_dir.join(unit).exists()
|
||||
})
|
||||
.map(|(unit, _)| unit.clone())
|
||||
.collect();
|
||||
@@ -1372,7 +1475,7 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
collect_unit_changes(
|
||||
&toplevel,
|
||||
scope,
|
||||
&old_toplevel.join(scope.etc_dir()),
|
||||
&old_unit_dir,
|
||||
&new_unit_dir,
|
||||
¤t_active_units,
|
||||
&mut units_to_stop,
|
||||
@@ -1383,6 +1486,9 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
&mut units_to_filter,
|
||||
)?;
|
||||
|
||||
// Restarted unconditionally below; don't list it as skipped.
|
||||
units_to_skip.remove("nixos-activation.service");
|
||||
|
||||
let print_units = |verb: &str, units: &HashMap<String, ()>| {
|
||||
if units.is_empty() {
|
||||
return;
|
||||
@@ -1483,6 +1589,7 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
// Toplevels with system.activatable = false do not ship this unit; mirror
|
||||
// the system scope's tolerance for a missing activate script.
|
||||
if new_unit_dir.join("nixos-activation.service").exists() {
|
||||
eprintln!("restarting the following user units: nixos-activation.service");
|
||||
match systemd.restart_unit("nixos-activation.service", "replace") {
|
||||
Ok(_) => {
|
||||
log::debug!("waiting for nixos activation to finish");
|
||||
@@ -1510,29 +1617,40 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
|
||||
let active_after = get_active_units(&systemd)?;
|
||||
|
||||
let mut to_reload = HashMap::new();
|
||||
let mut to_restart = HashMap::new();
|
||||
let mut to_start = HashMap::new();
|
||||
let mut to_skip = HashMap::new();
|
||||
|
||||
for unit in &migration_candidates {
|
||||
match active_after.get(unit) {
|
||||
// Honour X-* directives so reloadIfChanged/restartIfChanged hold.
|
||||
let new_unit_file = new_unit_dir.join(unit);
|
||||
let new_unit_info = parse_unit(&new_unit_file, &new_unit_file)?;
|
||||
|
||||
let action = match active_after.get(unit) {
|
||||
Some(unit_state) => {
|
||||
// Only act if /etc now wins (i.e. the higher-priority
|
||||
// copy is gone). Read errors are treated as "leave alone".
|
||||
let now_etc = unit_state
|
||||
.proxy
|
||||
.get("org.freedesktop.systemd1.Unit", "FragmentPath")
|
||||
.map(|p: String| p.starts_with(fragment_prefix))
|
||||
.map(|p: String| Path::new(&p).parent() == Some(fragment_dir))
|
||||
.unwrap_or(false);
|
||||
if now_etc {
|
||||
// Still running with the previous manager's binary;
|
||||
// restart so the /etc definition takes effect.
|
||||
to_restart.insert(unit.clone(), ());
|
||||
if !now_etc {
|
||||
// Still shadowed (or read error); leave it alone.
|
||||
continue;
|
||||
}
|
||||
// else: still shadowed by ~/.config, leave it alone.
|
||||
MigrationAction::for_active_unit(unit, &new_unit_info)
|
||||
}
|
||||
None => {
|
||||
// Stopped by the previous manager; start the /etc copy.
|
||||
to_start.insert(unit.clone(), ());
|
||||
}
|
||||
}
|
||||
// Stopped by the previous manager; start the /etc copy.
|
||||
None => MigrationAction::for_stopped_unit(&new_unit_info),
|
||||
};
|
||||
match action {
|
||||
MigrationAction::Skip => to_skip.insert(unit.clone(), ()),
|
||||
MigrationAction::Reload => to_reload.insert(unit.clone(), ()),
|
||||
MigrationAction::Restart => to_restart.insert(unit.clone(), ()),
|
||||
MigrationAction::Start => to_start.insert(unit.clone(), ()),
|
||||
};
|
||||
}
|
||||
|
||||
// Re-start active targets so any other newly-unmasked dependencies are
|
||||
@@ -1543,6 +1661,24 @@ fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
if !to_skip.is_empty() {
|
||||
print_units("NOT restarting (post-activation)", &to_skip);
|
||||
}
|
||||
|
||||
print_units("reloading (post-activation)", &to_reload);
|
||||
for unit in to_reload.keys() {
|
||||
match systemd.reload_unit(unit, "replace") {
|
||||
Ok(job_path) => {
|
||||
submitted_jobs.borrow_mut().insert(job_path, Job::Reload);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to reload user unit {unit}: {err}");
|
||||
exit_code = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
block_on_jobs(&dbus_conn, &submitted_jobs);
|
||||
|
||||
print_units("restarting (post-activation)", &to_restart);
|
||||
for unit in to_restart.keys() {
|
||||
match systemd.restart_unit(unit, "replace") {
|
||||
@@ -2863,4 +2999,107 @@ After=dev-disk-by\x2dlabel-root.device
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn unit_info(
|
||||
sections: &[(&str, &[(&str, &str)])],
|
||||
) -> HashMap<String, HashMap<String, Vec<String>>> {
|
||||
sections
|
||||
.iter()
|
||||
.map(|(section, kvs)| {
|
||||
(
|
||||
section.to_string(),
|
||||
kvs.iter()
|
||||
.map(|(k, v)| (k.to_string(), vec![v.to_string()]))
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_action_for_active_unit() {
|
||||
use super::MigrationAction;
|
||||
|
||||
// Plain service: restart.
|
||||
assert_eq!(
|
||||
MigrationAction::for_active_unit("foo.service", &unit_info(&[])),
|
||||
MigrationAction::Restart
|
||||
);
|
||||
|
||||
// reloadIfChanged must reload, not restart.
|
||||
assert_eq!(
|
||||
MigrationAction::for_active_unit(
|
||||
"foo.service",
|
||||
&unit_info(&[("Service", &[("X-ReloadIfChanged", "true")])])
|
||||
),
|
||||
MigrationAction::Reload
|
||||
);
|
||||
|
||||
// X-RestartIfChanged=false (restartIfChanged = false) must skip.
|
||||
assert_eq!(
|
||||
MigrationAction::for_active_unit(
|
||||
"foo.service",
|
||||
&unit_info(&[("Service", &[("X-RestartIfChanged", "false")])])
|
||||
),
|
||||
MigrationAction::Skip
|
||||
);
|
||||
|
||||
// RefuseManualStop must skip.
|
||||
assert_eq!(
|
||||
MigrationAction::for_active_unit(
|
||||
"foo.service",
|
||||
&unit_info(&[("Unit", &[("RefuseManualStop", "yes")])])
|
||||
),
|
||||
MigrationAction::Skip
|
||||
);
|
||||
|
||||
// X-OnlyManualStart must skip.
|
||||
assert_eq!(
|
||||
MigrationAction::for_active_unit(
|
||||
"foo.service",
|
||||
&unit_info(&[("Unit", &[("X-OnlyManualStart", "yes")])])
|
||||
),
|
||||
MigrationAction::Skip
|
||||
);
|
||||
|
||||
// Units that cannot be restarted directly must skip.
|
||||
for unit in [
|
||||
"sysinit.target",
|
||||
"basic.target",
|
||||
"multi-user.target",
|
||||
"graphical.target",
|
||||
"foo.path",
|
||||
"bar.slice",
|
||||
] {
|
||||
assert_eq!(
|
||||
MigrationAction::for_active_unit(unit, &unit_info(&[])),
|
||||
MigrationAction::Skip,
|
||||
"{unit}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_action_for_stopped_unit() {
|
||||
use super::MigrationAction;
|
||||
|
||||
assert_eq!(
|
||||
MigrationAction::for_stopped_unit(&unit_info(&[])),
|
||||
MigrationAction::Start
|
||||
);
|
||||
assert_eq!(
|
||||
MigrationAction::for_stopped_unit(&unit_info(&[(
|
||||
"Unit",
|
||||
&[("RefuseManualStart", "true")]
|
||||
)])),
|
||||
MigrationAction::Skip
|
||||
);
|
||||
assert_eq!(
|
||||
MigrationAction::for_stopped_unit(&unit_info(&[(
|
||||
"Unit",
|
||||
&[("X-OnlyManualStart", "true")]
|
||||
)])),
|
||||
MigrationAction::Skip
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "ty";
|
||||
version = "0.0.38";
|
||||
version = "0.0.40";
|
||||
__structuredAttrs = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
@@ -25,7 +25,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
repo = "ty";
|
||||
tag = finalAttrs.version;
|
||||
fetchSubmodules = true;
|
||||
hash = "sha256-70Y5i9m2h2+Jc44jLOf+gXX/PeDbURRJ80y+6h5SlRk=";
|
||||
hash = "sha256-kdfPnyQXYtf3BDrYCFGfX0bMoPGjRpyH3aUeRZBiUKY=";
|
||||
};
|
||||
|
||||
# For Darwin platforms, remove the integration test for file notifications,
|
||||
@@ -39,7 +39,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
|
||||
cargoBuildFlags = [ "--package=ty" ];
|
||||
|
||||
cargoHash = "sha256-+c2JfB55w9otmmgTFIDMwkpASJV7bIMEf0uqRXjk/QM=";
|
||||
cargoHash = "sha256-yUbHTzUGNdpm3b1s/SkcpFGdp7WjN+xO+CVrPPwrh6A=";
|
||||
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
buildInputs = [ rust-jemalloc-sys ];
|
||||
@@ -101,6 +101,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
mainProgram = "ty";
|
||||
maintainers = with lib.maintainers; [
|
||||
bengsparks
|
||||
ddogfoodd
|
||||
figsoda
|
||||
GaetanLepage
|
||||
];
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
zlib,
|
||||
pahole,
|
||||
kmod,
|
||||
ubootTools,
|
||||
fetchpatch,
|
||||
rustc-unwrapped,
|
||||
rust-bindgen-unwrapped,
|
||||
@@ -116,31 +115,6 @@ lib.makeOverridable (
|
||||
;
|
||||
};
|
||||
|
||||
# Folding in `ubootTools` in the default nativeBuildInputs is problematic, as
|
||||
# it makes updating U-Boot cumbersome, since it will go above the current
|
||||
# threshold of rebuilds
|
||||
#
|
||||
# To prevent these needless rounds of staging for U-Boot builds, we can
|
||||
# limit the inclusion of ubootTools to target platforms where uImage *may*
|
||||
# be produced.
|
||||
#
|
||||
# This command lists those (kernel-named) platforms:
|
||||
# .../linux $ grep -l uImage ./arch/*/Makefile | cut -d'/' -f3 | sort
|
||||
#
|
||||
# This is still a guesstimation, but since none of our cached platforms
|
||||
# coincide in that list, this gives us "perfect" decoupling here.
|
||||
linuxPlatformsUsingUImage = [
|
||||
"arc"
|
||||
"arm"
|
||||
"csky"
|
||||
"mips"
|
||||
"powerpc"
|
||||
"sh"
|
||||
"sparc"
|
||||
"xtensa"
|
||||
];
|
||||
needsUbootTools = lib.elem stdenv.hostPlatform.linuxArch linuxPlatformsUsingUImage;
|
||||
|
||||
config =
|
||||
let
|
||||
attrName = attr: "CONFIG_" + attr;
|
||||
@@ -267,7 +241,6 @@ lib.makeOverridable (
|
||||
kmod
|
||||
hexdump
|
||||
]
|
||||
++ optional needsUbootTools ubootTools
|
||||
++ optionals (lib.versionAtLeast version "5.2") [
|
||||
cpio
|
||||
pahole
|
||||
@@ -510,7 +483,7 @@ lib.makeOverridable (
|
||||
|
||||
preFixup = ''
|
||||
if [ -z "''${dontStrip-}" -a -e $out/vmlinux ]; then
|
||||
strip -v -S -p $out/vmlinux
|
||||
$STRIP -v -S -p $out/vmlinux
|
||||
fi
|
||||
'';
|
||||
|
||||
@@ -536,12 +509,10 @@ lib.makeOverridable (
|
||||
kernelAtLeast = lib.versionAtLeast baseVersion;
|
||||
};
|
||||
|
||||
# Some image types need special install targets (e.g. uImage is installed with make uinstall on arm)
|
||||
# Some image types need special install targets
|
||||
installTargets = [
|
||||
(stdenv.hostPlatform.linux-kernel.installTarget or (
|
||||
if target == "uImage" && stdenv.hostPlatform.linuxArch == "arm" then
|
||||
"uinstall"
|
||||
else if
|
||||
if
|
||||
(target == "zImage" || target == "Image.gz" || target == "vmlinuz.efi")
|
||||
&& builtins.elem stdenv.hostPlatform.linuxArch [
|
||||
"arm"
|
||||
|
||||
@@ -773,6 +773,9 @@ let
|
||||
SQUASHFS_LZ4 = yes;
|
||||
SQUASHFS_ZSTD = yes;
|
||||
|
||||
EROFS_FS_ZIP_DEFLATE = whenAtLeast "6.6" yes;
|
||||
EROFS_FS_ZIP_ZSTD = whenAtLeast "6.10" yes;
|
||||
|
||||
# Native Language Support modules, needed by some filesystems
|
||||
NLS = yes;
|
||||
NLS_DEFAULT = freeform "utf8";
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
{
|
||||
"testing": {
|
||||
"version": "7.1-rc4",
|
||||
"hash": "sha256:06z73f1vprl4adbdj01h3407p3f8bl8jsz91zx454c62k2jg3w40",
|
||||
"version": "7.1-rc6",
|
||||
"hash": "sha256:1fmbsjhdrkzim6vzqc40raikv1szfw28q0lbvap8a1g77an0qi58",
|
||||
"lts": false
|
||||
},
|
||||
"6.1": {
|
||||
"version": "6.1.174",
|
||||
"hash": "sha256:0vp07x4v82qnmc1pifv3ynp2ab5mvlbfnpqvs5893bi3yrnk927d",
|
||||
"version": "6.1.175",
|
||||
"hash": "sha256:11fapr04y96p9ja6mfzm7bcd3zb4dzyw6qrh7c11bss9wjlq9s9p",
|
||||
"lts": true
|
||||
},
|
||||
"5.15": {
|
||||
"version": "5.15.208",
|
||||
"hash": "sha256:0wmi50q8vgblhbh77d1a4sw4snymr6srqd22bxcjg9i7wcv70gdm",
|
||||
"version": "5.15.209",
|
||||
"hash": "sha256:1d0yhbpqlkr1znahky15dfavr6dzb3wb8c15k9qqvkf2xb3pfv9l",
|
||||
"lts": true
|
||||
},
|
||||
"5.10": {
|
||||
"version": "5.10.257",
|
||||
"hash": "sha256:1lghcrxc1fqarvym03jrcda2a3labc887ci9yjqgbmv3nphzvc88",
|
||||
"version": "5.10.258",
|
||||
"hash": "sha256:1rdldzb3g33v6zvcmxafqpkjgqpp4n5qlxwb77wfd5jpzhgcnz4y",
|
||||
"lts": true
|
||||
},
|
||||
"6.6": {
|
||||
"version": "6.6.141",
|
||||
"hash": "sha256:1qbzxgqs7q9gyqfrf0j7p0pgjxnjj5mibamhm280mf9anqp6bhiv",
|
||||
"version": "6.6.142",
|
||||
"hash": "sha256:0w1bdzp9x1sqcr9xlk7dvylhs7kycghjabfgd3iv49ydfmx61xmj",
|
||||
"lts": true
|
||||
},
|
||||
"6.12": {
|
||||
"version": "6.12.91",
|
||||
"hash": "sha256:0sbrb612b653w64g5jkpbf68y0fka2sgnwblam41k7wz2sgapwhg",
|
||||
"version": "6.12.92",
|
||||
"hash": "sha256:0gly5wld3x8l0f3zk9pspsw1q2d7zbjbx4c2ndb49b1wvfvpdqqg",
|
||||
"lts": true
|
||||
},
|
||||
"6.18": {
|
||||
"version": "6.18.33",
|
||||
"hash": "sha256:10mp1ypsdz42jr26g1xxbw806mvpy0n35418fhsgxxlr4lqgy5kg",
|
||||
"version": "6.18.34",
|
||||
"hash": "sha256:0q6palsvwx0gnisjr658hlngfpvyzv0k5q4pvdk23122zcr4f334",
|
||||
"lts": true
|
||||
},
|
||||
"7.0": {
|
||||
"version": "7.0.10",
|
||||
"hash": "sha256:1p1j9s0b4qv9m0pm6vj477rqgyd1b0lsk0gy74cks3n2cbmpfj89",
|
||||
"version": "7.0.11",
|
||||
"hash": "sha256:012307ni1v555a1rgzsxsg99pj8fplrghvhw0jk3c4d0vmb86v75",
|
||||
"lts": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ lib.mapAttrs (n: make) (
|
||||
# on how to request an upload.
|
||||
# Sort following the sorting in `./default.nix` `bootstrapFiles` argument.
|
||||
|
||||
armv5tel-unknown-linux-gnueabi = sheevaplug;
|
||||
armv5tel-unknown-linux-gnueabi = armv5tel-multiplatform;
|
||||
armv6l-unknown-linux-gnueabihf = raspberryPi;
|
||||
armv7l-unknown-linux-gnueabihf = armv7l-hf-multiplatform;
|
||||
aarch64-unknown-linux-gnu = aarch64-multiplatform;
|
||||
|
||||
@@ -189,7 +189,7 @@ let
|
||||
pkgs.pkgsLLVM.stdenv
|
||||
pkgs.pkgsStatic.bash
|
||||
pkgs.pkgsCross.arm-embedded.stdenv
|
||||
pkgs.pkgsCross.sheevaplug.stdenv # for armv5tel
|
||||
pkgs.pkgsCross.armv5tel-multiplatform.stdenv
|
||||
pkgs.pkgsCross.raspberryPi.stdenv # for armv6l
|
||||
pkgs.pkgsCross.armv7l-hf-multiplatform.stdenv
|
||||
pkgs.pkgsCross.m68k.stdenv
|
||||
|
||||
@@ -197,8 +197,8 @@ in
|
||||
|
||||
crossIphone32 = mapTestOnCross systems.examples.iphone32 darwinCommon;
|
||||
|
||||
# Test some cross builds to the Sheevaplug
|
||||
crossSheevaplugLinux = mapTestOnCross systems.examples.sheevaplug (
|
||||
# Test some cross builds to ARMv5
|
||||
armv5tel = mapTestOnCross systems.examples.armv5tel-multiplatform (
|
||||
linuxCommon
|
||||
// {
|
||||
ubootSheevaplug = nativePlatforms;
|
||||
@@ -235,8 +235,6 @@ in
|
||||
# Linux on armv7l-hf
|
||||
armv7l-hf = mapTestOnCross systems.examples.armv7l-hf-multiplatform linuxCommon;
|
||||
|
||||
pogoplug4 = mapTestOnCross systems.examples.pogoplug4 linuxCommon;
|
||||
|
||||
# Linux on aarch64
|
||||
aarch64 = mapTestOnCross systems.examples.aarch64-multiplatform linuxCommon;
|
||||
aarch64-musl = mapTestOnCross systems.examples.aarch64-multiplatform-musl linuxCommon;
|
||||
|
||||
Reference in New Issue
Block a user