mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-11-04 11:50:04 +00:00
Compare commits
62 Commits
staging-ne
...
staging-ni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
575fbaf778 | ||
|
|
1b0661cb8e | ||
|
|
a43b7eab39 | ||
|
|
f43d13a970 | ||
|
|
43be3d4cfb | ||
|
|
abae798b6c | ||
|
|
cf9a0df709 | ||
|
|
4d7096f6c6 | ||
|
|
fadbd12b88 | ||
|
|
12214b623a | ||
|
|
3328cd18b2 | ||
|
|
0fa2336746 | ||
|
|
3b574a194d | ||
|
|
573530121c | ||
|
|
7de48641a5 | ||
|
|
0032afe410 | ||
|
|
ea33e2134e | ||
|
|
0835e2960f | ||
|
|
7732580291 | ||
|
|
70cffbcf42 | ||
|
|
2c4e5922cf | ||
|
|
964de0a0cf | ||
|
|
f4b42644cb | ||
|
|
2d5423b857 | ||
|
|
ad8d8b2dd6 | ||
|
|
fdc0faecfb | ||
|
|
addb72b9dc | ||
|
|
48b13a3d84 | ||
|
|
957f263e78 | ||
|
|
e9035336b3 | ||
|
|
74801e0478 | ||
|
|
045ca2d4ec | ||
|
|
f8e3bf8a2e | ||
|
|
1de0066f4b | ||
|
|
7d47ee0895 | ||
|
|
55a9de48ba | ||
|
|
c3c3cb0a91 | ||
|
|
314856eb73 | ||
|
|
f45f87ad31 | ||
|
|
d88a77cd07 | ||
|
|
4f5a4f6b95 | ||
|
|
5958536127 | ||
|
|
6f5ee22df1 | ||
|
|
16c8316a19 | ||
|
|
17e20319af | ||
|
|
4f64ec7425 | ||
|
|
7c24500e5f | ||
|
|
71c3728e60 | ||
|
|
bccbc81037 | ||
|
|
5586e18a7f | ||
|
|
3ee7fa5291 | ||
|
|
66cf75f595 | ||
|
|
94c56eb596 | ||
|
|
0d3c836963 | ||
|
|
f65c491441 | ||
|
|
fa391feabe | ||
|
|
e5d47c704e | ||
|
|
215954bfe9 | ||
|
|
45e574d8f1 | ||
|
|
97f93996c6 | ||
|
|
43e45601c1 | ||
|
|
5563d930fa |
@@ -10,6 +10,7 @@
|
||||
junit-xml,
|
||||
ptpython,
|
||||
pydantic,
|
||||
pytest,
|
||||
python,
|
||||
ovmfvartool,
|
||||
remote-pdb,
|
||||
@@ -80,11 +81,14 @@ buildPythonApplication {
|
||||
doCheck = true;
|
||||
|
||||
nativeCheckInputs = [
|
||||
pytest
|
||||
ruff
|
||||
ty
|
||||
];
|
||||
|
||||
checkPhase = ''
|
||||
echo -e "\x1b[32m## run doctests\x1b[0m"
|
||||
pytest --doctest-modules -p no:cacheprovider test_driver
|
||||
echo -e "\x1b[32m## run ty\x1b[0m"
|
||||
# Installation adds the driver to PYTHONPATH; keep ty imports in the source tree.
|
||||
PYTHONPATH=".:$PYTHONPATH" ty check --error-on-warning test_driver extract-docstrings.py
|
||||
|
||||
@@ -87,6 +87,30 @@ def get_tmp_dir() -> Path:
|
||||
|
||||
|
||||
def pythonize_name(name: str) -> str:
|
||||
"""Turn a machine name into a valid Python identifier.
|
||||
|
||||
Names that already are valid identifiers stay the same:
|
||||
|
||||
>>> pythonize_name("node_1")
|
||||
'node_1'
|
||||
|
||||
Every character that is not allowed in an identifier becomes "_":
|
||||
|
||||
>>> pythonize_name("my-vm")
|
||||
'my_vm'
|
||||
>>> pythonize_name("web.example.org")
|
||||
'web_example_org'
|
||||
>>> pythonize_name("münchen")
|
||||
'm_nchen'
|
||||
|
||||
A leading digit is replaced rather than prefixed, so it gets lost, while
|
||||
digits later in the name are kept:
|
||||
|
||||
>>> pythonize_name("1node")
|
||||
'_node'
|
||||
>>> pythonize_name("12node")
|
||||
'_2node'
|
||||
"""
|
||||
return re.sub(r"^[^A-Za-z_]|[^A-Za-z0-9_]", "_", name)
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,22 @@ def as_timedelta(duration: Duration) -> dt.timedelta:
|
||||
Bare numbers are interpreted as seconds. This keeps existing test scripts
|
||||
that pass plain integers working while `datetime.timedelta` becomes the preferred
|
||||
way to express durations.
|
||||
|
||||
>>> as_timedelta(dt.timedelta(minutes=1))
|
||||
datetime.timedelta(seconds=60)
|
||||
>>> as_timedelta(90)
|
||||
datetime.timedelta(seconds=90)
|
||||
>>> as_timedelta(1.5)
|
||||
datetime.timedelta(seconds=1, microseconds=500000)
|
||||
|
||||
A `bool` is an `int` in Python, but it is never a valid duration:
|
||||
|
||||
>>> as_timedelta(True)
|
||||
Traceback (most recent call last):
|
||||
TypeError: expected a duration, got bool: True
|
||||
>>> as_timedelta("1")
|
||||
Traceback (most recent call last):
|
||||
TypeError: expected a timedelta, int, or float duration, got str
|
||||
"""
|
||||
if isinstance(duration, dt.timedelta):
|
||||
return duration
|
||||
@@ -27,7 +43,13 @@ def as_timedelta(duration: Duration) -> dt.timedelta:
|
||||
|
||||
|
||||
def as_seconds(duration: Duration) -> float:
|
||||
"""Coerce a `Duration` into a floating-point number of seconds."""
|
||||
"""Coerce a `Duration` into a floating-point number of seconds.
|
||||
|
||||
>>> as_seconds(90)
|
||||
90.0
|
||||
>>> as_seconds(dt.timedelta(minutes=1, milliseconds=500))
|
||||
60.5
|
||||
"""
|
||||
return as_timedelta(duration).total_seconds()
|
||||
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ class TerminalLogger(AbstractLogger):
|
||||
elapsed = time.monotonic() - self._start_time
|
||||
if "machine" in attributes:
|
||||
message = f"{attributes['machine']}: {message}"
|
||||
return f"[{elapsed:.2f}s] {message}"
|
||||
return Style.DIM + f"[{elapsed:.2f}s]" + Style.RESET_ALL + f" {message}" # ty: ignore[unsupported-operator]
|
||||
|
||||
@staticmethod
|
||||
def _eprint(*args: object, **kwargs: Any) -> None:
|
||||
@@ -322,10 +322,27 @@ class XMLLogger(AbstractLogger):
|
||||
self.xml.endDocument()
|
||||
self.logfile_handle.close()
|
||||
|
||||
def sanitise(self, message: str) -> str:
|
||||
@staticmethod
|
||||
def sanitise(message: str) -> str:
|
||||
r"""Remove the characters of Unicode's "Other" categories, such as
|
||||
control characters, from serial output before it is written to XML.
|
||||
|
||||
>>> XMLLogger.sanitise("booting\x00...\x07")
|
||||
'booting...'
|
||||
>>> XMLLogger.sanitise("Grüße")
|
||||
'Grüße'
|
||||
"""
|
||||
return "".join(ch for ch in message if unicodedata.category(ch)[0] != "C")
|
||||
|
||||
def maybe_prefix(self, message: str, attributes: dict[str, str]) -> str:
|
||||
@staticmethod
|
||||
def maybe_prefix(message: str, attributes: dict[str, str]) -> str:
|
||||
"""Prefix the message with the name of the machine, if there is one.
|
||||
|
||||
>>> XMLLogger.maybe_prefix("booting", {"machine": "server"})
|
||||
'server: booting'
|
||||
>>> XMLLogger.maybe_prefix("booting", {})
|
||||
'booting'
|
||||
"""
|
||||
if "machine" in attributes:
|
||||
return f"{attributes['machine']}: {message}"
|
||||
return message
|
||||
|
||||
@@ -5,7 +5,7 @@ import os
|
||||
import platform
|
||||
import queue
|
||||
import re
|
||||
import select
|
||||
import selectors
|
||||
import shlex
|
||||
import shutil
|
||||
import socket
|
||||
@@ -139,7 +139,22 @@ XDOTOOL_MODIFIER_ALIASES = {
|
||||
def xdotool_key_sequence(key: str) -> str:
|
||||
"""Translate a QEMU-style key chord to xdotool syntax.
|
||||
|
||||
For example, "ctrl-alt-ret" becomes "ctrl+alt+Return".
|
||||
>>> xdotool_key_sequence("ctrl-alt-ret")
|
||||
'ctrl+alt+Return'
|
||||
>>> xdotool_key_sequence("meta_l-x")
|
||||
'super+x'
|
||||
>>> xdotool_key_sequence("alt-f4")
|
||||
'alt+F4'
|
||||
|
||||
A modifier on its own is a regular key:
|
||||
|
||||
>>> xdotool_key_sequence("shift")
|
||||
'Shift_L'
|
||||
|
||||
A "-" that follows a modifier is the key itself, not a separator:
|
||||
|
||||
>>> xdotool_key_sequence("ctrl--")
|
||||
'ctrl+-'
|
||||
"""
|
||||
parts = key.split("-")
|
||||
modifiers = []
|
||||
@@ -154,9 +169,42 @@ def xdotool_key_sequence(key: str) -> str:
|
||||
|
||||
|
||||
def make_command(args: list) -> str:
|
||||
"""Join arguments into a shell command, quoting each one as a single word.
|
||||
|
||||
>>> make_command(["echo", "a b", 1])
|
||||
"echo 'a b' 1"
|
||||
>>> make_command(["ls", "*"])
|
||||
"ls '*'"
|
||||
"""
|
||||
return " ".join(map(shlex.quote, (map(str, args))))
|
||||
|
||||
|
||||
def parse_systemctl_property(line: str) -> tuple[str, str] | None:
|
||||
"""Split a line of `systemctl show` output into its property and value.
|
||||
|
||||
>>> parse_systemctl_property("Id=foo.service")
|
||||
('Id', 'foo.service')
|
||||
|
||||
Only the first "=" separates the two, and values can be empty:
|
||||
|
||||
>>> parse_systemctl_property("Environment=A=1 B=2")
|
||||
('Environment', 'A=1 B=2')
|
||||
>>> parse_systemctl_property("Description=")
|
||||
('Description', '')
|
||||
|
||||
Lines that aren't properties yield `None`:
|
||||
|
||||
>>> parse_systemctl_property("") is None
|
||||
True
|
||||
>>> parse_systemctl_property("no property") is None
|
||||
True
|
||||
"""
|
||||
match = re.match(r"^([^=]+)=(.*)$", line)
|
||||
if match is None:
|
||||
return None
|
||||
return match[1], match[2]
|
||||
|
||||
|
||||
def retry(
|
||||
fn: Callable,
|
||||
timeout_seconds: int | dt.timedelta | None = None,
|
||||
@@ -217,6 +265,13 @@ class QemuStartCommand:
|
||||
These Nix commands have the particular characteristic that the
|
||||
machine name can be extracted out of them via a regex match.
|
||||
(Admittedly a _very_ implicit contract, TODO fix this eventually.)
|
||||
|
||||
>>> QemuStartCommand("/nix/store/hash-nixos-vm/bin/run-foo-vm").machine_name
|
||||
'foo'
|
||||
>>> QemuStartCommand("run-foo-bar-vm").machine_name
|
||||
'foo-bar'
|
||||
>>> QemuStartCommand("/some/script").machine_name
|
||||
'machine'
|
||||
"""
|
||||
match = re.search("run-(.+)-vm$", self._cmd)
|
||||
name = "machine"
|
||||
@@ -397,6 +452,29 @@ class BaseMachine(ABC):
|
||||
"""Shutdown the machine gracefully, waiting for it to exit."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def _systemctl_command(q: str, user: str | None = None) -> str:
|
||||
r"""Build a `systemctl` command, or a `systemctl --user` one for `user`.
|
||||
|
||||
>>> print(BaseMachine._systemctl_command("list-jobs --no-pager"))
|
||||
systemctl list-jobs --no-pager
|
||||
>>> print(BaseMachine._systemctl_command("list-jobs --no-pager", "alice"))
|
||||
su -l alice --shell /bin/sh -c $'XDG_RUNTIME_DIR=/run/user/`id -u` systemctl --user list-jobs --no-pager'
|
||||
|
||||
Single quotes survive the surrounding `$'...'`:
|
||||
|
||||
>>> print(BaseMachine._systemctl_command("show 'foo bar'", "alice"))
|
||||
su -l alice --shell /bin/sh -c $'XDG_RUNTIME_DIR=/run/user/`id -u` systemctl --user show \'foo bar\''
|
||||
"""
|
||||
if user is None:
|
||||
return f"systemctl {q}"
|
||||
escaped = q.replace("'", "\\'")
|
||||
return (
|
||||
f"su -l {user} --shell /bin/sh -c "
|
||||
"$'XDG_RUNTIME_DIR=/run/user/`id -u` "
|
||||
f"systemctl --user {escaped}'"
|
||||
)
|
||||
|
||||
def systemctl(self, q: str, user: str | None = None) -> tuple[int, str]:
|
||||
"""
|
||||
Runs `systemctl` commands with optional support for
|
||||
@@ -411,14 +489,7 @@ class BaseMachine(ABC):
|
||||
machine.systemctl("list-jobs --no-pager", "any-user")
|
||||
```
|
||||
"""
|
||||
if user is not None:
|
||||
q = q.replace("'", "\\'")
|
||||
return self.execute(
|
||||
f"su -l {user} --shell /bin/sh -c "
|
||||
"$'XDG_RUNTIME_DIR=/run/user/`id -u` "
|
||||
f"systemctl --user {q}'"
|
||||
)
|
||||
return self.execute(f"systemctl {q}")
|
||||
return self.execute(self._systemctl_command(q, user))
|
||||
|
||||
def wait_for_unit(
|
||||
self,
|
||||
@@ -467,17 +538,10 @@ class BaseMachine(ABC):
|
||||
+ f" failed with exit code {status}"
|
||||
)
|
||||
|
||||
line_pattern = re.compile(r"^([^=]+)=(.*)$")
|
||||
|
||||
def tuple_from_line(line: str) -> tuple[str, str]:
|
||||
match = line_pattern.match(line)
|
||||
assert match is not None
|
||||
return match[1], match[2]
|
||||
|
||||
return dict(
|
||||
tuple_from_line(line)
|
||||
parsed
|
||||
for line in lines.split("\n")
|
||||
if line_pattern.match(line)
|
||||
if (parsed := parse_systemctl_property(line)) is not None
|
||||
)
|
||||
|
||||
def get_unit_property(
|
||||
@@ -505,12 +569,12 @@ class BaseMachine(ABC):
|
||||
f"produced invalid output: {lines}"
|
||||
)
|
||||
|
||||
line_pattern = re.compile(r"^([^=]+)=(.*)$")
|
||||
match = line_pattern.match(lines)
|
||||
assert match is not None, invalid_output_message
|
||||
parsed = parse_systemctl_property(lines)
|
||||
assert parsed is not None, invalid_output_message
|
||||
|
||||
assert match[1] == property, invalid_output_message
|
||||
return match[2]
|
||||
name, value = parsed
|
||||
assert name == property, invalid_output_message
|
||||
return value
|
||||
|
||||
def require_unit_state(self, unit: str, require_state: str = "active") -> None:
|
||||
"""
|
||||
@@ -742,7 +806,7 @@ class BaseMachine(ABC):
|
||||
)
|
||||
if ret.returncode != 0:
|
||||
raise MachineError(
|
||||
f"Cannot convert screenshot (pnmtopng returned code {ret.returncode})"
|
||||
f"{self.name}: Cannot convert screenshot (pnmtopng returned code {ret.returncode})"
|
||||
)
|
||||
|
||||
def get_screen_text_variants(self) -> list[str]:
|
||||
@@ -1178,15 +1242,18 @@ class QemuMachine(BaseMachine):
|
||||
"""Debugging: Dump the contents of the TTY<n>"""
|
||||
self.execute(f"fold -w 80 /dev/vcs{tty} | systemd-cat")
|
||||
|
||||
def _execute(
|
||||
self,
|
||||
command: str,
|
||||
check_return: bool = True,
|
||||
check_output: bool = True,
|
||||
timeout: dt.timedelta | None = dt.timedelta(minutes=15),
|
||||
) -> tuple[int, str]:
|
||||
self.connect()
|
||||
@staticmethod
|
||||
def _shell_command(command: str, timeout: dt.timedelta | None) -> str:
|
||||
"""Build the line that runs `command` over the shell of the machine.
|
||||
|
||||
The command runs with strict shell options, and its output gets
|
||||
base64-encoded so that it can be read back as a single line:
|
||||
|
||||
>>> print(QemuMachine._shell_command("echo 'hi'", dt.timedelta(seconds=10)))
|
||||
timeout 10.0 bash -c 'set -euo pipefail; echo '"'"'hi'"'"'' | (base64 -w 0; echo)
|
||||
>>> print(QemuMachine._shell_command("true", None).strip())
|
||||
bash -c 'set -euo pipefail; true' | (base64 -w 0; echo)
|
||||
"""
|
||||
# Always run command with shell opts
|
||||
command = f"set -euo pipefail; {command}"
|
||||
|
||||
@@ -1196,12 +1263,19 @@ class QemuMachine(BaseMachine):
|
||||
|
||||
# While sh is bash on NixOS, this is not the case for every distro.
|
||||
# We explicitly call bash here to allow for the driver to boot other distros as well.
|
||||
out_command = (
|
||||
f"{timeout_str} bash -c {shlex.quote(command)} | (base64 -w 0; echo)\n"
|
||||
)
|
||||
return f"{timeout_str} bash -c {shlex.quote(command)} | (base64 -w 0; echo)"
|
||||
|
||||
def _execute(
|
||||
self,
|
||||
command: str,
|
||||
check_return: bool = True,
|
||||
check_output: bool = True,
|
||||
timeout: dt.timedelta | None = dt.timedelta(minutes=15),
|
||||
) -> tuple[int, str]:
|
||||
self.connect()
|
||||
|
||||
assert self.shell
|
||||
self.shell.send(out_command.encode())
|
||||
self.shell.send(f"{self._shell_command(command, timeout)}\n".encode())
|
||||
|
||||
if not check_output:
|
||||
return (-2, "")
|
||||
@@ -1335,16 +1409,6 @@ class QemuMachine(BaseMachine):
|
||||
Wait for a connection to the guest root shell
|
||||
"""
|
||||
|
||||
def shell_ready(timeout: dt.timedelta) -> bool:
|
||||
"""We sent some data from the backdoor service running on the guest
|
||||
to indicate that the backdoor shell is ready.
|
||||
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.total_seconds())
|
||||
return bool(ready)
|
||||
|
||||
if self.connected:
|
||||
return
|
||||
|
||||
@@ -1354,9 +1418,15 @@ class QemuMachine(BaseMachine):
|
||||
assert self.shell
|
||||
|
||||
tic = time.monotonic()
|
||||
sel = selectors.DefaultSelector()
|
||||
sel.register(self.shell, selectors.EVENT_READ)
|
||||
|
||||
for _ in range(10):
|
||||
if shell_ready(timeout=dt.timedelta(seconds=30)):
|
||||
# We sent some data from the backdoor service running on the guest
|
||||
# to indicate that the backdoor shell is ready.
|
||||
# As soon as we read some data from the socket here, we assume that
|
||||
# our root shell is operational.
|
||||
if sel.select(timeout=30):
|
||||
break
|
||||
self.log("Guest root shell did not produce any data yet...")
|
||||
self.log(
|
||||
@@ -1477,6 +1547,7 @@ class QemuMachine(BaseMachine):
|
||||
s = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM)
|
||||
s.bind(str(path))
|
||||
s.listen(1)
|
||||
s.setblocking(False)
|
||||
return s
|
||||
|
||||
monitor_socket = create_socket(clear(self.monitor_path))
|
||||
@@ -1498,8 +1569,10 @@ class QemuMachine(BaseMachine):
|
||||
would block forever if QEMU exits before connecting.
|
||||
"""
|
||||
assert self.process
|
||||
while True:
|
||||
readable, _, _ = select.select([sock], [], [], 1.0)
|
||||
sel = selectors.DefaultSelector()
|
||||
sel.register(sock, selectors.EVENT_READ)
|
||||
for _ in range(60):
|
||||
readable = sel.select(1)
|
||||
if readable:
|
||||
conn, _ = sock.accept()
|
||||
return conn
|
||||
@@ -1509,8 +1582,11 @@ class QemuMachine(BaseMachine):
|
||||
if self.process.stdout:
|
||||
output = self.process.stdout.read().decode(errors="ignore")
|
||||
raise MachineError(
|
||||
f"QEMU process exited with code {rc} before connecting to {name} socket.\n{output}"
|
||||
f"{self.name}: QEMU process exited with code {rc} before connecting to {name} socket.\n{output}"
|
||||
)
|
||||
raise MachineError(
|
||||
f"{self.name}: Timed out waiting for QEMU connection on {name} socket"
|
||||
)
|
||||
|
||||
self.monitor = accept_or_fail(monitor_socket, "monitor")
|
||||
self.shell = accept_or_fail(shell_socket, "shell")
|
||||
@@ -1690,7 +1766,19 @@ class NspawnMachine(BaseMachine):
|
||||
vnc: NspawnVnc | None
|
||||
|
||||
@staticmethod
|
||||
def machine_name_from_start_command(start_command: str) -> str:
|
||||
def _machine_name_from_start_command(start_command: str) -> str:
|
||||
"""Extract the machine name from a start script of a container.
|
||||
|
||||
>>> NspawnMachine._machine_name_from_start_command(
|
||||
... "/nix/store/hash-container/bin/run-foo-nspawn"
|
||||
... )
|
||||
'foo'
|
||||
>>> NspawnMachine._machine_name_from_start_command("run-foo-bar-nspawn")
|
||||
'foo-bar'
|
||||
>>> NspawnMachine._machine_name_from_start_command("/some/script")
|
||||
Traceback (most recent call last):
|
||||
AssertionError: Could not extract node name from /some/script
|
||||
"""
|
||||
match = re.search("run-(.+)-nspawn", os.path.basename(start_command))
|
||||
assert match is not None, f"Could not extract node name from {start_command}"
|
||||
return match.group(1)
|
||||
@@ -1710,7 +1798,7 @@ class NspawnMachine(BaseMachine):
|
||||
# See analogous TODO in `QemuStartCommand::machine_name`.
|
||||
super().__init__(
|
||||
out_dir=out_dir,
|
||||
name=name or self.machine_name_from_start_command(start_command),
|
||||
name=name or self._machine_name_from_start_command(start_command),
|
||||
logger=logger,
|
||||
callbacks=callbacks,
|
||||
tmp_dir=tmp_dir,
|
||||
@@ -1743,6 +1831,33 @@ class NspawnMachine(BaseMachine):
|
||||
with self.nested("waiting for the X11 server"):
|
||||
retry(check_x, as_timedelta(timeout))
|
||||
|
||||
@staticmethod
|
||||
def _xdotool_command(key: str) -> str:
|
||||
"""Build the xdotool command that presses a QEMU-style key.
|
||||
|
||||
Literal characters get typed to avoid key syntax, e.g. "+" as a chord
|
||||
separator:
|
||||
|
||||
>>> print(NspawnMachine._xdotool_command("a"))
|
||||
xdotool type --clearmodifiers --delay 0 -- a
|
||||
>>> print(NspawnMachine._xdotool_command("+"))
|
||||
xdotool type --clearmodifiers --delay 0 -- +
|
||||
|
||||
Everything else is a key chord:
|
||||
|
||||
>>> print(NspawnMachine._xdotool_command("ctrl-alt-ret"))
|
||||
xdotool key --clearmodifiers ctrl+alt+Return
|
||||
>>> print(NspawnMachine._xdotool_command("\\n"))
|
||||
xdotool key --clearmodifiers Return
|
||||
"""
|
||||
if len(key) == 1 and key.isprintable():
|
||||
return make_command(
|
||||
["xdotool", "type", "--clearmodifiers", "--delay", 0, "--", key]
|
||||
)
|
||||
return make_command(
|
||||
["xdotool", "key", "--clearmodifiers", xdotool_key_sequence(key)]
|
||||
)
|
||||
|
||||
def send_key(
|
||||
self,
|
||||
key: str,
|
||||
@@ -1752,25 +1867,7 @@ class NspawnMachine(BaseMachine):
|
||||
_warn_if_numeric_duration(delay, "send_key")
|
||||
context = self.nested(f"sending key {repr(key)}") if log else nullcontext()
|
||||
with context:
|
||||
# Type literal characters to avoid key syntax, e.g. "+" as a chord separator.
|
||||
if len(key) == 1 and key.isprintable():
|
||||
command = [
|
||||
"xdotool",
|
||||
"type",
|
||||
"--clearmodifiers",
|
||||
"--delay",
|
||||
0,
|
||||
"--",
|
||||
key,
|
||||
]
|
||||
else:
|
||||
command = [
|
||||
"xdotool",
|
||||
"key",
|
||||
"--clearmodifiers",
|
||||
xdotool_key_sequence(key),
|
||||
]
|
||||
self.succeed(make_command(command))
|
||||
self.succeed(self._xdotool_command(key))
|
||||
if delay is not None:
|
||||
time.sleep(as_seconds(delay))
|
||||
|
||||
@@ -1799,7 +1896,7 @@ class NspawnMachine(BaseMachine):
|
||||
ret = subprocess.run(["xwdtopnm", xwd_path], stdout=ppm)
|
||||
if ret.returncode != 0:
|
||||
raise MachineError(
|
||||
f"Cannot convert screenshot (xwdtopnm returned code {ret.returncode})"
|
||||
f"{self.name}: Cannot convert screenshot (xwdtopnm returned code {ret.returncode})"
|
||||
)
|
||||
yield ppm_path
|
||||
|
||||
@@ -1836,6 +1933,28 @@ class NspawnMachine(BaseMachine):
|
||||
def is_up(self) -> bool:
|
||||
return self.process is not None
|
||||
|
||||
@staticmethod
|
||||
def _parse_sd_notify(data: bytes) -> tuple[bool, int | None]:
|
||||
"""Extract readiness and the leader PID from an `sd_notify` datagram.
|
||||
|
||||
>>> NspawnMachine._parse_sd_notify(b"READY=1\\n")
|
||||
(True, None)
|
||||
>>> NspawnMachine._parse_sd_notify(b"X_NSPAWN_LEADER_PID=42\\n")
|
||||
(False, 42)
|
||||
>>> NspawnMachine._parse_sd_notify(b"X_NSPAWN_LEADER_PID=42\\nREADY=1\\n")
|
||||
(True, 42)
|
||||
>>> NspawnMachine._parse_sd_notify(b"STATUS=Starting up\\n")
|
||||
(False, None)
|
||||
"""
|
||||
ready = False
|
||||
leader_pid = None
|
||||
for line in data.decode(errors="replace").splitlines():
|
||||
if line == "READY=1":
|
||||
ready = True
|
||||
if line.startswith("X_NSPAWN_LEADER_PID="):
|
||||
leader_pid = int(line.split("=")[1])
|
||||
return ready, leader_pid
|
||||
|
||||
def _drain_notify_socket(self) -> None:
|
||||
"""Continuously drain the container's `sd_notify` socket (NOTIFY_SOCKET)
|
||||
for the whole lifetime of the container, recording readiness and the
|
||||
@@ -1866,13 +1985,7 @@ class NspawnMachine(BaseMachine):
|
||||
continue
|
||||
except OSError:
|
||||
break
|
||||
ready = False
|
||||
leader_pid = None
|
||||
for line in data.decode(errors="replace").splitlines():
|
||||
if line == "READY=1":
|
||||
ready = True
|
||||
if line.startswith("X_NSPAWN_LEADER_PID="):
|
||||
leader_pid = int(line.split("=")[1])
|
||||
ready, leader_pid = self._parse_sd_notify(data)
|
||||
if ready or leader_pid is not None:
|
||||
with self._notify_lock:
|
||||
if ready:
|
||||
@@ -1898,7 +2011,9 @@ class NspawnMachine(BaseMachine):
|
||||
# Poll the socket until we have the container leader PID
|
||||
while True:
|
||||
if self.process.poll() is not None:
|
||||
raise MachineError("systemd-nspawn process exited unexpectedly")
|
||||
raise MachineError(
|
||||
f"{self.name}: systemd-nspawn process exited unexpectedly"
|
||||
)
|
||||
|
||||
with self._notify_lock:
|
||||
is_ready = self._notify_ready
|
||||
|
||||
@@ -12,6 +12,23 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QMPAPIError(RuntimeError):
|
||||
"""An error response from the QMP API.
|
||||
|
||||
>>> error = {"class": "GenericError", "desc": "boom"}
|
||||
>>> print(QMPAPIError({"error": error, "id": 3}))
|
||||
<QMP API error related to transaction 3 [GenericError]: boom>
|
||||
|
||||
The transaction id is absent from errors that the server hits before it
|
||||
could read the id:
|
||||
|
||||
>>> print(QMPAPIError({"error": error}))
|
||||
<QMP API error related to transaction None [GenericError]: boom>
|
||||
|
||||
>>> QMPAPIError({"error": {"class": "GenericError"}})
|
||||
Traceback (most recent call last):
|
||||
RuntimeError: Malformed QMP API error response
|
||||
"""
|
||||
|
||||
def __init__(self, message: dict[str, Any]):
|
||||
assert "error" in message, "Not an error message!"
|
||||
error = message["error"]
|
||||
|
||||
@@ -13,6 +13,35 @@ class PollingConditionError(Exception):
|
||||
|
||||
|
||||
class PollingCondition:
|
||||
"""A condition that gets checked periodically while a test runs.
|
||||
|
||||
The description comes from the `description` argument. Without one, it
|
||||
falls back to the docstring of the condition, and then to its name:
|
||||
|
||||
>>> from test_driver.logger import CompositeLogger
|
||||
>>> logger = CompositeLogger([])
|
||||
>>> def service_runs():
|
||||
... "The service is running"
|
||||
>>> def no_docstring():
|
||||
... pass
|
||||
>>> PollingCondition(service_runs, logger, description="custom").description
|
||||
'custom'
|
||||
>>> PollingCondition(service_runs, logger).description
|
||||
'The service is running'
|
||||
>>> PollingCondition(no_docstring, logger).description
|
||||
'no_docstring'
|
||||
|
||||
Log messages include the description:
|
||||
|
||||
>>> condition = PollingCondition(service_runs, logger)
|
||||
>>> condition.nested_message
|
||||
"Checking polling condition 'The service is running'"
|
||||
>>> condition.status_message(True)
|
||||
'Polling condition succeeded: The service is running'
|
||||
>>> condition.status_message(False)
|
||||
'Polling condition failed: The service is running'
|
||||
"""
|
||||
|
||||
condition: Callable[[], bool]
|
||||
interval: dt.timedelta
|
||||
description: str | None
|
||||
|
||||
@@ -30,7 +30,6 @@ let
|
||||
cfg = config.fonts.fontconfig;
|
||||
|
||||
fcBool = x: "<bool>" + (lib.boolToString x) + "</bool>";
|
||||
pkg = pkgs.fontconfig;
|
||||
|
||||
# configuration file to read fontconfig cache
|
||||
# priority 0
|
||||
@@ -49,7 +48,7 @@ let
|
||||
inherit fontconfig;
|
||||
fontDirectories = config.fonts.packages;
|
||||
};
|
||||
cache = makeCache pkgs.fontconfig;
|
||||
cache = makeCache cfg.package;
|
||||
cache32 = makeCache pkgs.pkgsi686Linux.fontconfig;
|
||||
in
|
||||
pkgs.writeText "fc-00-nixos-cache.conf" ''
|
||||
@@ -209,7 +208,7 @@ let
|
||||
# Replace default linked config with a different variant
|
||||
replaceDefaultConfig = defaultConfig: newConfig: ''
|
||||
rm $dst/${defaultConfig}
|
||||
ln -s ${pkg.out}/share/fontconfig/conf.avail/${newConfig} \
|
||||
ln -s ${cfg.package.out}/share/fontconfig/conf.avail/${newConfig} \
|
||||
$dst/
|
||||
'';
|
||||
|
||||
@@ -224,7 +223,7 @@ let
|
||||
mkdir -p $dst
|
||||
|
||||
# fonts.conf
|
||||
ln -s ${pkg.out}/etc/fonts/fonts.conf \
|
||||
ln -s ${cfg.package.out}/etc/fonts/fonts.conf \
|
||||
$dst/../fonts.conf
|
||||
# TODO: remove this legacy symlink once people stop using packages built before #95358 was merged
|
||||
mkdir -p $out/etc/fonts/2.11
|
||||
@@ -232,7 +231,7 @@ let
|
||||
$out/etc/fonts/2.11/fonts.conf
|
||||
|
||||
# fontconfig default config files
|
||||
ln -s ${pkg.out}/etc/fonts/conf.d/*.conf \
|
||||
ln -s ${cfg.package.out}/etc/fonts/conf.d/*.conf \
|
||||
$dst/
|
||||
|
||||
${lib.optionalString (!cfg.antialias) (
|
||||
@@ -352,6 +351,8 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
package = lib.mkPackageOption pkgs "fontconfig" { };
|
||||
|
||||
confPackages = lib.mkOption {
|
||||
internal = true;
|
||||
type = with lib.types; listOf path;
|
||||
@@ -627,14 +628,14 @@ in
|
||||
};
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf cfg.enable {
|
||||
environment.systemPackages = [ pkgs.fontconfig ];
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
environment.etc.fonts.source = "${fontconfigEtc}/etc/fonts/";
|
||||
security.apparmor.includes."abstractions/fonts" = ''
|
||||
# fonts.conf
|
||||
r ${pkg.out}/etc/fonts/fonts.conf,
|
||||
r ${cfg.package.out}/etc/fonts/fonts.conf,
|
||||
|
||||
# fontconfig default config files
|
||||
r ${pkg.out}/etc/fonts/conf.d/*.conf,
|
||||
r ${cfg.package.out}/etc/fonts/conf.d/*.conf,
|
||||
|
||||
# 00-nixos-cache.conf
|
||||
r ${cacheConf},
|
||||
@@ -644,7 +645,7 @@ in
|
||||
|
||||
# 50-user.conf
|
||||
${lib.optionalString cfg.includeUserConf ''
|
||||
r ${pkg.out}/etc/fonts/conf.d.bak/50-user.conf,
|
||||
r ${cfg.package.out}/etc/fonts/conf.d.bak/50-user.conf,
|
||||
''}
|
||||
|
||||
# local.conf (indirect priority 51)
|
||||
|
||||
@@ -89,6 +89,7 @@ let
|
||||
"systemd-fsck-root.service"
|
||||
"systemd-growfs@.service"
|
||||
"systemd-growfs-root.service"
|
||||
"systemd-loop@.service"
|
||||
"systemd-remount-fs.service"
|
||||
"systemd-pstore.service"
|
||||
"local-fs.target"
|
||||
|
||||
@@ -97,6 +97,7 @@ let
|
||||
"systemd-factory-reset-complete.service"
|
||||
"factory-reset-now.target"
|
||||
"systemd-fsck@.service"
|
||||
"systemd-loop@.service"
|
||||
"systemd-halt.service"
|
||||
"systemd-hibernate-resume.service"
|
||||
"systemd-journald-audit.socket"
|
||||
@@ -613,6 +614,7 @@ in
|
||||
"${cfg.package}/lib/systemd/systemd-bsod"
|
||||
"${cfg.package}/lib/systemd/systemd-sysroot-fstab-check"
|
||||
"${cfg.package}/lib/systemd/systemd-factory-reset"
|
||||
"${cfg.package}/bin/systemd-dissect"
|
||||
|
||||
# generators
|
||||
"${cfg.package}/lib/systemd/system-generators/systemd-debug-generator"
|
||||
|
||||
@@ -60,6 +60,10 @@
|
||||
# NixOS.
|
||||
systemd.services."systemd-pcrlogin@".unitConfig.ConditionPathExists =
|
||||
"/run/systemd/nvpcr/login.auth";
|
||||
|
||||
systemd.sockets."systemd-pcrextend" = {
|
||||
wantedBy = [ "sockets.target" ];
|
||||
};
|
||||
}
|
||||
)
|
||||
(
|
||||
@@ -101,6 +105,10 @@
|
||||
"${cfg.package}/lib/systemd/system-generators/systemd-tpm2-generator"
|
||||
"${cfg.package}/lib/systemd/systemd-pcrextend"
|
||||
];
|
||||
|
||||
boot.initrd.systemd.sockets."systemd-pcrextend" = {
|
||||
wantedBy = [ "sockets.target" ];
|
||||
};
|
||||
}
|
||||
)
|
||||
(
|
||||
|
||||
@@ -28,6 +28,8 @@ let
|
||||
|
||||
consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles;
|
||||
|
||||
runScript = hostPkgs.callPackage ./run-virtiofsd-qemu { };
|
||||
|
||||
driveOptions =
|
||||
{ ... }:
|
||||
{
|
||||
@@ -326,33 +328,28 @@ let
|
||||
''
|
||||
)}
|
||||
|
||||
${lib.optionalString useVirtiofs ''
|
||||
echo "Starting virtiofs daemons..."
|
||||
NIX_VIRTIOFS_DIR=$(mktemp -d)
|
||||
${lib.concatLines (
|
||||
lib.mapAttrsToList (tag: share: ''
|
||||
${lib.getExe hostPkgs.virtiofsd} \
|
||||
--socket-path="$NIX_VIRTIOFS_DIR"/"${tag}" \
|
||||
--shared-dir="$(readlink -f "${share.source}")" \
|
||||
${if share.writable then "--writeback" else "--readonly"} \
|
||||
--sandbox=none \
|
||||
--seccomp=none \
|
||||
--cache=${share.cache} \
|
||||
--no-announce-submounts \
|
||||
--translate-uid=host:65534:0:1 \
|
||||
--translate-gid=host:65534:0:1 \
|
||||
&
|
||||
'') cfg.sharedDirectories
|
||||
)}
|
||||
''}
|
||||
export NIX_VIRTIOFS_DIR=$(mktemp -d)
|
||||
|
||||
# Start QEMU.
|
||||
exec ${
|
||||
qemu-common.qemuBinaryWith {
|
||||
qemuPkg = qemu;
|
||||
forceAccel = cfg.qemu.forceAccel;
|
||||
}
|
||||
} \
|
||||
exec ${lib.getExe runScript} \
|
||||
${
|
||||
lib.optionalString (useVirtiofs && cfg.sharedDirectories != { })
|
||||
"--virtiofs-paths ${
|
||||
lib.concatStringsSep " " (
|
||||
lib.mapAttrsToList (
|
||||
tag: share:
|
||||
"${tag}:${share.source}:${share.cache}:${if share.writable then "writeback" else "readonly"}"
|
||||
) cfg.sharedDirectories
|
||||
)
|
||||
}"
|
||||
} \
|
||||
-- \
|
||||
${
|
||||
qemu-common.qemuBinaryWith {
|
||||
qemuPkg = qemu;
|
||||
forceAccel = cfg.qemu.forceAccel;
|
||||
}
|
||||
} \
|
||||
-name ${config.system.name} \
|
||||
-m ${toString config.virtualisation.memorySize} \
|
||||
-smp ${toString config.virtualisation.cores} \
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{ python3Packages }:
|
||||
|
||||
python3Packages.callPackage ./package.nix { }
|
||||
49
nixos/modules/virtualisation/run-virtiofsd-qemu/package.nix
Normal file
49
nixos/modules/virtualisation/run-virtiofsd-qemu/package.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
lib,
|
||||
buildPythonApplication,
|
||||
virtiofsd,
|
||||
ruff,
|
||||
setuptools,
|
||||
ty,
|
||||
}:
|
||||
|
||||
buildPythonApplication {
|
||||
pname = "run-virtiofsd-qemu";
|
||||
version = "1.0";
|
||||
pyproject = true;
|
||||
|
||||
src = ./.;
|
||||
|
||||
postPatch = ''
|
||||
substituteInPlace ./run_virtiofsd_qemu/__init__.py \
|
||||
--replace-fail "@virtiofsd@" "${lib.getExe virtiofsd}"
|
||||
'';
|
||||
|
||||
build-system = [
|
||||
setuptools
|
||||
];
|
||||
|
||||
propagatedBuildInputs = [
|
||||
virtiofsd
|
||||
];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
nativeCheckInputs = [
|
||||
ruff
|
||||
ty
|
||||
];
|
||||
|
||||
checkPhase = ''
|
||||
echo -e "\x1b[32m## run ty\x1b[0m"
|
||||
ty check --error-on-warning run_virtiofsd_qemu
|
||||
echo -e "\x1b[32m## run ruff check\x1b[0m"
|
||||
ruff check .
|
||||
echo -e "\x1b[32m## run ruff format\x1b[0m"
|
||||
ruff format --check --diff .
|
||||
'';
|
||||
|
||||
meta = {
|
||||
mainProgram = "run-virtiofsd-qemu";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[build-system]
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "run-virtiofsd-qemu"
|
||||
version = "1.0"
|
||||
|
||||
[project.scripts]
|
||||
run-virtiofsd-qemu = "run_virtiofsd_qemu:main"
|
||||
|
||||
[tool.setuptools.packages]
|
||||
find = {}
|
||||
@@ -0,0 +1,120 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
import signal
|
||||
import subprocess
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
from sys import exit
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def fatal_error(msg: str):
|
||||
logger.error(msg)
|
||||
exit(1)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def virtiofsd(virtiofs_dir: Path, tag: str, source: Path, cache: str, flag: str):
|
||||
socket_path = virtiofs_dir / tag
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"@virtiofsd@",
|
||||
f"--socket-path={socket_path}",
|
||||
f"--shared-dir={source.resolve()}",
|
||||
f"--{flag}",
|
||||
"--sandbox=none",
|
||||
"--seccomp=none",
|
||||
f"--cache={cache}",
|
||||
"--no-announce-submounts",
|
||||
"--translate-uid=host:65534:0:1",
|
||||
"--translate-gid=host:65534:0:1",
|
||||
)
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
while not await loop.run_in_executor(None, lambda: socket_path.exists()):
|
||||
if proc.returncode is not None:
|
||||
fatal_error(f"virtiofsd process for '{tag}' failed to start. Aborting.")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
yield
|
||||
|
||||
try:
|
||||
proc.terminate()
|
||||
async with asyncio.timeout(2):
|
||||
await proc.communicate()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning(
|
||||
f"virtiofsd process for '{tag}' still running after SIGTERM. Killing."
|
||||
)
|
||||
proc.kill()
|
||||
await proc.communicate()
|
||||
|
||||
|
||||
def extract_arguments() -> tuple[list[tuple[str, Path, str, str]], list[str]]:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--virtiofs-paths",
|
||||
nargs="+",
|
||||
help="Paths to be exposed with virtiofsd in the format 'tag:source:mode:cache'",
|
||||
)
|
||||
|
||||
args, qemu_args = parser.parse_known_args()
|
||||
if not qemu_args:
|
||||
logger.warning("No command to run specified, exiting")
|
||||
exit(0)
|
||||
|
||||
if qemu_args[0] != "--":
|
||||
fatal_error(
|
||||
"Usage error: expected command to run separated with '--' from the args pased to this program!"
|
||||
)
|
||||
qemu_args.pop(0)
|
||||
if not qemu_args:
|
||||
logger.warning("No command to run specified, exiting")
|
||||
exit(0)
|
||||
|
||||
defs = []
|
||||
for defin in args.virtiofs_paths:
|
||||
args = defin.split(":")
|
||||
if len(args) != 4:
|
||||
fatal_error(
|
||||
f"--virtio-paths definition '{defin}' doesn't have four params separated by ':'!"
|
||||
)
|
||||
defs.append((args[0], Path(args[1]), args[2], args[3]))
|
||||
|
||||
return defs, qemu_args
|
||||
|
||||
|
||||
async def amain():
|
||||
logging.basicConfig(format="%(levelname)s: %(funcName)s: %(message)s")
|
||||
args, qemu_args = extract_arguments()
|
||||
|
||||
try:
|
||||
virtiofs_dir = Path(environ["NIX_VIRTIOFS_DIR"])
|
||||
except KeyError:
|
||||
fatal_error("NIX_VIRTIOFS_DIR is unset!")
|
||||
|
||||
async with AsyncExitStack() as virtiofsds:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
virtiofsds.enter_async_context(virtiofsd(virtiofs_dir, *defin))
|
||||
for defin in args
|
||||
)
|
||||
)
|
||||
|
||||
proc = await asyncio.create_subprocess_exec(*qemu_args)
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.add_signal_handler(signal.SIGTERM, lambda: proc.terminate())
|
||||
loop.add_signal_handler(signal.SIGINT, lambda: proc.terminate())
|
||||
await proc.communicate()
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(amain())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
pkgs ? import ../../../.. { },
|
||||
}:
|
||||
pkgs.callPackage ./default.nix { }
|
||||
@@ -31,6 +31,12 @@ in
|
||||
displayManager.lightdm.extraConfig = ''
|
||||
logind-check-graphical = false
|
||||
'';
|
||||
|
||||
# `wait_for_x()` establishing a connection that is being teared down
|
||||
# before having any other connection to X11 causes the X server to reset
|
||||
# itself. This flag prevents that behavior.
|
||||
# Context: https://github.com/NixOS/nixpkgs/pull/578540#issuecomment-6593586095
|
||||
displayManager.xserverArgs = lib.mkAfter [ "-noreset" ];
|
||||
})
|
||||
];
|
||||
|
||||
|
||||
@@ -19,15 +19,25 @@
|
||||
|
||||
boot.initrd.availableKernelModules = [ "tpm_tis" ];
|
||||
|
||||
environment.systemPackages = with pkgs; [ cryptsetup ];
|
||||
environment.systemPackages = with pkgs; [
|
||||
cryptsetup
|
||||
tpm2-tools
|
||||
];
|
||||
|
||||
specialisation.boot-luks.configuration = {
|
||||
boot.initrd.luks.devices = lib.mkVMOverride {
|
||||
cryptroot = {
|
||||
device = "/dev/vdb";
|
||||
crypttabExtraOpts = [ "tpm2-device=auto" ];
|
||||
crypttabExtraOpts = [
|
||||
"tpm2-device=auto"
|
||||
"tpm2-measure-pcr=yes"
|
||||
];
|
||||
};
|
||||
};
|
||||
# The volume key measurement is only performed when the OS is explicitly
|
||||
# marked as measured (or booted through a measuring kernel stub).
|
||||
boot.kernelParams = [ "systemd.tpm2_measured_os=1" ];
|
||||
|
||||
virtualisation.rootDevice = "/dev/mapper/cryptroot";
|
||||
};
|
||||
};
|
||||
@@ -54,5 +64,11 @@
|
||||
# Boot and decrypt the disk
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
|
||||
|
||||
# The volume key has to be measured into PCR 15 through the
|
||||
# io.systemd.PCRExtend socket during initrd.
|
||||
machine.succeed("grep -q volume-key /run/log/systemd/tpm2-measure.log")
|
||||
pcr = machine.succeed("tpm2_pcrread sha256:15")
|
||||
assert "15: 0x0000000000000000000000000000000000000000000000000000000000000000" not in pcr, pcr
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ fn add_dependencies<P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug>(
|
||||
.position(|x| *x == 0)
|
||||
.unwrap_or(note.desc.len())];
|
||||
let parsed = serde_json::from_slice::<Vec<DLOpenNote>>(payload)?;
|
||||
for mut parsed_note in parsed {
|
||||
for parsed_note in parsed {
|
||||
if dlopen.use_priority
|
||||
>= parsed_note.priority.unwrap_or(DLOpenPriority::Recommended)
|
||||
|| parsed_note
|
||||
@@ -117,7 +117,9 @@ fn add_dependencies<P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug>(
|
||||
.map(|f| dlopen.features.contains(&f))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
dlopen_libraries.append(&mut parsed_note.soname);
|
||||
// SONAMEs in one note are ordered alternatives, not
|
||||
// independent dependencies.
|
||||
dlopen_libraries.push(parsed_note.soname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,39 +139,38 @@ fn add_dependencies<P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug>(
|
||||
.map(|p| Box::<Path>::from(Path::new(p)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for line in elf
|
||||
for sonames in elf
|
||||
.libraries
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.map(|s| vec![s.to_string()])
|
||||
.chain(dlopen_libraries)
|
||||
{
|
||||
let mut found = false;
|
||||
for path in &rpaths_as_path {
|
||||
let lib = path.join(&line);
|
||||
if lib.exists() {
|
||||
// No need to recurse. The queue will bring it back round.
|
||||
queue.push_back(StorePath {
|
||||
path: Box::from(lib.as_path()),
|
||||
dlopen: dlopen.clone(),
|
||||
});
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
let library = sonames
|
||||
.iter()
|
||||
.flat_map(|soname| rpaths_as_path.iter().map(move |path| path.join(soname)))
|
||||
.find(|lib| lib.exists());
|
||||
if let Some(lib) = library {
|
||||
// No need to recurse. The queue will bring it back round.
|
||||
queue.push_back(StorePath {
|
||||
path: lib.into_boxed_path(),
|
||||
dlopen: dlopen.clone(),
|
||||
});
|
||||
} else {
|
||||
// In Nix, glibc's own libraries lack rpath entries pointing to
|
||||
// themselves, so the dynamic linker (ld-linux-*.so.*) and libc.so.*
|
||||
// can never be resolved through rpath alone. They are always present
|
||||
// in the initrd: the linker via elf.interpreter above, and libc via
|
||||
// at least one binary's rpath. Suppress these known-benign cases.
|
||||
// See also: the ld*.so.? skip in stage-1.nix findLibs.
|
||||
let is_glibc_runtime = (line.starts_with("ld-") && line.contains(".so"))
|
||||
|| line.starts_with("libc.so");
|
||||
let is_glibc_runtime = matches!(
|
||||
sonames.as_slice(),
|
||||
[s] if (s.starts_with("ld-") && s.contains(".so")) || s.starts_with("libc.so")
|
||||
);
|
||||
|
||||
if !is_glibc_runtime {
|
||||
eprintln!(
|
||||
"Warning: Couldn't satisfy dependency {} for {:?}",
|
||||
line,
|
||||
sonames.join(" or "),
|
||||
OsStr::new(&source)
|
||||
);
|
||||
}
|
||||
@@ -314,13 +315,10 @@ fn main() -> eyre::Result<()> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let contents =
|
||||
fs::read(&args[1]).wrap_err_with(|| format!("failed to open file {:?}", &args[1]))?;
|
||||
let input = serde_json::from_slice::<Vec<StoreInput>>(&contents)
|
||||
.wrap_err_with(|| {
|
||||
let text = String::from_utf8_lossy(&contents);
|
||||
format!("failed to parse JSON '{}' in {:?}",
|
||||
text,
|
||||
&args[1])
|
||||
})?;
|
||||
let input = serde_json::from_slice::<Vec<StoreInput>>(&contents).wrap_err_with(|| {
|
||||
let text = String::from_utf8_lossy(&contents);
|
||||
format!("failed to parse JSON '{}' in {:?}", text, &args[1])
|
||||
})?;
|
||||
let output = &args[2];
|
||||
let out_path = Path::new(output);
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
bluez,
|
||||
tcl,
|
||||
acl,
|
||||
polkitSupport ? lib.meta.availableOn stdenv.hostPlatform polkit,
|
||||
polkit,
|
||||
kmod,
|
||||
coreutils,
|
||||
shadow,
|
||||
@@ -42,6 +44,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
tcl # For TCL bindings
|
||||
]
|
||||
++ lib.optional alsaSupport alsa-lib
|
||||
++ lib.optional polkitSupport polkit
|
||||
++ lib.optional systemdSupport systemdMinimal;
|
||||
|
||||
doInstallCheck = true;
|
||||
@@ -76,6 +79,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
"--with-writable-directory=/run/brltty"
|
||||
"--with-updatable-directory=/var/lib/brltty"
|
||||
"--with-api-socket-path=/var/lib/BrlAPI"
|
||||
(lib.enableFeature polkitSupport "polkit")
|
||||
];
|
||||
installFlags = [
|
||||
"install-systemd"
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "btrfs-progs";
|
||||
version = "7.1";
|
||||
version = "7.3";
|
||||
|
||||
src = fetchurl {
|
||||
url = "mirror://kernel/linux/kernel/people/kdave/btrfs-progs/btrfs-progs-v${finalAttrs.version}.tar.xz";
|
||||
hash = "sha256-0fVcwpcTmMkULqp50gPmPVhqO0uGf5VmZKHWgyLNTjQ=";
|
||||
hash = "sha256-03tjQD0l/gJXBMdY5qPaCXmuUMMMXwKqeORBDns0bjo=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "libslirp";
|
||||
version = "4.9.5";
|
||||
version = "4.9.6";
|
||||
|
||||
src = fetchFromGitLab {
|
||||
domain = "gitlab.freedesktop.org";
|
||||
owner = "slirp";
|
||||
repo = "libslirp";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-iWu3/Klsm8e9MH147BJhn9Vqyneq5ROqPvll1cnieL0=";
|
||||
hash = "sha256-nfR5eQrcpbkmn2tx/8l649NVPOsIFSFSBkW/vw0meCM=";
|
||||
};
|
||||
|
||||
__structuredAttrs = true;
|
||||
|
||||
@@ -108,6 +108,8 @@ chroot_add_resolv_conf "$mountPoint" || echo "$0: failed to set up resolv.conf"
|
||||
chroot "$mountPoint" "$system/sw/bin/systemd-tmpfiles" --create --remove -E 2> /dev/null || true
|
||||
)
|
||||
|
||||
unset TMPDIR
|
||||
# HOSTNAME is unset to not accidentally build a nixos configuration named like the ISO.
|
||||
# NIX_REMOTE is unset to not use the ISOs nix daemon by accident.
|
||||
unset HOSTNAME NIX_REMOTE TMPDIR
|
||||
|
||||
exec chroot "$mountPoint" "${command[@]}"
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
lib,
|
||||
bash,
|
||||
installShellFiles,
|
||||
runCommand,
|
||||
buildPackages,
|
||||
nixosTests,
|
||||
}:
|
||||
|
||||
stdenvNoCC.mkDerivation {
|
||||
stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
pname = "nixos-firewall-tool";
|
||||
version = lib.trivial.release;
|
||||
src = builtins.filterSource (name: _: !(lib.hasSuffix ".nix" name)) ./.;
|
||||
@@ -14,7 +16,6 @@ stdenvNoCC.mkDerivation {
|
||||
strictDeps = true;
|
||||
buildInputs = [ bash ];
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
nativeCheckInputs = [ buildPackages.shellcheck-minimal ];
|
||||
|
||||
postPatch = ''
|
||||
patchShebangs --host nixos-firewall-tool
|
||||
@@ -26,10 +27,13 @@ stdenvNoCC.mkDerivation {
|
||||
installShellCompletion nixos-firewall-tool.{bash,fish}
|
||||
'';
|
||||
|
||||
doCheck = buildPackages.shellcheck-minimal.compiler.bootstrapAvailable;
|
||||
checkPhase = ''
|
||||
shellcheck nixos-firewall-tool
|
||||
'';
|
||||
passthru.tests = {
|
||||
shellcheck = runCommand "nixos-firewall-tool-shellcheck" { } ''
|
||||
${lib.getExe buildPackages.shellcheck-minimal} ${finalAttrs.src}/nixos-firewall-tool
|
||||
touch $out
|
||||
'';
|
||||
inherit (nixosTests) firewall firewall-nftables;
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Tool to temporarily manipulate the NixOS firewall";
|
||||
@@ -42,4 +46,4 @@ stdenvNoCC.mkDerivation {
|
||||
platforms = lib.platforms.linux;
|
||||
mainProgram = "nixos-firewall-tool";
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "ty";
|
||||
version = "0.0.95";
|
||||
version = "0.0.96";
|
||||
__structuredAttrs = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
@@ -26,7 +26,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
repo = "ty";
|
||||
tag = finalAttrs.version;
|
||||
fetchSubmodules = true;
|
||||
hash = "sha256-e0Yg6e50I4w9NDEaTUdoWFFvvJyDvL4eulcImy/HRRM=";
|
||||
hash = "sha256-kjgmJZt8NH6fMppdAymBFWMWLKpTO3Pz1V6tzpndrhM=";
|
||||
};
|
||||
|
||||
# For Darwin platforms, remove the integration test for file notifications,
|
||||
@@ -40,7 +40,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
|
||||
cargoBuildFlags = [ "--package=ty" ];
|
||||
|
||||
cargoHash = "sha256-FaK/P6nBJqfSkUqv8qmmIeAPQh5ksIS/lpIBVkrGALg=";
|
||||
cargoHash = "sha256-gl1X1CJto66I+dRyoqhebRdtTvh9C7o0LMwb+7T/+04=";
|
||||
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
buildInputs = [ rust-jemalloc-sys ];
|
||||
|
||||
@@ -193,6 +193,7 @@ lib.makeOverridable (
|
||||
inherit pname version src;
|
||||
|
||||
__structuredAttrs = true;
|
||||
strictDeps = true;
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
|
||||
@@ -244,6 +244,7 @@ lib.makeOverridable (
|
||||
enableParallelBuilding = true;
|
||||
|
||||
__structuredAttrs = true;
|
||||
strictDeps = true;
|
||||
|
||||
passthru = rec {
|
||||
module = import ../../../../nixos/modules/system/boot/kernel_config.nix;
|
||||
|
||||
Reference in New Issue
Block a user