Merge staging-next into staging
This commit is contained in:
@@ -10,6 +10,17 @@ $ ./result/bin/nixos-test-driver
|
||||
>>>
|
||||
```
|
||||
|
||||
::: {.note}
|
||||
Tests using `systemd-nspawn` container machines require root privileges to run interactively,
|
||||
since the driver calls `systemd-nspawn` directly to start the containers:
|
||||
|
||||
```
|
||||
$ sudo ./result/bin/nixos-test-driver
|
||||
[...]
|
||||
>>>
|
||||
```
|
||||
:::
|
||||
|
||||
::: {.note}
|
||||
By executing the test driver in this way,
|
||||
the VMs executed may gain network & Internet access via their backdoor control interface,
|
||||
@@ -30,7 +41,7 @@ back into the test driver command line upon its completion. This allows
|
||||
you to inspect the state of the VMs after the test (e.g. to debug the
|
||||
test script).
|
||||
|
||||
## Shell access in interactive mode {#sec-nixos-test-shell-access}
|
||||
## Shell access to VMs in interactive mode {#sec-nixos-test-shell-access}
|
||||
|
||||
The function `<yourmachine>.shell_interact()` grants access to a shell running
|
||||
inside a virtual machine. To use it, replace `<yourmachine>` with the name of a
|
||||
@@ -63,7 +74,7 @@ using:
|
||||
Once the connection is established, you can enter commands in the socat terminal
|
||||
where socat is running.
|
||||
|
||||
## SSH Access for test machines {#sec-nixos-test-ssh-access}
|
||||
## SSH Access for test VMs {#sec-nixos-test-ssh-access}
|
||||
|
||||
An SSH-based backdoor to log into machines can be enabled with
|
||||
|
||||
@@ -149,10 +160,10 @@ must be configured to allow these connections.
|
||||
## Reuse VM state {#sec-nixos-test-reuse-vm-state}
|
||||
|
||||
You can re-use the VM states coming from a previous run by setting the
|
||||
`--keep-vm-state` flag.
|
||||
`--keep-machine-state` flag.
|
||||
|
||||
```ShellSession
|
||||
$ ./result/bin/nixos-test-driver --keep-vm-state
|
||||
$ ./result/bin/nixos-test-driver --keep-machine-state
|
||||
```
|
||||
|
||||
The machine state is stored in the `$TMPDIR/vm-state-machinename`
|
||||
|
||||
@@ -21,10 +21,44 @@ $ nix-store --read-log result
|
||||
|
||||
## System Requirements {#sec-running-nixos-tests-requirements}
|
||||
|
||||
NixOS tests require virtualization support.
|
||||
NixOS tests using QEMU virtual machine [`nodes`](#test-opt-nodes) require virtualization support.
|
||||
This means that the machine must have `kvm` in its [system features](https://nixos.org/manual/nix/stable/command-ref/conf-file.html?highlight=system-features#conf-system-features) list, or `apple-virt` in case of macOS.
|
||||
These features are autodetected locally, but `apple-virt` is only autodetected since Nix 2.19.0.
|
||||
|
||||
Features of **remote builders** must additionally be configured manually on the client, e.g. on NixOS with [`nix.buildMachines.*.supportedFeatures`](https://search.nixos.org/options?show=nix.buildMachines.*.supportedFeatures&sort=alpha_asc&query=nix.buildMachines) or through general [Nix configuration](https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds).
|
||||
|
||||
If you run the tests on a **macOS** machine, you also need a "remote" builder for Linux; possibly a VM. [nix-darwin](https://daiderd.com/nix-darwin/) users may enable [`nix.linux-builder.enable`](https://daiderd.com/nix-darwin/manual/index.html#opt-nix.linux-builder.enable) to launch such a VM.
|
||||
|
||||
NixOS tests using `systemd-nspawn` [`containers`](#test-opt-containers) require the Nix daemon to be
|
||||
configured with the following settings:
|
||||
|
||||
```nix
|
||||
{
|
||||
nix.settings = {
|
||||
auto-allocate-uids = true;
|
||||
extra-system-features = [ "uid-range" ];
|
||||
experimental-features = [
|
||||
"auto-allocate-uids"
|
||||
"cgroups"
|
||||
];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
See the documentation of the settings
|
||||
[`auto-allocate-uids`](https://nix.dev/manual/nix/stable/command-ref/conf-file#conf-auto-allocate-uids),
|
||||
[`uid-range`](https://nix.dev/manual/nix/stable/command-ref/conf-file.html?highlight=uid-range#conf-system-features), and
|
||||
[`cgroups`](https://nix.dev/manual/nix/stable/development/experimental-features#xp-feature-cgroups)
|
||||
for more information.
|
||||
|
||||
If the test uses both `systemd-nspawn` [`containers`](#test-opt-containers) and QEMU virtual machine [`nodes`](#test-opt-nodes)
|
||||
and requires them share a common VLAN,
|
||||
`/dev/net` must be present in the sandbox.
|
||||
This allows them to be bridged over a TAP interface.
|
||||
To make this path available, set the following option:
|
||||
|
||||
```nix
|
||||
{
|
||||
nix.settings.sandbox-paths = [ "/dev/net" ];
|
||||
}
|
||||
```
|
||||
|
||||
@@ -4,15 +4,14 @@ A NixOS test is a module that has the following structure:
|
||||
|
||||
```nix
|
||||
{
|
||||
|
||||
# One or more machines:
|
||||
# QEMU virtual machines:
|
||||
nodes = {
|
||||
machine =
|
||||
vm1 =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
# ...
|
||||
};
|
||||
machine2 =
|
||||
vm2 =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
# ...
|
||||
@@ -20,6 +19,20 @@ A NixOS test is a module that has the following structure:
|
||||
# …
|
||||
};
|
||||
|
||||
# systemd-nspawn containers:
|
||||
containers = {
|
||||
container1 =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
# ...
|
||||
};
|
||||
container2 =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
# ...
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
Python code…
|
||||
'';
|
||||
@@ -27,12 +40,13 @@ A NixOS test is a module that has the following structure:
|
||||
```
|
||||
|
||||
We refer to the whole test above as a test module, whereas the values
|
||||
in [`nodes.<name>`](#test-opt-nodes) are NixOS modules themselves.
|
||||
in [`nodes.<name>`](#test-opt-nodes) and [`containers.<name>`](#test-opt-containers)
|
||||
are NixOS modules themselves.
|
||||
|
||||
The option [`testScript`](#test-opt-testScript) is a piece of Python code that executes the
|
||||
test (described below). During the test, it will start one or more
|
||||
virtual machines, the configuration of which is described by
|
||||
the option [`nodes`](#test-opt-nodes).
|
||||
test (described [below](#ssec-test-script)). During the test, it will start one or more
|
||||
virtual machines and/or `systemd-nspawn` containers, the configuration of which is described by
|
||||
the options [`nodes`](#test-opt-nodes) and [`containers`](#test-opt-containers), respectively.
|
||||
|
||||
An example of a single-node test is
|
||||
[`login.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix).
|
||||
@@ -42,6 +56,12 @@ when switching between consoles, and so on. An interesting multi-node test is
|
||||
[`nfs/simple.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix).
|
||||
It uses two client nodes to test correct locking across server crashes.
|
||||
|
||||
A test can contain both virtual machines and containers.
|
||||
If configured to share a common VLAN,
|
||||
they can reach each other over the network.
|
||||
See [](https://github.com/applicative-systems/nixpkgs/blob/78100f9077ab50604ab9c9514e442bbc7ac7ca5b/nixos/tests/nixos-test-driver/containers.nix)
|
||||
for an example of this and [](#sec-running-nixos-tests-requirements) for the system requirements for this scenario.
|
||||
|
||||
## Calling a test {#sec-calling-nixos-tests}
|
||||
|
||||
Tests are invoked differently depending on whether the test is part of NixOS or lives in a different project.
|
||||
@@ -90,13 +110,54 @@ pkgs.testers.runNixOSTest {
|
||||
|
||||
`runNixOSTest` returns a derivation that runs the test.
|
||||
|
||||
## Configuring the nodes {#sec-nixos-test-nodes}
|
||||
## Test machines {#ssec-nixos-test-machines}
|
||||
|
||||
There are a few special NixOS options for test VMs:
|
||||
A NixOS test usually consists of one or more test machines. Each machine is either a
|
||||
QEMU virtual machine or a `systemd-nspawn` container.
|
||||
|
||||
`virtualisation.memorySize`
|
||||
QEMU virtual machines are defined in the
|
||||
[`nodes`](#test-opt-nodes) attribute set, whereas `systemd-nspawn` containers are defined in the
|
||||
[`containers`](#test-opt-containers) attribute set.
|
||||
|
||||
: The memory of the VM in MiB (1024×1024 bytes).
|
||||
To set NixOS options for all machines in the test, use the attribute
|
||||
[`defaults`](#test-opt-defaults). These options are applied to both virtual machines
|
||||
and containers. You can set separate defaults for virtual machines and containers
|
||||
using the attributes [`nodeDefaults`](#test-opt-nodeDefaults) and
|
||||
[`containerDefaults`](#test-opt-containerDefaults), respectively.
|
||||
|
||||
### Virtual machines vs. containers {#sec-nixos-test-vms-vs-containers}
|
||||
|
||||
QEMU virtual machines and `systemd-nspawn` containers offer different
|
||||
trade-offs which make them suitable for different use cases.
|
||||
|
||||
Some advantages of containers over virtual machines are:
|
||||
|
||||
- Containers share the kernel of the host system; they are
|
||||
significantly faster to start up than virtual machines.
|
||||
- Containers are more lightweight in terms of resource usage, which
|
||||
allows running more of them in parallel on a single host.
|
||||
- Containers can easily be run in virtualised environments, e.g., CI systems.
|
||||
- Containers allow direct bind-mounting of host device nodes, which enables
|
||||
testing of GPU code (CUDA), for example.
|
||||
|
||||
Some advantages of virtual machines over containers are:
|
||||
|
||||
- Virtual machines run a separate kernel, which allows testing kernel features
|
||||
(kernel modules, etc.).
|
||||
- Virtual machines support testing graphical applications on X11.
|
||||
- Virtual machines allow testing NixOS modules that use systemd's namespacing options (such as `ProtectSystem=` or `MountAPIVFS=`).
|
||||
- Virtual machines allow testing [`spcialisation`](options.html#opt-specialisation).
|
||||
(Switching to a specialisation requires the creation of SUID/SGID wrappers, which is disallowed in `systemd-nspawn` within the Nix sandbox.)
|
||||
- Virtual machines allow the execution of `setuid` binaries.
|
||||
|
||||
Refer to the sections on [QEMU virtual machines](#ssec-nixos-test-qemu-vms)
|
||||
and [systemd-nspawn containers](#ssec-nixos-test-nspawn-containers) below
|
||||
for more details on configuring each type of machine.
|
||||
|
||||
### Configuring test machines {#sec-nixos-test-machines-config}
|
||||
|
||||
The following special NixOS option can be used to configure
|
||||
machines in a NixOS test, whether they are virtual machines or containers:
|
||||
|
||||
`virtualisation.vlans`
|
||||
|
||||
@@ -104,6 +165,35 @@ There are a few special NixOS options for test VMs:
|
||||
[`nat.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nat.nix)
|
||||
for an example.
|
||||
|
||||
#### Configuring `systemd-nspawn` containers {#ssec-nixos-test-nspawn-containers}
|
||||
|
||||
Some options are specific to `systemd-nspawn` containers:
|
||||
|
||||
`virtualisation.systemd-nspawn.options`
|
||||
|
||||
: A list of additional command-line options to pass to
|
||||
`systemd-nspawn` when starting the container. For example, to
|
||||
bind mount a directory from the host into the container, you could
|
||||
use: `[ "--bind=/host/dir:/container/dir" ]`.
|
||||
|
||||
For more options, see the module
|
||||
[`nspawn-container`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/nspawn-container/default.nix).
|
||||
|
||||
Note that the paths used in `--bind` or `--bind-ro` options have to be accessible from within the Nix sandbox.
|
||||
Use the Nix option
|
||||
[`sandbox-paths`](https://nix.dev/manual/nix/stable/command-ref/conf-file#conf-sandbox-paths)
|
||||
and/or the module [`programs.nix-required-mounts`](#opt-programs.nix-required-mounts.enable) on the host
|
||||
to add additional paths to the sandbox.
|
||||
|
||||
#### Configuring QEMU virtual machines {#ssec-nixos-test-qemu-vms}
|
||||
|
||||
Some options are specific to QEMU virtual machines:
|
||||
|
||||
`virtualisation.memorySize`
|
||||
|
||||
: The memory of the VM in MiB (1024×1024 bytes).
|
||||
|
||||
|
||||
`virtualisation.writableStore`
|
||||
|
||||
: By default, the Nix store in the VM is not writable. If you enable
|
||||
@@ -114,13 +204,15 @@ There are a few special NixOS options for test VMs:
|
||||
For more options, see the module
|
||||
[`qemu-vm.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix).
|
||||
|
||||
## Writing the test script {#ssec-test-script}
|
||||
|
||||
The test script is a sequence of Python statements that perform various
|
||||
actions, such as starting VMs, executing commands in the VMs, and so on.
|
||||
Each virtual machine is represented as an object stored in the variable
|
||||
`name` if this is also the identifier of the machine in the declarative
|
||||
config. If you specified a node `nodes.machine`, the following example starts the
|
||||
machine, waits until it has finished booting, then executes a command
|
||||
and checks that the output is more-or-less correct:
|
||||
actions, such as starting machines, executing commands in them, and so on. For
|
||||
example, if you specified a virtual machine in `nodes.machine`, there will be
|
||||
a Python variable `machine` available in the test script that represents that
|
||||
virtual machine. The following example would start the machine, wait until it
|
||||
has finished booting, and then execute a command and check that the output is
|
||||
more-or-less correct:
|
||||
|
||||
```py
|
||||
machine.start()
|
||||
@@ -139,17 +231,20 @@ start_all()
|
||||
|
||||
Under the variable `t`, all assertions from [`unittest.TestCase`](https://docs.python.org/3/library/unittest.html) are available.
|
||||
|
||||
If the hostname of a node contains characters that can't be used in a
|
||||
If the hostname of a machine contains characters that can't be used in a
|
||||
Python variable name, those characters will be replaced with
|
||||
underscores in the variable name, so `nodes.machine-a` will be exposed
|
||||
to Python as `machine_a`.
|
||||
|
||||
## Machine objects {#ssec-machine-objects}
|
||||
### Methods available on machine objects {#ssec-machine-objects}
|
||||
|
||||
The following methods are available on machine objects:
|
||||
The following methods are available on machine objects (like `machine` in
|
||||
the examples above):
|
||||
|
||||
@PYTHON_MACHINE_METHODS@
|
||||
|
||||
### Testing user units {#ssec-testing-user-units}
|
||||
|
||||
To test user units declared by `systemd.user.services` the optional
|
||||
`user` argument can be used:
|
||||
|
||||
@@ -162,6 +257,84 @@ machine.wait_for_unit("xautolock.service", "x-session-user")
|
||||
This applies to `systemctl`, `get_unit_info`, `wait_for_unit`,
|
||||
`start_job` and `stop_job`.
|
||||
|
||||
### Failing tests early {#ssec-failing-tests-early}
|
||||
|
||||
To fail tests early when certain invariants are no longer met (instead of waiting for the build to time out), the decorator `polling_condition` is provided. For example, if we are testing a program `foo` that should not quit after being started, we might write the following:
|
||||
|
||||
```py
|
||||
@polling_condition
|
||||
def foo_running():
|
||||
machine.succeed("pgrep -x foo")
|
||||
|
||||
|
||||
machine.succeed("foo --start")
|
||||
machine.wait_until_succeeds("pgrep -x foo")
|
||||
|
||||
with foo_running:
|
||||
... # Put `foo` through its paces
|
||||
```
|
||||
|
||||
`polling_condition` takes the following (optional) arguments:
|
||||
|
||||
`seconds_interval`
|
||||
|
||||
: specifies how often the condition should be polled:
|
||||
|
||||
```py
|
||||
@polling_condition(seconds_interval=10)
|
||||
def foo_running():
|
||||
machine.succeed("pgrep -x foo")
|
||||
```
|
||||
|
||||
`description`
|
||||
|
||||
: is used in the log when the condition is checked. If this is not provided, the description is pulled from the docstring of the function. These two are therefore equivalent:
|
||||
|
||||
```py
|
||||
@polling_condition
|
||||
def foo_running():
|
||||
"check that foo is running"
|
||||
machine.succeed("pgrep -x foo")
|
||||
```
|
||||
|
||||
```py
|
||||
@polling_condition(description="check that foo is running")
|
||||
def foo_running():
|
||||
machine.succeed("pgrep -x foo")
|
||||
```
|
||||
|
||||
### Adding Python packages to the test script {#ssec-python-packages-in-test-script}
|
||||
|
||||
When additional Python libraries are required in the test script, they can be
|
||||
added using the parameter `extraPythonPackages`. For example, you could add
|
||||
`numpy` like this:
|
||||
|
||||
```nix
|
||||
{
|
||||
extraPythonPackages = p: [ p.numpy ];
|
||||
|
||||
nodes = { };
|
||||
|
||||
# Type checking on extra packages doesn't work yet
|
||||
skipTypeCheck = true;
|
||||
|
||||
testScript = ''
|
||||
import numpy as np
|
||||
assert str(np.zeros(4)) == "[0. 0. 0. 0.]"
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
In that case, `numpy` is chosen from the generic `python3Packages`.
|
||||
|
||||
### Linting and type checking test scripts {#ssec-test-script-checks}
|
||||
|
||||
Test scripts are automatically linted with
|
||||
[Pyflakes](https://pypi.org/project/pyflakes/) and type-checked with
|
||||
[Mypy](https://mypy.readthedocs.io/en/stable/).
|
||||
If there are any linting or type checking errors, the test will
|
||||
fail to evaluate.
|
||||
|
||||
For faster dev cycles it's also possible to disable the code-linters
|
||||
(this shouldn't be committed though):
|
||||
|
||||
@@ -209,76 +382,6 @@ way:
|
||||
}
|
||||
```
|
||||
|
||||
## Failing tests early {#ssec-failing-tests-early}
|
||||
|
||||
To fail tests early when certain invariants are no longer met (instead of waiting for the build to time out), the decorator `polling_condition` is provided. For example, if we are testing a program `foo` that should not quit after being started, we might write the following:
|
||||
|
||||
```py
|
||||
@polling_condition
|
||||
def foo_running():
|
||||
machine.succeed("pgrep -x foo")
|
||||
|
||||
|
||||
machine.succeed("foo --start")
|
||||
machine.wait_until_succeeds("pgrep -x foo")
|
||||
|
||||
with foo_running:
|
||||
... # Put `foo` through its paces
|
||||
```
|
||||
|
||||
`polling_condition` takes the following (optional) arguments:
|
||||
|
||||
`seconds_interval`
|
||||
|
||||
: specifies how often the condition should be polled:
|
||||
|
||||
```py
|
||||
@polling_condition(seconds_interval=10)
|
||||
def foo_running():
|
||||
machine.succeed("pgrep -x foo")
|
||||
```
|
||||
|
||||
`description`
|
||||
|
||||
: is used in the log when the condition is checked. If this is not provided, the description is pulled from the docstring of the function. These two are therefore equivalent:
|
||||
|
||||
```py
|
||||
@polling_condition
|
||||
def foo_running():
|
||||
"check that foo is running"
|
||||
machine.succeed("pgrep -x foo")
|
||||
```
|
||||
|
||||
```py
|
||||
@polling_condition(description="check that foo is running")
|
||||
def foo_running():
|
||||
machine.succeed("pgrep -x foo")
|
||||
```
|
||||
|
||||
## Adding Python packages to the test script {#ssec-python-packages-in-test-script}
|
||||
|
||||
When additional Python libraries are required in the test script, they can be
|
||||
added using the parameter `extraPythonPackages`. For example, you could add
|
||||
`numpy` like this:
|
||||
|
||||
```nix
|
||||
{
|
||||
extraPythonPackages = p: [ p.numpy ];
|
||||
|
||||
nodes = { };
|
||||
|
||||
# Type checking on extra packages doesn't work yet
|
||||
skipTypeCheck = true;
|
||||
|
||||
testScript = ''
|
||||
import numpy as np
|
||||
assert str(np.zeros(4)) == "[0. 0. 0. 0.]"
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
In that case, `numpy` is chosen from the generic `python3Packages`.
|
||||
|
||||
## Overriding a test {#sec-override-nixos-test}
|
||||
|
||||
The NixOS test framework returns tests with multiple overriding methods.
|
||||
@@ -297,7 +400,7 @@ The NixOS test framework returns tests with multiple overriding methods.
|
||||
: Evaluates the test with additional NixOS modules and/or arguments.
|
||||
|
||||
`module`
|
||||
: A NixOS module to add to all the nodes in the test. Sets test option [`extraBaseModules`](#test-opt-extraBaseModules).
|
||||
: A NixOS module to add to all the machines in the test. Sets test option [`extraBaseModules`](#test-opt-extraBaseModules).
|
||||
|
||||
`specialArgs`
|
||||
: An attribute set of arguments to pass to all NixOS modules. These override the existing arguments, as well as any `_module.args.<name>` that the modules may define. Sets test option [`node.specialArgs`](#test-opt-node.specialArgs).
|
||||
@@ -345,7 +448,52 @@ list-id: test-options-list
|
||||
source: @NIXOS_TEST_OPTIONS_JSON@
|
||||
```
|
||||
|
||||
## Accessing VMs in the sandbox with SSH {#sec-test-sandbox-breakpoint}
|
||||
## Debugging test machines {#sec-test-sandbox-breakpoint}
|
||||
|
||||
You can set the [`enableDebugHook`](#test-opt-enableDebugHook) option to pause
|
||||
a test on the first failure and have it print instructions on how to enter the
|
||||
sandbox shell of the test. Suppose you have the following test module:
|
||||
|
||||
```nix
|
||||
{
|
||||
name = "foo";
|
||||
|
||||
nodes.machine = { };
|
||||
|
||||
enableDebugHook = true;
|
||||
sshBackdoor.enable = true;
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("false") # this will fail
|
||||
'';
|
||||
}
|
||||
```
|
||||
|
||||
The test will fail with an output like this:
|
||||
|
||||
```
|
||||
vm-test-run-foo> !!! Breakpoint reached, run 'sudo /nix/store/eeeee-attach/bin/attach <PID>'
|
||||
```
|
||||
|
||||
You can then enter the sandbox shell:
|
||||
|
||||
```
|
||||
$ sudo /nix/store/eeeee-attach/bin/attach <PID>
|
||||
bash#
|
||||
```
|
||||
|
||||
There, you can attach to a [`pdb`](https://docs.python.org/3/library/pdb.html) session
|
||||
to step through the Python test script:
|
||||
|
||||
```
|
||||
bash# telnet 127.0.0.1 4444
|
||||
pdb$
|
||||
```
|
||||
|
||||
Note that it is also possible to set breakpoints in the test script using `debug.breakpoint()`.
|
||||
|
||||
### SSH access to test VMs {#sec-test-vm-ssh-access}
|
||||
|
||||
::: {.note}
|
||||
For debugging with SSH access into the machines, it's recommended to try using
|
||||
@@ -356,24 +504,15 @@ This feature is mostly intended to debug flaky test failures that aren't
|
||||
reproducible elsewhere.
|
||||
:::
|
||||
|
||||
As explained in [](#sec-nixos-test-ssh-access), it's possible to configure an
|
||||
SSH backdoor based on AF_VSOCK. This can be used to SSH into a VM of a running
|
||||
build in a sandbox.
|
||||
|
||||
This can be done when something in the test fails, e.g.
|
||||
If you set the [`sshBackdoor.enable`](#test-opt-sshBackdoor.enable) option,
|
||||
QEMU virtual machines will open an SSH backdoor based on AF_VSOCK
|
||||
(see [](#sec-nixos-test-ssh-access)).
|
||||
Once you are in the sandbox shell, you can access the VMs (for example, `machine`)
|
||||
with SSH over vsock:
|
||||
|
||||
```nix
|
||||
{
|
||||
nodes.machine = { };
|
||||
|
||||
sshBackdoor.enable = true;
|
||||
enableDebugHook = true;
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("false") # this will fail
|
||||
'';
|
||||
}
|
||||
```
|
||||
bash# ssh -F ./ssh_config vsock/3
|
||||
```
|
||||
|
||||
For the AF_VSOCK feature to work, `/dev/vhost-vsock` is needed in the sandbox
|
||||
@@ -383,24 +522,24 @@ which can be done with e.g.
|
||||
nix-build -A nixosTests.foo --option sandbox-paths /dev/vhost-vsock
|
||||
```
|
||||
|
||||
This will halt the test execution on a test-failure and print instructions
|
||||
on how to enter the sandbox shell of the VM test. Inside, one can log into
|
||||
e.g. `machine` with
|
||||
|
||||
```
|
||||
ssh -F ./ssh_config vsock/3
|
||||
```
|
||||
|
||||
As described in [](#sec-nixos-test-ssh-access), the numbers for vsock start at
|
||||
`3` instead of `1`. So the first VM in the network (sorted alphabetically) can
|
||||
be accessed with `vsock/3`.
|
||||
|
||||
Alternatively, it's possible to explicitly set a breakpoint with
|
||||
`debug.breakpoint()`. This also has the benefit, that one can step through
|
||||
`testScript` with `pdb` like this:
|
||||
### SSH access to test containers {#sec-test-container-ssh-access}
|
||||
|
||||
If you set the [`sshBackdoor.enable`](#test-opt-sshBackdoor.enable) option,
|
||||
each `systemd-nspawn` container will open an SSH backdoor.
|
||||
Once the container starts,
|
||||
it will print instructions on how to log into the container via SSH.
|
||||
If the test fails,
|
||||
attach to the sandbox as described above,
|
||||
and then use the provided SSH command to log into the container.
|
||||
For example:
|
||||
|
||||
```
|
||||
$ sudo /nix/store/eeeee-attach <id>
|
||||
bash# telnet 127.0.0.1 4444
|
||||
pdb$ …
|
||||
$ sudo /nix/store/eeeee-attach <PID>
|
||||
bash# ssh -o User=root -o ProxyCommand="socat - UNIX-CLIENT:/run/systemd/nspawn/unix-export/machine/ssh" bash
|
||||
[root@machine:~]# hostname
|
||||
machine
|
||||
```
|
||||
|
||||
@@ -88,6 +88,9 @@
|
||||
"module-virtualisation-xen-introduction": [
|
||||
"index.html#module-virtualisation-xen-introduction"
|
||||
],
|
||||
"sec-nixos-test-vms-vs-containers": [
|
||||
"index.html#sec-nixos-test-vms-vs-containers"
|
||||
],
|
||||
"sec-override-nixos-test": [
|
||||
"index.html#sec-override-nixos-test"
|
||||
],
|
||||
@@ -100,6 +103,51 @@
|
||||
"sec-wireless-imperative": [
|
||||
"index.html#sec-wireless-imperative"
|
||||
],
|
||||
"sec-test-container-ssh-access": [
|
||||
"index.html#sec-test-container-ssh-access"
|
||||
],
|
||||
"sec-test-vm-ssh-access": [
|
||||
"index.html#sec-test-vm-ssh-access"
|
||||
],
|
||||
"ssec-all-machine-objects": [
|
||||
"index.html#ssec-all-machine-objects"
|
||||
],
|
||||
"ssec-nixos-test-machines": [
|
||||
"index.html#ssec-nixos-test-machines"
|
||||
],
|
||||
"ssec-nixos-test-nspawn-containers": [
|
||||
"index.html#ssec-nixos-test-nspawn-containers"
|
||||
],
|
||||
"ssec-nixos-test-qemu-vms": [
|
||||
"index.html#ssec-nixos-test-qemu-vms"
|
||||
],
|
||||
"ssec-nspawn-machine-objects": [
|
||||
"index.html#ssec-nspawn-machine-objects"
|
||||
],
|
||||
"ssec-qemu-machine-objects": [
|
||||
"index.html#ssec-qemu-machine-objects"
|
||||
],
|
||||
"ssec-test-script": [
|
||||
"index.html#ssec-test-script"
|
||||
],
|
||||
"ssec-test-script-checks": [
|
||||
"index.html#ssec-test-script-checks"
|
||||
],
|
||||
"ssec-testing-user-units": [
|
||||
"index.html#ssec-testing-user-units"
|
||||
],
|
||||
"test-opt-containerDefaults": [
|
||||
"index.html#test-opt-containerDefaults"
|
||||
],
|
||||
"test-opt-containers": [
|
||||
"index.html#test-opt-containers"
|
||||
],
|
||||
"test-opt-extraBaseModules": [
|
||||
"index.html#test-opt-extraBaseModules"
|
||||
],
|
||||
"test-opt-nodeDefaults": [
|
||||
"index.html#test-opt-nodeDefaults"
|
||||
],
|
||||
"test-opt-rawTestDerivationArg": [
|
||||
"index.html#test-opt-rawTestDerivationArg"
|
||||
],
|
||||
@@ -2037,7 +2085,8 @@
|
||||
"sec-call-nixos-test-outside-nixos": [
|
||||
"index.html#sec-call-nixos-test-outside-nixos"
|
||||
],
|
||||
"sec-nixos-test-nodes": [
|
||||
"sec-nixos-test-machines-config": [
|
||||
"index.html#sec-nixos-test-machines-config",
|
||||
"index.html#sec-nixos-test-nodes"
|
||||
],
|
||||
"ssec-machine-objects": [
|
||||
@@ -2070,8 +2119,8 @@
|
||||
"test-opt-enableOCR": [
|
||||
"index.html#test-opt-enableOCR"
|
||||
],
|
||||
"test-opt-extraBaseModules": [
|
||||
"index.html#test-opt-extraBaseModules"
|
||||
"test-opt-extraBaseNodeModules": [
|
||||
"index.html#test-opt-extraBaseNodeModules"
|
||||
],
|
||||
"test-opt-extraDriverArgs": [
|
||||
"index.html#test-opt-extraDriverArgs"
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
designed to run on affordable, low-power devices. Available as [services.meshtasticd]
|
||||
(#opt-services.meshtasticd.enable).
|
||||
|
||||
- [Goupile](https://goupile.org/en), an open-source design tool for secure forms including Clinical Report Forms (eCRF). Available as [services.goupile](#opt-services.goupile.enable).
|
||||
|
||||
- [knot-resolver](https://www.knot-resolver.cz/) in version 6. Available as `services.knot-resolver`. A module for knot-resolver 5 was already available as `services.kresd`.
|
||||
|
||||
- [ImmichFrame](https://immichframe.dev/), display your photos from Immich as a digital photo frame. Available as `services.immichframe`.
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
qemu_test,
|
||||
setuptools,
|
||||
socat,
|
||||
systemd,
|
||||
tesseract4,
|
||||
util-linux,
|
||||
vde2,
|
||||
|
||||
enableNspawn ? false,
|
||||
enableOCR ? false,
|
||||
extraPythonPackages ? (_: [ ]),
|
||||
}:
|
||||
@@ -51,8 +54,12 @@ buildPythonApplication {
|
||||
netpbm
|
||||
qemu_pkg
|
||||
socat
|
||||
util-linux
|
||||
vde2
|
||||
]
|
||||
++ lib.optionals enableNspawn [
|
||||
systemd
|
||||
]
|
||||
++ lib.optionals enableOCR [
|
||||
imagemagick_light
|
||||
tesseract4
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ast
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
"""
|
||||
This program takes all the Machine class methods and prints its methods in
|
||||
@@ -41,6 +42,38 @@ some_function(param1, param2)
|
||||
|
||||
"""
|
||||
|
||||
def function_docstrings(functions: List[ast.FunctionDef]) -> str | None:
|
||||
"""Extracts docstrings from a list of function definitions."""
|
||||
documented_functions = [f for f in functions if ast.get_docstring(f) is not None]
|
||||
|
||||
if not documented_functions:
|
||||
return None
|
||||
|
||||
docstrings = []
|
||||
for function in documented_functions:
|
||||
docstr = ast.get_docstring(function)
|
||||
assert docstr is not None
|
||||
|
||||
args = ", ".join(a.arg for a in function.args.args[1:])
|
||||
args = f"({args})"
|
||||
|
||||
docstr = "\n".join(f" {line}" for line in docstr.strip().splitlines())
|
||||
|
||||
docstrings.append(f"{function.name}{args}\n\n:{docstr[1:]}\n")
|
||||
return "\n".join(docstrings)
|
||||
|
||||
def machine_methods(class_name: str, class_definitions: List[ast.ClassDef]) -> List[ast.FunctionDef]:
|
||||
"""Given a class name and a list of class definitions, returns the list of function definitions
|
||||
for the class matching the given name.
|
||||
"""
|
||||
machine_class = next(filter(lambda x: x.name == class_name, class_definitions))
|
||||
assert machine_class is not None
|
||||
|
||||
function_definitions = [
|
||||
node for node in machine_class.body if isinstance(node, ast.FunctionDef)
|
||||
]
|
||||
function_definitions.sort(key=lambda x: x.name)
|
||||
return function_definitions
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) != 2:
|
||||
@@ -49,26 +82,27 @@ def main() -> None:
|
||||
|
||||
module = ast.parse(Path(sys.argv[1]).read_text())
|
||||
|
||||
class_definitions = (node for node in module.body if isinstance(node, ast.ClassDef))
|
||||
class_definitions = [node for node in module.body if isinstance(node, ast.ClassDef)]
|
||||
|
||||
machine_class = next(filter(lambda x: x.name == "Machine", class_definitions))
|
||||
assert machine_class is not None
|
||||
base_machine_methods = machine_methods("BaseMachine", class_definitions)
|
||||
base_method_names = {method.name for method in base_machine_methods}
|
||||
|
||||
function_definitions = [
|
||||
node for node in machine_class.body if isinstance(node, ast.FunctionDef)
|
||||
qemu_machine_methods = [
|
||||
method for method in machine_methods("QemuMachine", class_definitions)
|
||||
if method.name not in base_method_names
|
||||
]
|
||||
function_definitions.sort(key=lambda x: x.name)
|
||||
|
||||
for function in function_definitions:
|
||||
docstr = ast.get_docstring(function)
|
||||
if docstr is not None:
|
||||
args = ", ".join(a.arg for a in function.args.args[1:])
|
||||
args = f"({args})"
|
||||
|
||||
docstr = "\n".join(f" {line}" for line in docstr.strip().splitlines())
|
||||
|
||||
print(f"{function.name}{args}\n\n:{docstr[1:]}\n")
|
||||
nspawn_machine_methods = [
|
||||
method for method in machine_methods("NspawnMachine", class_definitions)
|
||||
if method.name not in base_method_names
|
||||
]
|
||||
|
||||
print("#### Generic machine objects {#ssec-all-machine-objects} \n")
|
||||
print(function_docstrings(base_machine_methods))
|
||||
print("#### QEMU VM objects {#ssec-qemu-machine-objects}\n")
|
||||
print(function_docstrings(qemu_machine_methods) or "No methods specific to QEMU virtual machines.")
|
||||
print("#### `systemd-nspawn` container objects {#ssec-nspawn-machine-objects}\n")
|
||||
print(function_docstrings(nspawn_machine_methods) or "No methods specific to `systemd-nspawn` containers.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
import ptpython.ipython
|
||||
@@ -16,7 +18,7 @@ from test_driver.logger import (
|
||||
|
||||
|
||||
class EnvDefault(argparse.Action):
|
||||
"""An argpars Action that takes values from the specified
|
||||
"""An argparse Action that takes values from the specified
|
||||
environment variable as the flags default value.
|
||||
"""
|
||||
|
||||
@@ -55,9 +57,15 @@ def writeable_dir(arg: str) -> Path:
|
||||
def main() -> None:
|
||||
arg_parser = argparse.ArgumentParser(prog="nixos-test-driver")
|
||||
arg_parser.add_argument(
|
||||
"-K",
|
||||
"--keep-vm-state",
|
||||
help="re-use a VM state coming from a previous run",
|
||||
help=argparse.SUPPRESS,
|
||||
dest="keep_machine_state",
|
||||
action="store_true",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"-K",
|
||||
"--keep-machine-state",
|
||||
help="re-use a machine state coming from a previous run",
|
||||
action="store_true",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
@@ -71,13 +79,37 @@ def main() -> None:
|
||||
help="Enable interactive debugging breakpoints for sandboxed runs",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"--start-scripts",
|
||||
metavar="START-SCRIPT",
|
||||
"--vm-names",
|
||||
metavar="VM-NAME",
|
||||
action=EnvDefault,
|
||||
envvar="startScripts",
|
||||
envvar="vmNames",
|
||||
nargs="*",
|
||||
help="names of participating virtual machines",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"--vm-start-scripts",
|
||||
metavar="VM-START-SCRIPT",
|
||||
action=EnvDefault,
|
||||
envvar="vmStartScripts",
|
||||
nargs="*",
|
||||
help="start scripts for participating virtual machines",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"--container-names",
|
||||
metavar="CONTAINER-NAME",
|
||||
action=EnvDefault,
|
||||
envvar="containerNames",
|
||||
nargs="*",
|
||||
help="names of participating containers",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"--container-start-scripts",
|
||||
metavar="CONTAINER-START-SCRIPT",
|
||||
action=EnvDefault,
|
||||
envvar="containerStartScripts",
|
||||
nargs="*",
|
||||
help="start scripts for participating containers",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"--vlans",
|
||||
metavar="VLAN",
|
||||
@@ -97,8 +129,8 @@ def main() -> None:
|
||||
arg_parser.add_argument(
|
||||
"-o",
|
||||
"--output_directory",
|
||||
help="""The path to the directory where outputs copied from the VM will be placed.
|
||||
By e.g. Machine.copy_from_vm or Machine.screenshot""",
|
||||
help="""The path to the directory where outputs copied from the machine will be placed.
|
||||
By e.g. NspawnMachine.copy_from_machine or QemuMachine.screenshot""",
|
||||
default=Path.cwd(),
|
||||
type=writeable_dir,
|
||||
)
|
||||
@@ -122,6 +154,12 @@ def main() -> None:
|
||||
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
if "--keep-vm-state" in sys.argv:
|
||||
warnings.warn(
|
||||
"The flag '--keep-vm-state' is deprecated. Use '--keep-machine-state' instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
output_directory = args.output_directory.resolve()
|
||||
logger = CompositeLogger([TerminalLogger()])
|
||||
|
||||
@@ -131,21 +169,33 @@ def main() -> None:
|
||||
if args.junit_xml:
|
||||
logger.add_logger(JunitXMLLogger(output_directory / args.junit_xml))
|
||||
|
||||
if not args.keep_vm_state:
|
||||
logger.info("Machine state will be reset. To keep it, pass --keep-vm-state")
|
||||
if not args.keep_machine_state:
|
||||
logger.info(
|
||||
"Machine state will be reset. To keep it, pass --keep-machine-state"
|
||||
)
|
||||
|
||||
debugger: DebugAbstract = DebugNop()
|
||||
if args.debug_hook_attach is not None:
|
||||
debugger = Debug(logger, args.debug_hook_attach)
|
||||
|
||||
assert len(args.vm_names) == len(args.vm_start_scripts), (
|
||||
f"the number of vm names and vm start scripts must be the same: {args.vm_names} vs. {args.vm_start_scripts}"
|
||||
)
|
||||
assert len(args.container_names) == len(args.container_start_scripts), (
|
||||
f"the number of container names and container start scripts must be the same: {args.container_names} vs. {args.container_start_scripts}"
|
||||
)
|
||||
|
||||
with Driver(
|
||||
args.start_scripts,
|
||||
args.vlans,
|
||||
args.testscript.read_text(),
|
||||
output_directory,
|
||||
logger,
|
||||
args.keep_vm_state,
|
||||
args.global_timeout,
|
||||
vm_names=args.vm_names,
|
||||
vm_start_scripts=args.vm_start_scripts,
|
||||
container_names=args.container_names,
|
||||
container_start_scripts=args.container_start_scripts,
|
||||
vlans=args.vlans,
|
||||
tests=args.testscript.read_text(),
|
||||
out_dir=output_directory,
|
||||
logger=logger,
|
||||
keep_machine_state=args.keep_machine_state,
|
||||
global_timeout=args.global_timeout,
|
||||
debug=debugger,
|
||||
) as driver:
|
||||
if offset := args.dump_vsocks:
|
||||
@@ -170,7 +220,16 @@ def generate_driver_symbols() -> None:
|
||||
in user's test scripts. That list is then used by pyflakes to lint those
|
||||
scripts.
|
||||
"""
|
||||
d = Driver([], [], "", Path(), CompositeLogger([]))
|
||||
d = Driver(
|
||||
vm_names=[],
|
||||
vm_start_scripts=[],
|
||||
container_names=[],
|
||||
container_start_scripts=[],
|
||||
vlans=[],
|
||||
tests="",
|
||||
out_dir=Path(),
|
||||
logger=CompositeLogger([]),
|
||||
)
|
||||
test_symbols = d.test_symbols()
|
||||
with open("driver-symbols", "w") as fp:
|
||||
fp.write(",".join(test_symbols.keys()))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
@@ -16,7 +17,12 @@ from colorama import Style
|
||||
from test_driver.debug import DebugAbstract, DebugNop
|
||||
from test_driver.errors import MachineError, RequestedAssertionFailed
|
||||
from test_driver.logger import AbstractLogger
|
||||
from test_driver.machine import Machine, NixStartScript, retry
|
||||
from test_driver.machine import (
|
||||
BaseMachine,
|
||||
NspawnMachine,
|
||||
QemuMachine,
|
||||
retry,
|
||||
)
|
||||
from test_driver.polling_condition import PollingCondition
|
||||
from test_driver.vlan import VLan
|
||||
|
||||
@@ -63,7 +69,8 @@ class Driver:
|
||||
|
||||
tests: str
|
||||
vlans: list[VLan]
|
||||
machines: list[Machine]
|
||||
machines_qemu: list[QemuMachine]
|
||||
machines_nspawn: list[NspawnMachine]
|
||||
polling_conditions: list[PollingCondition]
|
||||
global_timeout: int
|
||||
race_timer: threading.Timer
|
||||
@@ -72,12 +79,15 @@ class Driver:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
start_scripts: list[str],
|
||||
vm_names: list[str],
|
||||
vm_start_scripts: list[str],
|
||||
container_names: list[str],
|
||||
container_start_scripts: list[str],
|
||||
vlans: list[int],
|
||||
tests: str,
|
||||
out_dir: Path,
|
||||
logger: AbstractLogger,
|
||||
keep_vm_state: bool = False,
|
||||
keep_machine_state: bool = False,
|
||||
global_timeout: int = 24 * 60 * 60 * 7,
|
||||
debug: DebugAbstract = DebugNop(),
|
||||
):
|
||||
@@ -94,25 +104,95 @@ class Driver:
|
||||
vlans = list(set(vlans))
|
||||
self.vlans = [VLan(nr, tmp_dir, self.logger) for nr in vlans]
|
||||
|
||||
def cmd(scripts: list[str]) -> Iterator[NixStartScript]:
|
||||
for s in scripts:
|
||||
yield NixStartScript(s)
|
||||
|
||||
self.polling_conditions = []
|
||||
|
||||
self.machines = [
|
||||
Machine(
|
||||
start_command=cmd,
|
||||
keep_vm_state=keep_vm_state,
|
||||
name=cmd.machine_name,
|
||||
self.machines_qemu = [
|
||||
QemuMachine(
|
||||
name=name,
|
||||
start_command=vm_start_script,
|
||||
keep_machine_state=keep_machine_state,
|
||||
tmp_dir=tmp_dir,
|
||||
callbacks=[self.check_polling_conditions],
|
||||
out_dir=self.out_dir,
|
||||
logger=self.logger,
|
||||
)
|
||||
for cmd in cmd(start_scripts)
|
||||
for name, vm_start_script in zip(vm_names, vm_start_scripts)
|
||||
]
|
||||
|
||||
if len(container_start_scripts) > 0:
|
||||
self._init_nspawn_environment()
|
||||
|
||||
self.machines_nspawn = [
|
||||
NspawnMachine(
|
||||
name=name,
|
||||
start_command=container_start_script,
|
||||
tmp_dir=tmp_dir,
|
||||
logger=self.logger,
|
||||
keep_machine_state=keep_machine_state,
|
||||
callbacks=[self.check_polling_conditions],
|
||||
out_dir=self.out_dir,
|
||||
)
|
||||
for name, container_start_script in zip(
|
||||
container_names,
|
||||
container_start_scripts,
|
||||
)
|
||||
]
|
||||
|
||||
def _init_nspawn_environment(self) -> None:
|
||||
assert os.geteuid() == 0, (
|
||||
f"systemd-nspawn requires root to work. You are {os.geteuid()}"
|
||||
)
|
||||
|
||||
# set up prerequisites for systemd-nspawn containers.
|
||||
# these are not guaranteed to be set up in the Nix sandbox.
|
||||
# if running interactively as root, these will already be set up.
|
||||
|
||||
# check if /run is writable by root
|
||||
if not os.access("/run", os.W_OK):
|
||||
Path("/run").mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(["mount", "-t", "tmpfs", "none", "/run"], check=True)
|
||||
Path("/run/netns").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# check if /var/run is a symlink to /run
|
||||
if not (os.path.exists("/var/run") and os.path.samefile("/var/run", "/run")):
|
||||
Path("/var").mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(["ln", "-s", "/run", "/var/run"], check=True)
|
||||
|
||||
# check if /sys/fs/cgroup is mounted as cgroup2
|
||||
with open("/proc/mounts", encoding="utf-8") as mounts:
|
||||
for line in mounts:
|
||||
parts = line.split()
|
||||
if len(parts) >= 3 and parts[1] == "/sys/fs/cgroup":
|
||||
if parts[2] == "cgroup2":
|
||||
break
|
||||
else:
|
||||
Path("/sys/fs/cgroup").mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(
|
||||
["mount", "-t", "cgroup2", "none", "/sys/fs/cgroup"], check=True
|
||||
)
|
||||
|
||||
# systemd-nspawn requires that /etc/os-release exists
|
||||
# It supports SYSTEMD_NSPAWN_CHECK_OS_RELEASE=0, but that
|
||||
# would try to "fix" it by bind mounting, which is worse.
|
||||
if not os.path.isfile("/etc/os-release"):
|
||||
subprocess.run(["touch", "/etc/os-release"], check=True)
|
||||
|
||||
# ensure /etc/machine-id exists and is non-empty
|
||||
if (
|
||||
not os.path.isfile("/etc/machine-id")
|
||||
or os.path.getsize("/etc/machine-id") == 0
|
||||
):
|
||||
subprocess.run(
|
||||
["systemd-machine-id-setup"], check=True
|
||||
) # set up /etc/machine-id
|
||||
|
||||
@property
|
||||
def machines(self) -> list[QemuMachine | NspawnMachine]:
|
||||
machines = self.machines_qemu + self.machines_nspawn
|
||||
# Sort the machines by name for consistency with `nodesAndContainers` in <nixos/lib/testing/network.nix>.
|
||||
machines.sort(key=lambda machine: machine.name)
|
||||
return machines
|
||||
|
||||
def __enter__(self) -> "Driver":
|
||||
return self
|
||||
|
||||
@@ -148,7 +228,8 @@ class Driver:
|
||||
general_symbols = dict(
|
||||
start_all=self.start_all,
|
||||
test_script=self.test_script,
|
||||
machines=self.machines,
|
||||
machines_qemu=self.machines_qemu,
|
||||
machines_nspawn=self.machines_nspawn,
|
||||
vlans=self.vlans,
|
||||
driver=self,
|
||||
log=self.logger,
|
||||
@@ -161,7 +242,7 @@ class Driver:
|
||||
serial_stdout_off=self.serial_stdout_off,
|
||||
serial_stdout_on=self.serial_stdout_on,
|
||||
polling_condition=self.polling_condition,
|
||||
Machine=Machine, # for typing
|
||||
BaseMachine=BaseMachine, # for typing
|
||||
t=AssertionTester(),
|
||||
debug=self.debug,
|
||||
)
|
||||
@@ -186,14 +267,14 @@ class Driver:
|
||||
def dump_machine_ssh(self, offset: int) -> None:
|
||||
print("SSH backdoor enabled, the machines can be accessed like this:")
|
||||
print(
|
||||
f"{Style.BRIGHT}Note:{Style.RESET_ALL} this requires {Style.BRIGHT}systemd-ssh-proxy(1){Style.RESET_ALL} to be enabled (default on NixOS 25.05 and newer)."
|
||||
f"{Style.BRIGHT}Note:{Style.RESET_ALL} vsocks require {Style.BRIGHT}systemd-ssh-proxy(1){Style.RESET_ALL} to be enabled (default on NixOS 25.05 and newer)."
|
||||
)
|
||||
names = [machine.name for machine in self.machines]
|
||||
longest_name = len(max(names, key=len))
|
||||
for num, name in enumerate(names, start=offset + 1):
|
||||
longest_name = len(max((machine.name for machine in self.machines), key=len))
|
||||
for index, machine in enumerate(self.machines, start=offset + 1):
|
||||
name = machine.name
|
||||
spaces = " " * (longest_name - len(name) + 2)
|
||||
print(
|
||||
f" {name}:{spaces}{Style.BRIGHT}ssh -o User=root vsock/{num}{Style.RESET_ALL}"
|
||||
f" {name}:{spaces}{Style.BRIGHT}{machine.ssh_backdoor_command(index)}{Style.RESET_ALL}"
|
||||
)
|
||||
|
||||
def test_script(self) -> None:
|
||||
@@ -252,8 +333,16 @@ class Driver:
|
||||
def start_all(self) -> None:
|
||||
"""Start all machines"""
|
||||
with self.logger.nested("start all VMs"):
|
||||
threads = []
|
||||
for machine in self.machines:
|
||||
machine.start()
|
||||
# Create a thread for each machine's start method
|
||||
t = threading.Thread(target=machine.start, name=f"start-{machine.name}")
|
||||
threads.append(t)
|
||||
t.start()
|
||||
|
||||
# Wait for all startup threads to complete before proceeding
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
def join_all(self) -> None:
|
||||
"""Wait for all machines to shut down"""
|
||||
@@ -279,19 +368,19 @@ class Driver:
|
||||
start_command: str,
|
||||
*,
|
||||
name: str | None = None,
|
||||
keep_vm_state: bool = False,
|
||||
) -> Machine:
|
||||
keep_machine_state: bool = False,
|
||||
) -> BaseMachine:
|
||||
"""
|
||||
Create a `QemuMachine`. This currently only supports qemu "nodes", not containers.
|
||||
"""
|
||||
tmp_dir = get_tmp_dir()
|
||||
|
||||
cmd = NixStartScript(start_command)
|
||||
name = name or cmd.machine_name
|
||||
|
||||
return Machine(
|
||||
return QemuMachine(
|
||||
tmp_dir=tmp_dir,
|
||||
out_dir=self.out_dir,
|
||||
start_command=cmd,
|
||||
start_command=start_command,
|
||||
name=name,
|
||||
keep_vm_state=keep_vm_state,
|
||||
keep_machine_state=keep_machine_state,
|
||||
logger=self.logger,
|
||||
)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,8 @@ class VLan:
|
||||
pid: int
|
||||
fd: io.TextIOBase
|
||||
|
||||
plug_process: subprocess.Popen
|
||||
|
||||
logger: AbstractLogger
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -58,6 +60,7 @@ class VLan:
|
||||
def __init__(self, nr: int, tmp_dir: Path, logger: AbstractLogger):
|
||||
self.nr = nr
|
||||
self.socket_dir = tmp_dir / f"vde{self.nr}.ctl"
|
||||
self.tap_name = f"vde-tap{self.nr}"
|
||||
self.logger = logger
|
||||
|
||||
# TODO: don't side-effect environment here
|
||||
@@ -114,6 +117,13 @@ class VLan:
|
||||
if "1000 Success" in line:
|
||||
break
|
||||
|
||||
# This is needed to allow systemd-nspawn containers to communicate
|
||||
# with VMs connected to the VLAN.
|
||||
self.logger.info(f"creating tap interface {self.tap_name}")
|
||||
self.plug_process = subprocess.Popen(
|
||||
["vde_plug2tap", "-s", self.socket_dir, self.tap_name],
|
||||
)
|
||||
|
||||
assert (self.socket_dir / "ctl").exists(), "cannot start vde_switch"
|
||||
|
||||
self.logger.info(f"running vlan (pid {self.pid}; ctl {self.socket_dir})")
|
||||
@@ -122,4 +132,7 @@ class VLan:
|
||||
self.logger.info(f"kill vlan (pid {self.pid})")
|
||||
assert self.process.stdin is not None
|
||||
self.process.stdin.close()
|
||||
if self.plug_process:
|
||||
self.plug_process.terminate()
|
||||
self.plug_process.wait()
|
||||
self.process.terminate()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from test_driver.debug import DebugAbstract
|
||||
from test_driver.driver import Driver
|
||||
from test_driver.vlan import VLan
|
||||
from test_driver.machine import Machine
|
||||
from test_driver.machine import BaseMachine, NspawnMachine, QemuMachine
|
||||
from test_driver.logger import AbstractLogger
|
||||
from typing import Callable, Iterator, ContextManager, Optional, List, Dict, Any, Union
|
||||
from typing_extensions import Protocol
|
||||
@@ -34,8 +34,9 @@ class CreateMachineProtocol(Protocol):
|
||||
start_command: str | dict,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
keep_vm_state: bool = False,
|
||||
) -> Machine:
|
||||
keep_machine_state: bool = False,
|
||||
**kwargs: Any, # to allow usage of deprecated keep_vm_state
|
||||
) -> BaseMachine:
|
||||
raise Exception("This is just type information for the Nix test driver")
|
||||
|
||||
|
||||
@@ -43,7 +44,7 @@ start_all: Callable[[], None]
|
||||
subtest: Callable[[str], ContextManager[None]]
|
||||
retry: RetryProtocol
|
||||
test_script: Callable[[], None]
|
||||
machines: List[Machine]
|
||||
machines: List[BaseMachine]
|
||||
vlans: List[VLan]
|
||||
driver: Driver
|
||||
log: AbstractLogger
|
||||
|
||||
@@ -56,6 +56,7 @@ pkgs.lib.throwIf (args ? specialArgs)
|
||||
{
|
||||
machine ? null,
|
||||
nodes ? { },
|
||||
containers ? { },
|
||||
testScript,
|
||||
enableOCR ? false,
|
||||
globalTimeout ? (60 * 60),
|
||||
|
||||
@@ -14,18 +14,17 @@ let
|
||||
qemu_pkg = config.qemu.package;
|
||||
imagemagick_light = hostPkgs.imagemagick_light.override { inherit (hostPkgs) libtiff; };
|
||||
tesseract4 = hostPkgs.tesseract4.override { enableLanguages = [ "eng" ]; };
|
||||
|
||||
enableNspawn = config.containers != { };
|
||||
# We want `pkgs.systemd`, *not* `python3Packages.system`.
|
||||
systemd = hostPkgs.systemd;
|
||||
};
|
||||
|
||||
vlans = map (
|
||||
m: (m.virtualisation.vlans ++ (lib.mapAttrsToList (_: v: v.vlan) m.virtualisation.interfaces))
|
||||
) (lib.attrValues config.nodes);
|
||||
) ((lib.attrValues config.nodes) ++ (lib.attrValues config.containers));
|
||||
vms = map (m: m.system.build.vm) (lib.attrValues config.nodes);
|
||||
|
||||
nodeHostNames =
|
||||
let
|
||||
nodesList = map (c: c.system.name) (lib.attrValues config.nodes);
|
||||
in
|
||||
nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine";
|
||||
containers = map (m: m.system.build.nspawn) (lib.attrValues config.containers);
|
||||
|
||||
pythonizeName =
|
||||
name:
|
||||
@@ -38,8 +37,22 @@ let
|
||||
|
||||
uniqueVlans = lib.unique (builtins.concatLists vlans);
|
||||
vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
|
||||
pythonizedNames = map pythonizeName nodeHostNames;
|
||||
machineNames = map (name: "${name}: Machine;") pythonizedNames;
|
||||
|
||||
vmMachineNames = map (c: c.system.name) (lib.attrValues config.nodes);
|
||||
containerMachineNames = map (c: c.system.name) (lib.attrValues config.containers);
|
||||
|
||||
theOnlyMachine =
|
||||
let
|
||||
exactlyOneMachine = lib.length (lib.attrValues config.nodes) == 1;
|
||||
allMachineNames = map (c: c.system.name) (lib.attrValues config.allMachines);
|
||||
in
|
||||
lib.optional (exactlyOneMachine && !lib.elem "machine" allMachineNames) "machine";
|
||||
|
||||
pythonizedVmNames = map pythonizeName (vmMachineNames ++ theOnlyMachine);
|
||||
vmMachineTypeHints = map (name: "${name}: QemuMachine;") pythonizedVmNames;
|
||||
|
||||
pythonizedContainerNames = map pythonizeName containerMachineNames;
|
||||
containerMachineTypeHints = map (name: "${name}: NspawnMachine;") pythonizedContainerNames;
|
||||
|
||||
withChecks = lib.warnIf config.skipLint "Linting is disabled";
|
||||
|
||||
@@ -62,12 +75,16 @@ let
|
||||
''
|
||||
mkdir -p $out/bin
|
||||
|
||||
vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
|
||||
vmNames=(${lib.escapeShellArgs vmMachineNames})
|
||||
vmStartScripts=(${lib.escapeShellArgs (map lib.getExe vms)})
|
||||
containerNames=(${lib.escapeShellArgs containerMachineNames})
|
||||
containerStartScripts=(${lib.escapeShellArgs (map lib.getExe containers)})
|
||||
|
||||
${lib.optionalString (!config.skipTypeCheck) ''
|
||||
# prepend type hints so the test script can be type checked with mypy
|
||||
cat "${../test-script-prepend.py}" >> testScriptWithTypes
|
||||
echo "${toString machineNames}" >> testScriptWithTypes
|
||||
echo "${toString vmMachineTypeHints}" >> testScriptWithTypes
|
||||
echo "${toString containerMachineTypeHints}" >> testScriptWithTypes
|
||||
echo "${toString vlanNames}" >> testScriptWithTypes
|
||||
echo -n "$testScript" >> testScriptWithTypes
|
||||
|
||||
@@ -90,7 +107,9 @@ let
|
||||
echo "See https://nixos.org/manual/nixos/stable/#test-opt-skipLint"
|
||||
|
||||
PYFLAKES_BUILTINS="$(
|
||||
echo -n ${lib.escapeShellArg (lib.concatStringsSep "," pythonizedNames)},
|
||||
echo -n ${
|
||||
lib.escapeShellArg (lib.concatStringsSep "," (pythonizedVmNames ++ pythonizedContainerNames))
|
||||
},
|
||||
cat ${lib.escapeShellArg "driver-symbols"}
|
||||
)" ${hostPkgs.python3Packages.pyflakes}/bin/pyflakes $out/test-script
|
||||
''}
|
||||
@@ -98,7 +117,10 @@ let
|
||||
# set defaults through environment
|
||||
# see: ./test-driver/test-driver.py argparse implementation
|
||||
wrapProgram $out/bin/nixos-test-driver \
|
||||
--set startScripts "''${vmStartScripts[*]}" \
|
||||
--set vmStartScripts "''${vmStartScripts[*]}" \
|
||||
--set vmNames "''${vmNames[*]}" \
|
||||
--set containerStartScripts "''${containerStartScripts[*]}" \
|
||||
--set containerNames "''${containerNames[*]}" \
|
||||
--set testScript "$out/test-script" \
|
||||
--set globalTimeout "${toString config.globalTimeout}" \
|
||||
--set vlans '${toString vlans}' \
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
{ lib, nodes, ... }:
|
||||
testModuleArgs@{
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
attrNames
|
||||
concatMap
|
||||
concatMapAttrsStringSep
|
||||
concatMapStrings
|
||||
flip
|
||||
forEach
|
||||
head
|
||||
listToAttrs
|
||||
@@ -20,22 +22,15 @@ let
|
||||
zipLists
|
||||
;
|
||||
|
||||
nodeNumbers = listToAttrs (zipListsWith nameValuePair (attrNames nodes) (range 1 254));
|
||||
nodeNumbers = listToAttrs (
|
||||
zipListsWith nameValuePair (attrNames testModuleArgs.config.allMachines) (range 1 254)
|
||||
);
|
||||
|
||||
networkModule =
|
||||
{
|
||||
config,
|
||||
nodes,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{ config, ... }:
|
||||
let
|
||||
qemu-common = import ../qemu-common.nix { inherit (pkgs) lib stdenv; };
|
||||
|
||||
interfaces = lib.attrValues config.virtualisation.allInterfaces;
|
||||
|
||||
interfacesNumbered = zipLists interfaces (range 1 255);
|
||||
|
||||
# Automatically assign IP addresses to requested interfaces.
|
||||
assignIPs = lib.filter (i: i.assignIP) interfaces;
|
||||
ipInterfaces = forEach assignIPs (
|
||||
@@ -56,17 +51,6 @@ let
|
||||
}
|
||||
);
|
||||
|
||||
qemuOptions = lib.flatten (
|
||||
forEach interfacesNumbered (
|
||||
{ fst, snd }: qemu-common.qemuNICFlags snd fst.vlan config.virtualisation.test.nodeNumber
|
||||
)
|
||||
);
|
||||
udevRules = forEach interfaces (
|
||||
interface:
|
||||
# MAC Addresses for QEMU network devices are lowercase, and udev string comparison is case-sensitive.
|
||||
''SUBSYSTEM=="net",ACTION=="add",ATTR{address}=="${toLower (qemu-common.qemuNicMac interface.vlan config.virtualisation.test.nodeNumber)}",NAME="${interface.name}"''
|
||||
);
|
||||
|
||||
networkConfig = {
|
||||
networking.hostName = mkDefault config.virtualisation.test.nodeName;
|
||||
|
||||
@@ -80,33 +64,51 @@ let
|
||||
optionalString (ipInterfaces != [ ])
|
||||
(head (head ipInterfaces).value.ipv6.addresses).address;
|
||||
|
||||
# Put the IP addresses of all VMs in this machine's
|
||||
# /etc/hosts file. If a machine has multiple
|
||||
# interfaces, use the IP address corresponding to
|
||||
# the first interface (i.e. the first network in its
|
||||
# virtualisation.vlans option).
|
||||
networking.extraHosts = flip concatMapStrings (attrNames nodes) (
|
||||
m':
|
||||
# Generate /etc/hosts including every remote's primary IP addresses
|
||||
# (whichever VLAN they may belong to) as well as all IP addresses from
|
||||
# VLANs that both the local machine and the remote machine share.
|
||||
networking.extraHosts =
|
||||
let
|
||||
config = nodes.${m'};
|
||||
hostnames =
|
||||
optionalString (
|
||||
config.networking.domain != null
|
||||
) "${config.networking.hostName}.${config.networking.domain} "
|
||||
+ "${config.networking.hostName}\n";
|
||||
localVlans = config.virtualisation.vlans;
|
||||
in
|
||||
optionalString (
|
||||
config.networking.primaryIPAddress != ""
|
||||
) "${config.networking.primaryIPAddress} ${hostnames}"
|
||||
+ optionalString (
|
||||
config.networking.primaryIPv6Address != ""
|
||||
) "${config.networking.primaryIPv6Address} ${hostnames}"
|
||||
);
|
||||
concatMapAttrsStringSep "" (
|
||||
mName: remoteConfig:
|
||||
let
|
||||
remoteInterfaces = remoteConfig.networking.interfaces;
|
||||
sharedIps = lib.flatten (
|
||||
lib.mapAttrsToList (
|
||||
ifaceName: ifaceCfg:
|
||||
let
|
||||
remoteIfaceMeta = remoteConfig.virtualisation.allInterfaces."${ifaceName}" or { };
|
||||
vlanId = remoteIfaceMeta.vlan or null;
|
||||
in
|
||||
if vlanId != null && builtins.elem vlanId localVlans then
|
||||
builtins.map (addr: addr.address) ifaceCfg.ipv4.addresses
|
||||
++ builtins.map (addr: addr.address) ifaceCfg.ipv6.addresses
|
||||
else
|
||||
[ ]
|
||||
) remoteInterfaces
|
||||
);
|
||||
|
||||
virtualisation.qemu.options = qemuOptions;
|
||||
boot.initrd.services.udev.rules = concatMapStrings (x: x + "\n") udevRules;
|
||||
# We also want to test router protocols that enable connections
|
||||
# between nodes even if they don't share a VLAN, so we include
|
||||
# the primary IPs of all machines in the hosts file.
|
||||
primaryIPs = [
|
||||
remoteConfig.networking.primaryIPAddress
|
||||
remoteConfig.networking.primaryIPv6Address
|
||||
];
|
||||
|
||||
allReachableIps = lib.lists.uniqueStrings (sharedIps ++ primaryIPs);
|
||||
|
||||
hostnames =
|
||||
optionalString (
|
||||
remoteConfig.networking.domain != null
|
||||
) "${remoteConfig.networking.hostName}.${remoteConfig.networking.domain} "
|
||||
+ "${remoteConfig.networking.hostName}\n";
|
||||
in
|
||||
builtins.concatStringsSep "" (map (ip: "${ip} ${hostnames}") allReachableIps)
|
||||
) testModuleArgs.config.allMachines;
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
key = "network-interfaces";
|
||||
@@ -117,6 +119,31 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
qemuNetworkModule =
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
qemu-common = import ../qemu-common.nix { inherit (pkgs) lib stdenv; };
|
||||
|
||||
interfaces = lib.attrValues config.virtualisation.allInterfaces;
|
||||
|
||||
interfacesNumbered = zipLists interfaces (range 1 255);
|
||||
|
||||
qemuOptions = lib.flatten (
|
||||
forEach interfacesNumbered (
|
||||
{ fst, snd }: qemu-common.qemuNICFlags snd fst.vlan config.virtualisation.test.nodeNumber
|
||||
)
|
||||
);
|
||||
udevRules = map (
|
||||
interface:
|
||||
# MAC Addresses for QEMU network devices are lowercase, and udev string comparison is case-sensitive.
|
||||
''SUBSYSTEM=="net",ACTION=="add",ATTR{address}=="${toLower (qemu-common.qemuNicMac interface.vlan config.virtualisation.test.nodeNumber)}",NAME="${interface.name}"''
|
||||
) interfaces;
|
||||
in
|
||||
{
|
||||
virtualisation.qemu.options = qemuOptions;
|
||||
boot.initrd.services.udev.rules = concatMapStrings (x: x + "\n") udevRules;
|
||||
};
|
||||
|
||||
nodeNumberModule = (
|
||||
regular@{ config, name, ... }:
|
||||
{
|
||||
@@ -127,7 +154,7 @@ let
|
||||
# We need to force this in specialisations, otherwise it'd be
|
||||
# readOnly = true;
|
||||
description = ''
|
||||
The `name` in `nodes.<name>`; stable across `specialisations`.
|
||||
The `name` in `nodes.<name>` and `containers.<name>`; stable across `specialisations`.
|
||||
'';
|
||||
};
|
||||
virtualisation.test.nodeNumber = mkOption {
|
||||
@@ -136,7 +163,7 @@ let
|
||||
readOnly = true;
|
||||
default = nodeNumbers.${config.virtualisation.test.nodeName};
|
||||
description = ''
|
||||
A unique number assigned for each node in `nodes`.
|
||||
A unique number assigned for each machine in `nodes` and `containers`.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -172,5 +199,10 @@ in
|
||||
nodeNumberModule
|
||||
];
|
||||
};
|
||||
extraBaseNodeModules = {
|
||||
imports = [
|
||||
qemuNetworkModule
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ let
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
../../modules/virtualisation/qemu-vm.nix
|
||||
../../modules/testing/test-instrumentation.nix # !!! should only get added for automated test runs
|
||||
{
|
||||
key = "no-manual";
|
||||
@@ -32,7 +31,9 @@ in
|
||||
# This is mostly a Hydra optimization, so we don't rebuild all the tests every time switch-to-configuration-ng changes.
|
||||
key = "no-switch-to-configuration";
|
||||
system.switch.enable = mkDefault (
|
||||
config.isSpecialisation || config.specialisation != { } || config.virtualisation.installBootLoader
|
||||
config.isSpecialisation
|
||||
|| config.specialisation != { }
|
||||
|| (!config.boot.isContainer && config.virtualisation.installBootLoader)
|
||||
);
|
||||
}
|
||||
)
|
||||
|
||||
+157
-40
@@ -2,7 +2,6 @@ testModuleArgs@{
|
||||
config,
|
||||
lib,
|
||||
hostPkgs,
|
||||
nodes,
|
||||
options,
|
||||
...
|
||||
}:
|
||||
@@ -12,12 +11,9 @@ let
|
||||
literalExpression
|
||||
literalMD
|
||||
mapAttrs
|
||||
mkDefault
|
||||
mkIf
|
||||
mkMerge
|
||||
mkOption
|
||||
mkForce
|
||||
optional
|
||||
optionalAttrs
|
||||
types
|
||||
;
|
||||
@@ -49,15 +45,11 @@ let
|
||||
./nixos-test-base.nix
|
||||
{
|
||||
key = "nodes";
|
||||
_module.args.nodes = config.nodesCompat;
|
||||
_module.args = {
|
||||
inherit (config) containers;
|
||||
nodes = config.nodesCompat;
|
||||
};
|
||||
}
|
||||
(
|
||||
{ config, ... }:
|
||||
{
|
||||
virtualisation.qemu.package = testModuleArgs.config.qemu.package;
|
||||
virtualisation.host.pkgs = hostPkgs;
|
||||
}
|
||||
)
|
||||
(
|
||||
{ options, ... }:
|
||||
{
|
||||
@@ -73,6 +65,62 @@ let
|
||||
testModuleArgs.config.extraBaseModules
|
||||
];
|
||||
};
|
||||
baseQemuOS = baseOS.extendModules {
|
||||
modules = [
|
||||
../../modules/virtualisation/qemu-vm.nix
|
||||
config.nodeDefaults
|
||||
{
|
||||
key = "base-qemu";
|
||||
virtualisation.qemu.package = testModuleArgs.config.qemu.package;
|
||||
virtualisation.host.pkgs = hostPkgs;
|
||||
}
|
||||
testModuleArgs.config.extraBaseNodeModules
|
||||
];
|
||||
};
|
||||
baseNspawnOS = baseOS.extendModules {
|
||||
modules = [
|
||||
../../modules/virtualisation/nspawn-container
|
||||
config.containerDefaults
|
||||
(
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
key = "base-nspawn";
|
||||
|
||||
# PAM requires setuid and doesn't work in the build sandbox.
|
||||
# https://github.com/NixOS/nix/blob/959c244a1265f4048390f3ad21679219d7b27a99/src/libstore/unix/build/linux-derivation-builder.cc#L63
|
||||
services.openssh.settings.UsePAM = false;
|
||||
|
||||
# Networking for tests is statically configured by default.
|
||||
# dhcpcd times out after blocking for a long time, which slows down tests.
|
||||
# See https://github.com/NixOS/nixpkgs/pull/478109#discussion_r2867570799
|
||||
networking.useDHCP = lib.mkDefault false;
|
||||
|
||||
# Disable Info manual directory generation to prevent build failures.
|
||||
#
|
||||
# Context: 'install-info' (from texinfo) is triggered during system-path
|
||||
# generation to index manuals, but it requires 'gzip' in the $PATH to
|
||||
# decompress them.
|
||||
# When 'networking.useDHCP' is set to false, transitive dependencies
|
||||
# (like dhcpcd or other network tools) that normally pull 'gzip' into
|
||||
# the system environment are removed. This leaves 'install-info'
|
||||
# stranded without 'gzip', causing the 'system-path' derivation to fail.
|
||||
# Since nspawn containers are typically minimal, disabling 'info'
|
||||
# is a cleaner fix than explicitly adding 'gzip' to systemPackages.
|
||||
documentation.info.enable = lib.mkDefault false;
|
||||
|
||||
# Gross, insecure hack to make login work. See above.
|
||||
security.pam.services.login = {
|
||||
text = ''
|
||||
auth sufficient ${pkgs.linux-pam}/lib/security/pam_permit.so
|
||||
account sufficient ${pkgs.linux-pam}/lib/security/pam_permit.so
|
||||
password sufficient ${pkgs.linux-pam}/lib/security/pam_permit.so
|
||||
session sufficient ${pkgs.linux-pam}/lib/security/pam_permit.so
|
||||
'';
|
||||
};
|
||||
}
|
||||
)
|
||||
];
|
||||
};
|
||||
|
||||
# TODO (lib): Dedup with run.nix, add to lib/options.nix
|
||||
mkOneUp = opt: f: lib.mkOverride (opt.highestPrio - 1) (f opt.value);
|
||||
@@ -109,25 +157,74 @@ in
|
||||
|
||||
node.type = mkOption {
|
||||
type = types.raw;
|
||||
default = baseOS.type;
|
||||
default = baseQemuOS.type;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
nodes = mkOption {
|
||||
type = types.lazyAttrsOf config.node.type;
|
||||
default = { };
|
||||
visible = "shallow";
|
||||
description = ''
|
||||
An attribute set of NixOS configuration modules.
|
||||
An attribute set of NixOS configuration modules representing QEMU vms that can be started during a test.
|
||||
|
||||
The configurations are augmented by the [`defaults`](#test-opt-defaults) option.
|
||||
|
||||
They are assigned network addresses according to the `nixos/lib/testing/network.nix` module.
|
||||
|
||||
A few special options are available, that aren't in a plain NixOS configuration. See [Configuring the nodes](#sec-nixos-test-nodes)
|
||||
A few special options are available, that aren't in a plain NixOS configuration. See [Configuring virtual machines](#ssec-nixos-test-qemu-vms)
|
||||
'';
|
||||
};
|
||||
|
||||
container.type = mkOption {
|
||||
type = types.raw;
|
||||
default = baseNspawnOS.type;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
containers = mkOption {
|
||||
type = types.lazyAttrsOf config.container.type;
|
||||
default = { };
|
||||
visible = "shallow";
|
||||
description = ''
|
||||
An attribute set of NixOS configuration modules representing systemd-nspawn containers that can be started during a test.
|
||||
|
||||
The configurations are augmented by the [`defaults`](#test-opt-defaults) option.
|
||||
|
||||
They are assigned network addresses according to the `nixos/lib/testing/network.nix` module.
|
||||
|
||||
A few special options are available, that aren't in a plain NixOS configuration. See [Configuring containers](#ssec-nixos-test-nspawn-containers)
|
||||
'';
|
||||
};
|
||||
|
||||
allMachines = mkOption {
|
||||
readOnly = true;
|
||||
internal = true;
|
||||
description = ''
|
||||
Basically a merge of [{option}`nodes`](#test-opt-nodes) and [{option}`containers`](#test-opt-containers).
|
||||
|
||||
This ensures that there are no name collisions between nodes and containers.
|
||||
'';
|
||||
default =
|
||||
let
|
||||
overlappingNames = lib.intersectLists (lib.attrNames config.nodes) (
|
||||
lib.attrNames config.containers
|
||||
);
|
||||
in
|
||||
lib.throwIfNot (overlappingNames == [ ])
|
||||
"The following names are used in both `nodes` and `containers`: ${lib.concatStringsSep ", " overlappingNames}"
|
||||
(config.nodes // config.containers);
|
||||
};
|
||||
|
||||
defaults = mkOption {
|
||||
description = ''
|
||||
NixOS configuration that is applied to all [{option}`nodes`](#test-opt-nodes) and [{option}`containers`](#test-opt-containers).
|
||||
'';
|
||||
type = types.deferredModule;
|
||||
default = { };
|
||||
};
|
||||
|
||||
nodeDefaults = mkOption {
|
||||
description = ''
|
||||
NixOS configuration that is applied to all [{option}`nodes`](#test-opt-nodes).
|
||||
'';
|
||||
@@ -135,7 +232,23 @@ in
|
||||
default = { };
|
||||
};
|
||||
|
||||
containerDefaults = mkOption {
|
||||
description = ''
|
||||
NixOS configuration that is applied to all [{option}`containers`](#test-opt-containers).
|
||||
'';
|
||||
type = types.deferredModule;
|
||||
default = { };
|
||||
};
|
||||
|
||||
extraBaseModules = mkOption {
|
||||
description = ''
|
||||
NixOS configuration that, like [{option}`defaults`](#test-opt-defaults), is applied to all [{option}`nodes`](#test-opt-nodes) and [{option}`containers`](#test-opt-containers) and can not be undone with [`specialisation.<name>.inheritParentConfig`](https://search.nixos.org/options?show=specialisation.%3Cname%3E.inheritParentConfig&from=0&size=50&sort=relevance&type=packages&query=specialisation).
|
||||
'';
|
||||
type = types.deferredModule;
|
||||
default = { };
|
||||
};
|
||||
|
||||
extraBaseNodeModules = mkOption {
|
||||
description = ''
|
||||
NixOS configuration that, like [{option}`defaults`](#test-opt-defaults), is applied to all [{option}`nodes`](#test-opt-nodes) and can not be undone with [`specialisation.<name>.inheritParentConfig`](https://search.nixos.org/options?show=specialisation.%3Cname%3E.inheritParentConfig&from=0&size=50&sort=relevance&type=packages&query=specialisation).
|
||||
'';
|
||||
@@ -145,7 +258,7 @@ in
|
||||
|
||||
node.pkgs = mkOption {
|
||||
description = ''
|
||||
The Nixpkgs to use for the nodes.
|
||||
The Nixpkgs to use for the nodes and containers.
|
||||
|
||||
Setting this will make the `nixpkgs.*` options read-only, to avoid mistakenly testing with a Nixpkgs configuration that diverges from regular use.
|
||||
'';
|
||||
@@ -160,7 +273,7 @@ in
|
||||
description = ''
|
||||
Whether to make the `nixpkgs.*` options read-only. This is only relevant when [`node.pkgs`](#test-opt-node.pkgs) is set.
|
||||
|
||||
Set this to `false` when any of the [`nodes`](#test-opt-nodes) needs to configure any of the `nixpkgs.*` options. This will slow down evaluation of your test a bit.
|
||||
Set this to `false` when any of the [`nodes`](#test-opt-nodes) or [{option}`containers`](#test-opt-containers) need to configure any of the `nixpkgs.*` options. This will slow down evaluation of your test a bit.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = config.node.pkgs != null;
|
||||
@@ -188,6 +301,7 @@ in
|
||||
};
|
||||
|
||||
config = {
|
||||
_module.args.containers = config.containers;
|
||||
_module.args.nodes = config.nodesCompat;
|
||||
nodesCompat = mapAttrs (
|
||||
name: config:
|
||||
@@ -201,6 +315,7 @@ in
|
||||
) config.nodes;
|
||||
|
||||
passthru.nodes = config.nodesCompat;
|
||||
passthru.containers = config.containers;
|
||||
|
||||
extraDriverArgs = mkIf config.sshBackdoor.enable [
|
||||
"--dump-vsocks=${toString config.sshBackdoor.vsockOffset}"
|
||||
@@ -211,33 +326,35 @@ in
|
||||
nixpkgs.pkgs = config.node.pkgs;
|
||||
imports = [ ../../modules/misc/nixpkgs/read-only.nix ];
|
||||
})
|
||||
(mkIf config.sshBackdoor.enable (
|
||||
let
|
||||
inherit (config.sshBackdoor) vsockOffset;
|
||||
in
|
||||
{ config, ... }:
|
||||
{
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PermitRootLogin = "yes";
|
||||
PermitEmptyPasswords = "yes";
|
||||
};
|
||||
(mkIf config.sshBackdoor.enable {
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PermitRootLogin = "yes";
|
||||
PermitEmptyPasswords = "yes";
|
||||
};
|
||||
};
|
||||
|
||||
security.pam.services.sshd = {
|
||||
allowNullPassword = true;
|
||||
};
|
||||
|
||||
virtualisation.qemu.options = [
|
||||
"-device vhost-vsock-pci,guest-cid=${
|
||||
toString (config.virtualisation.test.nodeNumber + vsockOffset)
|
||||
}"
|
||||
];
|
||||
}
|
||||
))
|
||||
security.pam.services.sshd = {
|
||||
allowNullPassword = true;
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
nodeDefaults = mkIf config.sshBackdoor.enable (
|
||||
let
|
||||
inherit (config.sshBackdoor) vsockOffset;
|
||||
in
|
||||
{ config, ... }:
|
||||
{
|
||||
virtualisation.qemu.options = [
|
||||
"-device vhost-vsock-pci,guest-cid=${
|
||||
toString (config.virtualisation.test.nodeNumber + vsockOffset)
|
||||
}"
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
# Docs: nixos/doc/manual/development/writing-nixos-tests.section.md
|
||||
/**
|
||||
See https://nixos.org/manual/nixos/unstable#sec-override-nixos-test
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
config,
|
||||
hostPkgs,
|
||||
lib,
|
||||
containers,
|
||||
options,
|
||||
...
|
||||
}:
|
||||
@@ -96,12 +97,15 @@ in
|
||||
requiredSystemFeatures = [
|
||||
"nixos-test"
|
||||
]
|
||||
# Containers use systemd-nspawn, which requires pid 0 inside of the sandbox.
|
||||
++ lib.optional (builtins.length (lib.attrNames containers) > 0) "uid-range"
|
||||
++ lib.optional isLinux "kvm"
|
||||
++ lib.optional isDarwin "apple-virt";
|
||||
|
||||
nativeBuildInputs = lib.optionals config.enableDebugHook [
|
||||
hostPkgs.openssh
|
||||
hostPkgs.inetutils
|
||||
hostPkgs.socat # to allow SSH backdoor connections for systemd-nspawn containers
|
||||
];
|
||||
|
||||
buildCommand = ''
|
||||
|
||||
@@ -56,11 +56,12 @@ in
|
||||
# reuse memoized config
|
||||
v
|
||||
) config.nodesCompat;
|
||||
containers = config.containers;
|
||||
}
|
||||
else
|
||||
config.testScript;
|
||||
|
||||
defaults =
|
||||
nodeDefaults =
|
||||
{ config, name, ... }:
|
||||
{
|
||||
# Make sure all derivations referenced by the test
|
||||
|
||||
@@ -1651,6 +1651,7 @@
|
||||
./services/web-apps/goatcounter.nix
|
||||
./services/web-apps/gotify-server.nix
|
||||
./services/web-apps/gotosocial.nix
|
||||
./services/web-apps/goupile.nix
|
||||
./services/web-apps/grav.nix
|
||||
./services/web-apps/grocy.nix
|
||||
./services/web-apps/guacamole-client.nix
|
||||
|
||||
@@ -298,7 +298,6 @@ in
|
||||
let
|
||||
enabledTables = lib.filterAttrs (_: table: table.enable) cfg.tables;
|
||||
deletionsScript = pkgs.writeScript "nftables-deletions" ''
|
||||
#! ${pkgs.nftables}/bin/nft -f
|
||||
${
|
||||
if cfg.flushRuleset then
|
||||
"flush ruleset"
|
||||
@@ -313,9 +312,9 @@ in
|
||||
${cfg.extraDeletions}
|
||||
'';
|
||||
deletionsScriptVar = "/var/lib/nftables/deletions.nft";
|
||||
makeDeletions = "${pkgs.nftables}/bin/nft -f ${deletionsScriptVar}";
|
||||
ensureDeletions = pkgs.writeShellScript "nftables-ensure-deletions" ''
|
||||
touch ${deletionsScriptVar}
|
||||
chmod +x ${deletionsScriptVar}
|
||||
'';
|
||||
saveDeletionsScript = pkgs.writeShellScript "nftables-save-deletions" ''
|
||||
cp ${deletionsScript} ${deletionsScriptVar}
|
||||
@@ -380,7 +379,7 @@ in
|
||||
saveDeletionsScript
|
||||
];
|
||||
ExecStop = [
|
||||
deletionsScriptVar
|
||||
makeDeletions
|
||||
cleanupDeletionsScript
|
||||
];
|
||||
StateDirectory = "nftables";
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.goupile;
|
||||
settingsFormat = pkgs.formats.ini { };
|
||||
in
|
||||
{
|
||||
options.services.goupile = {
|
||||
enable = lib.mkEnableOption "Goupile server";
|
||||
package = lib.mkPackageOption pkgs "goupile" { };
|
||||
|
||||
enableSandbox = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Enable the sandbox option.";
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
options = {
|
||||
HTTP.Port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8889;
|
||||
description = "The port goupile runs on";
|
||||
};
|
||||
Data.RootDirectory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/goupile";
|
||||
description = "Goupile's data directory.";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { }; # default will be lost for submodules if overriden
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
HTTP.Port = 8888;
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
The options for `systemd.services.goupile` in ini format.
|
||||
|
||||
The configuration options available can be found here
|
||||
https://github.com/Koromix/rygel/blob/goupile/3.11.1/src/goupile/server/admin.cc#L41
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
The configuration file to be passed to goupile server.
|
||||
|
||||
By default the configuration file is created from `services.goupile.settings`.
|
||||
'';
|
||||
};
|
||||
|
||||
hostName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "goupile";
|
||||
description = "Nginx service name for goupile service.";
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable (
|
||||
lib.mkMerge [
|
||||
{
|
||||
services.nginx = {
|
||||
enable = lib.mkDefault true;
|
||||
virtualHosts.${cfg.hostName} = {
|
||||
locations."/".proxyPass = "http://${cfg.hostName}:${builtins.toString cfg.settings.HTTP.Port}";
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
services.goupile.configFile = settingsFormat.generate "goupile.ini" cfg.settings;
|
||||
}
|
||||
{
|
||||
systemd.services.goupile = {
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
documentation = [ "https://goupile.org/en" ];
|
||||
description = "Goupile eCRF";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${lib.getExe cfg.package} \
|
||||
${lib.optionalString cfg.enableSandbox "--sandbox"} \
|
||||
-C ${cfg.configFile}
|
||||
'';
|
||||
|
||||
DynamicUser = true;
|
||||
|
||||
RuntimeDirectory = "goupile";
|
||||
RuntimeDirectoryPreserve = "yes";
|
||||
StateDirectory = "goupile";
|
||||
UMask = 0077;
|
||||
WorkingDirectory = "%S/goupile";
|
||||
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"~@privileged"
|
||||
"~@resources"
|
||||
"~@obsolete"
|
||||
"~@mount"
|
||||
"@system-service"
|
||||
"@file-system"
|
||||
"@basic-io"
|
||||
"@clock"
|
||||
];
|
||||
|
||||
ProtectHome = true;
|
||||
PrivateUsers = true;
|
||||
PrivateDevices = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelModules = true;
|
||||
|
||||
CapabilityBoundingSet = [
|
||||
"CAP_SYS_PTRACE"
|
||||
"CAP_CHOWN"
|
||||
"CAP_DAC_OVERRIDE"
|
||||
"CAP_FOWNER"
|
||||
"CAP_KILL" # Required for child process management
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
"CAP_SETGID"
|
||||
"CAP_SETUID"
|
||||
"CAP_SYS_CHROOT"
|
||||
"CAP_SYS_RESOURCE"
|
||||
];
|
||||
|
||||
Restart = "always";
|
||||
RestartSec = 20;
|
||||
TimeoutStopSec = 30;
|
||||
LimitNOFILE = 4096;
|
||||
};
|
||||
};
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
meta.maintainers = lib.teams.ngi.members;
|
||||
}
|
||||
@@ -86,7 +86,8 @@ in
|
||||
|
||||
options.testing = {
|
||||
backdoor = lib.mkEnableOption "backdoor service in stage 2" // {
|
||||
default = true;
|
||||
# See assertion below for why the backdoor doesn't work with containers.
|
||||
default = !config.boot.isContainer;
|
||||
};
|
||||
|
||||
initrdBackdoor = lib.mkEnableOption ''
|
||||
@@ -105,7 +106,20 @@ in
|
||||
{
|
||||
assertion = cfg.initrdBackdoor -> config.boot.initrd.systemd.enable;
|
||||
message = ''
|
||||
testing.initrdBackdoor requires boot.initrd.systemd.enable to be enabled.
|
||||
`testing.initrdBackdoor` requires `boot.initrd.systemd.enable` to be enabled.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = config.boot.isContainer -> !cfg.backdoor;
|
||||
message = ''
|
||||
`testing.backdoor` uses virtio console, which does not work with
|
||||
containers (we use `nsenter` instead).
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = config.boot.isContainer -> !cfg.initrdBackdoor;
|
||||
message = ''
|
||||
`testing.initrdBackdoor` does not work with containers as there is no initrd.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
@@ -71,7 +71,7 @@ in
|
||||
virtualisation.vlans = lib.mkOption {
|
||||
type = types.listOf types.ints.unsigned;
|
||||
default = if cfg.interfaces == { } then [ 1 ] else [ ];
|
||||
defaultText = lib.literalExpression "if cfg.interfaces == {} then [ 1 ] else [ ]";
|
||||
defaultText = lib.literalExpression "if config.virtualisation.interfaces == {} then [ 1 ] else [ ]";
|
||||
example = [
|
||||
1
|
||||
2
|
||||
|
||||
@@ -70,6 +70,45 @@ in
|
||||
config = {
|
||||
boot.isNspawnContainer = true;
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.specialisation == { };
|
||||
message = ''
|
||||
Setting 'specialisation' is disallowed for systemd-nspawn container configurations.
|
||||
Activating a specialisation requires creating SUID wrappers (e.g., for 'sudo'),
|
||||
which is prohibited within the Nix build sandbox where the test is run.
|
||||
'';
|
||||
}
|
||||
{
|
||||
# Check every interface defined in allInterfaces.
|
||||
# Containers try to create a bridge "${config.system.name}-${interfaceName}"
|
||||
assertion = lib.all (
|
||||
iface:
|
||||
let
|
||||
hostName = "${config.system.name}-${iface.name}";
|
||||
in
|
||||
lib.stringLength hostName <= 15
|
||||
) (lib.attrValues cfg.allInterfaces);
|
||||
|
||||
message =
|
||||
let
|
||||
offendingInterfaces = lib.filter (
|
||||
iface: lib.stringLength "${config.system.name}-${iface.name}" > 15
|
||||
) (lib.attrValues cfg.allInterfaces);
|
||||
offenderList = map (
|
||||
i:
|
||||
"${config.system.name}-${i.name} (${toString (lib.stringLength "${config.system.name}-${i.name}")} chars)"
|
||||
) offendingInterfaces;
|
||||
in
|
||||
''
|
||||
The following generated host interface names exceed the Linux 15-character limit:
|
||||
${lib.concatStringsSep "\n " offenderList}
|
||||
|
||||
Please shorten 'config.system.name' or the interface names in 'virtualisation.interfaces'.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
# TODO(arianvp): Remove after https://github.com/NixOS/nixpkgs/pull/480686 is merged
|
||||
console.enable = true;
|
||||
|
||||
@@ -94,6 +133,9 @@ in
|
||||
# > kind of unit allocation or registration with systemd-machined.
|
||||
"--keep-unit"
|
||||
"--register=no"
|
||||
|
||||
# Send a READY=1 notification to a socket when the container is fully booted.
|
||||
"--notify-ready=yes"
|
||||
];
|
||||
|
||||
system.build.nspawn =
|
||||
|
||||
@@ -68,7 +68,9 @@ def ensure_vlan_bridge(vlan: int) -> typing.Generator[str, None, None]:
|
||||
ipv6_addr = f"2001:db8:{vlan}::fe/64"
|
||||
|
||||
bridge_name = f"br{vlan}"
|
||||
tap_name = f"vde-tap{vlan}"
|
||||
bridge_path = Path("/sys/class/net") / bridge_name
|
||||
tap_path = Path("/sys/class/net") / tap_name
|
||||
try:
|
||||
# To avoid racing against other nspawn containers that also
|
||||
# need this vlan, grab an exclusive lock.
|
||||
@@ -80,6 +82,19 @@ def ensure_vlan_bridge(vlan: int) -> typing.Generator[str, None, None]:
|
||||
run_ip("addr", "add", ipv4_addr, "dev", bridge_name)
|
||||
run_ip("addr", "add", ipv6_addr, "dev", bridge_name)
|
||||
|
||||
if tap_path.exists():
|
||||
logger.info(f"attaching {tap_name} to {bridge_name}")
|
||||
run_ip("link", "set", tap_name, "master", bridge_name)
|
||||
run_ip("link", "set", tap_name, "up")
|
||||
else:
|
||||
logger.warning(
|
||||
f"TAP {tap_name} not found; container will be isolated from VDE"
|
||||
)
|
||||
if not Path("/dev/net").exists():
|
||||
logger.warning(
|
||||
"A common reason for this is that /dev/net is not available in the Nix sandbox. Try adding /dev/net to extra-sandbox-paths."
|
||||
)
|
||||
|
||||
yield bridge_name
|
||||
finally:
|
||||
# To avoid racing against other nspawn containers that also
|
||||
@@ -126,6 +141,7 @@ def mk_veth(
|
||||
def run(
|
||||
container_name: str,
|
||||
root_dir_str: str,
|
||||
shared_dir_str: typing.Optional[str],
|
||||
interfaces: dict,
|
||||
nspawn_options: list[str],
|
||||
init: str,
|
||||
@@ -166,12 +182,19 @@ def run(
|
||||
flush=True,
|
||||
)
|
||||
|
||||
shared_dir = Path(shared_dir_str) if shared_dir_str else None
|
||||
|
||||
cp = subprocess.Popen(
|
||||
[
|
||||
"@systemd-nspawn@",
|
||||
*nspawn_options,
|
||||
f"--directory={root_dir}",
|
||||
f"--network-namespace-path={netns.path}",
|
||||
*(
|
||||
[f"--bind={shared_dir}:/tmp/shared"]
|
||||
if shared_dir is not None
|
||||
else []
|
||||
),
|
||||
init,
|
||||
*cmdline,
|
||||
],
|
||||
@@ -218,6 +241,11 @@ def main():
|
||||
required=True,
|
||||
help="Path to container root directory (overridable with RUN_NSPAWN_ROOT_DIR)",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"--shared-dir",
|
||||
required=False,
|
||||
help="Path to a shared directory to bind-mount into the container at /tmp/shared (overridable with RUN_NSPAWN_SHARED_DIR)",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"--interfaces-json",
|
||||
dest="interfaces",
|
||||
@@ -239,6 +267,7 @@ def main():
|
||||
run(
|
||||
container_name=args.container_name,
|
||||
root_dir_str=os.getenv("RUN_NSPAWN_ROOT_DIR", default=args.root_dir),
|
||||
shared_dir_str=os.getenv("RUN_NSPAWN_SHARED_DIR", default=args.shared_dir),
|
||||
interfaces=args.interfaces,
|
||||
nspawn_options=nspawn_options,
|
||||
init=args.init,
|
||||
|
||||
@@ -168,6 +168,7 @@ in
|
||||
node-name = runTest ./nixos-test-driver/node-name.nix;
|
||||
busybox = runTest ./nixos-test-driver/busybox.nix;
|
||||
console-log = runTest ./nixos-test-driver/console-log.nix;
|
||||
containers = runTest ./nixos-test-driver/containers.nix;
|
||||
driver-timeout =
|
||||
pkgs.runCommand "ensure-timeout-induced-failure"
|
||||
{
|
||||
@@ -689,6 +690,7 @@ in
|
||||
gotenberg = runTest ./gotenberg.nix;
|
||||
gotify-server = runTest ./gotify-server.nix;
|
||||
gotosocial = runTest ./web-apps/gotosocial.nix;
|
||||
goupile = runTest ./web-apps/goupile;
|
||||
grafana = handleTest ./grafana { };
|
||||
graphite = runTest ./graphite.nix;
|
||||
grav = runTest ./web-apps/grav.nix;
|
||||
@@ -1137,9 +1139,6 @@ in
|
||||
nixos-rebuild-target-host = runTest {
|
||||
imports = [ ./nixos-rebuild-target-host.nix ];
|
||||
};
|
||||
nixos-rebuild-target-host-interrupted = runTest {
|
||||
imports = [ ./nixos-rebuild-target-host-interrupted.nix ];
|
||||
};
|
||||
nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; };
|
||||
nixpkgs-config-allow-unfree =
|
||||
pkgs.callPackage ../modules/misc/nixpkgs/test-nixpkgs-config-allow-unfree.nix
|
||||
@@ -1648,6 +1647,7 @@ in
|
||||
teleports = runTest ./teleports.nix;
|
||||
temporal = runTest ./temporal.nix;
|
||||
terminal-emulators = handleTest ./terminal-emulators.nix { };
|
||||
test-containers-bittorrent = runTest ./test-containers-bittorrent.nix;
|
||||
thanos = runTest ./thanos.nix;
|
||||
thelounge = handleTest ./thelounge.nix { };
|
||||
tiddlywiki = runTest ./tiddlywiki.nix;
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
{ hostPkgs, ... }:
|
||||
|
||||
# This test recreates a remote deployment scenario where the connection
|
||||
# between deployer and target is closed during the deployment - in this
|
||||
# case because the connection goes over a 'reverse ssh' tunnel service
|
||||
# that has changes that are being deployed.
|
||||
|
||||
# This is not seamless (the deployer doesn't get to see the logs after
|
||||
# the disconnect), but is a lot better than the old behaviour, where
|
||||
# the switch was aborted and the connection never restored.
|
||||
|
||||
{
|
||||
name = "nixos-rebuild-target-host-interrupted";
|
||||
|
||||
# TODO: remove overlay from nixos/modules/profiles/installation-device.nix
|
||||
# make it a _small package instead, then remove pkgsReadOnly = false;.
|
||||
node.pkgsReadOnly = false;
|
||||
|
||||
nodes = {
|
||||
deployer =
|
||||
{
|
||||
nodes,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
../modules/profiles/installation-device.nix
|
||||
];
|
||||
|
||||
nix.settings = {
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = 1;
|
||||
};
|
||||
|
||||
system.includeBuildDependencies = true;
|
||||
|
||||
virtualisation = {
|
||||
cores = 2;
|
||||
memorySize = 3072;
|
||||
};
|
||||
|
||||
services.openssh.enable = true;
|
||||
users.users.root.openssh.authorizedKeys.keys = [ nodes.target.system.build.publicKey ];
|
||||
system.extraDependencies = [
|
||||
# so that it doesn't need to be built inside the test
|
||||
pkgs.nixVersions.latest
|
||||
];
|
||||
|
||||
system.build.privateKey = snakeOilPrivateKey;
|
||||
system.build.publicKey = snakeOilPublicKey;
|
||||
system.switch.enable = true;
|
||||
|
||||
services.getty.autologinUser = lib.mkForce "root";
|
||||
};
|
||||
|
||||
target =
|
||||
{
|
||||
nodes,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
|
||||
targetConfig = {
|
||||
documentation.enable = false;
|
||||
services.openssh.enable = true;
|
||||
system.build.privateKey = snakeOilPrivateKey;
|
||||
system.build.publicKey = snakeOilPublicKey;
|
||||
|
||||
users.users.root.openssh.authorizedKeys.keys = [ nodes.deployer.system.build.publicKey ];
|
||||
users.users.alice.openssh.authorizedKeys.keys = [ nodes.deployer.system.build.publicKey ];
|
||||
users.users.bob.openssh.authorizedKeys.keys = [ nodes.deployer.system.build.publicKey ];
|
||||
|
||||
users.users.alice.extraGroups = [ "wheel" ];
|
||||
users.users.bob.extraGroups = [ "wheel" ];
|
||||
|
||||
# Disable sudo for root to ensure sudo isn't called without `--sudo`
|
||||
security.sudo.extraRules = lib.mkForce [
|
||||
{
|
||||
groups = [ "wheel" ];
|
||||
commands = [ { command = "ALL"; } ];
|
||||
}
|
||||
{
|
||||
users = [ "alice" ];
|
||||
commands = [
|
||||
{
|
||||
command = "ALL";
|
||||
options = [ "NOPASSWD" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
nix.settings.trusted-users = [ "@wheel" ];
|
||||
|
||||
services.autossh-ng.sessions.will-be-interrupted-by-rebuild = {
|
||||
user = "root";
|
||||
destination = "deployer";
|
||||
extraArguments = "-R2222:localhost:22";
|
||||
hostKeyChecking = false;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ ./common/user-account.nix ];
|
||||
|
||||
config = lib.mkMerge [
|
||||
targetConfig
|
||||
{
|
||||
system.build = {
|
||||
inherit targetConfig;
|
||||
};
|
||||
system.switch.enable = true;
|
||||
|
||||
networking.hostName = "target";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
sshConfig = builtins.toFile "ssh.conf" ''
|
||||
UserKnownHostsFile=/dev/null
|
||||
StrictHostKeyChecking=no
|
||||
'';
|
||||
|
||||
targetConfigJSON = hostPkgs.writeText "target-configuration.json" (
|
||||
builtins.toJSON nodes.target.system.build.targetConfig
|
||||
);
|
||||
|
||||
targetNetworkJSON = hostPkgs.writeText "target-network.json" (
|
||||
builtins.toJSON nodes.target.system.build.networkConfig
|
||||
);
|
||||
|
||||
configFile =
|
||||
hostname:
|
||||
hostPkgs.writeText "configuration.nix" # nix
|
||||
''
|
||||
{ lib, pkgs, modulesPath, ... }: {
|
||||
imports = [
|
||||
(modulesPath + "/virtualisation/qemu-vm.nix")
|
||||
(modulesPath + "/testing/test-instrumentation.nix")
|
||||
(modulesPath + "/../tests/common/user-account.nix")
|
||||
(lib.modules.importJSON ./target-configuration.json)
|
||||
(lib.modules.importJSON ./target-network.json)
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
boot.loader.grub = {
|
||||
enable = true;
|
||||
device = "/dev/vda";
|
||||
forceInstall = true;
|
||||
};
|
||||
|
||||
# needed to make NIX_SSHOPTS work for nix-copy-closure
|
||||
# 2.31.3 (current default) break, 2.32.6 and 2.33.3 (current latest) work
|
||||
# let's use the default here again once the fix has made it there.
|
||||
nix.package = pkgs.nixVersions.latest;
|
||||
|
||||
# We're changing the '-E' parameter to the new hostname here,
|
||||
# not because we care about the logs, but because we want to
|
||||
# force the scenario where the connection is broken during the
|
||||
# deployment (because the autossh-ng service is stopped and
|
||||
# started):
|
||||
services.autossh-ng.sessions.will-be-interrupted-by-rebuild.extraArguments = "-R2222:localhost:22 -E ${hostname}";
|
||||
|
||||
# this will be asserted to validate the switch happened:
|
||||
networking.hostName = "${hostname}";
|
||||
}
|
||||
'';
|
||||
in
|
||||
# python
|
||||
''
|
||||
start_all()
|
||||
target.wait_for_open_port(22)
|
||||
|
||||
deployer.wait_until_succeeds("ping -c1 target")
|
||||
deployer.succeed("install -Dm 600 ${nodes.deployer.system.build.privateKey} ~root/.ssh/id_ecdsa")
|
||||
deployer.succeed("install ${sshConfig} ~root/.ssh/config")
|
||||
|
||||
target.succeed("nixos-generate-config")
|
||||
target.succeed("install -Dm 600 ${nodes.target.system.build.privateKey} ~root/.ssh/id_ecdsa")
|
||||
deployer.succeed("scp alice@target:/etc/nixos/hardware-configuration.nix /root/hardware-configuration.nix")
|
||||
target.wait_for_unit("autossh-ng-will-be-interrupted-by-rebuild.service")
|
||||
|
||||
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("${targetNetworkJSON}", "/root/target-network.json")
|
||||
deployer.copy_from_host("${targetConfigJSON}", "/root/target-configuration.json")
|
||||
|
||||
with subtest("Deploy to alice@target via reverse ssh"):
|
||||
deployer.wait_for_unit("multi-user.target")
|
||||
# Uses TTY/send_chars instead of deployer.succeed to set NIX_SSHOPTS
|
||||
deployer.send_chars("NIX_SSHOPTS=\"-p 2222\" nixos-rebuild switch -I nixos-config=/root/configuration-1.nix --target-host alice@localhost --sudo\n")
|
||||
|
||||
# the connection breaks, but the 'switch' should now continue in the background:
|
||||
deployer.wait_until_tty_matches("1", "error: while running command with remote sudo")
|
||||
|
||||
def deployed(last_try: bool) -> bool:
|
||||
target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname", timeout=20).rstrip()
|
||||
if last_try:
|
||||
print(f"Still seeing hostname {target_hostname}")
|
||||
return target_hostname == "config-1-deployed"
|
||||
retry(deployed)
|
||||
|
||||
with subtest("Deploy to bob@target via reverse ssh with password-based sudo"):
|
||||
deployer.wait_for_unit("multi-user.target")
|
||||
# Uses TTY/send_chars instead of deployer.succeed to set NIX_SSHOPTS and for ask-sudo-password
|
||||
deployer.send_chars("NIX_SSHOPTS=\"-p 2222\" nixos-rebuild switch -I nixos-config=/root/configuration-2.nix --target-host bob@localhost --ask-sudo-password\n")
|
||||
deployer.wait_until_tty_matches("1", "password for bob")
|
||||
deployer.send_chars("${nodes.target.users.users.bob.password}\n")
|
||||
|
||||
# the connection breaks, but the 'switch' should now continue in the background:
|
||||
deployer.wait_until_tty_matches("1", "error: while running command with remote sudo")
|
||||
|
||||
def deployed(last_try: bool) -> bool:
|
||||
target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname", timeout=20).rstrip()
|
||||
if last_try:
|
||||
print(f"Still seeing hostname {target_hostname}")
|
||||
return target_hostname == "config-2-deployed"
|
||||
retry(deployed)
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "containers";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ jfly ];
|
||||
|
||||
nodes = {
|
||||
n1 = {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
};
|
||||
n2 = {
|
||||
virtualisation.vlans = [
|
||||
2
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
containers = {
|
||||
c1 = {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
};
|
||||
c2 = {
|
||||
virtualisation.vlans = [ 2 ];
|
||||
};
|
||||
c12 = {
|
||||
virtualisation.vlans = [
|
||||
1
|
||||
2
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = /* python */ ''
|
||||
c1.start()
|
||||
c2.start()
|
||||
c12.start()
|
||||
|
||||
c1.succeed("echo hello > /hello.txt")
|
||||
c1.copy_from_machine("/hello.txt")
|
||||
|
||||
c1.systemctl("start network-online.target")
|
||||
c2.systemctl("start network-online.target")
|
||||
c12.systemctl("start network-online.target")
|
||||
c1.wait_for_unit("network-online.target")
|
||||
c2.wait_for_unit("network-online.target")
|
||||
c12.wait_for_unit("network-online.target")
|
||||
|
||||
# Confirm containers in vlan 1 can talk to each other.
|
||||
c1.succeed("ping -c 1 c12")
|
||||
c12.succeed("ping -c 1 c1")
|
||||
|
||||
# Confirm containers in vlan 2 can talk to each other.
|
||||
c2.succeed("ping -c 1 c12")
|
||||
c12.succeed("ping -c 1 c2")
|
||||
|
||||
# Confirm containers in separate vlans cannot talk to each other.
|
||||
c1.fail("ping -c 1 -W 1 c2")
|
||||
|
||||
n1.start()
|
||||
n2.start()
|
||||
n1.systemctl("start network-online.target")
|
||||
n2.systemctl("start network-online.target")
|
||||
n1.wait_for_unit("network-online.target")
|
||||
n2.wait_for_unit("network-online.target")
|
||||
|
||||
# Confirm containers and nodes in the same vlan can talk to each other.
|
||||
c1.succeed("ping -c 1 n1")
|
||||
n1.succeed("ping -c 1 c1")
|
||||
c2.succeed("ping -c 1 n2")
|
||||
n2.succeed("ping -c 1 c2")
|
||||
|
||||
# Confirm containers and nodes in different vlans cannot talk to each other.
|
||||
c1.fail("ping -c 1 -W 1 n2")
|
||||
n1.fail("ping -c 1 -W 1 c2")
|
||||
c2.fail("ping -c 1 -W 1 n1")
|
||||
n2.fail("ping -c 1 -W 1 c1")
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
# This test runs a Bittorrent tracker on one machine, and verifies
|
||||
# that two client machines can download the torrent using
|
||||
# `aria2c'. The first client (behind a NAT router) downloads
|
||||
# from the initial seeder running on the tracker. Then we kill the
|
||||
# initial seeder. The second client downloads from the first client,
|
||||
# which only works if the first client successfully uses the UPnP-IGD
|
||||
# protocol to poke a hole in the NAT.
|
||||
|
||||
# We use aria2 as the initial seeder because transmission
|
||||
# fails in the sandbox because of systemd hardening settings,
|
||||
# namely MountAPIVFS=yes, so we get the following error:
|
||||
|
||||
# $ journalctl --unit transmission.service
|
||||
# (n-daemon)[417]: transmission.service: Failed to create destination mount point node '/run/transmission/run/host/.os-release-stage/', ignoring: Read-only file system
|
||||
# (n-daemon)[417]: transmission.service: Failed to mount /run/systemd/propagate/.os-release-stage to /run/transmission/run/host/.os-release-stage/: No such file or directory
|
||||
# (n-daemon)[417]: transmission.service: Failed to set up mount namespacing: /run/host/.os-release-stage/: No such file or directory
|
||||
# (n-daemon)[417]: transmission.service: Failed at step NAMESPACE spawning /nix/store/zfksw9bllp95pl45d1nxmpd2lks42bkj-transmission-4.0.6/bin/transmission-daemon: No such file or directory
|
||||
# systemd[1]: transmission.service: Main process exited, code=exited, status=226/NAMESPACE
|
||||
|
||||
{ lib, hostPkgs, ... }:
|
||||
|
||||
let
|
||||
|
||||
# Some random file to serve.
|
||||
file = hostPkgs.hello.src;
|
||||
|
||||
internalRouterAddress = "192.168.3.1";
|
||||
internalClient1Address = "192.168.3.2";
|
||||
|
||||
# cannot use documentation networks (198.51.100.0/24 or 192.0.2.0/24) here
|
||||
# because miniupnpd recognizes them as such and refuses to work with them
|
||||
# https://github.com/miniupnp/miniupnp/blob/2a74cb2f27cacf06d2b50c187e8f90aa1f5c2528/miniupnpd/miniupnpd.c#L998
|
||||
externalRouterAddress = "80.100.100.1";
|
||||
externalClient2Address = "80.100.100.2";
|
||||
externalTrackerAddress = "80.100.100.3";
|
||||
|
||||
download-dir = "/tmp/aria2-downloads";
|
||||
peerConfig =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [
|
||||
pkgs.aria2
|
||||
pkgs.transmission_4 # only needed for transmission-create
|
||||
];
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
name = "bittorrent";
|
||||
meta = {
|
||||
maintainers = [
|
||||
lib.maintainers.kmein
|
||||
];
|
||||
};
|
||||
|
||||
containers = {
|
||||
tracker =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
imports = [ peerConfig ];
|
||||
|
||||
virtualisation.vlans = [ 1 ];
|
||||
networking.firewall.enable = false;
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{
|
||||
address = externalTrackerAddress;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
|
||||
# We need Apache on the tracker to serve the torrents.
|
||||
services.httpd = {
|
||||
enable = true;
|
||||
virtualHosts = {
|
||||
"torrentserver.org" = {
|
||||
adminAddr = "foo@example.org";
|
||||
documentRoot = "/tmp";
|
||||
};
|
||||
};
|
||||
};
|
||||
services.opentracker.enable = true;
|
||||
};
|
||||
|
||||
router =
|
||||
{ pkgs, containers, ... }:
|
||||
{
|
||||
virtualisation.vlans = [
|
||||
1
|
||||
2
|
||||
];
|
||||
networking.nat.enable = true;
|
||||
networking.nat.internalInterfaces = [ "eth2" ];
|
||||
networking.nat.externalInterface = "eth1";
|
||||
networking.firewall.enable = true;
|
||||
networking.firewall.trustedInterfaces = [ "eth2" ];
|
||||
networking.interfaces.eth0.ipv4.addresses = [ ];
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{
|
||||
address = externalRouterAddress;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
networking.interfaces.eth2.ipv4.addresses = [
|
||||
{
|
||||
address = internalRouterAddress;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
networking.nftables.enable = true;
|
||||
services.miniupnpd = {
|
||||
enable = true;
|
||||
externalInterface = "eth1";
|
||||
internalIPs = [ "eth2" ];
|
||||
appendConfig = ''
|
||||
ext_ip=${externalRouterAddress}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
client1 =
|
||||
{ pkgs, containers, ... }:
|
||||
{
|
||||
imports = [ peerConfig ];
|
||||
environment.systemPackages = [ pkgs.miniupnpc ];
|
||||
|
||||
virtualisation.vlans = [ 2 ];
|
||||
networking.interfaces.eth0.ipv4.addresses = [ ];
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{
|
||||
address = internalClient1Address;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
networking.defaultGateway = internalRouterAddress;
|
||||
networking.firewall.enable = false;
|
||||
};
|
||||
|
||||
client2 =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
imports = [ peerConfig ];
|
||||
|
||||
virtualisation.vlans = [ 1 ];
|
||||
networking.interfaces.eth0.ipv4.addresses = [ ];
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{
|
||||
address = externalClient2Address;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
networking.firewall.enable = false;
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ containers, ... }:
|
||||
''
|
||||
start_all()
|
||||
|
||||
# Wait for network and miniupnpd.
|
||||
router.systemctl("start network-online.target")
|
||||
router.wait_for_unit("network-online.target")
|
||||
router.wait_for_unit("miniupnpd")
|
||||
|
||||
# Create the torrent.
|
||||
tracker.succeed("mkdir -p ${download-dir}")
|
||||
tracker.succeed(
|
||||
"cp ${file} ${download-dir}/test.tar.bz2"
|
||||
)
|
||||
tracker.succeed(
|
||||
"transmission-create ${download-dir}/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
|
||||
)
|
||||
tracker.succeed("chmod 644 /tmp/test.torrent")
|
||||
|
||||
# Start the tracker
|
||||
tracker.systemctl("start network-online.target")
|
||||
tracker.wait_for_unit("network-online.target")
|
||||
tracker.wait_for_unit("opentracker.service")
|
||||
tracker.wait_for_open_port(6969)
|
||||
|
||||
# --- Start the initial seeder using aria2 ---
|
||||
# https://stackoverflow.com/a/44528978
|
||||
tracker.execute(
|
||||
"aria2c --enable-dht=false --seed-time=999 --dir=${download-dir} "
|
||||
"-V --seed-ratio=0.0 "
|
||||
"/tmp/test.torrent >/dev/null &"
|
||||
)
|
||||
|
||||
# --- Wait until the tracker shows we are seeding ---
|
||||
tracker.wait_until_succeeds("curl -s http://localhost:6969/stats | grep -q 'serving 1 torrents'")
|
||||
|
||||
# Now we should be able to download from the client behind the NAT.
|
||||
tracker.wait_for_unit("httpd")
|
||||
|
||||
def connect_from(machine):
|
||||
machine.systemctl("start network-online.target")
|
||||
machine.wait_for_unit("network-online.target")
|
||||
machine.execute(
|
||||
"aria2c --enable-dht=false --seed-time=999 --dir=${download-dir} "
|
||||
"http://${externalTrackerAddress}/test.torrent >/dev/null &"
|
||||
)
|
||||
machine.wait_until_succeeds(
|
||||
"cmp ${download-dir}/test.tar.bz2 ${file}"
|
||||
) # Wait for download to finish and verify
|
||||
|
||||
connect_from(client1)
|
||||
|
||||
# --- Bring down the initial seeder ---
|
||||
tracker.succeed("pkill aria2c")
|
||||
|
||||
# Now download from the second client. This can only succeed if
|
||||
# the first client created a NAT hole in the router.
|
||||
connect_from(client2)
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import os
|
||||
import openpyxl
|
||||
import tempfile
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
BASE_URL = "http://localhost:8889"
|
||||
|
||||
# NOTE: these are the passwords
|
||||
ADMIN_PASSWD = "car-shop-in-the-mall"
|
||||
ALICE_PASSWD = "user-goes-to-the-car-shop"
|
||||
|
||||
|
||||
def run_test():
|
||||
is_headful = os.getenv("HEADFUL") == "1"
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=not is_headful)
|
||||
context = browser.new_context(
|
||||
accept_downloads=True, record_video_dir="/tmp/videos/"
|
||||
)
|
||||
# more default timeout for slow nixos test vms
|
||||
context.set_default_timeout(90 * 1000)
|
||||
page = context.new_page()
|
||||
|
||||
page.goto(f"{BASE_URL}/admin")
|
||||
|
||||
# admin and doman setup
|
||||
page.get_by_role("textbox", name="Domain name *").fill("domain")
|
||||
page.get_by_role("textbox", name="Domain title *").fill("domain")
|
||||
page.get_by_role("textbox", name="Password *").fill(ADMIN_PASSWD)
|
||||
page.get_by_role("textbox", name="Confirmation").fill(ADMIN_PASSWD)
|
||||
page.get_by_role("textbox", name="Decryption key *").click()
|
||||
page.get_by_role("button", name="Installer").click()
|
||||
|
||||
# login to admin dashboard as admin
|
||||
page.get_by_role("textbox", name="Username *").fill("admin")
|
||||
page.get_by_role("textbox", name="Password *").fill(ADMIN_PASSWD)
|
||||
page.get_by_role("button", name="Login").click()
|
||||
|
||||
# create a sample project, it will switch the view to project's configure page
|
||||
page.get_by_text("Create new project").click()
|
||||
page.get_by_role("textbox", name="Name *").fill("proj1")
|
||||
page.get_by_role("button", name="Create").click()
|
||||
|
||||
# create a test non-root user, alice
|
||||
page.get_by_text("Create new user").click()
|
||||
page.get_by_role("textbox", name="Username *").fill("alice")
|
||||
page.get_by_role("button", name="No", exact=True).click()
|
||||
page.get_by_role("textbox", name="Password *").fill(ALICE_PASSWD)
|
||||
page.get_by_role("textbox", name="Confirmation").fill(ALICE_PASSWD)
|
||||
page.get_by_role("button", name="Create").click()
|
||||
|
||||
# give alice, permissions to access the project
|
||||
page.get_by_role("button", name="Assign").nth(1).click()
|
||||
page.get_by_text("Read", exact=True).click()
|
||||
page.get_by_text("Save", exact=True).click()
|
||||
page.get_by_text("Export", exact=True).click()
|
||||
page.get_by_text("Download", exact=True).click()
|
||||
|
||||
# Open the project in new page
|
||||
page.locator("form").get_by_role("button", name="Edit").click()
|
||||
with page.expect_popup() as page1_info:
|
||||
page.get_by_role("link", name="access").click()
|
||||
page1 = page1_info.value
|
||||
page1.set_default_timeout(120 * 1000)
|
||||
|
||||
# fill entries as admin (enter 1 for everything)
|
||||
page1.get_by_role("button", name="Create new record").click()
|
||||
|
||||
page1.locator("#ins_tiles").get_by_text("Introduction").click()
|
||||
page1.get_by_role("textbox", name="Inclusion date *").fill("2000-01-01")
|
||||
page1.get_by_role("spinbutton", name="Age *").click()
|
||||
page1.get_by_role("spinbutton", name="Age *").fill("1")
|
||||
page1.get_by_role("button", name="Save").click()
|
||||
page1.wait_for_timeout(1000)
|
||||
|
||||
page1.get_by_role("button", name="Advanced").click()
|
||||
page1.get_by_role("spinbutton", name="Age *").click()
|
||||
page1.get_by_role("spinbutton", name="Age *").fill("1")
|
||||
page1.get_by_role("button", name="Save").click()
|
||||
page1.wait_for_timeout(1000)
|
||||
|
||||
page1.get_by_role("button", name="Page layout").click()
|
||||
page1.get_by_role("spinbutton", name="Variable A1").fill("1")
|
||||
page1.get_by_role("button", name="Save").click()
|
||||
page1.wait_for_timeout(1000)
|
||||
|
||||
# create export #1
|
||||
page1.get_by_role("button", name="Data").click()
|
||||
page1.wait_for_timeout(1000)
|
||||
|
||||
page1.get_by_role("button", name="Data exports").click()
|
||||
with page1.expect_download() as download_info:
|
||||
page1.get_by_role("button", name="Create export").click()
|
||||
|
||||
# logout as admin
|
||||
page.get_by_role("button", name="admin", exact=True).click()
|
||||
with page.expect_popup() as page2_info:
|
||||
page.get_by_role("link", name="access").click()
|
||||
page2 = page2_info.value
|
||||
page2.set_default_timeout(120 * 1000)
|
||||
|
||||
page2.get_by_role("button", name="admin").click()
|
||||
page2.get_by_role("button", name="Logout").click()
|
||||
|
||||
# login as alice
|
||||
page2.get_by_role("textbox", name="Username *").fill("alice")
|
||||
page2.get_by_role("textbox", name="Password *").fill(ALICE_PASSWD)
|
||||
page2.get_by_role("button", name="Login").click()
|
||||
|
||||
# create entry as alice (fill `2` for everything)
|
||||
page2.get_by_role("button", name="Create new record").click()
|
||||
|
||||
page2.get_by_text("1 Introduction").click()
|
||||
page2.get_by_role("textbox", name="Inclusion date *").fill("2000-01-01")
|
||||
page2.get_by_role("spinbutton", name="Age *").click()
|
||||
page2.get_by_role("spinbutton", name="Age *").fill("2")
|
||||
page2.get_by_role("button", name="Save").click()
|
||||
page2.wait_for_timeout(1000)
|
||||
|
||||
page2.get_by_role("button", name="Advanced").click()
|
||||
page2.get_by_role("spinbutton", name="Age *").click()
|
||||
page2.get_by_role("spinbutton", name="Age *").fill("2")
|
||||
page2.get_by_role("button", name="Save").click()
|
||||
page2.wait_for_timeout(1000)
|
||||
|
||||
page2.get_by_role("button", name="Page layout").click()
|
||||
page2.get_by_role("spinbutton", name="Variable A1").click()
|
||||
page2.get_by_role("spinbutton", name="Variable A1").fill("2")
|
||||
page2.get_by_role("button", name="Save").click()
|
||||
page2.wait_for_timeout(1000)
|
||||
|
||||
# create export #2
|
||||
page2.get_by_role("button", name="Data").click()
|
||||
page2.wait_for_timeout(1000)
|
||||
|
||||
page2.get_by_role("button", name="Data exports").click()
|
||||
page2.get_by_role("button", name="Previous exports").click()
|
||||
|
||||
with page2.expect_download() as download1_info:
|
||||
page2.locator("a").filter(has_text="Download").click()
|
||||
|
||||
download1 = download1_info.value
|
||||
save_path1 = os.path.join(tempfile.gettempdir(), download1.suggested_filename)
|
||||
download1.save_as(save_path1)
|
||||
|
||||
print(f"exported all records to {save_path1}")
|
||||
|
||||
page2.get_by_role("button", name="Data exports").click()
|
||||
with page2.expect_download() as download2_info:
|
||||
page2.get_by_role("button", name="Create export").click()
|
||||
|
||||
download2 = download2_info.value
|
||||
save_path2 = os.path.join(tempfile.gettempdir(), download2.suggested_filename)
|
||||
download2.save_as(save_path2)
|
||||
|
||||
print(f"exported all records to {save_path2}")
|
||||
|
||||
context.close()
|
||||
browser.close()
|
||||
|
||||
# check that exported files have correct entries
|
||||
|
||||
wb1 = openpyxl.load_workbook(save_path1)
|
||||
for sheet, cell in zip(["intro", "advanced", "layout"], ["D2", "D2", "C2"]):
|
||||
val = wb1[sheet][cell].value
|
||||
assert val == 1, f"Sheet {sheet}, Cell {cell}: Expected 1 (admin), got {val}"
|
||||
|
||||
wb2 = openpyxl.load_workbook(save_path2)
|
||||
for sheet, cell in zip(["intro", "advanced", "layout"], ["D3", "D3", "C3"]):
|
||||
val = wb2[sheet][cell].value
|
||||
assert val == 2, f"Sheet {sheet}, Cell {cell}: Expected 2 (alice), got {val}"
|
||||
|
||||
print("Test passed successfully!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_test()
|
||||
@@ -0,0 +1,153 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
python = pkgs.python3.withPackages (
|
||||
ps: with ps; [
|
||||
requests
|
||||
playwright
|
||||
openpyxl
|
||||
]
|
||||
);
|
||||
|
||||
runScript = "${lib.getExe python} ${./basic_interaction_test.py}";
|
||||
|
||||
run-goupile-test = pkgs.writeShellScriptBin "run-goupile-test" ''
|
||||
set -euo pipefail
|
||||
|
||||
export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}
|
||||
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
|
||||
# check if attached to a terminal
|
||||
if [ -t 1 ]; then
|
||||
# interactive testing
|
||||
export HEADFUL=''${HEADFUL:-1}
|
||||
export PWDEBUG=''${PWDEBUG:-0}
|
||||
export DISPLAY=''${DISPLAY:-:0}
|
||||
if [ "$(id -u)" = "0" ] && [ -d "/home/alice" ]; then
|
||||
runuser -u alice \
|
||||
-w DISPLAY,HEADFUL,PWDEBUG,PLAYWRIGHT_BROWSERS_PATH,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD \
|
||||
-- ${runScript}
|
||||
else
|
||||
${runScript}
|
||||
fi
|
||||
else
|
||||
# non-interactive nixos test
|
||||
|
||||
# Print instructions to the nix logs
|
||||
cat <<'EOF' | tee >(systemd-cat -t goupile-e2e)
|
||||
================================================================================
|
||||
NOTE: The goupile e2e test can be run interactively either inside the vm or on the host
|
||||
- First, run `nix-build -A nixosTests.goupile.driverInteractive` and `./result/bin/nixos-test-driver`
|
||||
- Run `start_all()` inside the repl
|
||||
- Then `$(nix-build -A nixosTests.goupile.interactive-script)/bin/run-goupile-test` to run the full test interactively
|
||||
- Or `env PWDEBUG=1 $(nix-build -A nixosTests.goupile.interactive-script)/bin/run-goupile-test` to show the playwright inspector to debug
|
||||
================================================================================
|
||||
EOF
|
||||
|
||||
echo "Starting smoke test..." | systemd-cat -t goupile-e2e
|
||||
${runScript} 2>&1 | tee >(systemd-cat -t goupile-e2e)
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "goupile";
|
||||
|
||||
passthru.interactive-script = run-goupile-test;
|
||||
|
||||
nodes.machine =
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
services.goupile = {
|
||||
enable = true;
|
||||
enableSandbox = true;
|
||||
settings.HTTP.Port = 8889;
|
||||
};
|
||||
#systemd.services.goupile.environment.DEFAULT_SECCOMP_ACTION = "Log"; # Block|Log|Kill
|
||||
networking = {
|
||||
firewall.allowedTCPPorts = [ config.services.nginx.defaultHTTPListenPort ];
|
||||
hostName = "goupile";
|
||||
domain = "local";
|
||||
};
|
||||
|
||||
# goupile tries to resolve it at runtime, resolve it instead of patching it out
|
||||
# as the dns resolution step serves a purpose, to force glibc to load NSS libraries
|
||||
# see server/goupile.cc and search for getaddrinfo or www.example.com
|
||||
networking.extraHosts = ''
|
||||
127.0.0.1 www.example.com
|
||||
'';
|
||||
|
||||
environment.systemPackages = [
|
||||
python
|
||||
run-goupile-test
|
||||
];
|
||||
|
||||
# more cores and memory to improve chromium performance
|
||||
virtualisation.memorySize = lib.mkForce 8192;
|
||||
virtualisation.cores = 4;
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
port = builtins.toString nodes.machine.services.goupile.settings.HTTP.Port;
|
||||
in
|
||||
# py
|
||||
''
|
||||
import os
|
||||
start_all()
|
||||
|
||||
machine.wait_for_unit("goupile.service")
|
||||
machine.wait_for_open_port(${port})
|
||||
|
||||
machine.succeed("curl -q http://localhost:${port}")
|
||||
machine.succeed("curl -q http://goupile.local")
|
||||
machine.succeed("curl -q http://localhost")
|
||||
|
||||
machine.succeed("run-goupile-test")
|
||||
out_dir = os.environ.get("out", os.getcwd())
|
||||
machine.copy_from_vm("/tmp/videos", out_dir)
|
||||
'';
|
||||
|
||||
# Debug interactively with:
|
||||
# - nix-build -A nixosTests.goupile.driverInteractive
|
||||
# - ./result/bin/nixos-test-driver
|
||||
# - run_tests()
|
||||
# ssh -o User=root vsock%3 (can also do vsock/3, but % works with scp etc.)
|
||||
interactive.sshBackdoor.enable = true;
|
||||
|
||||
interactive.nodes.machine =
|
||||
{ config, ... }:
|
||||
let
|
||||
port = config.services.goupile.settings.HTTP.Port;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
# enable graphical session + users (alice, bob)
|
||||
../../common/x11.nix
|
||||
../../common/user-account.nix
|
||||
];
|
||||
services.xserver.enable = true;
|
||||
test-support.displayManager.auto.user = "alice";
|
||||
|
||||
virtualisation.forwardPorts = [
|
||||
{
|
||||
from = "host";
|
||||
host.port = port;
|
||||
guest.port = port;
|
||||
}
|
||||
];
|
||||
|
||||
# forwarded ports need to be accessible
|
||||
networking.firewall.allowedTCPPorts = [ port ];
|
||||
};
|
||||
|
||||
meta.maintainers = lib.teams.ngi.members;
|
||||
}
|
||||
@@ -3008,8 +3008,8 @@ let
|
||||
mktplcRef = {
|
||||
publisher = "mesonbuild";
|
||||
name = "mesonbuild";
|
||||
version = "1.28.1";
|
||||
hash = "sha256-Cu2sBg8wTjGLOMF4bCOG8noXZXZB2j5wSXZS2VxxNoA=";
|
||||
version = "1.28.2";
|
||||
hash = "sha256-Wb3cfATe8pc+LftmKyFj3q6kmdTHUMtoIHlChKKeEoU=";
|
||||
};
|
||||
meta = {
|
||||
changelog = "https://marketplace.visualstudio.com/items/mesonbuild.mesonbuild/changelog";
|
||||
@@ -4458,8 +4458,8 @@ let
|
||||
mktplcRef = {
|
||||
name = "vscode-stylelint";
|
||||
publisher = "stylelint";
|
||||
version = "2.0.2";
|
||||
hash = "sha256-nJYy7HFycKXTQCHgaLP46CGl0hlgaexL1QZ8icGpeVo=";
|
||||
version = "2.1.0";
|
||||
hash = "sha256-cL86Gv2HAtvqNd+2vJPuKAgKVrp5pg6IECFm1Di8Eqk=";
|
||||
};
|
||||
meta = {
|
||||
description = "Official Stylelint extension for Visual Studio Code";
|
||||
|
||||
@@ -15,13 +15,13 @@ let
|
||||
vsix = stdenv.mkDerivation (finalAttrs: {
|
||||
name = "gitlens-${finalAttrs.version}.vsix";
|
||||
pname = "gitlens-vsix";
|
||||
version = "17.11.0";
|
||||
version = "17.11.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "gitkraken";
|
||||
repo = "vscode-gitlens";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-MMUfl8Vc6mAjs0ZPWV0lHQdqRkKKY0FEx7mbz/yrk9k=";
|
||||
hash = "sha256-BN6qgPYhZ+FuYnwmV0S3y2vOR4ZLC+VGWuEEPqfOqi4=";
|
||||
};
|
||||
|
||||
pnpmDeps = fetchPnpmDeps {
|
||||
|
||||
@@ -472,13 +472,13 @@
|
||||
"vendorHash": "sha256-mzDFyk2oImRXt72kFV5Ln++ScgoecpJEJtzUKjvCaws="
|
||||
},
|
||||
"grafana_grafana": {
|
||||
"hash": "sha256-ifE5W6sUo/BTxO+noss+nqw+LDPlkxdpySlJ08n7Kd4=",
|
||||
"hash": "sha256-XXnmPZstCrZ2NDMx/azDpvXknuEwqJ+GW0hiaH3+bDQ=",
|
||||
"homepage": "https://registry.terraform.io/providers/grafana/grafana",
|
||||
"owner": "grafana",
|
||||
"repo": "terraform-provider-grafana",
|
||||
"rev": "v4.27.1",
|
||||
"rev": "v4.28.1",
|
||||
"spdx": "MPL-2.0",
|
||||
"vendorHash": "sha256-OCdknvuL/+khhFdopVLKEYKBwLbyPnziKamHDUEX+Zk="
|
||||
"vendorHash": "sha256-OMrFGY8rIf32E6/TKSmR/AQPj+GS1e9V5UyPxzhXaNE="
|
||||
},
|
||||
"gridscale_gridscale": {
|
||||
"hash": "sha256-FAKvQ/MEod5Ck0PG4ffQ+gQp6zZ0JDRXPOrOiDpWMls=",
|
||||
|
||||
@@ -356,10 +356,16 @@ rec {
|
||||
export ${name}
|
||||
'') runtimeEnv
|
||||
)
|
||||
+ lib.optionalString (runtimeInputs != [ ]) ''
|
||||
+ ''
|
||||
|
||||
export PATH="${lib.makeBinPath runtimeInputs}${lib.optionalString inheritPath ":$PATH"}"
|
||||
export PATH="${
|
||||
lib.concatStringsSep ":" (
|
||||
(lib.optionals (runtimeInputs != [ ]) [ (lib.makeBinPath runtimeInputs) ])
|
||||
++ (lib.optionals inheritPath [ "$PATH" ])
|
||||
)
|
||||
}"
|
||||
''
|
||||
|
||||
+ ''
|
||||
|
||||
${text}
|
||||
|
||||
@@ -83,6 +83,50 @@ linkFarm "writeShellApplication-tests" {
|
||||
'';
|
||||
};
|
||||
|
||||
test-no-inherit-path-no-runtimeInputs = checkShellApplication {
|
||||
name = "test-no-inherit-path-no-runtimeInputs";
|
||||
inheritPath = false;
|
||||
runtimeInputs = [ ];
|
||||
text = ''
|
||||
if [[ ''${#PATH} -eq 0 ]]; then
|
||||
echo -n "PATH is empty"
|
||||
fi
|
||||
'';
|
||||
expected = "PATH is empty";
|
||||
};
|
||||
|
||||
test-no-inherit-path-runtimeInputs = checkShellApplication {
|
||||
name = "test-no-inherit-path-runtimeInputs";
|
||||
inheritPath = false;
|
||||
runtimeInputs = [ hello ];
|
||||
text = ''
|
||||
extra_colon_pattern='(^:|:$)'
|
||||
if [[ ''${PATH} =~ $extra_colon_pattern ]]; then
|
||||
echo "PATH should not start or end with a colon: $PATH"
|
||||
fi
|
||||
if [[ ''${#PATH} -gt 0 ]]; then
|
||||
echo -n "PATH is not empty"
|
||||
fi
|
||||
'';
|
||||
expected = "PATH is not empty";
|
||||
};
|
||||
|
||||
test-inherit-path-no-runtimeInputs = checkShellApplication {
|
||||
name = "test-inherit-path-no-runtimeInputs";
|
||||
inheritPath = true;
|
||||
runtimeInputs = [ ];
|
||||
text = ''
|
||||
extra_colon_pattern='(^:|:$)'
|
||||
if [[ ''${PATH} =~ $extra_colon_pattern ]]; then
|
||||
echo "PATH should not start or end with a colon: $PATH"
|
||||
fi
|
||||
if [[ ''${#PATH} -gt 0 ]]; then
|
||||
echo -n "PATH is not empty"
|
||||
fi
|
||||
'';
|
||||
expected = "PATH is not empty";
|
||||
};
|
||||
|
||||
test-check-phase = checkShellApplication {
|
||||
name = "test-check-phase";
|
||||
text = "";
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
zlib,
|
||||
curl,
|
||||
expat,
|
||||
fuse,
|
||||
fuse3,
|
||||
openssl,
|
||||
autoreconfHook,
|
||||
@@ -30,9 +29,10 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
expat
|
||||
openssl
|
||||
python3
|
||||
]
|
||||
++ lib.optionals stdenv.hostPlatform.isLinux [ fuse3 ]
|
||||
++ lib.optionals stdenv.hostPlatform.isDarwin [ fuse ];
|
||||
fuse3
|
||||
];
|
||||
|
||||
env.CFLAGS = lib.optionalString stdenv.hostPlatform.isDarwin "-DFUSE_DARWIN_ENABLE_EXTENSIONS=0";
|
||||
|
||||
meta = {
|
||||
homepage = "http://afflib.sourceforge.net/";
|
||||
|
||||
@@ -18,18 +18,18 @@
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "audio-mirroring";
|
||||
version = "0.1.1";
|
||||
version = "0.1.3";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "mkg20001";
|
||||
repo = "audio-mirroring";
|
||||
tag = "v${version}";
|
||||
hash = "sha256-f4V5ZJvXhdwqS4kx99Lr2Eb8r08PRd3T4mbRoAyyIqE=";
|
||||
hash = "sha256-Idu15ZfY8JYVZhub0LRXYtWdiVCMVRyC3MVTX4JcbzY=";
|
||||
};
|
||||
|
||||
cargoDeps = rustPlatform.fetchCargoVendor {
|
||||
inherit pname version src;
|
||||
hash = "sha256-+mAdxaaQOO7AIn/o/J13FbHIvtepk8/okGxO6p6aGzI=";
|
||||
hash = "sha256-kiDGCl3De5dhDwwCf1F38gnGtfNpAVot0G0+Gxmyyp0=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
diff --git a/src/btfs.cc b/src/btfs.cc
|
||||
index eaac245..6bae7c0 100644
|
||||
--- a/src/btfs.cc
|
||||
+++ b/src/btfs.cc
|
||||
@@ -19,6 +19,10 @@ along with BTFS. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define FUSE_USE_VERSION 31
|
||||
|
||||
+#ifdef __APPLE__
|
||||
+#define FUSE_DARWIN_ENABLE_EXTENSIONS 0
|
||||
+#endif
|
||||
+
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -733,15 +737,9 @@ btfs_listxattr(const char *path, char *data, size_t len) {
|
||||
return xattrslen;
|
||||
}
|
||||
|
||||
-#ifdef __APPLE__
|
||||
-static int
|
||||
-btfs_getxattr(const char *path, const char *key, char *value, size_t len,
|
||||
- uint32_t position) {
|
||||
-#else
|
||||
static int
|
||||
btfs_getxattr(const char *path, const char *key, char *value, size_t len) {
|
||||
uint32_t position = 0;
|
||||
-#endif
|
||||
char xattr[16];
|
||||
int xattrlen = 0;
|
||||
|
||||
@@ -22,6 +22,11 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
sha256 = "sha256-JuofC4TpbZ56qiUrHeoK607YHVbwqwLGMIdUpsTm9Ic=";
|
||||
};
|
||||
|
||||
patches = [
|
||||
# https://github.com/johang/btfs/pull/103
|
||||
./disable-macfuse-extensions.patch
|
||||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
autoreconfHook
|
||||
pkg-config
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "cgl";
|
||||
version = "0.60.9";
|
||||
version = "0.60.10";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "coin-or";
|
||||
repo = "Cgl";
|
||||
rev = "releases/${finalAttrs.version}";
|
||||
hash = "sha256-E84yCrgpRMjt7owPLPk1ATW+aeHNw8V24DHgkb6boIE=";
|
||||
hash = "sha256-zkq8pdn4m56sGd3I6xID3M+u7BxVp0S5naKBjqAdeyE=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
|
||||
buildGoModule (finalAttrs: {
|
||||
pname = "cnspec";
|
||||
version = "13.0.0";
|
||||
version = "13.1.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "mondoohq";
|
||||
repo = "cnspec";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-qA48TBt1S4M6xyvfBELxbJd0R7PwY34naZctb4XRnwo=";
|
||||
hash = "sha256-579zSogioTKdsqOwTptJUqN1IEWnPzEmWrSjllqIYOY=";
|
||||
};
|
||||
|
||||
proxyVendor = true;
|
||||
|
||||
vendorHash = "sha256-CwR0/L+ptBKjBLLZ7I96+jxJyCAgM7V0etXz+H0vlhI=";
|
||||
vendorHash = "sha256-ZPJGtI5HTetjSDfkXmF2elyXPO7AmQn1zmXzEjNIIXc=";
|
||||
|
||||
subPackages = [ "apps/cnspec" ];
|
||||
|
||||
|
||||
@@ -8,17 +8,17 @@
|
||||
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "codebook";
|
||||
version = "0.3.32";
|
||||
version = "0.3.34";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "blopker";
|
||||
repo = "codebook";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-UkE1ND1ditGIlplHG6EslK2uDvRWz7jmn2UmUhlYbdE=";
|
||||
hash = "sha256-BMPwYw7BHywyDJLgHzJt6HsrI23Y+Ng+vcUdFNJH68M=";
|
||||
};
|
||||
|
||||
buildAndTestSubdir = "crates/codebook-lsp";
|
||||
cargoHash = "sha256-27+9vjTHBxJ3WM2e3xmTO2CmJvsmqN4nhqD0Sf0YtEw=";
|
||||
cargoHash = "sha256-q6oEHXGxItR9GW2vqpj2i6AN0hH8ybMQ+vkX4aljt/I=";
|
||||
|
||||
env = {
|
||||
CARGO_PROFILE_RELEASE_LTO = "fat";
|
||||
|
||||
@@ -29,6 +29,8 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
mbedtls
|
||||
];
|
||||
|
||||
env.CFLAGS = lib.optionalString stdenv.hostPlatform.isDarwin "-DFUSE_DARWIN_ENABLE_EXTENSIONS=0";
|
||||
|
||||
meta = {
|
||||
description = "Read BitLocker encrypted partitions in Linux";
|
||||
homepage = "https://github.com/Aorimn/dislocker";
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
let
|
||||
pname = "fflogs";
|
||||
version = "8.20.113";
|
||||
version = "9.0.24";
|
||||
src = fetchurl {
|
||||
url = "https://github.com/RPGLogs/Uploaders-fflogs/releases/download/v${version}/fflogs-v${version}.AppImage";
|
||||
hash = "sha256-mwbATBhkbeZ2f4KAytOgp8XbCL4dY7S7OPHj//4kqGQ=";
|
||||
hash = "sha256-9L9eNpK2MI3P+mhUDCAzfi3YDdWpHGjiUS5LjksUjqo=";
|
||||
};
|
||||
extracted = appimageTools.extractType2 { inherit pname version src; };
|
||||
in
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
|
||||
buildGoModule (finalAttrs: {
|
||||
pname = "olm";
|
||||
version = "1.4.2";
|
||||
version = "1.4.3";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "fosrl";
|
||||
repo = "olm";
|
||||
tag = finalAttrs.version;
|
||||
hash = "sha256-Tily8Srpr5GpKTYl3Ivm1b/VN2yEzbbHHABeoJvo3wo=";
|
||||
hash = "sha256-4dzbSW9AoFitypVOD/N4/mnUJwh0USgOwVqcopLkcYs=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-lqH/pMWeDsTJa39uJwHntCAUs0BwJiB0aMyFaI++5ms=";
|
||||
vendorHash = "sha256-D93SPwXAeoTLCbScjyH8AB9TJIF2b/UbLNMIQYi+B+c=";
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "ft2-clone";
|
||||
version = "2.11";
|
||||
version = "2.12";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "8bitbubsy";
|
||||
repo = "ft2-clone";
|
||||
rev = "v${finalAttrs.version}";
|
||||
hash = "sha256-thOQcsnFkDJh0P2Yu/1rCmt/M3Ikr88ffFHUDrgFNyk=";
|
||||
hash = "sha256-Ca4vp2uEF7rZJ+0lAmVqC/6F+2CgbDLK2GkbG5Tn//0=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ cmake ];
|
||||
|
||||
@@ -6,20 +6,21 @@
|
||||
fetchFromGitLab,
|
||||
nix-update-script,
|
||||
versionCheckHook,
|
||||
writableTmpDirAsHomeHook,
|
||||
}:
|
||||
|
||||
buildGoModule (finalAttrs: {
|
||||
pname = "gitlab-runner";
|
||||
version = "18.8.0";
|
||||
version = "18.9.0";
|
||||
|
||||
src = fetchFromGitLab {
|
||||
owner = "gitlab-org";
|
||||
repo = "gitlab-runner";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-rS7+BUdec+Z4G/dd5D/NHe3gbELWicg0Nmgx4zJAIX4=";
|
||||
hash = "sha256-U13SouwEfCVy5M8fv6rkCX0F+ecVYdsocvAdt3yxPJA=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-Br9TW+sg7PDOE2d8lVQ9Xv9+UD7JHzitdTOcyodHr+s=";
|
||||
vendorHash = "sha256-Ak1Q8FnTD8LKcN9xRc1gpcnUiambGC3CJP84cwQqTtM=";
|
||||
|
||||
# For patchShebangs
|
||||
buildInputs = [ bash ];
|
||||
@@ -85,6 +86,8 @@ buildGoModule (finalAttrs: {
|
||||
"-X ${ldflagsPackageVariablePrefix}.REVISION=v${finalAttrs.version}"
|
||||
];
|
||||
|
||||
nativeCheckInputs = [ writableTmpDirAsHomeHook ];
|
||||
|
||||
preCheck = ''
|
||||
# Make the tests pass outside of GitLab CI
|
||||
export CI=0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/shells/bash_test.go b/shells/bash_test.go
|
||||
index 9ed9e65ff..02b6e6d5f 100644
|
||||
index bbbe949f4..955992d3f 100644
|
||||
--- a/shells/bash_test.go
|
||||
+++ b/shells/bash_test.go
|
||||
@@ -4,11 +4,9 @@ package shells
|
||||
@@ -11,10 +11,10 @@ index 9ed9e65ff..02b6e6d5f 100644
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
- "github.com/stretchr/testify/require"
|
||||
"gitlab.com/gitlab-org/gitlab-runner/common"
|
||||
)
|
||||
|
||||
@@ -90,65 +88,6 @@ func TestBash_CheckForErrors(t *testing.T) {
|
||||
"gitlab.com/gitlab-org/gitlab-runner/common"
|
||||
"gitlab.com/gitlab-org/gitlab-runner/common/spec"
|
||||
@@ -78,65 +76,6 @@ func TestBash_CheckForErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,19 +43,15 @@ stdenv.mkDerivation rec {
|
||||
sha256 = "0017xg5agj3dy0hx71ijdcrxb72bjqv7x6aq7c9zxzyyw0mkxj0k";
|
||||
})
|
||||
(fetchpatch {
|
||||
url = "https://sources.debian.org/data/main/g/glob2/0.9.4.4-6/debian/patches/10_pthread_underlinkage.patch";
|
||||
url = "https://sources.debian.org/data/main/g/glob2/0.9.4.4-11/debian/patches/10_pthread_underlinkage.patch";
|
||||
sha256 = "sha256-L9POADlkgQbUQEUmx4s3dxXG9tS0w2IefpRGuQNRMI0=";
|
||||
})
|
||||
(fetchpatch {
|
||||
url = "https://sources.debian.org/data/main/g/glob2/0.9.4.4-6/debian/patches/link-boost-system.patch";
|
||||
sha256 = "sha256-ne6F2ZowB+TUmg3ePuUoPNxXI0ZJC6HEol3oQQHJTy4=";
|
||||
url = "https://sources.debian.org/data/main/g/glob2/0.9.4.4-11/debian/patches/scons.patch";
|
||||
sha256 = "sha256-kHuFQCmkCkogqK6vfHKGYeZrMvsdQ7h8B3CcCtjLr50=";
|
||||
})
|
||||
(fetchpatch {
|
||||
url = "https://sources.debian.org/data/main/g/glob2/0.9.4.4-6/debian/patches/scons.patch";
|
||||
sha256 = "sha256-Gah7SoVcd/Aljs0Nqo3YF0lZImUWtrGM4HbbQ4yrhHU=";
|
||||
})
|
||||
(fetchpatch {
|
||||
url = "https://sources.debian.org/data/main/g/glob2/0.9.4.4-6/debian/patches/boost-1.69.patch";
|
||||
url = "https://sources.debian.org/data/main/g/glob2/0.9.4.4-11/debian/patches/boost-1.69.patch";
|
||||
sha256 = "sha256-D7agFR4uyIHxQz690Q8EHPF+rTEoiGUpgkm7r5cL5SI=";
|
||||
})
|
||||
];
|
||||
@@ -77,6 +73,7 @@ stdenv.mkDerivation rec {
|
||||
scons
|
||||
bsdiff # bspatch
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
libGLU
|
||||
libGL
|
||||
@@ -98,8 +95,6 @@ stdenv.mkDerivation rec {
|
||||
"DATADIR=${placeholder "out"}/share/globulation2/glob2"
|
||||
];
|
||||
|
||||
env.NIX_LDFLAGS = "-lboost_system";
|
||||
|
||||
meta = {
|
||||
description = "RTS without micromanagement";
|
||||
mainProgram = "glob2";
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
lib,
|
||||
fetchFromGitHub,
|
||||
versionCheckHook,
|
||||
installShellFiles,
|
||||
|
||||
stdenv,
|
||||
clangStdenv,
|
||||
llvmPackages,
|
||||
nixosTests,
|
||||
|
||||
# https://goupile.org/en/build recommends a Paranoid build
|
||||
# which is not bit by bit reproducible, whereas others are
|
||||
profile ? "Paranoid",
|
||||
}:
|
||||
|
||||
assert lib.assertOneOf "profile" profile [
|
||||
"Fast"
|
||||
"Debug"
|
||||
"Paranoid"
|
||||
];
|
||||
|
||||
let
|
||||
stdenv' = if (profile == "Paranoid") then clangStdenv else stdenv;
|
||||
in
|
||||
stdenv'.mkDerivation (finalAttrs: {
|
||||
pname = "goupile";
|
||||
version = "3.12.1";
|
||||
|
||||
# https://github.com/Koromix/rygel/tags
|
||||
src = fetchFromGitHub {
|
||||
owner = "Koromix";
|
||||
repo = "rygel";
|
||||
tag = "goupile/${finalAttrs.version}";
|
||||
hash = "sha256-Pn/0tjezVKJedAtqj69avxeIK2l3l9FGioYSyEao12E=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
installShellFiles
|
||||
]
|
||||
++ lib.optionals (profile == "Paranoid") [
|
||||
llvmPackages.bintools
|
||||
];
|
||||
|
||||
# pipe2() is only exposed with _GNU_SOURCE
|
||||
NIX_CFLAGS_COMPILE = [ "-D_GNU_SOURCE" ];
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
./bootstrap.sh
|
||||
echo "goupile = ${finalAttrs.version}" >FelixVersions.ini
|
||||
./felix -s -p${profile} goupile
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
installBin bin/${profile}/goupile
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
doInstallCheck = true;
|
||||
versionCheckProgramArg = "--version";
|
||||
nativeInstallCheckInputs = [ versionCheckHook ];
|
||||
|
||||
passthru.tests = { inherit (nixosTests) goupile; };
|
||||
|
||||
meta = {
|
||||
changelog = "https://github.com/Koromix/rygel/blob/${finalAttrs.src.rev}/src/goupile/CHANGELOG.md";
|
||||
description = "Free design tool for secure forms including Clinical Report Forms (eCRF)";
|
||||
homepage = "https://goupile.org/en";
|
||||
license = lib.licenses.gpl3Plus; # sdpx headers
|
||||
platforms = lib.platforms.linux; # https://goupile.org/en/build
|
||||
mainProgram = "goupile";
|
||||
teams = with lib.teams; [ ngi ];
|
||||
};
|
||||
})
|
||||
@@ -6,105 +6,106 @@
|
||||
buildGoModule,
|
||||
buildNpmPackage,
|
||||
systemd,
|
||||
grafana-alloy,
|
||||
installShellFiles,
|
||||
versionCheckHook,
|
||||
nixosTests,
|
||||
nix-update-script,
|
||||
installShellFiles,
|
||||
testers,
|
||||
lld,
|
||||
useLLD ? stdenv.hostPlatform.isArmv7,
|
||||
}:
|
||||
|
||||
buildGoModule (finalAttrs: {
|
||||
pname = "grafana-alloy";
|
||||
version = "1.12.2";
|
||||
version = "1.14.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "grafana";
|
||||
repo = "alloy";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-C/yqsUjEwKnGRkxMOQkKfGdeERbvO/e7D7c3CyJ+cVY=";
|
||||
hash = "sha256-zgbbbuq+sb+nU1vgzaxEHGY77k+TXFrlvcvs/NSqQAM=";
|
||||
};
|
||||
|
||||
npmDeps = fetchNpmDeps {
|
||||
src = "${finalAttrs.src}/internal/web/ui";
|
||||
hash = "sha256-3J1Slka5bi+72NUaHBmDTtG1faJWRkOlkClKnUyiUsk=";
|
||||
hash = "sha256-GT0yisPn+3FCtWL3he0i5zPMlaWNparQDefU69G4Yis=";
|
||||
};
|
||||
|
||||
frontend = buildNpmPackage {
|
||||
pname = "alloy-frontend";
|
||||
inherit (finalAttrs) version src;
|
||||
|
||||
inherit (finalAttrs) npmDeps;
|
||||
sourceRoot = "${finalAttrs.src.name}/internal/web/ui";
|
||||
|
||||
inherit (finalAttrs) npmDeps;
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir $out
|
||||
mkdir -p $out
|
||||
cp -av dist $out/share
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
|
||||
proxyVendor = true;
|
||||
vendorHash = "sha256-Bq/6ld2LldSDhksNqGMHXZAeNHh74D07o2ETpQqMcP4=";
|
||||
patchPhase = ''
|
||||
cp -av ${finalAttrs.frontend}/share internal/web/ui/dist
|
||||
'';
|
||||
|
||||
nativeBuildInputs = [
|
||||
installShellFiles
|
||||
modRoot = "collector";
|
||||
|
||||
proxyVendor = true;
|
||||
vendorHash = "sha256-A1mbMmpUxg5T7//X5PL1CPGB1OMPhertFvz4sPFTgOg=";
|
||||
|
||||
subPackages = [ "." ];
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X github.com/grafana/alloy/internal/build.Version=${finalAttrs.version}"
|
||||
"-X github.com/grafana/alloy/internal/build.Branch=v${finalAttrs.version}"
|
||||
"-X github.com/grafana/alloy/internal/build.Revision=v${finalAttrs.version}"
|
||||
"-X github.com/grafana/alloy/internal/build.BuildUser=nix@nixpkgs"
|
||||
"-X github.com/grafana/alloy/internal/build.BuildDate=1970-01-01T00:00:00Z"
|
||||
];
|
||||
|
||||
tags = [
|
||||
"embedalloyui"
|
||||
"netgo"
|
||||
]
|
||||
++ lib.optionals useLLD [ lld ];
|
||||
++ lib.optionals stdenv.hostPlatform.isLinux [
|
||||
"promtail_journal_enabled"
|
||||
];
|
||||
|
||||
env =
|
||||
lib.optionalAttrs useLLD {
|
||||
NIX_CFLAGS_LINK = "-fuse-ld=lld";
|
||||
}
|
||||
// lib.optionalAttrs (stdenv.hostPlatform.isLinux) {
|
||||
# uses go-systemd, which uses libsystemd headers
|
||||
# Uses go-systemd, which uses libsystemd headers.
|
||||
# https://github.com/coreos/go-systemd/issues/351
|
||||
NIX_CFLAGS_COMPILE = "-I${lib.getDev systemd}/include";
|
||||
};
|
||||
|
||||
ldflags =
|
||||
let
|
||||
prefix = "github.com/grafana/alloy/internal/build";
|
||||
in
|
||||
[
|
||||
"-s"
|
||||
"-w"
|
||||
# https://github.com/grafana/alloy/blob/3201389252d2c011bee15ace0c9f4cdbcb978f9f/Makefile#L110
|
||||
"-X ${prefix}.Branch=v${finalAttrs.version}"
|
||||
"-X ${prefix}.Version=${finalAttrs.version}"
|
||||
"-X ${prefix}.Revision=v${finalAttrs.version}"
|
||||
"-X ${prefix}.BuildUser=nix"
|
||||
"-X ${prefix}.BuildDate=1970-01-01T00:00:00Z"
|
||||
];
|
||||
nativeBuildInputs = [
|
||||
installShellFiles
|
||||
]
|
||||
++ lib.optionals useLLD [ lld ];
|
||||
|
||||
tags = [
|
||||
"netgo"
|
||||
"builtinassets"
|
||||
"promtail_journal_enabled"
|
||||
];
|
||||
postInstall = lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
|
||||
mv -v $out/bin/otel_engine $out/bin/alloy
|
||||
|
||||
patchPhase = ''
|
||||
# Copy frontend build in
|
||||
cp -va "${finalAttrs.frontend}/share" "internal/web/ui/dist"
|
||||
installShellCompletion --cmd alloy \
|
||||
--bash <($out/bin/alloy completion bash) \
|
||||
--fish <($out/bin/alloy completion fish) \
|
||||
--zsh <($out/bin/alloy completion zsh)
|
||||
'';
|
||||
|
||||
subPackages = [
|
||||
"."
|
||||
];
|
||||
|
||||
checkFlags = [
|
||||
"-tags"
|
||||
"nonetwork" # disable network tests
|
||||
"-tags"
|
||||
"nodocker" # disable docker tests
|
||||
];
|
||||
doInstallCheck = true;
|
||||
nativeInstallCheckInputs = [ versionCheckHook ];
|
||||
versionCheckProgramArg = "-v";
|
||||
|
||||
# go-systemd uses libsystemd under the hood, which does dlopen(libsystemd) at
|
||||
# runtime.
|
||||
# Add to RUNPATH so it can be found.
|
||||
# runtime. Add to RPATH so it can be found.
|
||||
postFixup = lib.optionalString stdenv.hostPlatform.isLinux ''
|
||||
patchelf \
|
||||
--set-rpath "${
|
||||
@@ -113,20 +114,9 @@ buildGoModule (finalAttrs: {
|
||||
$out/bin/alloy
|
||||
'';
|
||||
|
||||
postInstall = lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
|
||||
installShellCompletion --cmd alloy \
|
||||
--bash <($out/bin/alloy completion bash) \
|
||||
--fish <($out/bin/alloy completion fish) \
|
||||
--zsh <($out/bin/alloy completion zsh)
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
tests = {
|
||||
inherit (nixosTests) alloy;
|
||||
version = testers.testVersion {
|
||||
version = "v${finalAttrs.version}";
|
||||
package = grafana-alloy;
|
||||
};
|
||||
};
|
||||
updateScript = nix-update-script {
|
||||
extraArgs = [
|
||||
@@ -134,21 +124,26 @@ buildGoModule (finalAttrs: {
|
||||
"v(.+)"
|
||||
];
|
||||
};
|
||||
# for nix-update to be able to find and update the hash
|
||||
# For nix-update to be able to find and update the hash.
|
||||
inherit (finalAttrs) npmDeps;
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Open source OpenTelemetry Collector distribution with built-in Prometheus pipelines and support for metrics, logs, traces, and profiles";
|
||||
mainProgram = "alloy";
|
||||
license = lib.licenses.asl20;
|
||||
description = "OpenTelemetry Collector distribution with programmable pipelines";
|
||||
longDescription = ''
|
||||
Grafana Alloy is an open source OpenTelemetry Collector distribution with
|
||||
built-in Prometheus pipelines and support for metrics, logs, traces, and
|
||||
profiles.
|
||||
'';
|
||||
homepage = "https://grafana.com/oss/alloy";
|
||||
changelog = "https://github.com/grafana/alloy/blob/${finalAttrs.src.rev}/CHANGELOG.md";
|
||||
license = lib.licenses.asl20;
|
||||
platforms = lib.platforms.unix;
|
||||
maintainers = with lib.maintainers; [
|
||||
azahi
|
||||
flokli
|
||||
hbjydev
|
||||
];
|
||||
platforms = lib.platforms.unix;
|
||||
mainProgram = "alloy";
|
||||
};
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
withDocumentation ? stdenv.buildPlatform.canExecute stdenv.hostPlatform,
|
||||
}:
|
||||
let
|
||||
version = "1.46.0";
|
||||
version = "1.47.1";
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
inherit version;
|
||||
@@ -34,10 +34,10 @@ rustPlatform.buildRustPackage {
|
||||
owner = "casey";
|
||||
repo = "just";
|
||||
tag = version;
|
||||
hash = "sha256-NE54LKS2bYBfQL+yLJPaG4iF7EiJfDqBfnsrlPo1+OE=";
|
||||
hash = "sha256-HGrUiPe4vVYNISovTb9PZt8s6xCUg+OWkrp8dPm9tWg=";
|
||||
};
|
||||
|
||||
cargoHash = "sha256-yyaJAWp6luizA/aQuUGhdxRX2Ofri4CeLIO3/ndSCzc=";
|
||||
cargoHash = "sha256-ZRcYVvodaQmQtBGnkTIOI3PXC6YQ1kqycm6Xh/MwIqA=";
|
||||
|
||||
nativeBuildInputs =
|
||||
lib.optionals (installShellCompletions || installManPages) [ installShellFiles ]
|
||||
|
||||
@@ -24,11 +24,11 @@ let
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "keycloak";
|
||||
version = "26.5.5";
|
||||
version = "26.5.6";
|
||||
|
||||
src = fetchzip {
|
||||
url = "https://github.com/keycloak/keycloak/releases/download/${finalAttrs.version}/keycloak-${finalAttrs.version}.zip";
|
||||
hash = "sha256-k6keuENMQ1S+4YN67E6vc48W8x4Le0Bw9E1+UBLyxh0=";
|
||||
hash = "sha256-lkBSzM0kPYe3301EJkY/NShaKpBz+7NuAK/MPNLwMX4=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -332,7 +332,10 @@ stdenv.mkDerivation rec {
|
||||
The Programs handle Schematic Capture, and PCB Layout with Gerber output.
|
||||
'';
|
||||
license = lib.licenses.gpl3Plus;
|
||||
maintainers = with lib.maintainers; [ korken89 ];
|
||||
maintainers = with lib.maintainers; [
|
||||
korken89
|
||||
ryand56
|
||||
];
|
||||
platforms = lib.platforms.all;
|
||||
broken = stdenv.hostPlatform.isDarwin;
|
||||
mainProgram = "kicad";
|
||||
|
||||
Generated
+44
-44
@@ -167,16 +167,16 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/collections",
|
||||
"version": "v12.53.0",
|
||||
"version": "v12.54.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/collections.git",
|
||||
"reference": "f35c084f0d9bc57895515cb4d0665797c66285fd"
|
||||
"reference": "86f874536cbda5f35c23a9908ee7f176caa4496e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/collections/zipball/f35c084f0d9bc57895515cb4d0665797c66285fd",
|
||||
"reference": "f35c084f0d9bc57895515cb4d0665797c66285fd",
|
||||
"url": "https://api.github.com/repos/illuminate/collections/zipball/86f874536cbda5f35c23a9908ee7f176caa4496e",
|
||||
"reference": "86f874536cbda5f35c23a9908ee7f176caa4496e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -223,11 +223,11 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2026-02-16T14:10:38+00:00"
|
||||
"time": "2026-02-25T15:25:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/conditionable",
|
||||
"version": "v12.53.0",
|
||||
"version": "v12.54.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/conditionable.git",
|
||||
@@ -273,7 +273,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/contracts",
|
||||
"version": "v12.53.0",
|
||||
"version": "v12.54.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/contracts.git",
|
||||
@@ -321,16 +321,16 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/filesystem",
|
||||
"version": "v12.53.0",
|
||||
"version": "v12.54.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/filesystem.git",
|
||||
"reference": "c4c3f8612f218afcf09f3c7f5c7dc9e282626800"
|
||||
"reference": "b91eede30e1bde98cb51fb4c4f28269a8dea593e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/filesystem/zipball/c4c3f8612f218afcf09f3c7f5c7dc9e282626800",
|
||||
"reference": "c4c3f8612f218afcf09f3c7f5c7dc9e282626800",
|
||||
"url": "https://api.github.com/repos/illuminate/filesystem/zipball/b91eede30e1bde98cb51fb4c4f28269a8dea593e",
|
||||
"reference": "b91eede30e1bde98cb51fb4c4f28269a8dea593e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -384,11 +384,11 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2026-02-13T20:26:32+00:00"
|
||||
"time": "2026-03-09T14:26:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/macroable",
|
||||
"version": "v12.53.0",
|
||||
"version": "v12.54.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/macroable.git",
|
||||
@@ -434,16 +434,16 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/reflection",
|
||||
"version": "v12.53.0",
|
||||
"version": "v12.54.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/reflection.git",
|
||||
"reference": "6188e97a587371b9951c2a7e337cd760308c17d7"
|
||||
"reference": "348cf5da9de89b596d7723be6425fb048e2bf4bb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/reflection/zipball/6188e97a587371b9951c2a7e337cd760308c17d7",
|
||||
"reference": "6188e97a587371b9951c2a7e337cd760308c17d7",
|
||||
"url": "https://api.github.com/repos/illuminate/reflection/zipball/348cf5da9de89b596d7723be6425fb048e2bf4bb",
|
||||
"reference": "348cf5da9de89b596d7723be6425fb048e2bf4bb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -481,20 +481,20 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2026-02-04T15:21:22+00:00"
|
||||
"time": "2026-02-25T15:25:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/support",
|
||||
"version": "v12.53.0",
|
||||
"version": "v12.54.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/support.git",
|
||||
"reference": "18d7d75366ddb9eded3b7f05173f791da47faf34"
|
||||
"reference": "e54208c0b5693becd8d3bec02f07e8db9aa4f512"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/support/zipball/18d7d75366ddb9eded3b7f05173f791da47faf34",
|
||||
"reference": "18d7d75366ddb9eded3b7f05173f791da47faf34",
|
||||
"url": "https://api.github.com/repos/illuminate/support/zipball/e54208c0b5693becd8d3bec02f07e8db9aa4f512",
|
||||
"reference": "e54208c0b5693becd8d3bec02f07e8db9aa4f512",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -561,20 +561,20 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2026-02-23T15:44:06+00:00"
|
||||
"time": "2026-03-06T15:24:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
"version": "v0.3.13",
|
||||
"version": "v0.3.14",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/prompts.git",
|
||||
"reference": "ed8c466571b37e977532fb2fd3c272c784d7050d"
|
||||
"reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/ed8c466571b37e977532fb2fd3c272c784d7050d",
|
||||
"reference": "ed8c466571b37e977532fb2fd3c272c784d7050d",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/9f0e371244eedfe2ebeaa72c79c54bb5df6e0176",
|
||||
"reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -618,22 +618,22 @@
|
||||
"description": "Add beautiful and user-friendly forms to your command-line applications.",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/prompts/issues",
|
||||
"source": "https://github.com/laravel/prompts/tree/v0.3.13"
|
||||
"source": "https://github.com/laravel/prompts/tree/v0.3.14"
|
||||
},
|
||||
"time": "2026-02-06T12:17:10+00:00"
|
||||
"time": "2026-03-01T09:02:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "3.11.1",
|
||||
"version": "3.11.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CarbonPHP/carbon.git",
|
||||
"reference": "f438fcc98f92babee98381d399c65336f3a3827f"
|
||||
"reference": "6a7e652845bb018c668220c2a545aded8594fbbf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f",
|
||||
"reference": "f438fcc98f92babee98381d399c65336f3a3827f",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf",
|
||||
"reference": "6a7e652845bb018c668220c2a545aded8594fbbf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -725,7 +725,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-29T09:26:29+00:00"
|
||||
"time": "2026-03-11T17:23:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/clock",
|
||||
@@ -958,16 +958,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.4.6",
|
||||
"version": "v7.4.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "6d643a93b47398599124022eb24d97c153c12f27"
|
||||
"reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/6d643a93b47398599124022eb24d97c153c12f27",
|
||||
"reference": "6d643a93b47398599124022eb24d97c153c12f27",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d",
|
||||
"reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1032,7 +1032,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.4.6"
|
||||
"source": "https://github.com/symfony/console/tree/v7.4.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1052,7 +1052,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-25T17:02:47+00:00"
|
||||
"time": "2026-03-06T14:06:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -2495,11 +2495,11 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.40",
|
||||
"version": "2.1.41",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
|
||||
"reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a2eae8f20856b3afe74bf1f9726ce8c11438e300",
|
||||
"reference": "a2eae8f20856b3afe74bf1f9726ce8c11438e300",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2544,7 +2544,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-23T15:04:35+00:00"
|
||||
"time": "2026-03-16T18:24:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
}:
|
||||
php.buildComposerProject2 (finalAttrs: {
|
||||
pname = "laravel";
|
||||
version = "5.24.7";
|
||||
version = "5.24.9";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "laravel";
|
||||
repo = "installer";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-szyoqX4wgJpQZO9H/WVq70A5n/3qV1SdBCKQc9vm4WY=";
|
||||
hash = "sha256-RlY6is5rRks2mXdE2/EXuSWX2CxJuK+q8yfsDcZMFBo=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ makeWrapper ];
|
||||
|
||||
composerLock = ./composer.lock;
|
||||
vendorHash = "sha256-yX6EmbopVUpbbVBfep1Rk84wUK5sxjrlzvii+s39SqA=";
|
||||
vendorHash = "sha256-o7YryCZjTm/O4ts21NjODqacdXnjWZUH8Dmr8fPnDEg=";
|
||||
|
||||
# Adding npm (nodejs) and php composer to path
|
||||
postInstall = ''
|
||||
|
||||
@@ -31,15 +31,15 @@
|
||||
writeScript,
|
||||
}:
|
||||
let
|
||||
id = "354596705";
|
||||
id = "373278730";
|
||||
in
|
||||
stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
pname = "multiviewer-for-f1";
|
||||
version = "2.5.1";
|
||||
version = "2.7.1";
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://releases.multiviewer.dev/download/${id}/multiviewer_${finalAttrs.version}_amd64.deb";
|
||||
hash = "sha256-9ts5CZD14CzJHiC3YoKWIEKiFpOrcUX1tRUhE4it5Mo=";
|
||||
url = "https://releases.multiviewer.app/download/${id}/multiviewer_${finalAttrs.version}_amd64.deb";
|
||||
hash = "sha256-BKXw8a4fUT+B7KBc6p/Heo+sAtWAG5b/D2iohuNOotY=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -25,14 +25,12 @@ from .models import (
|
||||
Profile,
|
||||
Remote,
|
||||
)
|
||||
from .process import PRESERVE_ENV, SSH_DEFAULT_OPTS, run_wrapper, run_wrapper_bg
|
||||
from .process import Args as ProcessArgs
|
||||
from .process import PRESERVE_ENV, SSH_DEFAULT_OPTS, run_wrapper
|
||||
from .utils import Args, dict_to_flags
|
||||
|
||||
FLAKE_FLAGS: Final = ["--extra-experimental-features", "nix-command flakes"]
|
||||
FLAKE_REPL_TEMPLATE: Final = "repl.nix.template"
|
||||
SYSTEMD_RUN_UNIT_PREFIX: Final = "nixos-rebuild-switch-to-configuration"
|
||||
SYSTEMD_RUN_CMD_PREFIX: Final = [
|
||||
SWITCH_TO_CONFIGURATION_CMD_PREFIX: Final = [
|
||||
"systemd-run",
|
||||
"-E",
|
||||
# Will be set to new value early in switch-to-configuration script,
|
||||
@@ -43,10 +41,11 @@ SYSTEMD_RUN_CMD_PREFIX: Final = [
|
||||
"-E",
|
||||
"NIXOS_NO_CHECK",
|
||||
"--collect",
|
||||
"--wait",
|
||||
"--no-ask-password",
|
||||
"--pipe",
|
||||
"--quiet",
|
||||
"--service-type=exec",
|
||||
"--unit=nixos-rebuild-switch-to-configuration",
|
||||
]
|
||||
logger: Final = logging.getLogger(__name__)
|
||||
|
||||
@@ -207,17 +206,20 @@ def copy_closure(
|
||||
append_local_env=env,
|
||||
)
|
||||
|
||||
def nix_copy(to_host: Remote, from_host: Remote) -> None:
|
||||
def nix_copy(to_host: Remote | None, from_host: Remote | None) -> None:
|
||||
host_flags = []
|
||||
if from_host is not None:
|
||||
host_flags += ["--from", f"{from_host.store_type}://{from_host.host}"]
|
||||
if to_host is not None:
|
||||
host_flags += ["--to", f"{to_host.store_type}://{to_host.host}"]
|
||||
|
||||
run_wrapper(
|
||||
[
|
||||
"nix",
|
||||
*FLAKE_FLAGS,
|
||||
"copy",
|
||||
*dict_to_flags(copy_flags),
|
||||
"--from",
|
||||
f"ssh://{from_host.host}",
|
||||
"--to",
|
||||
f"ssh://{to_host.host}",
|
||||
*host_flags,
|
||||
closure,
|
||||
],
|
||||
append_local_env=env,
|
||||
@@ -226,9 +228,12 @@ def copy_closure(
|
||||
match (to_host, from_host):
|
||||
case (x, y) if x == y:
|
||||
return
|
||||
case (Remote(_) as host, None) | (None, Remote(_) as host):
|
||||
# nix-copy-closure doesn't support store types other than "ssh".
|
||||
case (Remote(_) as host, None) | (None, Remote(_) as host) if (
|
||||
host.store_type == "ssh"
|
||||
):
|
||||
nix_copy_closure(host, to=bool(to_host))
|
||||
case (Remote(_), Remote(_)):
|
||||
case (Remote(_), _) | (_, Remote(_)):
|
||||
nix_copy(to_host, from_host)
|
||||
|
||||
|
||||
@@ -437,7 +442,10 @@ def get_generations(profile: Profile) -> list[Generation]:
|
||||
)
|
||||
|
||||
return sorted(
|
||||
[parse_path(p, profile) for p in profile.path.parent.glob("system-*-link")],
|
||||
[
|
||||
parse_path(p, profile)
|
||||
for p in profile.path.parent.glob(f"{profile.name}-*-link")
|
||||
],
|
||||
key=lambda d: d.id,
|
||||
)
|
||||
|
||||
@@ -662,80 +670,6 @@ def set_profile(
|
||||
remote=target_host,
|
||||
sudo=sudo,
|
||||
)
|
||||
|
||||
|
||||
def _has_systemd(target_host: Remote | None) -> bool:
|
||||
r = run_wrapper(
|
||||
["test", "-d", "/run/systemd/system"],
|
||||
remote=target_host,
|
||||
check=False,
|
||||
)
|
||||
return r.returncode == 0
|
||||
|
||||
|
||||
def _run_action(
|
||||
action: Literal[Action.SWITCH, Action.BOOT, Action.TEST, Action.DRY_ACTIVATE],
|
||||
path_to_config: Path,
|
||||
install_bootloader: bool,
|
||||
target_host: Remote | None,
|
||||
sudo: bool,
|
||||
prefix: ProcessArgs | None = None,
|
||||
) -> None:
|
||||
cmd: ProcessArgs = [path_to_config / "bin/switch-to-configuration", str(action)]
|
||||
if prefix:
|
||||
cmd = [*prefix, *cmd]
|
||||
|
||||
run_wrapper(
|
||||
cmd,
|
||||
env={
|
||||
"LOCALE_ARCHIVE": PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "1" if install_bootloader else "0",
|
||||
},
|
||||
remote=target_host,
|
||||
sudo=sudo,
|
||||
)
|
||||
|
||||
|
||||
def _run_action_with_systemd(
|
||||
action: Literal[Action.SWITCH, Action.BOOT, Action.TEST, Action.DRY_ACTIVATE],
|
||||
path_to_config: Path,
|
||||
install_bootloader: bool,
|
||||
target_host: Remote | None,
|
||||
sudo: bool,
|
||||
) -> None:
|
||||
unique_unit_name = SYSTEMD_RUN_UNIT_PREFIX + "-" + uuid.uuid4().hex[:8]
|
||||
journalctl = run_wrapper_bg(
|
||||
[
|
||||
"journalctl",
|
||||
"-f",
|
||||
f"--unit={unique_unit_name}",
|
||||
"--output=cat",
|
||||
],
|
||||
remote=target_host,
|
||||
sudo=sudo,
|
||||
)
|
||||
|
||||
try:
|
||||
_run_action(
|
||||
action=action,
|
||||
path_to_config=path_to_config,
|
||||
install_bootloader=install_bootloader,
|
||||
target_host=target_host,
|
||||
sudo=sudo,
|
||||
prefix=[*SYSTEMD_RUN_CMD_PREFIX, f"--unit={unique_unit_name}"],
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
run_wrapper(
|
||||
["systemctl", "stop", unique_unit_name],
|
||||
remote=target_host,
|
||||
sudo=sudo,
|
||||
)
|
||||
raise
|
||||
finally:
|
||||
journalctl.terminate()
|
||||
|
||||
|
||||
def switch_to_configuration(
|
||||
path_to_config: Path,
|
||||
action: Literal[Action.SWITCH, Action.BOOT, Action.TEST, Action.DRY_ACTIVATE],
|
||||
@@ -759,26 +693,29 @@ def switch_to_configuration(
|
||||
if not path_to_config.exists():
|
||||
raise NixOSRebuildError(f"specialisation not found: {specialisation}")
|
||||
|
||||
if _has_systemd(target_host):
|
||||
_run_action_with_systemd(
|
||||
action=action,
|
||||
path_to_config=path_to_config,
|
||||
install_bootloader=install_bootloader,
|
||||
target_host=target_host,
|
||||
sudo=sudo,
|
||||
)
|
||||
else:
|
||||
r = run_wrapper(
|
||||
["test", "-d", "/run/systemd/system"],
|
||||
remote=target_host,
|
||||
check=False,
|
||||
)
|
||||
cmd = SWITCH_TO_CONFIGURATION_CMD_PREFIX
|
||||
if r.returncode:
|
||||
logger.debug(
|
||||
"skipping systemd-run to switch configuration since systemd is "
|
||||
"not working in target host"
|
||||
)
|
||||
_run_action(
|
||||
action=action,
|
||||
path_to_config=path_to_config,
|
||||
install_bootloader=install_bootloader,
|
||||
target_host=target_host,
|
||||
sudo=sudo,
|
||||
)
|
||||
cmd = []
|
||||
|
||||
run_wrapper(
|
||||
[*cmd, path_to_config / "bin/switch-to-configuration", str(action)],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "1" if install_bootloader else "0",
|
||||
},
|
||||
remote=target_host,
|
||||
sudo=sudo,
|
||||
)
|
||||
|
||||
|
||||
def upgrade_channels(all_channels: bool = False, sudo: bool = False) -> None:
|
||||
|
||||
@@ -9,7 +9,7 @@ from collections.abc import Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from ipaddress import AddressValueError, IPv6Address
|
||||
from typing import Final, Literal, NamedTuple, Self, TextIO, TypedDict, Unpack, override
|
||||
from typing import Final, Literal, Self, TextIO, TypedDict, Unpack, override
|
||||
|
||||
from . import tmpdir
|
||||
|
||||
@@ -46,6 +46,7 @@ class Remote:
|
||||
host: str
|
||||
opts: list[str]
|
||||
sudo_password: str | None
|
||||
store_type: str
|
||||
|
||||
@classmethod
|
||||
def from_arg(
|
||||
@@ -57,13 +58,18 @@ class Remote:
|
||||
if not host:
|
||||
return None
|
||||
|
||||
try:
|
||||
store_type, host = host.split("://", 1)
|
||||
except ValueError:
|
||||
store_type = "ssh"
|
||||
|
||||
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)
|
||||
return cls(host, opts, sudo_password, store_type)
|
||||
|
||||
@staticmethod
|
||||
def _validate_opts(opts: list[str], ask_sudo_password: bool | None) -> None:
|
||||
@@ -117,18 +123,17 @@ def cleanup_ssh() -> None:
|
||||
atexit.register(cleanup_ssh)
|
||||
|
||||
|
||||
class _RunParams(NamedTuple):
|
||||
args: Args
|
||||
popen_env: dict[str, str] | None
|
||||
process_input: str | None
|
||||
|
||||
|
||||
def _build_run_params(
|
||||
def run_wrapper(
|
||||
args: Args,
|
||||
env: Mapping[str, EnvValue] | None,
|
||||
remote: Remote | None,
|
||||
sudo: bool,
|
||||
) -> _RunParams:
|
||||
*,
|
||||
check: bool = True,
|
||||
env: Mapping[str, EnvValue] | None = None,
|
||||
append_local_env: Mapping[str, str] | None = None,
|
||||
remote: Remote | None = None,
|
||||
sudo: bool = False,
|
||||
**kwargs: Unpack[RunKwargs],
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
"Wrapper around `subprocess.run` that supports extra functionality."
|
||||
process_input = None
|
||||
run_args: list[Arg] = list(args)
|
||||
final_args: list[Arg]
|
||||
@@ -190,64 +195,9 @@ def _build_run_params(
|
||||
final_args = run_args
|
||||
popen_env = None if env is None else resolved_env
|
||||
|
||||
return _RunParams(final_args, popen_env, process_input)
|
||||
|
||||
|
||||
def run_wrapper_bg(
|
||||
args: Args,
|
||||
env: Mapping[str, EnvValue] | None = None,
|
||||
remote: Remote | None = None,
|
||||
sudo: bool = False,
|
||||
) -> subprocess.Popen[str]:
|
||||
"Wrapper around `subprocess.Popen` that supports extra functionality."
|
||||
(final_args, popen_env, process_input) = _build_run_params(args, env, remote, sudo)
|
||||
|
||||
logger.debug(
|
||||
"calling Popen with args=%r",
|
||||
_sanitize_env_run_args(list(final_args)),
|
||||
)
|
||||
|
||||
if process_input:
|
||||
stdin = subprocess.PIPE
|
||||
else:
|
||||
stdin = None
|
||||
|
||||
r = subprocess.Popen(
|
||||
final_args,
|
||||
env=popen_env,
|
||||
stdin=stdin,
|
||||
# Hope nobody is using NixOS with non-UTF8 encodings, but
|
||||
# "surrogateescape" should still work in those systems.
|
||||
text=True,
|
||||
errors="surrogateescape",
|
||||
)
|
||||
|
||||
if r.stdin and process_input:
|
||||
r.stdin.write(process_input)
|
||||
r.stdin.write("\n")
|
||||
r.stdin.close()
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def run_wrapper(
|
||||
args: Args,
|
||||
*,
|
||||
check: bool = True,
|
||||
env: Mapping[str, EnvValue] | None = None,
|
||||
append_local_env: Mapping[str, str] | None = None,
|
||||
remote: Remote | None = None,
|
||||
sudo: bool = False,
|
||||
**kwargs: Unpack[RunKwargs],
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
"Wrapper around `subprocess.run` that supports extra functionality."
|
||||
(final_args, popen_env, process_input) = _build_run_params(
|
||||
list(args), env, remote, sudo
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
"calling run with args=%r, kwargs=%r, env=%r, append_local_env=%r",
|
||||
_sanitize_env_run_args(list(final_args)),
|
||||
_sanitize_env_run_args(remote_run_args if remote else run_args),
|
||||
kwargs,
|
||||
env,
|
||||
append_local_env,
|
||||
|
||||
@@ -166,15 +166,8 @@ def test_parse_args() -> None:
|
||||
{"NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM": "1"},
|
||||
clear=True,
|
||||
)
|
||||
@patch("uuid.uuid4")
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_nix_boot(
|
||||
mock_popen: Mock, mock_run: Mock, mock_uuid4: Mock, tmp_path: Path
|
||||
) -> None:
|
||||
test_uuid = uuid.UUID("58fe9784-f60a-42bc-aa94-eb8f1a7e5c17")
|
||||
mock_uuid4.return_value = test_uuid
|
||||
|
||||
def test_execute_nix_boot(mock_run: Mock, tmp_path: Path) -> None:
|
||||
nixpkgs_path = tmp_path / "nixpkgs"
|
||||
(nixpkgs_path / ".git").mkdir(parents=True)
|
||||
config_path = tmp_path / "test"
|
||||
@@ -245,8 +238,7 @@ def test_execute_nix_boot(
|
||||
),
|
||||
call(
|
||||
[
|
||||
*nr.nix.SYSTEMD_RUN_CMD_PREFIX,
|
||||
f"--unit={nr.nix.SYSTEMD_RUN_UNIT_PREFIX}-{test_uuid.hex[:8]}",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
config_path / "bin/switch-to-configuration",
|
||||
"boot",
|
||||
],
|
||||
@@ -267,8 +259,7 @@ def test_execute_nix_boot(
|
||||
# https://github.com/NixOS/nixpkgs/issues/437872
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_nix_build(mock_popen: Mock, mock_run: Mock, tmp_path: Path) -> None:
|
||||
def test_execute_nix_build(mock_run: Mock, tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "test"
|
||||
config_path.touch()
|
||||
|
||||
@@ -310,8 +301,7 @@ def test_execute_nix_build(mock_popen: Mock, mock_run: Mock, tmp_path: Path) ->
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_nix_build_vm(mock_popen: Mock, mock_run: Mock, tmp_path: Path) -> None:
|
||||
def test_execute_nix_build_vm(mock_run: Mock, tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "test"
|
||||
config_path.touch()
|
||||
|
||||
@@ -360,10 +350,7 @@ def test_execute_nix_build_vm(mock_popen: Mock, mock_run: Mock, tmp_path: Path)
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_nix_build_image_flake(
|
||||
mock_popen: Mock, mock_run: Mock, tmp_path: Path
|
||||
) -> None:
|
||||
def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "test"
|
||||
config_path.touch()
|
||||
|
||||
@@ -445,18 +432,11 @@ def test_execute_nix_build_image_flake(
|
||||
{"NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM": "1"},
|
||||
clear=True,
|
||||
)
|
||||
@patch("uuid.uuid4")
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_nix_switch_flake(
|
||||
mock_popen: Mock, mock_run: Mock, mock_uuid4: Mock, tmp_path: Path
|
||||
) -> None:
|
||||
def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "test"
|
||||
config_path.touch()
|
||||
|
||||
test_uuid = uuid.UUID("58fe9784-f60a-42bc-aa94-eb8f1a7e5c17")
|
||||
mock_uuid4.return_value = test_uuid
|
||||
|
||||
def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]:
|
||||
if args[0] == "nix":
|
||||
return CompletedProcess([], 0, str(config_path))
|
||||
@@ -526,8 +506,7 @@ def test_execute_nix_switch_flake(
|
||||
"env",
|
||||
"-i",
|
||||
"NIXOS_INSTALL_BOOTLOADER=1",
|
||||
*nr.nix.SYSTEMD_RUN_CMD_PREFIX,
|
||||
f"--unit={nr.nix.SYSTEMD_RUN_UNIT_PREFIX}-{test_uuid.hex[:8]}",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
config_path / "bin/switch-to-configuration",
|
||||
"switch",
|
||||
],
|
||||
@@ -544,13 +523,11 @@ def test_execute_nix_switch_flake(
|
||||
clear=True,
|
||||
)
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
@patch("uuid.uuid4", autospec=True)
|
||||
@patch(get_qualified_name(nr.services.cleanup_ssh), autospec=True)
|
||||
def test_execute_nix_switch_build_target_host(
|
||||
mock_cleanup_ssh: Mock,
|
||||
mock_uuid4: Mock,
|
||||
mock_popen: Mock,
|
||||
mock_run: Mock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
@@ -574,8 +551,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
return CompletedProcess([], 0)
|
||||
|
||||
mock_run.side_effect = run_side_effect
|
||||
test_uuid = uuid.UUID("58fe9784-f60a-42bc-aa94-eb8f1a7e5c17")
|
||||
mock_uuid4.side_effect = [uuid.UUID(int=0), uuid.UUID(int=1), test_uuid]
|
||||
mock_uuid4.return_value = uuid.UUID(int=0)
|
||||
|
||||
nr.execute(
|
||||
[
|
||||
@@ -668,7 +644,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
"--realise",
|
||||
str(config_path),
|
||||
"--add-root",
|
||||
"/tmp/tmpdir/00000000000000000000000000000001",
|
||||
"/tmp/tmpdir/00000000000000000000000000000000",
|
||||
],
|
||||
check=True,
|
||||
stdout=PIPE,
|
||||
@@ -772,8 +748,7 @@ def test_execute_nix_switch_build_target_host(
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"sh",
|
||||
*nr.nix.SYSTEMD_RUN_CMD_PREFIX,
|
||||
f"--unit={nr.nix.SYSTEMD_RUN_UNIT_PREFIX}-{test_uuid.hex[:8]}",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
str(config_path / "bin/switch-to-configuration"),
|
||||
"switch",
|
||||
],
|
||||
@@ -789,20 +764,13 @@ def test_execute_nix_switch_build_target_host(
|
||||
{"NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM": "1"},
|
||||
clear=True,
|
||||
)
|
||||
@patch("uuid.uuid4")
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
@patch(get_qualified_name(nr.services.cleanup_ssh), autospec=True)
|
||||
def test_execute_nix_switch_flake_target_host(
|
||||
mock_cleanup_ssh: Mock,
|
||||
mock_popen: Mock,
|
||||
mock_run: Mock,
|
||||
mock_uuid4: Mock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
test_uuid = uuid.UUID("58fe9784-f60a-42bc-aa94-eb8f1a7e5c17")
|
||||
mock_uuid4.return_value = test_uuid
|
||||
|
||||
config_path = tmp_path / "test"
|
||||
config_path.touch()
|
||||
|
||||
@@ -897,8 +865,7 @@ def test_execute_nix_switch_flake_target_host(
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"sh",
|
||||
*nr.nix.SYSTEMD_RUN_CMD_PREFIX,
|
||||
f"--unit={nr.nix.SYSTEMD_RUN_UNIT_PREFIX}-{test_uuid.hex[:8]}",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
str(config_path / "bin/switch-to-configuration"),
|
||||
"switch",
|
||||
],
|
||||
@@ -914,20 +881,13 @@ def test_execute_nix_switch_flake_target_host(
|
||||
{"NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM": "1"},
|
||||
clear=True,
|
||||
)
|
||||
@patch("uuid.uuid4")
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
@patch(get_qualified_name(nr.services.cleanup_ssh), autospec=True)
|
||||
def test_execute_nix_switch_flake_build_host(
|
||||
mock_cleanup_ssh: Mock,
|
||||
mock_popen: Mock,
|
||||
mock_run: Mock,
|
||||
mock_uuid4: Mock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
test_uuid = uuid.UUID("58fe9784-f60a-42bc-aa94-eb8f1a7e5c17")
|
||||
mock_uuid4.return_value = test_uuid
|
||||
|
||||
config_path = tmp_path / "test"
|
||||
config_path.touch()
|
||||
|
||||
@@ -1024,8 +984,7 @@ def test_execute_nix_switch_flake_build_host(
|
||||
),
|
||||
call(
|
||||
[
|
||||
*nr.nix.SYSTEMD_RUN_CMD_PREFIX,
|
||||
f"--unit={nr.nix.SYSTEMD_RUN_UNIT_PREFIX}-{test_uuid.hex[:8]}",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
config_path / "bin/switch-to-configuration",
|
||||
"switch",
|
||||
],
|
||||
@@ -1037,10 +996,7 @@ def test_execute_nix_switch_flake_build_host(
|
||||
|
||||
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_switch_rollback(
|
||||
mock_popen: Mock, mock_run: Mock, tmp_path: Path
|
||||
) -> None:
|
||||
def test_execute_switch_rollback(mock_run: Mock, tmp_path: Path) -> None:
|
||||
nixpkgs_path = tmp_path / "nixpkgs"
|
||||
(nixpkgs_path / ".git").mkdir(parents=True)
|
||||
|
||||
@@ -1117,8 +1073,7 @@ def test_execute_switch_rollback(
|
||||
|
||||
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_build(mock_popen: Mock, mock_run: Mock, tmp_path: Path) -> None:
|
||||
def test_execute_build(mock_run: Mock, tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "test"
|
||||
config_path.touch()
|
||||
mock_run.side_effect = [
|
||||
@@ -1147,9 +1102,8 @@ def test_execute_build(mock_popen: Mock, mock_run: Mock, tmp_path: Path) -> None
|
||||
|
||||
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_build_dry_run_build_and_target_remote(
|
||||
mock_popen: Mock, mock_run: Mock, tmp_path: Path
|
||||
mock_run: Mock, tmp_path: Path
|
||||
) -> None:
|
||||
config_path = tmp_path / "test"
|
||||
config_path.touch()
|
||||
@@ -1220,8 +1174,7 @@ def test_execute_build_dry_run_build_and_target_remote(
|
||||
|
||||
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_test_flake(mock_popen: Mock, mock_run: Mock, tmp_path: Path) -> None:
|
||||
def test_execute_test_flake(mock_run: Mock, tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "test"
|
||||
config_path.touch()
|
||||
|
||||
@@ -1271,13 +1224,11 @@ def test_execute_test_flake(mock_popen: Mock, mock_run: Mock, tmp_path: Path) ->
|
||||
|
||||
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
@patch("pathlib.Path.exists", autospec=True, return_value=True)
|
||||
@patch("pathlib.Path.mkdir", autospec=True)
|
||||
def test_execute_test_rollback(
|
||||
mock_path_mkdir: Mock,
|
||||
mock_path_exists: Mock,
|
||||
mock_popen: Mock,
|
||||
mock_run: Mock,
|
||||
) -> None:
|
||||
def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]:
|
||||
@@ -1340,15 +1291,8 @@ def test_execute_test_rollback(
|
||||
{"NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM": "1"},
|
||||
clear=True,
|
||||
)
|
||||
@patch("uuid.uuid4", autospec=True)
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_switch_store_path(
|
||||
mock_popen: Mock, mock_run: Mock, mock_uuid4: Mock, tmp_path: Path
|
||||
) -> None:
|
||||
test_uuid = uuid.UUID("58fe9784-f60a-42bc-aa94-eb8f1a7e5c17")
|
||||
mock_uuid4.return_value = test_uuid
|
||||
|
||||
def test_execute_switch_store_path(mock_run: Mock, tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "test-system"
|
||||
config_path.mkdir()
|
||||
|
||||
@@ -1386,8 +1330,7 @@ def test_execute_switch_store_path(
|
||||
),
|
||||
call(
|
||||
[
|
||||
*nr.nix.SYSTEMD_RUN_CMD_PREFIX,
|
||||
f"--unit={nr.nix.SYSTEMD_RUN_UNIT_PREFIX}-{test_uuid.hex[:8]}",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
config_path / "bin/switch-to-configuration",
|
||||
"switch",
|
||||
],
|
||||
@@ -1406,20 +1349,13 @@ def test_execute_switch_store_path(
|
||||
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
@patch("uuid.uuid4")
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@patch(get_qualified_name(nr.services.cleanup_ssh), autospec=True)
|
||||
@patch("subprocess.Popen")
|
||||
def test_execute_switch_store_path_target_host(
|
||||
mock_popen: Mock,
|
||||
mock_cleanup_ssh: Mock,
|
||||
mock_run: Mock,
|
||||
mock_uuid4: Mock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
test_uuid = uuid.UUID("58fe9784-f60a-42bc-aa94-eb8f1a7e5c17")
|
||||
mock_uuid4.return_value = test_uuid
|
||||
|
||||
config_path = tmp_path / "test-system"
|
||||
config_path.mkdir()
|
||||
|
||||
@@ -1512,8 +1448,7 @@ def test_execute_switch_store_path_target_host(
|
||||
"-c",
|
||||
"""'exec env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"sh",
|
||||
*nr.nix.SYSTEMD_RUN_CMD_PREFIX,
|
||||
f"--unit={nr.nix.SYSTEMD_RUN_UNIT_PREFIX}-{test_uuid.hex[:8]}",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
str(config_path / "bin/switch-to-configuration"),
|
||||
"switch",
|
||||
],
|
||||
|
||||
@@ -43,7 +43,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)
|
||||
target_host = m.Remote("target@remote", [], None, "ssh")
|
||||
assert m.Flake.parse("/path/to/flake", target_host) == m.Flake(
|
||||
"/path/to/flake", 'nixosConfigurations."remote"'
|
||||
)
|
||||
@@ -162,9 +162,9 @@ def test_flake_from_arg(
|
||||
return_value=subprocess.CompletedProcess([], 0, "remote-hostname\n"),
|
||||
),
|
||||
):
|
||||
assert m.Flake.from_arg("/path/to", m.Remote("user@host", [], None)) == m.Flake(
|
||||
"/path/to", 'nixosConfigurations."remote-hostname"'
|
||||
)
|
||||
assert m.Flake.from_arg(
|
||||
"/path/to", m.Remote("user@host", [], None, "ssh")
|
||||
) == m.Flake("/path/to", 'nixosConfigurations."remote-hostname"')
|
||||
|
||||
|
||||
@patch("pathlib.Path.mkdir", autospec=True)
|
||||
|
||||
@@ -2,7 +2,7 @@ import sys
|
||||
import textwrap
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from subprocess import PIPE, CompletedProcess, Popen
|
||||
from subprocess import PIPE, CompletedProcess
|
||||
from typing import Any
|
||||
from unittest.mock import ANY, Mock, call, patch
|
||||
|
||||
@@ -83,7 +83,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)
|
||||
build_host = m.Remote("user@host", [], None, "ssh")
|
||||
monkeypatch.setenv("NIX_SSHOPTS", "--ssh opts")
|
||||
|
||||
def run_wrapper_side_effect(
|
||||
@@ -177,7 +177,7 @@ def test_build_remote_flake(
|
||||
) -> None:
|
||||
monkeypatch.chdir(tmpdir)
|
||||
flake = m.Flake.parse("/flake.nix#hostname")
|
||||
build_host = m.Remote("user@host", [], None)
|
||||
build_host = m.Remote("user@host", [], None, "ssh")
|
||||
monkeypatch.setenv("NIX_SSHOPTS", "--ssh opts")
|
||||
|
||||
assert n.build_remote_flake(
|
||||
@@ -237,8 +237,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)
|
||||
build_host = m.Remote("user@build.host", [], None)
|
||||
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")
|
||||
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(
|
||||
@@ -246,6 +247,21 @@ def test_copy_closure(monkeypatch: MonkeyPatch) -> None:
|
||||
append_local_env={"NIX_SSHOPTS": " ".join(p.SSH_DEFAULT_OPTS)},
|
||||
)
|
||||
|
||||
with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run:
|
||||
n.copy_closure(closure, target_host_ng)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
"nix",
|
||||
"--extra-experimental-features",
|
||||
"nix-command flakes",
|
||||
"copy",
|
||||
"--to",
|
||||
"ssh-ng://user@target.host",
|
||||
closure,
|
||||
],
|
||||
append_local_env={"NIX_SSHOPTS": " ".join(p.SSH_DEFAULT_OPTS)},
|
||||
)
|
||||
|
||||
monkeypatch.setenv("NIX_SSHOPTS", "--ssh build-opt")
|
||||
with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run:
|
||||
n.copy_closure(closure, None, build_host, {"copy_flag": True})
|
||||
@@ -458,6 +474,10 @@ def test_get_generations(tmp_path: Path) -> None:
|
||||
(tmp_path / "system-3-link").symlink_to(nixos_path)
|
||||
(tmp_path / "system-2-link").symlink_to(nixos_path)
|
||||
|
||||
# An alternate profile; this shouldn't appear.
|
||||
(tmp_path / "custom").symlink_to(tmp_path / "custom-1-link")
|
||||
(tmp_path / "custom-1-link").symlink_to(nixos_path)
|
||||
|
||||
assert n.get_generations(m.Profile("system", tmp_path / "system")) == [
|
||||
m.Generation(id=1, current=False, timestamp=ANY),
|
||||
m.Generation(id=2, current=True, timestamp=ANY),
|
||||
@@ -465,6 +485,27 @@ def test_get_generations(tmp_path: Path) -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_get_generations_with_profile(tmp_path: Path) -> None:
|
||||
nixos_path = tmp_path / "nixos-system"
|
||||
nixos_path.mkdir()
|
||||
|
||||
(tmp_path / "custom").symlink_to(tmp_path / "custom-2-link")
|
||||
# In the "wrong" order on purpose to make sure we are sorting the results
|
||||
(tmp_path / "custom-1-link").symlink_to(nixos_path)
|
||||
(tmp_path / "custom-3-link").symlink_to(nixos_path)
|
||||
(tmp_path / "custom-2-link").symlink_to(nixos_path)
|
||||
|
||||
# An alternate profile; none of these should appear.
|
||||
(tmp_path / "system").symlink_to(tmp_path / "system-1-link")
|
||||
(tmp_path / "system-1-link").symlink_to(nixos_path)
|
||||
|
||||
assert n.get_generations(m.Profile("custom", tmp_path / "custom")) == [
|
||||
m.Generation(id=1, current=False, timestamp=ANY),
|
||||
m.Generation(id=2, current=True, timestamp=ANY),
|
||||
m.Generation(id=3, current=False, timestamp=ANY),
|
||||
]
|
||||
|
||||
|
||||
def test_get_generations_from_nix_env(tmp_path: Path) -> None:
|
||||
path = tmp_path / "test"
|
||||
path.touch()
|
||||
@@ -493,7 +534,7 @@ def test_get_generations_from_nix_env(tmp_path: Path) -> None:
|
||||
sudo=False,
|
||||
)
|
||||
|
||||
remote = m.Remote("user@host", [], "password")
|
||||
remote = m.Remote("user@host", [], "password", "ssh")
|
||||
with patch(
|
||||
get_qualified_name(n.run_wrapper, n), autospec=True, return_value=return_value
|
||||
) as mock_run:
|
||||
@@ -555,7 +596,6 @@ def test_list_generations(mock_get_generations: Mock, tmp_path: Path) -> None:
|
||||
|
||||
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
|
||||
def test_diff_closures(mock_run: Mock) -> None:
|
||||
|
||||
n.diff_closures(
|
||||
Path("/run/current-system"), Path("/nix/var/nix/profiles/system"), None
|
||||
)
|
||||
@@ -607,7 +647,7 @@ def test_rollback(mock_run: Mock, tmp_path: Path) -> None:
|
||||
sudo=False,
|
||||
)
|
||||
|
||||
target_host = m.Remote("user@localhost", [], None)
|
||||
target_host = m.Remote("user@localhost", [], None, "ssh")
|
||||
assert n.rollback(profile, target_host, True) == profile.path
|
||||
mock_run.assert_called_with(
|
||||
["nix-env", "--rollback", "-p", path],
|
||||
@@ -647,7 +687,7 @@ def test_rollback_temporary_profile(tmp_path: Path) -> None:
|
||||
sudo=False,
|
||||
)
|
||||
|
||||
target_host = m.Remote("user@localhost", [], None)
|
||||
target_host = m.Remote("user@localhost", [], None, "ssh")
|
||||
assert (
|
||||
n.rollback_temporary_profile(m.Profile("foo", path), target_host, True)
|
||||
== path.parent / "foo-2083-link"
|
||||
@@ -703,159 +743,139 @@ def test_set_profile(mock_run: Mock) -> None:
|
||||
|
||||
|
||||
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
|
||||
@patch(get_qualified_name(n.run_wrapper_bg, n), autospec=True)
|
||||
def test_switch_to_configuration_without_systemd_run(
|
||||
mock_run_bg: Mock, mock_run: Mock, monkeypatch: MonkeyPatch
|
||||
mock_run: Any, monkeypatch: MonkeyPatch
|
||||
) -> None:
|
||||
profile_path = Path("/path/to/profile")
|
||||
config_path = Path("/path/to/config")
|
||||
mock_run.return_value = CompletedProcess([], 1)
|
||||
|
||||
proc = Popen(["echo"])
|
||||
try:
|
||||
mock_run_bg.return_value = p
|
||||
mock_run.return_value = CompletedProcess([], 1)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "")
|
||||
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "")
|
||||
|
||||
n.switch_to_configuration(
|
||||
profile_path,
|
||||
m.Action.SWITCH,
|
||||
sudo=False,
|
||||
target_host=None,
|
||||
specialisation=None,
|
||||
install_bootloader=False,
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[profile_path / "bin/switch-to-configuration", "switch"],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "0",
|
||||
},
|
||||
n.switch_to_configuration(
|
||||
profile_path,
|
||||
m.Action.SWITCH,
|
||||
sudo=False,
|
||||
remote=None,
|
||||
target_host=None,
|
||||
specialisation=None,
|
||||
install_bootloader=False,
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[profile_path / "bin/switch-to-configuration", "switch"],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "0",
|
||||
},
|
||||
sudo=False,
|
||||
remote=None,
|
||||
)
|
||||
|
||||
with pytest.raises(m.NixOSRebuildError) as e:
|
||||
n.switch_to_configuration(
|
||||
config_path,
|
||||
m.Action.BOOT,
|
||||
sudo=False,
|
||||
target_host=None,
|
||||
specialisation="special",
|
||||
)
|
||||
assert (
|
||||
str(e.value)
|
||||
== "error: '--specialisation' can only be used with 'switch' and 'test'"
|
||||
with pytest.raises(m.NixOSRebuildError) as e:
|
||||
n.switch_to_configuration(
|
||||
config_path,
|
||||
m.Action.BOOT,
|
||||
sudo=False,
|
||||
target_host=None,
|
||||
specialisation="special",
|
||||
)
|
||||
assert (
|
||||
str(e.value)
|
||||
== "error: '--specialisation' can only be used with 'switch' and 'test'"
|
||||
)
|
||||
|
||||
target_host = m.Remote("user@localhost", [], None)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "/path/to/locale")
|
||||
mp.setenv("PATH", "/path/to/bin")
|
||||
mp.setattr(Path, Path.exists.__name__, lambda self: True)
|
||||
target_host = m.Remote("user@localhost", [], None, "ssh")
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "/path/to/locale")
|
||||
mp.setenv("PATH", "/path/to/bin")
|
||||
mp.setattr(Path, Path.exists.__name__, lambda self: True)
|
||||
|
||||
n.switch_to_configuration(
|
||||
Path("/path/to/config"),
|
||||
m.Action.TEST,
|
||||
sudo=True,
|
||||
target_host=target_host,
|
||||
install_bootloader=True,
|
||||
specialisation="special",
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
config_path / "specialisation/special/bin/switch-to-configuration",
|
||||
"test",
|
||||
],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "1",
|
||||
},
|
||||
n.switch_to_configuration(
|
||||
Path("/path/to/config"),
|
||||
m.Action.TEST,
|
||||
sudo=True,
|
||||
remote=target_host,
|
||||
target_host=target_host,
|
||||
install_bootloader=True,
|
||||
specialisation="special",
|
||||
)
|
||||
finally:
|
||||
proc.communicate(timeout=1)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
config_path / "specialisation/special/bin/switch-to-configuration",
|
||||
"test",
|
||||
],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "1",
|
||||
},
|
||||
sudo=True,
|
||||
remote=target_host,
|
||||
)
|
||||
|
||||
|
||||
@patch("uuid.uuid4")
|
||||
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
|
||||
@patch(get_qualified_name(n.run_wrapper_bg, n), autospec=True)
|
||||
def test_switch_to_configuration_with_systemd_run(
|
||||
mock_run_bg: Mock, mock_run: Mock, mock_uuid4: Mock, monkeypatch: MonkeyPatch
|
||||
mock_run: Mock, monkeypatch: MonkeyPatch
|
||||
) -> None:
|
||||
test_uuid = uuid.UUID("58fe9784-f60a-42bc-aa94-eb8f1a7e5c17")
|
||||
mock_uuid4.return_value = test_uuid
|
||||
|
||||
profile_path = Path("/path/to/profile")
|
||||
config_path = Path("/path/to/config")
|
||||
mock_run.return_value = CompletedProcess([], 0)
|
||||
|
||||
proc = Popen(["echo"])
|
||||
try:
|
||||
mock_run_bg.return_value = proc
|
||||
mock_run.return_value = CompletedProcess([], 0)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "")
|
||||
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "")
|
||||
|
||||
n.switch_to_configuration(
|
||||
profile_path,
|
||||
m.Action.SWITCH,
|
||||
sudo=False,
|
||||
target_host=None,
|
||||
specialisation=None,
|
||||
install_bootloader=False,
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
*n.SYSTEMD_RUN_CMD_PREFIX,
|
||||
f"--unit={n.SYSTEMD_RUN_UNIT_PREFIX}-{test_uuid.hex[:8]}",
|
||||
profile_path / "bin/switch-to-configuration",
|
||||
"switch",
|
||||
],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "0",
|
||||
},
|
||||
n.switch_to_configuration(
|
||||
profile_path,
|
||||
m.Action.SWITCH,
|
||||
sudo=False,
|
||||
remote=None,
|
||||
target_host=None,
|
||||
specialisation=None,
|
||||
install_bootloader=False,
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
*n.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
profile_path / "bin/switch-to-configuration",
|
||||
"switch",
|
||||
],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "0",
|
||||
},
|
||||
sudo=False,
|
||||
remote=None,
|
||||
)
|
||||
|
||||
target_host = m.Remote("user@localhost", [], None)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "/path/to/locale")
|
||||
mp.setenv("PATH", "/path/to/bin")
|
||||
mp.setattr(Path, Path.exists.__name__, lambda self: True)
|
||||
target_host = m.Remote("user@localhost", [], None, "ssh")
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "/path/to/locale")
|
||||
mp.setenv("PATH", "/path/to/bin")
|
||||
mp.setattr(Path, Path.exists.__name__, lambda self: True)
|
||||
|
||||
n.switch_to_configuration(
|
||||
Path("/path/to/config"),
|
||||
m.Action.TEST,
|
||||
sudo=True,
|
||||
target_host=target_host,
|
||||
install_bootloader=True,
|
||||
specialisation="special",
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
*n.SYSTEMD_RUN_CMD_PREFIX,
|
||||
f"--unit={n.SYSTEMD_RUN_UNIT_PREFIX}-{test_uuid.hex[:8]}",
|
||||
config_path / "specialisation/special/bin/switch-to-configuration",
|
||||
"test",
|
||||
],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "1",
|
||||
},
|
||||
n.switch_to_configuration(
|
||||
Path("/path/to/config"),
|
||||
m.Action.TEST,
|
||||
sudo=True,
|
||||
remote=target_host,
|
||||
target_host=target_host,
|
||||
install_bootloader=True,
|
||||
specialisation="special",
|
||||
)
|
||||
finally:
|
||||
proc.communicate(timeout=1)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
*n.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
config_path / "specialisation/special/bin/switch-to-configuration",
|
||||
"test",
|
||||
],
|
||||
env={
|
||||
"LOCALE_ARCHIVE": p.PRESERVE_ENV,
|
||||
"NIXOS_NO_CHECK": p.PRESERVE_ENV,
|
||||
"NIXOS_INSTALL_BOOTLOADER": "1",
|
||||
},
|
||||
sudo=True,
|
||||
remote=target_host,
|
||||
)
|
||||
|
||||
|
||||
@patch(
|
||||
|
||||
@@ -112,7 +112,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"),
|
||||
remote=m.Remote("user@localhost", ["--ssh", "opt"], "password", "ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@@ -142,7 +142,7 @@ def test_run_wrapper(mock_run: Any) -> None:
|
||||
check=True,
|
||||
sudo=True,
|
||||
env={"FOO": "bar"},
|
||||
remote=m.Remote("user@localhost", ["--ssh", "opt"], "password"),
|
||||
remote=m.Remote("user@localhost", ["--ssh", "opt"], "password", "ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@@ -181,7 +181,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),
|
||||
m.Remote("user@localhost", opts=[], sudo_password=None, store_type="ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@@ -208,6 +208,7 @@ def test_remote_from_name(monkeypatch: MonkeyPatch) -> None:
|
||||
"user@localhost",
|
||||
opts=[],
|
||||
sudo_password=None,
|
||||
store_type="ssh",
|
||||
)
|
||||
|
||||
with patch("getpass.getpass", autospec=True, return_value="password"):
|
||||
@@ -216,11 +217,12 @@ def test_remote_from_name(monkeypatch: MonkeyPatch) -> None:
|
||||
"user@localhost",
|
||||
opts=["-f", "foo", "-b", "bar", "-t"],
|
||||
sudo_password="password",
|
||||
store_type="ssh",
|
||||
)
|
||||
|
||||
|
||||
def test_ssh_host() -> None:
|
||||
remotes = {
|
||||
ssh_remotes = {
|
||||
"user@[fe80::1%25eth0]": "user@fe80::1%eth0",
|
||||
"[fe80::c98b%25enp4s0]": "fe80::c98b%enp4s0",
|
||||
"user@[2001::5fce:a:198]": "user@2001::5fce:a:198",
|
||||
@@ -231,12 +233,23 @@ def test_ssh_host() -> None:
|
||||
"localhost": "localhost",
|
||||
"user@example.org": "user@example.org",
|
||||
"example.org": "example.org",
|
||||
"ssh://explicit-store@localhost": "explicit-store@localhost",
|
||||
}
|
||||
ssh_ng_remotes = {
|
||||
"ssh-ng://example.org": "example.org",
|
||||
}
|
||||
|
||||
for host_input, expected in remotes.items():
|
||||
for host_input, expected in ssh_remotes.items():
|
||||
remote = m.Remote.from_arg(host_input, None, 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)
|
||||
assert remote is not None
|
||||
assert remote.ssh_host() == expected
|
||||
assert remote.store_type == "ssh-ng"
|
||||
|
||||
|
||||
@patch("subprocess.run", autospec=True)
|
||||
@@ -275,7 +288,7 @@ def test_custom_sudo_args(mock_run: Any) -> None:
|
||||
["test"],
|
||||
check=False,
|
||||
sudo=True,
|
||||
remote=m.Remote("user@localhost", [], None),
|
||||
remote=m.Remote("user@localhost", [], None, "ssh"),
|
||||
)
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
lndir,
|
||||
}:
|
||||
let
|
||||
version = "2.20.10";
|
||||
version = "2.20.11";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "paperless-ngx";
|
||||
repo = "paperless-ngx";
|
||||
tag = "v${version}";
|
||||
hash = "sha256-4kc1LyqcVKHQ/WnsZOL0zanbLIv27CGGgNZXVFBwCgQ=";
|
||||
hash = "sha256-Bn5k6h80nSNxWYsIpqVLXp+udzxDCY8f/jbgDvyATM0=";
|
||||
};
|
||||
|
||||
python = python3.override {
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "piday25";
|
||||
version = "0-unstable-2025-03-13";
|
||||
version = "0-unstable-2026-03-18";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "elkasztano";
|
||||
repo = "piday25";
|
||||
rev = "68b417a3016c58a2948cb3b39c9bde985d82bdb8";
|
||||
hash = "sha256-58ZBRmB990Tp+/nkuRZA+8cjCRFUBzdzu93Sk5uvKOE=";
|
||||
rev = "3fdeb37e33572c0924fc8f23a62824df8f6a9496";
|
||||
hash = "sha256-iNc6NUEekk793j3Ob02H9NB4SH1anYsWzixr8OiglOE=";
|
||||
};
|
||||
|
||||
cargoHash = "sha256-3uztB5/VevFyEz3S+VlAUPgDrNDJcwaTnHuXXYAX+MY=";
|
||||
|
||||
Generated
+1021
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
gradle_9,
|
||||
jdk25_headless,
|
||||
makeBinaryWrapper,
|
||||
nix-update-script,
|
||||
versionCheckHook,
|
||||
zig,
|
||||
}:
|
||||
|
||||
let
|
||||
jdk = jdk25_headless;
|
||||
gradle = gradle_9;
|
||||
gradleOverlay = gradle.override { java = jdk; };
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "pkl-lsp";
|
||||
version = "0.6.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "apple";
|
||||
repo = "pkl-lsp";
|
||||
tag = finalAttrs.version;
|
||||
hash = "sha256-V6MrDpdh4jnSiXWD0UbF/XXpLa95smCbdj9/jT0Xb3w=";
|
||||
leaveDotGit = true;
|
||||
postFetch = ''
|
||||
pushd $out
|
||||
git rev-parse HEAD | tr -d '\n' > .commit-hash
|
||||
rm -rf .git
|
||||
popd
|
||||
'';
|
||||
};
|
||||
|
||||
# Dependencies for tree-sitter compilation specific versions from gradle/libs.versions.toml
|
||||
treeSitterSrc = fetchFromGitHub {
|
||||
owner = "tree-sitter";
|
||||
repo = "tree-sitter";
|
||||
rev = "v0.25.3";
|
||||
hash = "sha256-xafeni6Z6QgPiKzvhCT2SyfPn0agLHo47y+6ExQXkzE=";
|
||||
};
|
||||
treeSitterPklSrc = fetchFromGitHub {
|
||||
owner = "apple";
|
||||
repo = "tree-sitter-pkl";
|
||||
rev = "v0.20.0";
|
||||
hash = "sha256-HfZ2NwO466Le2XFP1LZ2fLJgCq4Zq6hVpjChzsIoQgA=";
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
substituteInPlace buildSrc/src/main/kotlin/BuildInfo.kt \
|
||||
--replace-fail 'val jdkVersion: Int = 22' \
|
||||
'val jdkVersion: Int = ${lib.versions.major jdk.version}' \
|
||||
--replace-fail 'val executable: Path get() = installDir.resolve(if (os.isWindows) "zig.exe" else "zig")' \
|
||||
'val executable: Path get() = java.nio.file.Path.of("${lib.getExe zig}")' \
|
||||
|
||||
substituteInPlace build.gradle.kts \
|
||||
--replace-fail 'dependsOn(setupTreeSitterRepo)' "" \
|
||||
--replace-fail 'dependsOn(setupTreeSitterPklRepo)' "" \
|
||||
--replace-fail 'dependsOn(tasks.named("installZig"))' ""
|
||||
|
||||
# Ensure all pkl-cli platform variants are cached
|
||||
# Otherwise, deps.json only includes the current system's pkl-cli, and the tests fail
|
||||
cat >> build.gradle.kts << 'GRADLE_PATCH'
|
||||
val pklCliAllPlatforms by configurations.creating
|
||||
dependencies {
|
||||
for (platform in listOf("linux-amd64", "linux-aarch64", "macos-amd64", "macos-aarch64")) {
|
||||
pklCliAllPlatforms("org.pkl-lang:pkl-cli-$platform:''${libs.versions.pkl.get()}")
|
||||
}
|
||||
}
|
||||
GRADLE_PATCH
|
||||
|
||||
mkdir -p build/repos/{tree-sitter,tree-sitter-pkl}
|
||||
cp -r $treeSitterSrc/* build/repos/tree-sitter/
|
||||
cp -r $treeSitterPklSrc/* build/repos/tree-sitter-pkl/
|
||||
chmod +w -R build/repos/{tree-sitter,tree-sitter-pkl}
|
||||
'';
|
||||
|
||||
nativeBuildInputs = [
|
||||
gradleOverlay
|
||||
makeBinaryWrapper
|
||||
zig
|
||||
];
|
||||
|
||||
mitmCache = gradle.fetchDeps {
|
||||
inherit (finalAttrs) pname;
|
||||
data = ./deps.json;
|
||||
};
|
||||
|
||||
__darwinAllowLocalNetworking = true;
|
||||
|
||||
gradleFlags = [
|
||||
"-DreleaseBuild=true"
|
||||
"-Dfile.encoding=utf-8"
|
||||
"-Porg.gradle.java.installations.auto-download=false"
|
||||
"-Porg.gradle.java.installations.auto-detect=false"
|
||||
];
|
||||
|
||||
preBuild = ''
|
||||
gradleFlagsArray+=(-DcommitId=$(cat .commit-hash))
|
||||
'';
|
||||
|
||||
# running the checkPhase replaces the .jar produced by the buildPhase, and leads to this error:
|
||||
# Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
|
||||
# at org.pkl.lsp.cli.Main.main(Main.kt)
|
||||
# Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
|
||||
doCheck = false;
|
||||
|
||||
postInstallCheck = ''
|
||||
gradle test
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
install -D build/libs/pkl-lsp-${finalAttrs.version}.jar $out/lib/pkl-lsp/pkl-lsp.jar
|
||||
|
||||
makeWrapper ${lib.getExe' jdk "java"} $out/bin/pkl-lsp \
|
||||
--add-flags "-jar $out/lib/pkl-lsp/pkl-lsp.jar"
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
nativeInstallCheckInputs = [ versionCheckHook ];
|
||||
doInstallCheck = true;
|
||||
|
||||
passthru.updateScript = nix-update-script { };
|
||||
|
||||
meta = {
|
||||
description = "The Pkl Language Server";
|
||||
homepage = "https://pkl-lang.org/lsp/current/index.html";
|
||||
downloadPage = "https://github.com/apple/pkl-lsp";
|
||||
changelog = "https://pkl-lang.org/lsp/current/CHANGELOG.html#release-${finalAttrs.version}";
|
||||
license = lib.licenses.asl20;
|
||||
maintainers = with lib.maintainers; [ ryota2357 ];
|
||||
mainProgram = "pkl-lsp";
|
||||
platforms = lib.lists.intersectLists (
|
||||
lib.platforms.x86_64 ++ lib.platforms.aarch64
|
||||
) jdk.meta.platforms;
|
||||
sourceProvenance = with lib.sourceTypes; [
|
||||
fromSource
|
||||
binaryBytecode # mitm cache
|
||||
binaryNativeCode # mitm cache
|
||||
];
|
||||
};
|
||||
})
|
||||
@@ -6,10 +6,10 @@
|
||||
}:
|
||||
let
|
||||
pname = "remnote";
|
||||
version = "1.24.0";
|
||||
version = "1.24.7";
|
||||
src = fetchurl {
|
||||
url = "https://download2.remnote.io/remnote-desktop2/RemNote-${version}.AppImage";
|
||||
hash = "sha256-OV8o2AOoDXdz02tXbtelcIOVOT3PIiBYJf38mRuvWdM=";
|
||||
hash = "sha256-W4KM7QgkO+5Rr12IxlTlqp63LAakUJlMOX68JWBet6c=";
|
||||
};
|
||||
appimageContents = appimageTools.extractType2 { inherit pname version src; };
|
||||
in
|
||||
|
||||
@@ -16,18 +16,18 @@
|
||||
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "ruff";
|
||||
version = "0.15.5";
|
||||
version = "0.15.6";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "astral-sh";
|
||||
repo = "ruff";
|
||||
tag = finalAttrs.version;
|
||||
hash = "sha256-bemgVXV/Bkp0aXmWX+R6Aas2/naOx0XEGp0ofh+vyyM=";
|
||||
hash = "sha256-a8A9FdfrGCyg6TdunsZcXqAzeXb9pCWO/02f9Nl5juU=";
|
||||
};
|
||||
|
||||
cargoBuildFlags = [ "--package=ruff" ];
|
||||
|
||||
cargoHash = "sha256-NaWWX6EAVkEg/KQ+Up0t2fh/24fnTo6i5dDZoOWErjg=";
|
||||
cargoHash = "sha256-TD5FLdi4YJwDzJpCctNKYxUNj/VgMnB/OBp3exk3cZw=";
|
||||
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
'AC_CHECK_DECLS(fdatasync)' ""
|
||||
'';
|
||||
|
||||
env.CFLAGS = lib.optionalString stdenv.hostPlatform.isDarwin "-DFUSE_DARWIN_ENABLE_EXTENSIONS=0";
|
||||
|
||||
meta = {
|
||||
homepage = "https://github.com/archiecobbs/s3backer";
|
||||
description = "FUSE-based single file backing store via Amazon S3";
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
buildNpmPackage,
|
||||
fetchFromGitHub,
|
||||
makeWrapper,
|
||||
electron_39,
|
||||
electron_40,
|
||||
vulkan-loader,
|
||||
makeDesktopItem,
|
||||
copyDesktopItems,
|
||||
@@ -18,20 +18,20 @@
|
||||
}:
|
||||
|
||||
let
|
||||
electron = electron_39;
|
||||
electron = electron_40;
|
||||
in
|
||||
buildNpmPackage (finalAttrs: {
|
||||
pname = "shogihome";
|
||||
version = "1.26.1";
|
||||
version = "1.27.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "sunfish-shogi";
|
||||
repo = "shogihome";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-7kDk85tN4uP0WJnof8yyn0M85Qairls5ZqhKwwhRQxc=";
|
||||
hash = "sha256-T1MgcqCi9rwN86vgCAshokznMXh+masFLcO43sz2bo0=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-Sft5fEf86o1uUJ+yszx9XgQBGNRc+9aKRyR5rOelgQw=";
|
||||
npmDepsHash = "sha256-5tZQCxql6jZAEU+e/hkQYnaHy1l5dWaH/p2rbGDAX14=";
|
||||
|
||||
postPatch = ''
|
||||
substituteInPlace package.json \
|
||||
|
||||
@@ -22,9 +22,6 @@
|
||||
openssh,
|
||||
}:
|
||||
|
||||
let
|
||||
fuse = if stdenv.hostPlatform.isDarwin then macfuse-stubs.override { isFuse3 = true; } else fuse3;
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "sshfs-fuse";
|
||||
inherit version;
|
||||
@@ -46,7 +43,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
makeWrapper
|
||||
];
|
||||
buildInputs = [
|
||||
fuse
|
||||
fuse3
|
||||
glib
|
||||
];
|
||||
nativeCheckInputs = [
|
||||
@@ -75,7 +72,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
checkPhase = lib.optionalString (!stdenv.hostPlatform.isDarwin) ''
|
||||
# The tests need fusermount:
|
||||
mkdir bin
|
||||
cp ${fuse}/bin/fusermount3 bin/fusermount
|
||||
cp ${fuse3}/bin/fusermount3 bin/fusermount
|
||||
export PATH=bin:$PATH
|
||||
# Can't access /dev/fuse within the sandbox: "FUSE kernel module does not seem to be loaded"
|
||||
substituteInPlace test/util.py --replace "/dev/fuse" "/dev/null"
|
||||
|
||||
@@ -199,6 +199,12 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
# Not sure why this fails
|
||||
+ lib.optionalString stdenv.hostPlatform.isAarch64 ''
|
||||
rm llvm/test/tools/llvm-exegesis/AArch64/latency-by-opcode-name.s
|
||||
''
|
||||
# The second llvm-install-name-tool invocation fails with
|
||||
# "is not a Mach-O file" on aarch64-linux, even on a fresh copy of
|
||||
# the original yaml2obj output. Root cause unknown.
|
||||
+ lib.optionalString stdenv.hostPlatform.isAarch64 ''
|
||||
rm llvm/test/tools/llvm-objcopy/MachO/install-name-tool-change.test
|
||||
'';
|
||||
|
||||
postInstall = ''
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
|
||||
buildNpmPackage rec {
|
||||
pname = "vacuum-tube";
|
||||
version = "1.6.0";
|
||||
version = "1.6.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "shy1132";
|
||||
repo = "VacuumTube";
|
||||
tag = "v${version}";
|
||||
hash = "sha256-BnFI517pXKsHQ8AJMRzAlXBTLMLhjyEasIhZdSHtyC0=";
|
||||
hash = "sha256-DbcJJ9FL9LPCQrg6lGa5N9KC8stXLdaMI+2hKAiZFxQ=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-R7DISsTJv/DDi8uJTWF+6/P8K86BguxtZNsaL2qCxhY=";
|
||||
npmDepsHash = "sha256-/TAGGiNuT7YC29U9n6M+zD51kecbAPXzzEJU5ey1hXs=";
|
||||
makeCacheWritable = true;
|
||||
|
||||
env = {
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
}:
|
||||
let
|
||||
pname = "volanta";
|
||||
version = "1.15.3";
|
||||
build = "64ba2e0c";
|
||||
version = "1.16.3";
|
||||
build = "581a1e68";
|
||||
src = fetchurl {
|
||||
url = "https://cdn.volanta.app/software/volanta-app/${version}-${build}/volanta-${version}.AppImage";
|
||||
hash = "sha256-rTonFExYHXLuRWf98IsNE7KqGrRMRC+Hke6CGKJWLAA=";
|
||||
hash = "sha256-5187tE37dRyqjBa8P0Jwio2lBd8qd+tEZgl/98nGQy8=";
|
||||
};
|
||||
appImageContents = appimageTools.extract { inherit pname version src; };
|
||||
in
|
||||
|
||||
@@ -9,20 +9,20 @@
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "vultisig-cli";
|
||||
version = "0.5.0";
|
||||
version = "0.6.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "vultisig";
|
||||
repo = "vultisig-sdk";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-vpWoKxdiUSSI8xGYXmnduJnB3zB3jpBMxz+9eGXJgvM=";
|
||||
hash = "sha256-eQvWD0Jubtp0wfmuTBN4Mr4rKqoEvMiAGI5D8GAHYDY=";
|
||||
};
|
||||
|
||||
missingHashes = ./missing-hashes.json;
|
||||
|
||||
offlineCache = yarn-berry.fetchYarnBerryDeps {
|
||||
inherit (finalAttrs) src missingHashes;
|
||||
hash = "sha256-ZJfLfaTvJKyCh4FtOs7IyZskBBjrJLjI0/9hphclFvU=";
|
||||
hash = "sha256-SQ2C01dVSzJwzCvJUclcSiGTPz7RJfO3fYPCZbvnAHk=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "zed-discord-presence";
|
||||
version = "0.10.1";
|
||||
version = "0.11.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "xhyrom";
|
||||
repo = "zed-discord-presence";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-7bTLMrvcgT5/ziyD4ieDGx7218rezQToHqAwEAwYB/E=";
|
||||
hash = "sha256-HmSJipRWVB1rXyO5ZK1ksyCLDzSJD820Klo88A7NLx4=";
|
||||
};
|
||||
|
||||
cargoBuildFlags = [ "--package discord-presence-lsp" ];
|
||||
cargoHash = "sha256-cOG9VeKdi1mSYpOPGYXLwHksEKJypvSK6vMFaO7TYBg=";
|
||||
cargoHash = "sha256-x9sB90jW7v2SGggLILgLbBfFV7DkJazcrUiKAfIroMA=";
|
||||
|
||||
passthru.updateScript = nix-update-script { };
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
lib,
|
||||
mkCoqDerivation,
|
||||
coq,
|
||||
ceres-bs,
|
||||
equations,
|
||||
metarocq-erasure-plugin,
|
||||
version ? null,
|
||||
}:
|
||||
|
||||
(mkCoqDerivation {
|
||||
pname = "CakeMLExtraction";
|
||||
owner = "peregrine-project";
|
||||
repo = "cakeml-backend";
|
||||
opam-name = "rocq-cakeml-extraction";
|
||||
|
||||
inherit version;
|
||||
defaultVersion =
|
||||
let
|
||||
case = coq: mr: out: {
|
||||
cases = [
|
||||
coq
|
||||
mr
|
||||
];
|
||||
inherit out;
|
||||
};
|
||||
in
|
||||
with lib.versions;
|
||||
lib.switch
|
||||
[
|
||||
coq.coq-version
|
||||
metarocq-erasure-plugin.version
|
||||
]
|
||||
[
|
||||
(case (range "9.0" "9.1") (range "1.4" "1.5.1") "0.1.0")
|
||||
]
|
||||
null;
|
||||
release = {
|
||||
"0.1.0".sha256 = "sha256-diDUTj0l4vliov9+Lg8lNRdkLE7JAfJn8OU7J/HgmDE=";
|
||||
};
|
||||
releaseRev = v: "v${v}";
|
||||
|
||||
mlPlugin = false;
|
||||
useDune = false;
|
||||
|
||||
buildInputs = [
|
||||
equations
|
||||
metarocq-erasure-plugin
|
||||
ceres-bs
|
||||
];
|
||||
propagatedBuildInputs = [ coq.ocamlPackages.findlib ];
|
||||
|
||||
meta = with lib; {
|
||||
homepage = "https://peregrine-project.github.io/";
|
||||
description = "CakeML backend for Peregrine";
|
||||
maintainers = with maintainers; [ _4ever2 ];
|
||||
};
|
||||
})
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
pkg-config,
|
||||
mkCoqDerivation,
|
||||
coq,
|
||||
wasmcert,
|
||||
compcert,
|
||||
metarocq-erasure-plugin,
|
||||
metarocq-safechecker-plugin,
|
||||
ExtLib,
|
||||
version ? null,
|
||||
}:
|
||||
|
||||
with lib;
|
||||
mkCoqDerivation {
|
||||
pname = "CertiRocq";
|
||||
owner = "CertiRocq";
|
||||
repo = "certirocq";
|
||||
opam-name = "rocq-certirocq";
|
||||
mlPlugin = true;
|
||||
|
||||
inherit version;
|
||||
defaultVersion =
|
||||
let
|
||||
case = coq: mr: out: {
|
||||
cases = [
|
||||
coq
|
||||
mr
|
||||
];
|
||||
inherit out;
|
||||
};
|
||||
in
|
||||
lib.switch
|
||||
[
|
||||
coq.coq-version
|
||||
metarocq-erasure-plugin.version
|
||||
]
|
||||
[
|
||||
(case "9.1" "1.5.1-9.1" "0.9.1+9.1")
|
||||
]
|
||||
null;
|
||||
release = {
|
||||
"0.9.1+9.1".sha256 = "sha256-YsweBaoq8+QG63e7Llp/4bHldAFnSQSyMumJkb+Bsp0=";
|
||||
};
|
||||
releaseRev = v: "v${v}";
|
||||
|
||||
buildInputs = [
|
||||
pkgs.clang
|
||||
];
|
||||
|
||||
propagatedBuildInputs = [
|
||||
wasmcert
|
||||
compcert
|
||||
ExtLib
|
||||
metarocq-erasure-plugin
|
||||
metarocq-safechecker-plugin
|
||||
];
|
||||
|
||||
patchPhase = ''
|
||||
patchShebangs ./configure.sh
|
||||
patchShebangs ./clean_extraction.sh
|
||||
patchShebangs ./make_plugin.sh
|
||||
'';
|
||||
|
||||
configurePhase = ''
|
||||
./configure.sh local
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
make all
|
||||
make plugins
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
OUTDIR=$out/lib/coq/${coq.coq-version}/user-contrib
|
||||
|
||||
DST=$OUTDIR/CertiRocq/Plugin/runtime make -C runtime install
|
||||
COQLIBINSTALL=$OUTDIR make -C theories install
|
||||
COQLIBINSTALL=$OUTDIR make -C libraries install
|
||||
COQLIBINSTALL=$OUTDIR COQPLUGININSTALL=$OCAMLFIND_DESTDIR make -C plugin install
|
||||
COQLIBINSTALL=$OUTDIR COQPLUGININSTALL=$OCAMLFIND_DESTDIR make -C cplugin install
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "CertiRocq";
|
||||
maintainers = with maintainers; [
|
||||
womeier
|
||||
_4ever2
|
||||
];
|
||||
license = licenses.mit;
|
||||
};
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
libGL,
|
||||
openal,
|
||||
luajit,
|
||||
lua5_1,
|
||||
freetype,
|
||||
physfs,
|
||||
libmodplug,
|
||||
@@ -22,14 +21,14 @@
|
||||
cmake,
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "love";
|
||||
version = "11.5";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "love2d";
|
||||
repo = "love";
|
||||
rev = version;
|
||||
tag = finalAttrs.version;
|
||||
sha256 = "sha256-wZktNh4UB3QH2wAIIlnYUlNoXbjEDwUmPnT4vesZNm0=";
|
||||
};
|
||||
|
||||
@@ -40,7 +39,7 @@ stdenv.mkDerivation rec {
|
||||
buildInputs = [
|
||||
SDL2
|
||||
openal
|
||||
(if stdenv.isDarwin then lua5_1 else luajit)
|
||||
luajit
|
||||
freetype
|
||||
physfs
|
||||
libmodplug
|
||||
@@ -51,7 +50,7 @@ stdenv.mkDerivation rec {
|
||||
which
|
||||
libtool
|
||||
]
|
||||
++ lib.optionals stdenv.isLinux [
|
||||
++ lib.optionals stdenv.hostPlatform.isLinux [
|
||||
libx11 # SDL2 optional depend, for SDL_syswm.h
|
||||
libGLU
|
||||
libGL
|
||||
@@ -61,9 +60,10 @@ stdenv.mkDerivation rec {
|
||||
# On Darwin, autotools doesn't compile macOS-specific module (src/common/macosx.mm),
|
||||
# leading to stubbed functions and segfaults
|
||||
cmakeFlags = [
|
||||
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5" # Required by LÖVE's CMakeLists.txt
|
||||
"-DCMAKE_SKIP_BUILD_RPATH=ON" # Don't include build directory in RPATH
|
||||
"-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON" # Use install RPATH even during build
|
||||
(lib.cmakeFeature "CMAKE_POLICY_VERSION_MINIMUM" "3.5") # Required by LÖVE's CMakeLists.txt
|
||||
(lib.cmakeBool "CMAKE_SKIP_BUILD_RPATH" true) # Don't include build directory in RPATH
|
||||
(lib.cmakeBool "CMAKE_BUILD_WITH_INSTALL_RPATH" true) # Use install RPATH even during build
|
||||
(lib.cmakeBool "LOVE_JIT" true) # Enable LuaJIT support even though it is warned about for Apple
|
||||
];
|
||||
|
||||
env.NIX_CFLAGS_COMPILE = "-DluaL_reg=luaL_Reg"; # needed since luajit-2.1.0-beta3
|
||||
@@ -81,7 +81,7 @@ stdenv.mkDerivation rec {
|
||||
cp $src/platform/unix/love.6 $out/share/man/man1/love.1
|
||||
gzip -9n $out/share/man/man1/love.1
|
||||
''
|
||||
+ lib.optionalString stdenv.isLinux ''
|
||||
+ lib.optionalString stdenv.hostPlatform.isLinux ''
|
||||
# Install Linux-specific files (desktop, mime, icons)
|
||||
mkdir -p $out/share/applications
|
||||
mkdir -p $out/share/mime/packages
|
||||
@@ -112,4 +112,4 @@ stdenv.mkDerivation rec {
|
||||
platforms = lib.platforms.linux ++ lib.platforms.darwin;
|
||||
maintainers = [ lib.maintainers.raskin ];
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
|
||||
buildPythonPackage (finalAttrs: {
|
||||
pname = "gvm-tools";
|
||||
version = "25.4.8";
|
||||
version = "25.4.9";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "greenbone";
|
||||
repo = "gvm-tools";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-tKUaUo9Sr4d9mLdbbmn+OmAgUcEuwWSzCYY4BPJ4UKw=";
|
||||
hash = "sha256-dt7njGUqi6zfwUz0gSdOHWnSUJ+yJ7qJ3RttoPweR3c=";
|
||||
};
|
||||
|
||||
__darwinAllowLocalNetworking = true;
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "osc";
|
||||
version = "1.24.0";
|
||||
version = "1.25.0";
|
||||
format = "setuptools";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "openSUSE";
|
||||
repo = "osc";
|
||||
rev = version;
|
||||
hash = "sha256-EPt+HTvDhBEs1sf1yrG+aawRcP1yd/+kY4OTeVHHFt4=";
|
||||
hash = "sha256-ES4HhWlJx7fRf9rXWBeAANyCy1eC1Rz6yFczXvQ66Vo=";
|
||||
};
|
||||
|
||||
buildInputs = [ bashInteractive ]; # needed for bash-completion helper
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
|
||||
buildPythonPackage (finalAttrs: {
|
||||
pname = "polyswarm-api";
|
||||
version = "3.16.0";
|
||||
version = "3.17.1";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "polyswarm";
|
||||
repo = "polyswarm-api";
|
||||
tag = finalAttrs.version;
|
||||
hash = "sha256-mdsgHwbGThy2Lzvgzb0mItwJkNspLiqGZzBGGuQdatM=";
|
||||
hash = "sha256-nL+DolLBnw/yahNqeCYtepAMFw4yHWXTajz3+KxF9R8=";
|
||||
};
|
||||
|
||||
build-system = [ setuptools ];
|
||||
|
||||
@@ -62,5 +62,6 @@ buildPythonPackage rec {
|
||||
dotlambda
|
||||
];
|
||||
changelog = "https://github.com/libfuse/pyfuse3/blob/${src.tag}/Changes.rst";
|
||||
platforms = lib.platforms.linux;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ let
|
||||
in
|
||||
buildMongoDB {
|
||||
inherit avxSupport;
|
||||
version = "7.0.30";
|
||||
hash = "sha256-z0stPphy9EliDkanlDCHJ+ck3p1gUTMQ83uOW7kvlmI=";
|
||||
version = "7.0.31";
|
||||
hash = "sha256-Vk/XsnYut0Hfad/X6LZw6gJX1NHc4/6XT8y1KehpLMk=";
|
||||
patches = [
|
||||
# ModuleNotFoundError: No module named 'mongo_tooling_metrics':
|
||||
# NameError: name 'SConsToolingMetrics' is not defined:
|
||||
|
||||
@@ -8555,7 +8555,12 @@ with pkgs;
|
||||
);
|
||||
fuse = fuse2;
|
||||
fuse2 = lowPrio (if stdenv.hostPlatform.isDarwin then macfuse-stubs else fusePackages.fuse_2);
|
||||
fuse3 = fusePackages.fuse_3;
|
||||
fuse3 = lowPrio (
|
||||
if stdenv.hostPlatform.isDarwin then
|
||||
macfuse-stubs.override { isFuse3 = true; }
|
||||
else
|
||||
fusePackages.fuse_3
|
||||
);
|
||||
|
||||
gpm-ncurses = gpm.override { withNcurses = true; };
|
||||
|
||||
|
||||
@@ -68,9 +68,11 @@ let
|
||||
callPackage ../development/coq-modules/bignums { }
|
||||
else
|
||||
null;
|
||||
CakeMLExtraction = callPackage ../development/coq-modules/CakeMLExtraction { };
|
||||
category-theory = callPackage ../development/coq-modules/category-theory { };
|
||||
ceres = callPackage ../development/coq-modules/ceres { };
|
||||
ceres-bs = callPackage ../development/coq-modules/ceres-bs { };
|
||||
CertiRocq = callPackage ../development/coq-modules/CertiRocq { };
|
||||
Cheerios = callPackage ../development/coq-modules/Cheerios { };
|
||||
coinduction = callPackage ../development/coq-modules/coinduction { };
|
||||
CoLoR = callPackage ../development/coq-modules/CoLoR (
|
||||
|
||||
Reference in New Issue
Block a user