Merge branch 'master' into update/jetbrains
This commit is contained in:
commit
a934a7f6d5
@ -326,6 +326,8 @@ rec {
|
|||||||
|
|
||||||
# The value with a check that it is defined
|
# The value with a check that it is defined
|
||||||
valueDefined = if res.isDefined then res.mergedValue else
|
valueDefined = if res.isDefined then res.mergedValue else
|
||||||
|
# (nixos-option detects this specific error message and gives it special
|
||||||
|
# handling. If changed here, please change it there too.)
|
||||||
throw "The option `${showOption loc}' is used but not defined.";
|
throw "The option `${showOption loc}' is used but not defined.";
|
||||||
|
|
||||||
# Apply the 'apply' function to the merged value. This allows options to
|
# Apply the 'apply' function to the merged value. This allows options to
|
||||||
|
@ -79,6 +79,7 @@ rec {
|
|||||||
else if final.isAarch64 then "arm64"
|
else if final.isAarch64 then "arm64"
|
||||||
else if final.isx86_32 then "x86"
|
else if final.isx86_32 then "x86"
|
||||||
else if final.isx86_64 then "ia64"
|
else if final.isx86_64 then "ia64"
|
||||||
|
else if final.isMips then "mips"
|
||||||
else final.parsed.cpu.name;
|
else final.parsed.cpu.name;
|
||||||
|
|
||||||
qemuArch =
|
qemuArch =
|
||||||
|
@ -327,6 +327,7 @@ rec {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
gnuabi64 = { abi = "64"; };
|
||||||
|
|
||||||
musleabi = { float = "soft"; };
|
musleabi = { float = "soft"; };
|
||||||
musleabihf = { float = "hard"; };
|
musleabihf = { float = "hard"; };
|
||||||
|
@ -1194,6 +1194,12 @@
|
|||||||
githubId = 30435868;
|
githubId = 30435868;
|
||||||
name = "Okina Matara";
|
name = "Okina Matara";
|
||||||
};
|
};
|
||||||
|
chkno = {
|
||||||
|
email = "chuck@intelligence.org";
|
||||||
|
github = "chkno";
|
||||||
|
githubId = 1118859;
|
||||||
|
name = "Scott Worley";
|
||||||
|
};
|
||||||
choochootrain = {
|
choochootrain = {
|
||||||
email = "hurshal@imap.cc";
|
email = "hurshal@imap.cc";
|
||||||
github = "choochootrain";
|
github = "choochootrain";
|
||||||
@ -1563,6 +1569,12 @@
|
|||||||
githubId = 14032;
|
githubId = 14032;
|
||||||
name = "Daniel Brockman";
|
name = "Daniel Brockman";
|
||||||
};
|
};
|
||||||
|
dduan = {
|
||||||
|
email = "daniel@duan.ca";
|
||||||
|
github = "dduan";
|
||||||
|
githubId = 75067;
|
||||||
|
name = "Daniel Duan";
|
||||||
|
};
|
||||||
deepfire = {
|
deepfire = {
|
||||||
email = "_deepfire@feelingofgreen.ru";
|
email = "_deepfire@feelingofgreen.ru";
|
||||||
github = "deepfire";
|
github = "deepfire";
|
||||||
@ -7081,6 +7093,12 @@
|
|||||||
email = "kirill.wedens@gmail.com";
|
email = "kirill.wedens@gmail.com";
|
||||||
name = "wedens";
|
name = "wedens";
|
||||||
};
|
};
|
||||||
|
WhittlesJr = {
|
||||||
|
email = "alex.joseph.whitt@gmail.com";
|
||||||
|
github = "WhittlesJr";
|
||||||
|
githubId = 19174984;
|
||||||
|
name = "Alex Whitt";
|
||||||
|
};
|
||||||
willibutz = {
|
willibutz = {
|
||||||
email = "willibutz@posteo.de";
|
email = "willibutz@posteo.de";
|
||||||
github = "willibutz";
|
github = "willibutz";
|
||||||
|
@ -14,14 +14,14 @@
|
|||||||
starting VDE switch for network 1
|
starting VDE switch for network 1
|
||||||
<prompt>></prompt>
|
<prompt>></prompt>
|
||||||
</screen>
|
</screen>
|
||||||
You can then take any Perl statement, e.g.
|
You can then take any Python statement, e.g.
|
||||||
<screen>
|
<screen>
|
||||||
<prompt>></prompt> startAll
|
<prompt>></prompt> start_all()
|
||||||
<prompt>></prompt> testScript
|
<prompt>></prompt> test_script()
|
||||||
<prompt>></prompt> $machine->succeed("touch /tmp/foo")
|
<prompt>></prompt> machine.succeed("touch /tmp/foo")
|
||||||
<prompt>></prompt> print($machine->succeed("pwd")) # Show stdout of command
|
<prompt>></prompt> print(machine.succeed("pwd")) # Show stdout of command
|
||||||
</screen>
|
</screen>
|
||||||
The function <command>testScript</command> executes the entire test script
|
The function <command>test_script</command> executes the entire test script
|
||||||
and drops you back into the test driver command line upon its completion.
|
and drops you 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
|
This allows you to inspect the state of the VMs after the test (e.g. to debug
|
||||||
the test script).
|
the test script).
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<para>
|
<para>
|
||||||
A NixOS test is a Nix expression that has the following structure:
|
A NixOS test is a Nix expression that has the following structure:
|
||||||
<programlisting>
|
<programlisting>
|
||||||
import ./make-test.nix {
|
import ./make-test-python.nix {
|
||||||
|
|
||||||
# Either the configuration of a single machine:
|
# Either the configuration of a single machine:
|
||||||
machine =
|
machine =
|
||||||
@ -27,11 +27,11 @@ import ./make-test.nix {
|
|||||||
|
|
||||||
testScript =
|
testScript =
|
||||||
''
|
''
|
||||||
<replaceable>Perl code…</replaceable>
|
<replaceable>Python code…</replaceable>
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
</programlisting>
|
</programlisting>
|
||||||
The attribute <literal>testScript</literal> is a bit of Perl code that
|
The attribute <literal>testScript</literal> is a bit of Python code that
|
||||||
executes the test (described below). During the test, it will start one or
|
executes the test (described below). During the test, it will start one or
|
||||||
more virtual machines, the configuration of which is described by the
|
more virtual machines, the configuration of which is described by the
|
||||||
attribute <literal>machine</literal> (if you need only one machine in your
|
attribute <literal>machine</literal> (if you need only one machine in your
|
||||||
@ -96,26 +96,27 @@ xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualis
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The test script is a sequence of Perl statements that perform various
|
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
|
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
|
virtual machine is represented as an object stored in the variable
|
||||||
<literal>$<replaceable>name</replaceable></literal>, where
|
<literal><replaceable>name</replaceable></literal> if this is also the
|
||||||
<replaceable>name</replaceable> is the identifier of the machine (which is
|
identifier of the machine in the declarative config.
|
||||||
just <literal>machine</literal> if you didn’t specify multiple machines
|
If you didn't specify multiple machines using the <literal>nodes</literal>
|
||||||
using the <literal>nodes</literal> attribute). For instance, the following
|
attribute, it is just <literal>machine</literal>.
|
||||||
starts the machine, waits until it has finished booting, then executes a
|
The following example starts the machine, waits until it has finished booting,
|
||||||
command and checks that the output is more-or-less correct:
|
then executes a command and checks that the output is more-or-less correct:
|
||||||
<programlisting>
|
<programlisting>
|
||||||
$machine->start;
|
machine.start()
|
||||||
$machine->waitForUnit("default.target");
|
machine.wait_for_unit("default.target")
|
||||||
$machine->succeed("uname") =~ /Linux/ or die;
|
if not "Linux" in machine.succeed("uname"):
|
||||||
|
raise Exception("Wrong OS")
|
||||||
</programlisting>
|
</programlisting>
|
||||||
The first line is actually unnecessary; machines are implicitly started when
|
The first line is actually unnecessary; machines are implicitly started when
|
||||||
you first execute an action on them (such as <literal>waitForUnit</literal>
|
you first execute an action on them (such as <literal>wait_for_unit</literal>
|
||||||
or <literal>succeed</literal>). If you have multiple machines, you can speed
|
or <literal>succeed</literal>). If you have multiple machines, you can speed
|
||||||
up the test by starting them in parallel:
|
up the test by starting them in parallel:
|
||||||
<programlisting>
|
<programlisting>
|
||||||
startAll;
|
start_all()
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
@ -187,7 +188,7 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>getScreenText</methodname>
|
<methodname>get_screen_text</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -204,7 +205,7 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>sendMonitorCommand</methodname>
|
<methodname>send_monitor_command</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -215,23 +216,23 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>sendKeys</methodname>
|
<methodname>send_keys</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Simulate pressing keys on the virtual keyboard, e.g.,
|
Simulate pressing keys on the virtual keyboard, e.g.,
|
||||||
<literal>sendKeys("ctrl-alt-delete")</literal>.
|
<literal>send_keys("ctrl-alt-delete")</literal>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>sendChars</methodname>
|
<methodname>send_chars</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Simulate typing a sequence of characters on the virtual keyboard, e.g.,
|
Simulate typing a sequence of characters on the virtual keyboard, e.g.,
|
||||||
<literal>sendKeys("foobar\n")</literal> will type the string
|
<literal>send_keys("foobar\n")</literal> will type the string
|
||||||
<literal>foobar</literal> followed by the Enter key.
|
<literal>foobar</literal> followed by the Enter key.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@ -272,7 +273,7 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>waitUntilSucceeds</methodname>
|
<methodname>wait_until_succeeds</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -282,7 +283,7 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>waitUntilFails</methodname>
|
<methodname>wait_until_fails</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -292,7 +293,7 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>waitForUnit</methodname>
|
<methodname>wait_for_unit</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -302,7 +303,7 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>waitForFile</methodname>
|
<methodname>wait_for_file</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -312,7 +313,7 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>waitForOpenPort</methodname>
|
<methodname>wait_for_open_port</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -323,7 +324,7 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>waitForClosedPort</methodname>
|
<methodname>wait_for_closed_port</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -333,7 +334,7 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>waitForX</methodname>
|
<methodname>wait_for_x</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -343,13 +344,13 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>waitForText</methodname>
|
<methodname>wait_for_text</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Wait until the supplied regular expressions matches the textual contents
|
Wait until the supplied regular expressions matches the textual contents
|
||||||
of the screen by using optical character recognition (see
|
of the screen by using optical character recognition (see
|
||||||
<methodname>getScreenText</methodname>).
|
<methodname>get_screen_text</methodname>).
|
||||||
</para>
|
</para>
|
||||||
<note>
|
<note>
|
||||||
<para>
|
<para>
|
||||||
@ -361,23 +362,23 @@ startAll;
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>waitForWindow</methodname>
|
<methodname>wait_for_window</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Wait until an X11 window has appeared whose name matches the given
|
Wait until an X11 window has appeared whose name matches the given
|
||||||
regular expression, e.g., <literal>waitForWindow(qr/Terminal/)</literal>.
|
regular expression, e.g., <literal>wait_for_window("Terminal")</literal>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<methodname>copyFileFromHost</methodname>
|
<methodname>copy_file_from_host</methodname>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Copies a file from host to machine, e.g.,
|
Copies a file from host to machine, e.g.,
|
||||||
<literal>copyFileFromHost("myfile", "/etc/my/important/file")</literal>.
|
<literal>copy_file_from_host("myfile", "/etc/my/important/file")</literal>.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
The first argument is the file on the host. The file needs to be
|
The first argument is the file on the host. The file needs to be
|
||||||
@ -397,8 +398,8 @@ startAll;
|
|||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
<programlisting>
|
<programlisting>
|
||||||
$machine->systemctl("list-jobs --no-pager"); // runs `systemctl list-jobs --no-pager`
|
machine.systemctl("list-jobs --no-pager") # runs `systemctl list-jobs --no-pager`
|
||||||
$machine->systemctl("list-jobs --no-pager", "any-user"); // spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager`
|
machine.systemctl("list-jobs --no-pager", "any-user") # spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager`
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@ -408,14 +409,14 @@ $machine->systemctl("list-jobs --no-pager", "any-user"); // spawns a shell for `
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
To test user units declared by <literal>systemd.user.services</literal> the
|
To test user units declared by <literal>systemd.user.services</literal> the
|
||||||
optional <literal>$user</literal> argument can be used:
|
optional <literal>user</literal> argument can be used:
|
||||||
<programlisting>
|
<programlisting>
|
||||||
$machine->start;
|
machine.start()
|
||||||
$machine->waitForX;
|
machine.wait_for_x()
|
||||||
$machine->waitForUnit("xautolock.service", "x-session-user");
|
machine.wait_for_unit("xautolock.service", "x-session-user")
|
||||||
</programlisting>
|
</programlisting>
|
||||||
This applies to <literal>systemctl</literal>, <literal>getUnitInfo</literal>,
|
This applies to <literal>systemctl</literal>, <literal>get_unit_info</literal>,
|
||||||
<literal>waitForUnit</literal>, <literal>startJob</literal> and
|
<literal>wait_for_unit</literal>, <literal>start_job</literal> and
|
||||||
<literal>stopJob</literal>.
|
<literal>stop_job</literal>.
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
@ -19,14 +19,10 @@
|
|||||||
</arg>
|
</arg>
|
||||||
|
|
||||||
<arg>
|
<arg>
|
||||||
<option>--verbose</option>
|
<option>--all</option>
|
||||||
</arg>
|
</arg>
|
||||||
|
|
||||||
<arg>
|
<arg>
|
||||||
<option>--xml</option>
|
|
||||||
</arg>
|
|
||||||
|
|
||||||
<arg choice="plain">
|
|
||||||
<replaceable>option.name</replaceable>
|
<replaceable>option.name</replaceable>
|
||||||
</arg>
|
</arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
@ -62,22 +58,11 @@
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<option>--verbose</option>
|
<option>--all</option>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
This option enables verbose mode, which currently is just the Bash
|
Print the values of all options.
|
||||||
<command>set</command> <option>-x</option> debug mode.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
<varlistentry>
|
|
||||||
<term>
|
|
||||||
<option>--xml</option>
|
|
||||||
</term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
This option causes the output to be rendered as XML.
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -494,6 +494,20 @@
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
<option>--use-remote-sudo</option>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
When set, nixos-rebuild prefixes remote commands that run on
|
||||||
|
the <option>--build-host</option> and <option>--target-host</option>
|
||||||
|
systems with <command>sudo</command>. Setting this option allows
|
||||||
|
deploying as a non-root user.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -49,6 +49,12 @@
|
|||||||
zfs as soon as any zfs mountpoint is configured in <varname>fileSystems</varname>.
|
zfs as soon as any zfs mountpoint is configured in <varname>fileSystems</varname>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<command>nixos-option</command> has been rewritten in C++, speeding it up, improving correctness,
|
||||||
|
and adding a <option>--all</option> option which prints all options and their values.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
792
nixos/lib/test-driver/test-driver.py
Normal file
792
nixos/lib/test-driver/test-driver.py
Normal file
@ -0,0 +1,792 @@
|
|||||||
|
#! /somewhere/python3
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from xml.sax.saxutils import XMLGenerator
|
||||||
|
import _thread
|
||||||
|
import atexit
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import ptpython.repl
|
||||||
|
import pty
|
||||||
|
import queue
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
CHAR_TO_KEY = {
|
||||||
|
"A": "shift-a",
|
||||||
|
"N": "shift-n",
|
||||||
|
"-": "0x0C",
|
||||||
|
"_": "shift-0x0C",
|
||||||
|
"B": "shift-b",
|
||||||
|
"O": "shift-o",
|
||||||
|
"=": "0x0D",
|
||||||
|
"+": "shift-0x0D",
|
||||||
|
"C": "shift-c",
|
||||||
|
"P": "shift-p",
|
||||||
|
"[": "0x1A",
|
||||||
|
"{": "shift-0x1A",
|
||||||
|
"D": "shift-d",
|
||||||
|
"Q": "shift-q",
|
||||||
|
"]": "0x1B",
|
||||||
|
"}": "shift-0x1B",
|
||||||
|
"E": "shift-e",
|
||||||
|
"R": "shift-r",
|
||||||
|
";": "0x27",
|
||||||
|
":": "shift-0x27",
|
||||||
|
"F": "shift-f",
|
||||||
|
"S": "shift-s",
|
||||||
|
"'": "0x28",
|
||||||
|
'"': "shift-0x28",
|
||||||
|
"G": "shift-g",
|
||||||
|
"T": "shift-t",
|
||||||
|
"`": "0x29",
|
||||||
|
"~": "shift-0x29",
|
||||||
|
"H": "shift-h",
|
||||||
|
"U": "shift-u",
|
||||||
|
"\\": "0x2B",
|
||||||
|
"|": "shift-0x2B",
|
||||||
|
"I": "shift-i",
|
||||||
|
"V": "shift-v",
|
||||||
|
",": "0x33",
|
||||||
|
"<": "shift-0x33",
|
||||||
|
"J": "shift-j",
|
||||||
|
"W": "shift-w",
|
||||||
|
".": "0x34",
|
||||||
|
">": "shift-0x34",
|
||||||
|
"K": "shift-k",
|
||||||
|
"X": "shift-x",
|
||||||
|
"/": "0x35",
|
||||||
|
"?": "shift-0x35",
|
||||||
|
"L": "shift-l",
|
||||||
|
"Y": "shift-y",
|
||||||
|
" ": "spc",
|
||||||
|
"M": "shift-m",
|
||||||
|
"Z": "shift-z",
|
||||||
|
"\n": "ret",
|
||||||
|
"!": "shift-0x02",
|
||||||
|
"@": "shift-0x03",
|
||||||
|
"#": "shift-0x04",
|
||||||
|
"$": "shift-0x05",
|
||||||
|
"%": "shift-0x06",
|
||||||
|
"^": "shift-0x07",
|
||||||
|
"&": "shift-0x08",
|
||||||
|
"*": "shift-0x09",
|
||||||
|
"(": "shift-0x0A",
|
||||||
|
")": "shift-0x0B",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def eprint(*args, **kwargs):
|
||||||
|
print(*args, file=sys.stderr, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def create_vlan(vlan_nr):
|
||||||
|
global log
|
||||||
|
log.log("starting VDE switch for network {}".format(vlan_nr))
|
||||||
|
vde_socket = os.path.abspath("./vde{}.ctl".format(vlan_nr))
|
||||||
|
pty_master, pty_slave = pty.openpty()
|
||||||
|
vde_process = subprocess.Popen(
|
||||||
|
["vde_switch", "-s", vde_socket, "--dirmode", "0777"],
|
||||||
|
bufsize=1,
|
||||||
|
stdin=pty_slave,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
shell=False,
|
||||||
|
)
|
||||||
|
fd = os.fdopen(pty_master, "w")
|
||||||
|
fd.write("version\n")
|
||||||
|
# TODO: perl version checks if this can be read from
|
||||||
|
# an if not, dies. we could hang here forever. Fix it.
|
||||||
|
vde_process.stdout.readline()
|
||||||
|
if not os.path.exists(os.path.join(vde_socket, "ctl")):
|
||||||
|
raise Exception("cannot start vde_switch")
|
||||||
|
|
||||||
|
return (vlan_nr, vde_socket, vde_process, fd)
|
||||||
|
|
||||||
|
|
||||||
|
def retry(fn):
|
||||||
|
"""Call the given function repeatedly, with 1 second intervals,
|
||||||
|
until it returns True or a timeout is reached.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for _ in range(900):
|
||||||
|
if fn(False):
|
||||||
|
return
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
if not fn(True):
|
||||||
|
raise Exception("action timed out")
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
def __init__(self):
|
||||||
|
self.logfile = os.environ.get("LOGFILE", "/dev/null")
|
||||||
|
self.logfile_handle = open(self.logfile, "wb")
|
||||||
|
self.xml = XMLGenerator(self.logfile_handle, encoding="utf-8")
|
||||||
|
self.queue = queue.Queue(1000)
|
||||||
|
|
||||||
|
self.xml.startDocument()
|
||||||
|
self.xml.startElement("logfile", attrs={})
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.xml.endElement("logfile")
|
||||||
|
self.xml.endDocument()
|
||||||
|
self.logfile_handle.close()
|
||||||
|
|
||||||
|
def sanitise(self, message):
|
||||||
|
return "".join(ch for ch in message if unicodedata.category(ch)[0] != "C")
|
||||||
|
|
||||||
|
def maybe_prefix(self, message, attributes):
|
||||||
|
if "machine" in attributes:
|
||||||
|
return "{}: {}".format(attributes["machine"], message)
|
||||||
|
return message
|
||||||
|
|
||||||
|
def log_line(self, message, attributes):
|
||||||
|
self.xml.startElement("line", attributes)
|
||||||
|
self.xml.characters(message)
|
||||||
|
self.xml.endElement("line")
|
||||||
|
|
||||||
|
def log(self, message, attributes={}):
|
||||||
|
eprint(self.maybe_prefix(message, attributes))
|
||||||
|
self.drain_log_queue()
|
||||||
|
self.log_line(message, attributes)
|
||||||
|
|
||||||
|
def enqueue(self, message):
|
||||||
|
self.queue.put(message)
|
||||||
|
|
||||||
|
def drain_log_queue(self):
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
item = self.queue.get_nowait()
|
||||||
|
attributes = {"machine": item["machine"], "type": "serial"}
|
||||||
|
self.log_line(self.sanitise(item["msg"]), attributes)
|
||||||
|
except queue.Empty:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def nested(self, message, attributes={}):
|
||||||
|
eprint(self.maybe_prefix(message, attributes))
|
||||||
|
|
||||||
|
self.xml.startElement("nest", attrs={})
|
||||||
|
self.xml.startElement("head", attributes)
|
||||||
|
self.xml.characters(message)
|
||||||
|
self.xml.endElement("head")
|
||||||
|
|
||||||
|
tic = time.time()
|
||||||
|
self.drain_log_queue()
|
||||||
|
yield
|
||||||
|
self.drain_log_queue()
|
||||||
|
toc = time.time()
|
||||||
|
self.log("({:.2f} seconds)".format(toc - tic))
|
||||||
|
|
||||||
|
self.xml.endElement("nest")
|
||||||
|
|
||||||
|
|
||||||
|
class Machine:
|
||||||
|
def __init__(self, args):
|
||||||
|
if "name" in args:
|
||||||
|
self.name = args["name"]
|
||||||
|
else:
|
||||||
|
self.name = "machine"
|
||||||
|
try:
|
||||||
|
cmd = args["startCommand"]
|
||||||
|
self.name = re.search("run-(.+)-vm$", cmd).group(1)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.script = args.get("startCommand", self.create_startcommand(args))
|
||||||
|
|
||||||
|
tmp_dir = os.environ.get("TMPDIR", tempfile.gettempdir())
|
||||||
|
|
||||||
|
def create_dir(name):
|
||||||
|
path = os.path.join(tmp_dir, name)
|
||||||
|
os.makedirs(path, mode=0o700, exist_ok=True)
|
||||||
|
return path
|
||||||
|
|
||||||
|
self.state_dir = create_dir("vm-state-{}".format(self.name))
|
||||||
|
self.shared_dir = create_dir("xchg-shared")
|
||||||
|
|
||||||
|
self.booted = False
|
||||||
|
self.connected = False
|
||||||
|
self.pid = None
|
||||||
|
self.socket = None
|
||||||
|
self.monitor = None
|
||||||
|
self.logger = args["log"]
|
||||||
|
self.allow_reboot = args.get("allowReboot", False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_startcommand(args):
|
||||||
|
net_backend = "-netdev user,id=net0"
|
||||||
|
net_frontend = "-device virtio-net-pci,netdev=net0"
|
||||||
|
|
||||||
|
if "netBackendArgs" in args:
|
||||||
|
net_backend += "," + args["netBackendArgs"]
|
||||||
|
|
||||||
|
if "netFrontendArgs" in args:
|
||||||
|
net_frontend += "," + args["netFrontendArgs"]
|
||||||
|
|
||||||
|
start_command = (
|
||||||
|
"qemu-kvm -m 384 " + net_backend + " " + net_frontend + " $QEMU_OPTS "
|
||||||
|
)
|
||||||
|
|
||||||
|
if "hda" in args:
|
||||||
|
hda_path = os.path.abspath(args["hda"])
|
||||||
|
if args.get("hdaInterface", "") == "scsi":
|
||||||
|
start_command += (
|
||||||
|
"-drive id=hda,file="
|
||||||
|
+ hda_path
|
||||||
|
+ ",werror=report,if=none "
|
||||||
|
+ "-device scsi-hd,drive=hda "
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
start_command += (
|
||||||
|
"-drive file="
|
||||||
|
+ hda_path
|
||||||
|
+ ",if="
|
||||||
|
+ args["hdaInterface"]
|
||||||
|
+ ",werror=report "
|
||||||
|
)
|
||||||
|
|
||||||
|
if "cdrom" in args:
|
||||||
|
start_command += "-cdrom " + args["cdrom"] + " "
|
||||||
|
|
||||||
|
if "usb" in args:
|
||||||
|
start_command += (
|
||||||
|
"-device piix3-usb-uhci -drive "
|
||||||
|
+ "id=usbdisk,file="
|
||||||
|
+ args["usb"]
|
||||||
|
+ ",if=none,readonly "
|
||||||
|
+ "-device usb-storage,drive=usbdisk "
|
||||||
|
)
|
||||||
|
if "bios" in args:
|
||||||
|
start_command += "-bios " + args["bios"] + " "
|
||||||
|
|
||||||
|
start_command += args.get("qemuFlags", "")
|
||||||
|
|
||||||
|
return start_command
|
||||||
|
|
||||||
|
def is_up(self):
|
||||||
|
return self.booted and self.connected
|
||||||
|
|
||||||
|
def log(self, msg):
|
||||||
|
self.logger.log(msg, {"machine": self.name})
|
||||||
|
|
||||||
|
def nested(self, msg, attrs={}):
|
||||||
|
my_attrs = {"machine": self.name}
|
||||||
|
my_attrs.update(attrs)
|
||||||
|
return self.logger.nested(msg, my_attrs)
|
||||||
|
|
||||||
|
def wait_for_monitor_prompt(self):
|
||||||
|
while True:
|
||||||
|
answer = self.monitor.recv(1024).decode()
|
||||||
|
if answer.endswith("(qemu) "):
|
||||||
|
return answer
|
||||||
|
|
||||||
|
def send_monitor_command(self, command):
|
||||||
|
message = ("{}\n".format(command)).encode()
|
||||||
|
self.log("sending monitor command: {}".format(command))
|
||||||
|
self.monitor.send(message)
|
||||||
|
return self.wait_for_monitor_prompt()
|
||||||
|
|
||||||
|
def wait_for_unit(self, unit, user=None):
|
||||||
|
while True:
|
||||||
|
info = self.get_unit_info(unit, user)
|
||||||
|
state = info["ActiveState"]
|
||||||
|
if state == "failed":
|
||||||
|
raise Exception('unit "{}" reached state "{}"'.format(unit, state))
|
||||||
|
|
||||||
|
if state == "inactive":
|
||||||
|
status, jobs = self.systemctl("list-jobs --full 2>&1", user)
|
||||||
|
if "No jobs" in jobs:
|
||||||
|
info = self.get_unit_info(unit)
|
||||||
|
if info["ActiveState"] == state:
|
||||||
|
raise Exception(
|
||||||
|
(
|
||||||
|
'unit "{}" is inactive and there ' "are no pending jobs"
|
||||||
|
).format(unit)
|
||||||
|
)
|
||||||
|
if state == "active":
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_unit_info(self, unit, user=None):
|
||||||
|
status, lines = self.systemctl('--no-pager show "{}"'.format(unit), user)
|
||||||
|
if status != 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
line_pattern = re.compile(r"^([^=]+)=(.*)$")
|
||||||
|
|
||||||
|
def tuple_from_line(line):
|
||||||
|
match = line_pattern.match(line)
|
||||||
|
return match[1], match[2]
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
tuple_from_line(line)
|
||||||
|
for line in lines.split("\n")
|
||||||
|
if line_pattern.match(line)
|
||||||
|
)
|
||||||
|
|
||||||
|
def systemctl(self, q, user=None):
|
||||||
|
if user is not None:
|
||||||
|
q = q.replace("'", "\\'")
|
||||||
|
return self.execute(
|
||||||
|
(
|
||||||
|
"su -l {} -c "
|
||||||
|
"$'XDG_RUNTIME_DIR=/run/user/`id -u` "
|
||||||
|
"systemctl --user {}'"
|
||||||
|
).format(user, q)
|
||||||
|
)
|
||||||
|
return self.execute("systemctl {}".format(q))
|
||||||
|
|
||||||
|
def require_unit_state(self, unit, require_state="active"):
|
||||||
|
with self.nested(
|
||||||
|
"checking if unit ‘{}’ has reached state '{}'".format(unit, require_state)
|
||||||
|
):
|
||||||
|
info = self.get_unit_info(unit)
|
||||||
|
state = info["ActiveState"]
|
||||||
|
if state != require_state:
|
||||||
|
raise Exception(
|
||||||
|
"Expected unit ‘{}’ to to be in state ".format(unit)
|
||||||
|
+ "'active' but it is in state ‘{}’".format(state)
|
||||||
|
)
|
||||||
|
|
||||||
|
def execute(self, command):
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
out_command = "( {} ); echo '|!EOF' $?\n".format(command)
|
||||||
|
self.shell.send(out_command.encode())
|
||||||
|
|
||||||
|
output = ""
|
||||||
|
status_code_pattern = re.compile(r"(.*)\|\!EOF\s+(\d+)")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
chunk = self.shell.recv(4096).decode()
|
||||||
|
match = status_code_pattern.match(chunk)
|
||||||
|
if match:
|
||||||
|
output += match[1]
|
||||||
|
status_code = int(match[2])
|
||||||
|
return (status_code, output)
|
||||||
|
output += chunk
|
||||||
|
|
||||||
|
def succeed(self, *commands):
|
||||||
|
"""Execute each command and check that it succeeds."""
|
||||||
|
for command in commands:
|
||||||
|
with self.nested("must succeed: {}".format(command)):
|
||||||
|
status, output = self.execute(command)
|
||||||
|
if status != 0:
|
||||||
|
self.log("output: {}".format(output))
|
||||||
|
raise Exception(
|
||||||
|
"command `{}` failed (exit code {})".format(command, status)
|
||||||
|
)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def fail(self, *commands):
|
||||||
|
"""Execute each command and check that it fails."""
|
||||||
|
for command in commands:
|
||||||
|
with self.nested("must fail: {}".format(command)):
|
||||||
|
status, output = self.execute(command)
|
||||||
|
if status == 0:
|
||||||
|
raise Exception(
|
||||||
|
"command `{}` unexpectedly succeeded".format(command)
|
||||||
|
)
|
||||||
|
|
||||||
|
def wait_until_succeeds(self, command):
|
||||||
|
with self.nested("waiting for success: {}".format(command)):
|
||||||
|
while True:
|
||||||
|
status, output = self.execute(command)
|
||||||
|
if status == 0:
|
||||||
|
return output
|
||||||
|
|
||||||
|
def wait_until_fails(self, command):
|
||||||
|
with self.nested("waiting for failure: {}".format(command)):
|
||||||
|
while True:
|
||||||
|
status, output = self.execute(command)
|
||||||
|
if status != 0:
|
||||||
|
return output
|
||||||
|
|
||||||
|
def wait_for_shutdown(self):
|
||||||
|
if not self.booted:
|
||||||
|
return
|
||||||
|
|
||||||
|
with self.nested("waiting for the VM to power off"):
|
||||||
|
sys.stdout.flush()
|
||||||
|
self.process.wait()
|
||||||
|
|
||||||
|
self.pid = None
|
||||||
|
self.booted = False
|
||||||
|
self.connected = False
|
||||||
|
|
||||||
|
def get_tty_text(self, tty):
|
||||||
|
status, output = self.execute(
|
||||||
|
"fold -w$(stty -F /dev/tty{0} size | "
|
||||||
|
"awk '{{print $2}}') /dev/vcs{0}".format(tty)
|
||||||
|
)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def wait_until_tty_matches(self, tty, regexp):
|
||||||
|
matcher = re.compile(regexp)
|
||||||
|
with self.nested("waiting for {} to appear on tty {}".format(regexp, tty)):
|
||||||
|
while True:
|
||||||
|
text = self.get_tty_text(tty)
|
||||||
|
if len(matcher.findall(text)) > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def send_chars(self, chars):
|
||||||
|
with self.nested("sending keys ‘{}‘".format(chars)):
|
||||||
|
for char in chars:
|
||||||
|
self.send_key(char)
|
||||||
|
|
||||||
|
def wait_for_file(self, filename):
|
||||||
|
with self.nested("waiting for file ‘{}‘".format(filename)):
|
||||||
|
while True:
|
||||||
|
status, _ = self.execute("test -e {}".format(filename))
|
||||||
|
if status == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def wait_for_open_port(self, port):
|
||||||
|
def port_is_open(_):
|
||||||
|
status, _ = self.execute("nc -z localhost {}".format(port))
|
||||||
|
return status == 0
|
||||||
|
|
||||||
|
with self.nested("waiting for TCP port {}".format(port)):
|
||||||
|
retry(port_is_open)
|
||||||
|
|
||||||
|
def wait_for_closed_port(self, port):
|
||||||
|
def port_is_closed(_):
|
||||||
|
status, _ = self.execute("nc -z localhost {}".format(port))
|
||||||
|
return status != 0
|
||||||
|
|
||||||
|
retry(port_is_closed)
|
||||||
|
|
||||||
|
def start_job(self, jobname, user=None):
|
||||||
|
return self.systemctl("start {}".format(jobname), user)
|
||||||
|
|
||||||
|
def stop_job(self, jobname, user=None):
|
||||||
|
return self.systemctl("stop {}".format(jobname), user)
|
||||||
|
|
||||||
|
def wait_for_job(self, jobname):
|
||||||
|
return self.wait_for_unit(jobname)
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
if self.connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
with self.nested("waiting for the VM to finish booting"):
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
tic = time.time()
|
||||||
|
self.shell.recv(1024)
|
||||||
|
# TODO: Timeout
|
||||||
|
toc = time.time()
|
||||||
|
|
||||||
|
self.log("connected to guest root shell")
|
||||||
|
self.log("(connecting took {:.2f} seconds)".format(toc - tic))
|
||||||
|
self.connected = True
|
||||||
|
|
||||||
|
def screenshot(self, filename):
|
||||||
|
out_dir = os.environ.get("out", os.getcwd())
|
||||||
|
word_pattern = re.compile(r"^\w+$")
|
||||||
|
if word_pattern.match(filename):
|
||||||
|
filename = os.path.join(out_dir, "{}.png".format(filename))
|
||||||
|
tmp = "{}.ppm".format(filename)
|
||||||
|
|
||||||
|
with self.nested(
|
||||||
|
"making screenshot {}".format(filename),
|
||||||
|
{"image": os.path.basename(filename)},
|
||||||
|
):
|
||||||
|
self.send_monitor_command("screendump {}".format(tmp))
|
||||||
|
ret = subprocess.run("pnmtopng {} > {}".format(tmp, filename), shell=True)
|
||||||
|
os.unlink(tmp)
|
||||||
|
if ret.returncode != 0:
|
||||||
|
raise Exception("Cannot convert screenshot")
|
||||||
|
|
||||||
|
def get_screen_text(self):
|
||||||
|
if shutil.which("tesseract") is None:
|
||||||
|
raise Exception("get_screen_text used but enableOCR is false")
|
||||||
|
|
||||||
|
magick_args = (
|
||||||
|
"-filter Catrom -density 72 -resample 300 "
|
||||||
|
+ "-contrast -normalize -despeckle -type grayscale "
|
||||||
|
+ "-sharpen 1 -posterize 3 -negate -gamma 100 "
|
||||||
|
+ "-blur 1x65535"
|
||||||
|
)
|
||||||
|
|
||||||
|
tess_args = "-c debug_file=/dev/null --psm 11 --oem 2"
|
||||||
|
|
||||||
|
with self.nested("performing optical character recognition"):
|
||||||
|
with tempfile.NamedTemporaryFile() as tmpin:
|
||||||
|
self.send_monitor_command("screendump {}".format(tmpin.name))
|
||||||
|
|
||||||
|
cmd = "convert {} {} tiff:- | tesseract - - {}".format(
|
||||||
|
magick_args, tmpin.name, tess_args
|
||||||
|
)
|
||||||
|
ret = subprocess.run(cmd, shell=True, capture_output=True)
|
||||||
|
if ret.returncode != 0:
|
||||||
|
raise Exception(
|
||||||
|
"OCR failed with exit code {}".format(ret.returncode)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ret.stdout.decode("utf-8")
|
||||||
|
|
||||||
|
def wait_for_text(self, regex):
|
||||||
|
def screen_matches(last):
|
||||||
|
text = self.get_screen_text()
|
||||||
|
m = re.search(regex, text)
|
||||||
|
|
||||||
|
if last and not m:
|
||||||
|
self.log("Last OCR attempt failed. Text was: {}".format(text))
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
with self.nested("waiting for {} to appear on screen".format(regex)):
|
||||||
|
retry(screen_matches)
|
||||||
|
|
||||||
|
def send_key(self, key):
|
||||||
|
key = CHAR_TO_KEY.get(key, key)
|
||||||
|
self.send_monitor_command("sendkey {}".format(key))
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
if self.booted:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("starting vm")
|
||||||
|
|
||||||
|
def create_socket(path):
|
||||||
|
if os.path.exists(path):
|
||||||
|
os.unlink(path)
|
||||||
|
s = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM)
|
||||||
|
s.bind(path)
|
||||||
|
s.listen(1)
|
||||||
|
return s
|
||||||
|
|
||||||
|
monitor_path = os.path.join(self.state_dir, "monitor")
|
||||||
|
self.monitor_socket = create_socket(monitor_path)
|
||||||
|
|
||||||
|
shell_path = os.path.join(self.state_dir, "shell")
|
||||||
|
self.shell_socket = create_socket(shell_path)
|
||||||
|
|
||||||
|
qemu_options = (
|
||||||
|
" ".join(
|
||||||
|
[
|
||||||
|
"" if self.allow_reboot else "-no-reboot",
|
||||||
|
"-monitor unix:{}".format(monitor_path),
|
||||||
|
"-chardev socket,id=shell,path={}".format(shell_path),
|
||||||
|
"-device virtio-serial",
|
||||||
|
"-device virtconsole,chardev=shell",
|
||||||
|
"-device virtio-rng-pci",
|
||||||
|
"-serial stdio" if "DISPLAY" in os.environ else "-nographic",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
+ " "
|
||||||
|
+ os.environ.get("QEMU_OPTS", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
"QEMU_OPTS": qemu_options,
|
||||||
|
"SHARED_DIR": self.shared_dir,
|
||||||
|
"USE_TMPDIR": "1",
|
||||||
|
}
|
||||||
|
environment.update(dict(os.environ))
|
||||||
|
|
||||||
|
self.process = subprocess.Popen(
|
||||||
|
self.script,
|
||||||
|
bufsize=1,
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
shell=True,
|
||||||
|
cwd=self.state_dir,
|
||||||
|
env=environment,
|
||||||
|
)
|
||||||
|
self.monitor, _ = self.monitor_socket.accept()
|
||||||
|
self.shell, _ = self.shell_socket.accept()
|
||||||
|
|
||||||
|
def process_serial_output():
|
||||||
|
for line in self.process.stdout:
|
||||||
|
line = line.decode("unicode_escape").replace("\r", "").rstrip()
|
||||||
|
eprint("{} # {}".format(self.name, line))
|
||||||
|
self.logger.enqueue({"msg": line, "machine": self.name})
|
||||||
|
|
||||||
|
_thread.start_new_thread(process_serial_output, ())
|
||||||
|
|
||||||
|
self.wait_for_monitor_prompt()
|
||||||
|
|
||||||
|
self.pid = self.process.pid
|
||||||
|
self.booted = True
|
||||||
|
|
||||||
|
self.log("QEMU running (pid {})".format(self.pid))
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
if not self.booted:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.shell.send("poweroff\n".encode())
|
||||||
|
self.wait_for_shutdown()
|
||||||
|
|
||||||
|
def crash(self):
|
||||||
|
if not self.booted:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("forced crash")
|
||||||
|
self.send_monitor_command("quit")
|
||||||
|
self.wait_for_shutdown()
|
||||||
|
|
||||||
|
def wait_for_x(self):
|
||||||
|
"""Wait until it is possible to connect to the X server. Note that
|
||||||
|
testing the existence of /tmp/.X11-unix/X0 is insufficient.
|
||||||
|
"""
|
||||||
|
with self.nested("waiting for the X11 server"):
|
||||||
|
while True:
|
||||||
|
cmd = (
|
||||||
|
"journalctl -b SYSLOG_IDENTIFIER=systemd | "
|
||||||
|
+ 'grep "Reached target Current graphical"'
|
||||||
|
)
|
||||||
|
status, _ = self.execute(cmd)
|
||||||
|
if status != 0:
|
||||||
|
continue
|
||||||
|
status, _ = self.execute("[ -e /tmp/.X11-unix/X0 ]")
|
||||||
|
if status == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_window_names(self):
|
||||||
|
return self.succeed(
|
||||||
|
r"xwininfo -root -tree | sed 's/.*0x[0-9a-f]* \"\([^\"]*\)\".*/\1/; t; d'"
|
||||||
|
).splitlines()
|
||||||
|
|
||||||
|
def wait_for_window(self, regexp):
|
||||||
|
pattern = re.compile(regexp)
|
||||||
|
|
||||||
|
def window_is_visible(last_try):
|
||||||
|
names = self.get_window_names()
|
||||||
|
if last_try:
|
||||||
|
self.log(
|
||||||
|
"Last chance to match {} on the window list,".format(regexp)
|
||||||
|
+ " which currently contains: "
|
||||||
|
+ ", ".join(names)
|
||||||
|
)
|
||||||
|
return any(pattern.search(name) for name in names)
|
||||||
|
|
||||||
|
with self.nested("Waiting for a window to appear"):
|
||||||
|
retry(window_is_visible)
|
||||||
|
|
||||||
|
def sleep(self, secs):
|
||||||
|
time.sleep(secs)
|
||||||
|
|
||||||
|
def block(self):
|
||||||
|
"""Make the machine unreachable by shutting down eth1 (the multicast
|
||||||
|
interface used to talk to the other VMs). We keep eth0 up so that
|
||||||
|
the test driver can continue to talk to the machine.
|
||||||
|
"""
|
||||||
|
self.send_monitor_command("set_link virtio-net-pci.1 off")
|
||||||
|
|
||||||
|
def unblock(self):
|
||||||
|
"""Make the machine reachable.
|
||||||
|
"""
|
||||||
|
self.send_monitor_command("set_link virtio-net-pci.1 on")
|
||||||
|
|
||||||
|
|
||||||
|
def create_machine(args):
|
||||||
|
global log
|
||||||
|
args["log"] = log
|
||||||
|
args["redirectSerial"] = os.environ.get("USE_SERIAL", "0") == "1"
|
||||||
|
return Machine(args)
|
||||||
|
|
||||||
|
|
||||||
|
def start_all():
|
||||||
|
with log.nested("starting all VMs"):
|
||||||
|
for machine in machines:
|
||||||
|
machine.start()
|
||||||
|
|
||||||
|
|
||||||
|
def join_all():
|
||||||
|
with log.nested("waiting for all VMs to finish"):
|
||||||
|
for machine in machines:
|
||||||
|
machine.wait_for_shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def test_script():
|
||||||
|
exec(os.environ["testScript"])
|
||||||
|
|
||||||
|
|
||||||
|
def run_tests():
|
||||||
|
tests = os.environ.get("tests", None)
|
||||||
|
if tests is not None:
|
||||||
|
with log.nested("running the VM test script"):
|
||||||
|
try:
|
||||||
|
exec(tests)
|
||||||
|
except Exception as e:
|
||||||
|
eprint("error: {}".format(str(e)))
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
ptpython.repl.embed(locals(), globals())
|
||||||
|
|
||||||
|
# TODO: Collect coverage data
|
||||||
|
|
||||||
|
for machine in machines:
|
||||||
|
if machine.is_up():
|
||||||
|
machine.execute("sync")
|
||||||
|
|
||||||
|
if nr_tests != 0:
|
||||||
|
log.log("{} out of {} tests succeeded".format(nr_succeeded, nr_tests))
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def subtest(name):
|
||||||
|
global nr_tests
|
||||||
|
global nr_succeeded
|
||||||
|
|
||||||
|
with log.nested(name):
|
||||||
|
nr_tests += 1
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
nr_succeeded += 1
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log.log("error: {}".format(str(e)))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
global log
|
||||||
|
log = Logger()
|
||||||
|
|
||||||
|
vlan_nrs = list(dict.fromkeys(os.environ["VLANS"].split()))
|
||||||
|
vde_sockets = [create_vlan(v) for v in vlan_nrs]
|
||||||
|
for nr, vde_socket, _, _ in vde_sockets:
|
||||||
|
os.environ["QEMU_VDE_SOCKET_{}".format(nr)] = vde_socket
|
||||||
|
|
||||||
|
vm_scripts = sys.argv[1:]
|
||||||
|
machines = [create_machine({"startCommand": s}) for s in vm_scripts]
|
||||||
|
machine_eval = [
|
||||||
|
"{0} = machines[{1}]".format(m.name, idx) for idx, m in enumerate(machines)
|
||||||
|
]
|
||||||
|
exec("\n".join(machine_eval))
|
||||||
|
|
||||||
|
nr_tests = 0
|
||||||
|
nr_succeeded = 0
|
||||||
|
|
||||||
|
@atexit.register
|
||||||
|
def clean_up():
|
||||||
|
with log.nested("cleaning up"):
|
||||||
|
for machine in machines:
|
||||||
|
if machine.pid is None:
|
||||||
|
continue
|
||||||
|
log.log("killing {} (pid {})".format(machine.name, machine.pid))
|
||||||
|
machine.process.kill()
|
||||||
|
|
||||||
|
for _, _, process, _ in vde_sockets:
|
||||||
|
process.kill()
|
||||||
|
log.close()
|
||||||
|
|
||||||
|
tic = time.time()
|
||||||
|
run_tests()
|
||||||
|
toc = time.time()
|
||||||
|
print("test script finished in {:.2f}s".format(toc - tic))
|
279
nixos/lib/testing-python.nix
Normal file
279
nixos/lib/testing-python.nix
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
{ system
|
||||||
|
, pkgs ? import ../.. { inherit system config; }
|
||||||
|
# Use a minimal kernel?
|
||||||
|
, minimal ? false
|
||||||
|
# Ignored
|
||||||
|
, config ? {}
|
||||||
|
# Modules to add to each VM
|
||||||
|
, extraConfigurations ? [] }:
|
||||||
|
|
||||||
|
with import ./build-vms.nix { inherit system pkgs minimal extraConfigurations; };
|
||||||
|
with pkgs;
|
||||||
|
|
||||||
|
let
|
||||||
|
jquery-ui = callPackage ./testing/jquery-ui.nix { };
|
||||||
|
jquery = callPackage ./testing/jquery.nix { };
|
||||||
|
|
||||||
|
in rec {
|
||||||
|
|
||||||
|
inherit pkgs;
|
||||||
|
|
||||||
|
|
||||||
|
testDriver = let
|
||||||
|
testDriverScript = ./test-driver/test-driver.py;
|
||||||
|
in stdenv.mkDerivation {
|
||||||
|
name = "nixos-test-driver";
|
||||||
|
|
||||||
|
nativeBuildInputs = [ makeWrapper ];
|
||||||
|
buildInputs = [ (python3.withPackages (p: [ p.ptpython ])) ];
|
||||||
|
checkInputs = with python3Packages; [ pylint black ];
|
||||||
|
|
||||||
|
dontUnpack = true;
|
||||||
|
|
||||||
|
preferLocalBuild = true;
|
||||||
|
|
||||||
|
doCheck = true;
|
||||||
|
checkPhase = ''
|
||||||
|
pylint --errors-only ${testDriverScript}
|
||||||
|
black --check --diff ${testDriverScript}
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase =
|
||||||
|
''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp ${testDriverScript} $out/bin/nixos-test-driver
|
||||||
|
chmod u+x $out/bin/nixos-test-driver
|
||||||
|
# TODO: copy user script part into this file (append)
|
||||||
|
|
||||||
|
wrapProgram $out/bin/nixos-test-driver \
|
||||||
|
--prefix PATH : "${lib.makeBinPath [ qemu_test vde2 netpbm coreutils ]}" \
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
# Run an automated test suite in the given virtual network.
|
||||||
|
# `driver' is the script that runs the network.
|
||||||
|
runTests = driver:
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
name = "vm-test-run-${driver.testName}";
|
||||||
|
|
||||||
|
requiredSystemFeatures = [ "kvm" "nixos-test" ];
|
||||||
|
|
||||||
|
buildInputs = [ libxslt ];
|
||||||
|
|
||||||
|
buildCommand =
|
||||||
|
''
|
||||||
|
mkdir -p $out/nix-support
|
||||||
|
|
||||||
|
LOGFILE=$out/log.xml tests='exec(os.environ["testScript"])' ${driver}/bin/nixos-test-driver
|
||||||
|
|
||||||
|
# Generate a pretty-printed log.
|
||||||
|
xsltproc --output $out/log.html ${./test-driver/log2html.xsl} $out/log.xml
|
||||||
|
ln -s ${./test-driver/logfile.css} $out/logfile.css
|
||||||
|
ln -s ${./test-driver/treebits.js} $out/treebits.js
|
||||||
|
ln -s ${jquery}/js/jquery.min.js $out/
|
||||||
|
ln -s ${jquery}/js/jquery.js $out/
|
||||||
|
ln -s ${jquery-ui}/js/jquery-ui.min.js $out/
|
||||||
|
ln -s ${jquery-ui}/js/jquery-ui.js $out/
|
||||||
|
|
||||||
|
touch $out/nix-support/hydra-build-products
|
||||||
|
echo "report testlog $out log.html" >> $out/nix-support/hydra-build-products
|
||||||
|
|
||||||
|
for i in */xchg/coverage-data; do
|
||||||
|
mkdir -p $out/coverage-data
|
||||||
|
mv $i $out/coverage-data/$(dirname $(dirname $i))
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
makeTest =
|
||||||
|
{ testScript
|
||||||
|
, makeCoverageReport ? false
|
||||||
|
, enableOCR ? false
|
||||||
|
, name ? "unnamed"
|
||||||
|
, ...
|
||||||
|
} @ t:
|
||||||
|
|
||||||
|
let
|
||||||
|
# A standard store path to the vm monitor is built like this:
|
||||||
|
# /tmp/nix-build-vm-test-run-$name.drv-0/vm-state-machine/monitor
|
||||||
|
# The max filename length of a unix domain socket is 108 bytes.
|
||||||
|
# This means $name can at most be 50 bytes long.
|
||||||
|
maxTestNameLen = 50;
|
||||||
|
testNameLen = builtins.stringLength name;
|
||||||
|
|
||||||
|
testDriverName = with builtins;
|
||||||
|
if testNameLen > maxTestNameLen then
|
||||||
|
abort ("The name of the test '${name}' must not be longer than ${toString maxTestNameLen} " +
|
||||||
|
"it's currently ${toString testNameLen} characters long.")
|
||||||
|
else
|
||||||
|
"nixos-test-driver-${name}";
|
||||||
|
|
||||||
|
nodes = buildVirtualNetwork (
|
||||||
|
t.nodes or (if t ? machine then { machine = t.machine; } else { }));
|
||||||
|
|
||||||
|
testScript' =
|
||||||
|
# Call the test script with the computed nodes.
|
||||||
|
if lib.isFunction testScript
|
||||||
|
then testScript { inherit nodes; }
|
||||||
|
else testScript;
|
||||||
|
|
||||||
|
vlans = map (m: m.config.virtualisation.vlans) (lib.attrValues nodes);
|
||||||
|
|
||||||
|
vms = map (m: m.config.system.build.vm) (lib.attrValues nodes);
|
||||||
|
|
||||||
|
ocrProg = tesseract4.override { enableLanguages = [ "eng" ]; };
|
||||||
|
|
||||||
|
imagemagick_tiff = imagemagick_light.override { inherit libtiff; };
|
||||||
|
|
||||||
|
# Generate onvenience wrappers for running the test driver
|
||||||
|
# interactively with the specified network, and for starting the
|
||||||
|
# VMs from the command line.
|
||||||
|
driver = runCommand testDriverName
|
||||||
|
{ buildInputs = [ makeWrapper];
|
||||||
|
testScript = testScript';
|
||||||
|
preferLocalBuild = true;
|
||||||
|
testName = name;
|
||||||
|
}
|
||||||
|
''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
|
||||||
|
echo -n "$testScript" > $out/test-script
|
||||||
|
${python3Packages.black}/bin/black --check --diff $out/test-script
|
||||||
|
|
||||||
|
ln -s ${testDriver}/bin/nixos-test-driver $out/bin/
|
||||||
|
vms=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
|
||||||
|
wrapProgram $out/bin/nixos-test-driver \
|
||||||
|
--add-flags "''${vms[*]}" \
|
||||||
|
${lib.optionalString enableOCR
|
||||||
|
"--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \
|
||||||
|
--run "export testScript=\"\$(cat $out/test-script)\"" \
|
||||||
|
--set VLANS '${toString vlans}'
|
||||||
|
ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms
|
||||||
|
wrapProgram $out/bin/nixos-run-vms \
|
||||||
|
--add-flags "''${vms[*]}" \
|
||||||
|
${lib.optionalString enableOCR "--prefix PATH : '${ocrProg}/bin'"} \
|
||||||
|
--set tests 'start_all(); join_all();' \
|
||||||
|
--set VLANS '${toString vlans}' \
|
||||||
|
${lib.optionalString (builtins.length vms == 1) "--set USE_SERIAL 1"}
|
||||||
|
''; # "
|
||||||
|
|
||||||
|
passMeta = drv: drv // lib.optionalAttrs (t ? meta) {
|
||||||
|
meta = (drv.meta or {}) // t.meta;
|
||||||
|
};
|
||||||
|
|
||||||
|
test = passMeta (runTests driver);
|
||||||
|
report = passMeta (releaseTools.gcovReport { coverageRuns = [ test ]; });
|
||||||
|
|
||||||
|
nodeNames = builtins.attrNames nodes;
|
||||||
|
invalidNodeNames = lib.filter
|
||||||
|
(node: builtins.match "^[A-z_][A-z0-9_]+$" node == null) nodeNames;
|
||||||
|
|
||||||
|
in
|
||||||
|
if lib.length invalidNodeNames > 0 then
|
||||||
|
throw ''
|
||||||
|
Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})!
|
||||||
|
All machines are referenced as perl variables in the testing framework which will break the
|
||||||
|
script when special characters are used.
|
||||||
|
|
||||||
|
Please stick to alphanumeric chars and underscores as separation.
|
||||||
|
''
|
||||||
|
else
|
||||||
|
(if makeCoverageReport then report else test) // {
|
||||||
|
inherit nodes driver test;
|
||||||
|
};
|
||||||
|
|
||||||
|
runInMachine =
|
||||||
|
{ drv
|
||||||
|
, machine
|
||||||
|
, preBuild ? ""
|
||||||
|
, postBuild ? ""
|
||||||
|
, ... # ???
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
vm = buildVM { }
|
||||||
|
[ machine
|
||||||
|
{ key = "run-in-machine";
|
||||||
|
networking.hostName = "client";
|
||||||
|
nix.readOnlyStore = false;
|
||||||
|
virtualisation.writableStore = false;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
buildrunner = writeText "vm-build" ''
|
||||||
|
source $1
|
||||||
|
|
||||||
|
${coreutils}/bin/mkdir -p $TMPDIR
|
||||||
|
cd $TMPDIR
|
||||||
|
|
||||||
|
exec $origBuilder $origArgs
|
||||||
|
'';
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
startAll;
|
||||||
|
$client->waitForUnit("multi-user.target");
|
||||||
|
${preBuild}
|
||||||
|
$client->succeed("env -i ${bash}/bin/bash ${buildrunner} /tmp/xchg/saved-env >&2");
|
||||||
|
${postBuild}
|
||||||
|
$client->succeed("sync"); # flush all data before pulling the plug
|
||||||
|
'';
|
||||||
|
|
||||||
|
vmRunCommand = writeText "vm-run" ''
|
||||||
|
xchg=vm-state-client/xchg
|
||||||
|
${coreutils}/bin/mkdir $out
|
||||||
|
${coreutils}/bin/mkdir -p $xchg
|
||||||
|
|
||||||
|
for i in $passAsFile; do
|
||||||
|
i2=''${i}Path
|
||||||
|
_basename=$(${coreutils}/bin/basename ''${!i2})
|
||||||
|
${coreutils}/bin/cp ''${!i2} $xchg/$_basename
|
||||||
|
eval $i2=/tmp/xchg/$_basename
|
||||||
|
${coreutils}/bin/ls -la $xchg
|
||||||
|
done
|
||||||
|
|
||||||
|
unset i i2 _basename
|
||||||
|
export | ${gnugrep}/bin/grep -v '^xchg=' > $xchg/saved-env
|
||||||
|
unset xchg
|
||||||
|
|
||||||
|
export tests='${testScript}'
|
||||||
|
${testDriver}/bin/nixos-test-driver ${vm.config.system.build.vm}/bin/run-*-vm
|
||||||
|
''; # */
|
||||||
|
|
||||||
|
in
|
||||||
|
lib.overrideDerivation drv (attrs: {
|
||||||
|
requiredSystemFeatures = [ "kvm" ];
|
||||||
|
builder = "${bash}/bin/sh";
|
||||||
|
args = ["-e" vmRunCommand];
|
||||||
|
origArgs = attrs.args;
|
||||||
|
origBuilder = attrs.builder;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
runInMachineWithX = { require ? [], ... } @ args:
|
||||||
|
let
|
||||||
|
client =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
inherit require;
|
||||||
|
virtualisation.memorySize = 1024;
|
||||||
|
services.xserver.enable = true;
|
||||||
|
services.xserver.displayManager.slim.enable = false;
|
||||||
|
services.xserver.displayManager.auto.enable = true;
|
||||||
|
services.xserver.windowManager.default = "icewm";
|
||||||
|
services.xserver.windowManager.icewm.enable = true;
|
||||||
|
services.xserver.desktopManager.default = "none";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
runInMachine ({
|
||||||
|
machine = client;
|
||||||
|
preBuild =
|
||||||
|
''
|
||||||
|
$client->waitForX;
|
||||||
|
'';
|
||||||
|
} // args);
|
||||||
|
|
||||||
|
|
||||||
|
simpleTest = as: (makeTest as).test;
|
||||||
|
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
# This module is deprecated, since you can just say ‘fonts.fonts = [
|
|
||||||
# pkgs.corefonts ];’ instead.
|
|
||||||
|
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
options = {
|
|
||||||
|
|
||||||
fonts = {
|
|
||||||
|
|
||||||
enableCoreFonts = mkOption {
|
|
||||||
visible = false;
|
|
||||||
default = false;
|
|
||||||
description = ''
|
|
||||||
Whether to include Microsoft's proprietary Core Fonts. These fonts
|
|
||||||
are redistributable, but only verbatim, among other restrictions.
|
|
||||||
See <link xlink:href="http://corefonts.sourceforge.net/eula.htm"/>
|
|
||||||
for details.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
config = mkIf config.fonts.enableCoreFonts {
|
|
||||||
|
|
||||||
fonts.fonts = [ pkgs.corefonts ];
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
{ config, pkgs, lib, ... }:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
|
|
||||||
let cfg = config.fonts.fontconfig.ultimate;
|
|
||||||
|
|
||||||
latestVersion = pkgs.fontconfig.configVersion;
|
|
||||||
|
|
||||||
# The configuration to be included in /etc/font/
|
|
||||||
confPkg = pkgs.runCommand "font-ultimate-conf" { preferLocalBuild = true; } ''
|
|
||||||
support_folder=$out/etc/fonts/conf.d
|
|
||||||
latest_folder=$out/etc/fonts/${latestVersion}/conf.d
|
|
||||||
|
|
||||||
mkdir -p $support_folder
|
|
||||||
mkdir -p $latest_folder
|
|
||||||
|
|
||||||
# fontconfig ultimate substitutions
|
|
||||||
${optionalString (cfg.substitutions != "none") ''
|
|
||||||
ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/presets/${cfg.substitutions}/*.conf \
|
|
||||||
$support_folder
|
|
||||||
ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/presets/${cfg.substitutions}/*.conf \
|
|
||||||
$latest_folder
|
|
||||||
''}
|
|
||||||
|
|
||||||
# fontconfig ultimate various configuration files
|
|
||||||
ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/conf.d/*.conf \
|
|
||||||
$support_folder
|
|
||||||
ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/conf.d/*.conf \
|
|
||||||
$latest_folder
|
|
||||||
'';
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
|
|
||||||
options = {
|
|
||||||
|
|
||||||
fonts = {
|
|
||||||
|
|
||||||
fontconfig = {
|
|
||||||
|
|
||||||
ultimate = {
|
|
||||||
enable = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = ''
|
|
||||||
Enable fontconfig-ultimate settings (formerly known as
|
|
||||||
Infinality). Besides the customizable settings in this NixOS
|
|
||||||
module, fontconfig-ultimate also provides many font-specific
|
|
||||||
rendering tweaks.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
substitutions = mkOption {
|
|
||||||
type = types.enum ["free" "combi" "ms" "none"];
|
|
||||||
default = "free";
|
|
||||||
description = ''
|
|
||||||
Font substitutions to replace common Type 1 fonts with nicer
|
|
||||||
TrueType fonts. <literal>free</literal> uses free fonts,
|
|
||||||
<literal>ms</literal> uses Microsoft fonts,
|
|
||||||
<literal>combi</literal> uses a combination, and
|
|
||||||
<literal>none</literal> disables the substitutions.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
preset = mkOption {
|
|
||||||
type = types.enum ["ultimate1" "ultimate2" "ultimate3" "ultimate4" "ultimate5" "osx" "windowsxp"];
|
|
||||||
default = "ultimate3";
|
|
||||||
description = ''
|
|
||||||
FreeType rendering settings preset. Any of the presets may be
|
|
||||||
customized by setting environment variables.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
config = mkIf (config.fonts.fontconfig.enable && cfg.enable) {
|
|
||||||
|
|
||||||
fonts.fontconfig.confPackages = [ confPkg ];
|
|
||||||
environment.variables.INFINALITY_FT = cfg.preset;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -1,327 +0,0 @@
|
|||||||
#! @shell@ -e
|
|
||||||
|
|
||||||
# FIXME: rewrite this in a more suitable language.
|
|
||||||
|
|
||||||
usage () {
|
|
||||||
exec man nixos-option
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################
|
|
||||||
# Process Arguments #
|
|
||||||
#####################
|
|
||||||
|
|
||||||
xml=false
|
|
||||||
verbose=false
|
|
||||||
nixPath=""
|
|
||||||
|
|
||||||
option=""
|
|
||||||
exit_code=0
|
|
||||||
|
|
||||||
argfun=""
|
|
||||||
for arg; do
|
|
||||||
if test -z "$argfun"; then
|
|
||||||
case $arg in
|
|
||||||
-*)
|
|
||||||
sarg="$arg"
|
|
||||||
longarg=""
|
|
||||||
while test "$sarg" != "-"; do
|
|
||||||
case $sarg in
|
|
||||||
--*) longarg=$arg; sarg="--";;
|
|
||||||
-I) argfun="include_nixpath";;
|
|
||||||
-*) usage;;
|
|
||||||
esac
|
|
||||||
# remove the first letter option
|
|
||||||
sarg="-${sarg#??}"
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
*) longarg=$arg;;
|
|
||||||
esac
|
|
||||||
for larg in $longarg; do
|
|
||||||
case $larg in
|
|
||||||
--xml) xml=true;;
|
|
||||||
--verbose) verbose=true;;
|
|
||||||
--help) usage;;
|
|
||||||
-*) usage;;
|
|
||||||
*) if test -z "$option"; then
|
|
||||||
option="$larg"
|
|
||||||
else
|
|
||||||
usage
|
|
||||||
fi;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
else
|
|
||||||
case $argfun in
|
|
||||||
set_*)
|
|
||||||
var=$(echo $argfun | sed 's,^set_,,')
|
|
||||||
eval $var=$arg
|
|
||||||
;;
|
|
||||||
include_nixpath)
|
|
||||||
nixPath="-I $arg $nixPath"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
argfun=""
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if $verbose; then
|
|
||||||
set -x
|
|
||||||
else
|
|
||||||
set +x
|
|
||||||
fi
|
|
||||||
|
|
||||||
#############################
|
|
||||||
# Process the configuration #
|
|
||||||
#############################
|
|
||||||
|
|
||||||
evalNix(){
|
|
||||||
# disable `-e` flag, it's possible that the evaluation of `nix-instantiate` fails (e.g. due to broken pkgs)
|
|
||||||
set +e
|
|
||||||
result=$(nix-instantiate ${nixPath:+$nixPath} - --eval-only "$@" 2>&1)
|
|
||||||
exit_code=$?
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if test $exit_code -eq 0; then
|
|
||||||
sed '/^warning: Nix search path/d' <<EOF
|
|
||||||
$result
|
|
||||||
EOF
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
sed -n '
|
|
||||||
/^error/ { s/, at (string):[0-9]*:[0-9]*//; p; };
|
|
||||||
/^warning: Nix search path/ { p; };
|
|
||||||
' >&2 <<EOF
|
|
||||||
$result
|
|
||||||
EOF
|
|
||||||
exit_code=1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
header="let
|
|
||||||
nixos = import <nixpkgs/nixos> {};
|
|
||||||
nixpkgs = import <nixpkgs> {};
|
|
||||||
in with nixpkgs.lib;
|
|
||||||
"
|
|
||||||
|
|
||||||
# This function is used for converting the option definition path given by
|
|
||||||
# the user into accessors for reaching the definition and the declaration
|
|
||||||
# corresponding to this option.
|
|
||||||
generateAccessors(){
|
|
||||||
if result=$(evalNix --strict --show-trace <<EOF
|
|
||||||
$header
|
|
||||||
|
|
||||||
let
|
|
||||||
path = "${option:+$option}";
|
|
||||||
pathList = splitString "." path;
|
|
||||||
|
|
||||||
walkOptions = attrsNames: result:
|
|
||||||
if attrsNames == [] then
|
|
||||||
result
|
|
||||||
else
|
|
||||||
let name = head attrsNames; rest = tail attrsNames; in
|
|
||||||
if isOption result.options then
|
|
||||||
walkOptions rest {
|
|
||||||
options = result.options.type.getSubOptions "";
|
|
||||||
opt = ''(\${result.opt}.type.getSubOptions "")'';
|
|
||||||
cfg = ''\${result.cfg}."\${name}"'';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
walkOptions rest {
|
|
||||||
options = result.options.\${name};
|
|
||||||
opt = ''\${result.opt}."\${name}"'';
|
|
||||||
cfg = ''\${result.cfg}."\${name}"'';
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
walkResult = (if path == "" then x: x else walkOptions pathList) {
|
|
||||||
options = nixos.options;
|
|
||||||
opt = ''nixos.options'';
|
|
||||||
cfg = ''nixos.config'';
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
''let option = \${walkResult.opt}; config = \${walkResult.cfg}; in''
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
then
|
|
||||||
echo $result
|
|
||||||
else
|
|
||||||
# In case of error we want to ignore the error message roduced by the
|
|
||||||
# script above, as it is iterating over each attribute, which does not
|
|
||||||
# produce a nice error message. The following code is a fallback
|
|
||||||
# solution which is cause a nicer error message in the next
|
|
||||||
# evaluation.
|
|
||||||
echo "\"let option = nixos.options${option:+.$option}; config = nixos.config${option:+.$option}; in\""
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
header="$header
|
|
||||||
$(eval echo $(generateAccessors))
|
|
||||||
"
|
|
||||||
|
|
||||||
evalAttr(){
|
|
||||||
local prefix="$1"
|
|
||||||
local strict="$2"
|
|
||||||
local suffix="$3"
|
|
||||||
|
|
||||||
# If strict is set, then set it to "true".
|
|
||||||
test -n "$strict" && strict=true
|
|
||||||
|
|
||||||
evalNix ${strict:+--strict} <<EOF
|
|
||||||
$header
|
|
||||||
|
|
||||||
let
|
|
||||||
value = $prefix${suffix:+.$suffix};
|
|
||||||
strict = ${strict:-false};
|
|
||||||
cleanOutput = x: with nixpkgs.lib;
|
|
||||||
if isDerivation x then x.outPath
|
|
||||||
else if isFunction x then "<CODE>"
|
|
||||||
else if strict then
|
|
||||||
if isAttrs x then mapAttrs (n: cleanOutput) x
|
|
||||||
else if isList x then map cleanOutput x
|
|
||||||
else x
|
|
||||||
else x;
|
|
||||||
in
|
|
||||||
cleanOutput value
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
evalOpt(){
|
|
||||||
evalAttr "option" "" "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
evalCfg(){
|
|
||||||
local strict="$1"
|
|
||||||
evalAttr "config" "$strict"
|
|
||||||
}
|
|
||||||
|
|
||||||
findSources(){
|
|
||||||
local suffix=$1
|
|
||||||
evalNix --strict <<EOF
|
|
||||||
$header
|
|
||||||
|
|
||||||
option.$suffix
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Given a result from nix-instantiate, recover the list of attributes it
|
|
||||||
# contains.
|
|
||||||
attrNames() {
|
|
||||||
local attributeset=$1
|
|
||||||
# sed is used to replace un-printable subset by 0s, and to remove most of
|
|
||||||
# the inner-attribute set, which reduce the likelyhood to encounter badly
|
|
||||||
# pre-processed input.
|
|
||||||
echo "builtins.attrNames $attributeset" | \
|
|
||||||
sed 's,<[A-Z]*>,0,g; :inner; s/{[^\{\}]*};/0;/g; t inner;' | \
|
|
||||||
evalNix --strict
|
|
||||||
}
|
|
||||||
|
|
||||||
# map a simple list which contains strings or paths.
|
|
||||||
nixMap() {
|
|
||||||
local fun="$1"
|
|
||||||
local list="$2"
|
|
||||||
local elem
|
|
||||||
for elem in $list; do
|
|
||||||
test $elem = '[' -o $elem = ']' && continue;
|
|
||||||
$fun $elem
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# This duplicates the work made below, but it is useful for processing
|
|
||||||
# the output of nixos-option with other tools such as nixos-gui.
|
|
||||||
if $xml; then
|
|
||||||
evalNix --xml --no-location <<EOF
|
|
||||||
$header
|
|
||||||
|
|
||||||
let
|
|
||||||
sources = builtins.map (f: f.source);
|
|
||||||
opt = option;
|
|
||||||
cfg = config;
|
|
||||||
in
|
|
||||||
|
|
||||||
with nixpkgs.lib;
|
|
||||||
|
|
||||||
let
|
|
||||||
optStrict = v:
|
|
||||||
let
|
|
||||||
traverse = x :
|
|
||||||
if isAttrs x then
|
|
||||||
if x ? outPath then true
|
|
||||||
else all id (mapAttrsFlatten (n: traverseNoAttrs) x)
|
|
||||||
else traverseNoAttrs x;
|
|
||||||
traverseNoAttrs = x:
|
|
||||||
# do not continue in attribute sets
|
|
||||||
if isAttrs x then true
|
|
||||||
else if isList x then all id (map traverse x)
|
|
||||||
else true;
|
|
||||||
in assert traverse v; v;
|
|
||||||
in
|
|
||||||
|
|
||||||
if isOption opt then
|
|
||||||
optStrict ({}
|
|
||||||
// optionalAttrs (opt ? default) { inherit (opt) default; }
|
|
||||||
// optionalAttrs (opt ? example) { inherit (opt) example; }
|
|
||||||
// optionalAttrs (opt ? description) { inherit (opt) description; }
|
|
||||||
// optionalAttrs (opt ? type) { typename = opt.type.description; }
|
|
||||||
// optionalAttrs (opt ? options) { inherit (opt) options; }
|
|
||||||
// {
|
|
||||||
# to disambiguate the xml output.
|
|
||||||
_isOption = true;
|
|
||||||
declarations = sources opt.declarations;
|
|
||||||
definitions = sources opt.definitions;
|
|
||||||
value = cfg;
|
|
||||||
})
|
|
||||||
else
|
|
||||||
opt
|
|
||||||
EOF
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test "$(evalOpt "_type" 2> /dev/null)" = '"option"'; then
|
|
||||||
echo "Value:"
|
|
||||||
evalCfg 1
|
|
||||||
|
|
||||||
echo
|
|
||||||
|
|
||||||
echo "Default:"
|
|
||||||
if default=$(evalOpt "default" - 2> /dev/null); then
|
|
||||||
echo "$default"
|
|
||||||
else
|
|
||||||
echo "<None>"
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
if example=$(evalOpt "example" - 2> /dev/null); then
|
|
||||||
echo "Example:"
|
|
||||||
echo "$example"
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
echo "Description:"
|
|
||||||
echo
|
|
||||||
echo $(evalOpt "description")
|
|
||||||
|
|
||||||
echo $desc;
|
|
||||||
|
|
||||||
printPath () { echo " $1"; }
|
|
||||||
|
|
||||||
echo "Declared by:"
|
|
||||||
nixMap printPath "$(findSources "declarations")"
|
|
||||||
echo
|
|
||||||
echo "Defined by:"
|
|
||||||
nixMap printPath "$(findSources "files")"
|
|
||||||
echo
|
|
||||||
|
|
||||||
else
|
|
||||||
# echo 1>&2 "Warning: This value is not an option."
|
|
||||||
|
|
||||||
result=$(evalCfg "")
|
|
||||||
if [ ! -z "$result" ]; then
|
|
||||||
names=$(attrNames "$result" 2> /dev/null)
|
|
||||||
echo 1>&2 "This attribute set contains:"
|
|
||||||
escapeQuotes () { eval echo "$1"; }
|
|
||||||
nixMap escapeQuotes "$names"
|
|
||||||
else
|
|
||||||
echo 1>&2 "An error occurred while looking for attribute names. Are you sure that '$option' exists?"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit $exit_code
|
|
@ -0,0 +1,8 @@
|
|||||||
|
cmake_minimum_required (VERSION 2.6)
|
||||||
|
project (nixos-option)
|
||||||
|
|
||||||
|
add_executable(nixos-option nixos-option.cc libnix-copy-paste.cc)
|
||||||
|
target_link_libraries(nixos-option PRIVATE -lnixmain -lnixexpr -lnixstore -lnixutil)
|
||||||
|
target_compile_features(nixos-option PRIVATE cxx_std_17)
|
||||||
|
|
||||||
|
install (TARGETS nixos-option DESTINATION bin)
|
11
nixos/modules/installer/tools/nixos-option/default.nix
Normal file
11
nixos/modules/installer/tools/nixos-option/default.nix
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{lib, stdenv, boost, cmake, pkgconfig, nix, ... }:
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
name = "nixos-option";
|
||||||
|
src = ./.;
|
||||||
|
nativeBuildInputs = [ cmake pkgconfig ];
|
||||||
|
buildInputs = [ boost nix ];
|
||||||
|
meta = {
|
||||||
|
license = stdenv.lib.licenses.lgpl2Plus;
|
||||||
|
maintainers = with lib.maintainers; [ chkno ];
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
// These are useful methods inside the nix library that ought to be exported.
|
||||||
|
// Since they are not, copy/paste them here.
|
||||||
|
// TODO: Delete these and use the ones in the library as they become available.
|
||||||
|
|
||||||
|
#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
|
||||||
|
|
||||||
|
#include "libnix-copy-paste.hh"
|
||||||
|
#include <boost/format/alt_sstream.hpp> // for basic_altstringbuf...
|
||||||
|
#include <boost/format/alt_sstream_impl.hpp> // for basic_altstringbuf...
|
||||||
|
#include <boost/format/format_class.hpp> // for basic_format
|
||||||
|
#include <boost/format/format_fwd.hpp> // for format
|
||||||
|
#include <boost/format/format_implementation.hpp> // for basic_format::basi...
|
||||||
|
#include <boost/optional/optional.hpp> // for get_pointer
|
||||||
|
#include <iostream> // for operator<<, basic_...
|
||||||
|
#include <nix/types.hh> // for Strings, Error
|
||||||
|
#include <string> // for string, basic_string
|
||||||
|
|
||||||
|
using boost::format;
|
||||||
|
using nix::Error;
|
||||||
|
using nix::Strings;
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
// From nix/src/libexpr/attr-path.cc
|
||||||
|
Strings parseAttrPath(const string & s)
|
||||||
|
{
|
||||||
|
Strings res;
|
||||||
|
string cur;
|
||||||
|
string::const_iterator i = s.begin();
|
||||||
|
while (i != s.end()) {
|
||||||
|
if (*i == '.') {
|
||||||
|
res.push_back(cur);
|
||||||
|
cur.clear();
|
||||||
|
} else if (*i == '"') {
|
||||||
|
++i;
|
||||||
|
while (1) {
|
||||||
|
if (i == s.end())
|
||||||
|
throw Error(format("missing closing quote in selection path '%1%'") % s);
|
||||||
|
if (*i == '"')
|
||||||
|
break;
|
||||||
|
cur.push_back(*i++);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
cur.push_back(*i);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (!cur.empty())
|
||||||
|
res.push_back(cur);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From nix/src/nix/repl.cc
|
||||||
|
bool isVarName(const string & s)
|
||||||
|
{
|
||||||
|
if (s.size() == 0)
|
||||||
|
return false;
|
||||||
|
char c = s[0];
|
||||||
|
if ((c >= '0' && c <= '9') || c == '-' || c == '\'')
|
||||||
|
return false;
|
||||||
|
for (auto & i : s)
|
||||||
|
if (!((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == '-' ||
|
||||||
|
i == '\''))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From nix/src/nix/repl.cc
|
||||||
|
std::ostream & printStringValue(std::ostream & str, const char * string)
|
||||||
|
{
|
||||||
|
str << "\"";
|
||||||
|
for (const char * i = string; *i; i++)
|
||||||
|
if (*i == '\"' || *i == '\\')
|
||||||
|
str << "\\" << *i;
|
||||||
|
else if (*i == '\n')
|
||||||
|
str << "\\n";
|
||||||
|
else if (*i == '\r')
|
||||||
|
str << "\\r";
|
||||||
|
else if (*i == '\t')
|
||||||
|
str << "\\t";
|
||||||
|
else
|
||||||
|
str << *i;
|
||||||
|
str << "\"";
|
||||||
|
return str;
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <nix/types.hh>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
nix::Strings parseAttrPath(const std::string & s);
|
||||||
|
bool isVarName(const std::string & s);
|
||||||
|
std::ostream & printStringValue(std::ostream & str, const char * string);
|
618
nixos/modules/installer/tools/nixos-option/nixos-option.cc
Normal file
618
nixos/modules/installer/tools/nixos-option/nixos-option.cc
Normal file
@ -0,0 +1,618 @@
|
|||||||
|
#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
|
||||||
|
|
||||||
|
#include <exception> // for exception_ptr, current_exception
|
||||||
|
#include <functional> // for function
|
||||||
|
#include <iostream> // for operator<<, basic_ostream, ostrin...
|
||||||
|
#include <iterator> // for next
|
||||||
|
#include <list> // for _List_iterator
|
||||||
|
#include <memory> // for allocator, unique_ptr, make_unique
|
||||||
|
#include <new> // for operator new
|
||||||
|
#include <nix/args.hh> // for argvToStrings, UsageError
|
||||||
|
#include <nix/attr-path.hh> // for findAlongAttrPath
|
||||||
|
#include <nix/attr-set.hh> // for Attr, Bindings, Bindings::iterator
|
||||||
|
#include <nix/common-eval-args.hh> // for MixEvalArgs
|
||||||
|
#include <nix/eval-inline.hh> // for EvalState::forceValue
|
||||||
|
#include <nix/eval.hh> // for EvalState, initGC, operator<<
|
||||||
|
#include <nix/globals.hh> // for initPlugins, Settings, settings
|
||||||
|
#include <nix/nixexpr.hh> // for Pos
|
||||||
|
#include <nix/shared.hh> // for getArg, LegacyArgs, printVersion
|
||||||
|
#include <nix/store-api.hh> // for openStore
|
||||||
|
#include <nix/symbol-table.hh> // for Symbol, SymbolTable
|
||||||
|
#include <nix/types.hh> // for Error, Path, Strings, PathSet
|
||||||
|
#include <nix/util.hh> // for absPath, baseNameOf
|
||||||
|
#include <nix/value.hh> // for Value, Value::(anonymous), Value:...
|
||||||
|
#include <string> // for string, operator+, operator==
|
||||||
|
#include <utility> // for move
|
||||||
|
#include <variant> // for get, holds_alternative, variant
|
||||||
|
#include <vector> // for vector<>::iterator, vector
|
||||||
|
|
||||||
|
#include "libnix-copy-paste.hh"
|
||||||
|
|
||||||
|
using nix::absPath;
|
||||||
|
using nix::Bindings;
|
||||||
|
using nix::Error;
|
||||||
|
using nix::EvalError;
|
||||||
|
using nix::EvalState;
|
||||||
|
using nix::Path;
|
||||||
|
using nix::PathSet;
|
||||||
|
using nix::Strings;
|
||||||
|
using nix::Symbol;
|
||||||
|
using nix::tAttrs;
|
||||||
|
using nix::ThrownError;
|
||||||
|
using nix::tLambda;
|
||||||
|
using nix::tString;
|
||||||
|
using nix::UsageError;
|
||||||
|
using nix::Value;
|
||||||
|
|
||||||
|
// An ostream wrapper to handle nested indentation
|
||||||
|
class Out
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class Separator
|
||||||
|
{};
|
||||||
|
const static Separator sep;
|
||||||
|
enum LinePolicy
|
||||||
|
{
|
||||||
|
ONE_LINE,
|
||||||
|
MULTI_LINE
|
||||||
|
};
|
||||||
|
explicit Out(std::ostream & ostream) : ostream(ostream), policy(ONE_LINE), writeSinceSep(true) {}
|
||||||
|
Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy);
|
||||||
|
Out(Out & o, const std::string & start, const std::string & end, int count)
|
||||||
|
: Out(o, start, end, count < 2 ? ONE_LINE : MULTI_LINE)
|
||||||
|
{}
|
||||||
|
Out(const Out &) = delete;
|
||||||
|
Out(Out &&) = default;
|
||||||
|
Out & operator=(const Out &) = delete;
|
||||||
|
Out & operator=(Out &&) = delete;
|
||||||
|
~Out() { ostream << end; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::ostream & ostream;
|
||||||
|
std::string indentation;
|
||||||
|
std::string end;
|
||||||
|
LinePolicy policy;
|
||||||
|
bool writeSinceSep;
|
||||||
|
template <typename T> friend Out & operator<<(Out & o, T thing);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> Out & operator<<(Out & o, T thing)
|
||||||
|
{
|
||||||
|
if (!o.writeSinceSep && o.policy == Out::MULTI_LINE) {
|
||||||
|
o.ostream << o.indentation;
|
||||||
|
}
|
||||||
|
o.writeSinceSep = true;
|
||||||
|
o.ostream << thing;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> Out & operator<<<Out::Separator>(Out & o, Out::Separator /* thing */)
|
||||||
|
{
|
||||||
|
o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n");
|
||||||
|
o.writeSinceSep = false;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
Out::Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy)
|
||||||
|
: ostream(o.ostream), indentation(policy == ONE_LINE ? o.indentation : o.indentation + " "),
|
||||||
|
end(policy == ONE_LINE ? end : o.indentation + end), policy(policy), writeSinceSep(true)
|
||||||
|
{
|
||||||
|
o << start;
|
||||||
|
*this << Out::sep;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stuff needed for evaluation
|
||||||
|
struct Context
|
||||||
|
{
|
||||||
|
Context(EvalState & state, Bindings & autoArgs, Value optionsRoot, Value configRoot)
|
||||||
|
: state(state), autoArgs(autoArgs), optionsRoot(optionsRoot), configRoot(configRoot),
|
||||||
|
underscoreType(state.symbols.create("_type"))
|
||||||
|
{}
|
||||||
|
EvalState & state;
|
||||||
|
Bindings & autoArgs;
|
||||||
|
Value optionsRoot;
|
||||||
|
Value configRoot;
|
||||||
|
Symbol underscoreType;
|
||||||
|
};
|
||||||
|
|
||||||
|
Value evaluateValue(Context & ctx, Value & v)
|
||||||
|
{
|
||||||
|
ctx.state.forceValue(v);
|
||||||
|
if (ctx.autoArgs.empty()) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
Value called{};
|
||||||
|
ctx.state.autoCallFunction(ctx.autoArgs, v, called);
|
||||||
|
return called;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOption(Context & ctx, const Value & v)
|
||||||
|
{
|
||||||
|
if (v.type != tAttrs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto & atualType = v.attrs->find(ctx.underscoreType);
|
||||||
|
if (atualType == v.attrs->end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Value evaluatedType = evaluateValue(ctx, *atualType->value);
|
||||||
|
if (evaluatedType.type != tString) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return static_cast<std::string>(evaluatedType.string.s) == "option";
|
||||||
|
} catch (Error &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add quotes to a component of a path.
|
||||||
|
// These are needed for paths like:
|
||||||
|
// fileSystems."/".fsType
|
||||||
|
// systemd.units."dbus.service".text
|
||||||
|
std::string quoteAttribute(const std::string & attribute)
|
||||||
|
{
|
||||||
|
if (isVarName(attribute)) {
|
||||||
|
return attribute;
|
||||||
|
}
|
||||||
|
std::ostringstream buf;
|
||||||
|
printStringValue(buf, attribute.c_str());
|
||||||
|
return buf.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string appendPath(const std::string & prefix, const std::string & suffix)
|
||||||
|
{
|
||||||
|
if (prefix.empty()) {
|
||||||
|
return quoteAttribute(suffix);
|
||||||
|
}
|
||||||
|
return prefix + "." + quoteAttribute(suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool forbiddenRecursionName(std::string name) { return (!name.empty() && name[0] == '_') || name == "haskellPackages"; }
|
||||||
|
|
||||||
|
void recurse(const std::function<bool(const std::string & path, std::variant<Value, std::exception_ptr>)> & f,
|
||||||
|
Context & ctx, Value v, const std::string & path)
|
||||||
|
{
|
||||||
|
std::variant<Value, std::exception_ptr> evaluated;
|
||||||
|
try {
|
||||||
|
evaluated = evaluateValue(ctx, v);
|
||||||
|
} catch (Error &) {
|
||||||
|
evaluated = std::current_exception();
|
||||||
|
}
|
||||||
|
if (!f(path, evaluated)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (std::holds_alternative<std::exception_ptr>(evaluated)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Value & evaluated_value = std::get<Value>(evaluated);
|
||||||
|
if (evaluated_value.type != tAttrs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto & child : evaluated_value.attrs->lexicographicOrder()) {
|
||||||
|
if (forbiddenRecursionName(child->name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
recurse(f, ctx, *child->value, appendPath(path, child->name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls f on all the option names
|
||||||
|
void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, Value root)
|
||||||
|
{
|
||||||
|
recurse(
|
||||||
|
[f, &ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) {
|
||||||
|
bool isOpt = std::holds_alternative<std::exception_ptr>(v) || isOption(ctx, std::get<Value>(v));
|
||||||
|
if (isOpt) {
|
||||||
|
f(path);
|
||||||
|
}
|
||||||
|
return !isOpt;
|
||||||
|
},
|
||||||
|
ctx, root, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls f on all the config values inside one option.
|
||||||
|
// Simple options have one config value inside, like sound.enable = true.
|
||||||
|
// Compound options have multiple config values. For example, the option
|
||||||
|
// "users.users" has about 1000 config values inside it:
|
||||||
|
// users.users.avahi.createHome = false;
|
||||||
|
// users.users.avahi.cryptHomeLuks = null;
|
||||||
|
// users.users.avahi.description = "`avahi-daemon' privilege separation user";
|
||||||
|
// ...
|
||||||
|
// users.users.avahi.openssh.authorizedKeys.keyFiles = [ ];
|
||||||
|
// users.users.avahi.openssh.authorizedKeys.keys = [ ];
|
||||||
|
// ...
|
||||||
|
// users.users.avahi.uid = 10;
|
||||||
|
// users.users.avahi.useDefaultShell = false;
|
||||||
|
// users.users.cups.createHome = false;
|
||||||
|
// ...
|
||||||
|
// users.users.cups.useDefaultShell = false;
|
||||||
|
// users.users.gdm = ... ... ...
|
||||||
|
// users.users.messagebus = ... .. ...
|
||||||
|
// users.users.nixbld1 = ... .. ...
|
||||||
|
// ...
|
||||||
|
// users.users.systemd-timesync = ... .. ...
|
||||||
|
void mapConfigValuesInOption(
|
||||||
|
const std::function<void(const std::string & path, std::variant<Value, std::exception_ptr> v)> & f,
|
||||||
|
const std::string & path, Context & ctx)
|
||||||
|
{
|
||||||
|
Value * option;
|
||||||
|
try {
|
||||||
|
option = findAlongAttrPath(ctx.state, path, ctx.autoArgs, ctx.configRoot);
|
||||||
|
} catch (Error &) {
|
||||||
|
f(path, std::current_exception());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recurse(
|
||||||
|
[f, ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) {
|
||||||
|
bool leaf = std::holds_alternative<std::exception_ptr>(v) || std::get<Value>(v).type != tAttrs ||
|
||||||
|
ctx.state.isDerivation(std::get<Value>(v));
|
||||||
|
if (!leaf) {
|
||||||
|
return true; // Keep digging
|
||||||
|
}
|
||||||
|
f(path, v);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
ctx, *option, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string describeError(const Error & e) { return "«error: " + e.msg() + "»"; }
|
||||||
|
|
||||||
|
void describeDerivation(Context & ctx, Out & out, Value v)
|
||||||
|
{
|
||||||
|
// Copy-pasted from nix/src/nix/repl.cc :(
|
||||||
|
Bindings::iterator i = v.attrs->find(ctx.state.sDrvPath);
|
||||||
|
PathSet pathset;
|
||||||
|
try {
|
||||||
|
Path drvPath = i != v.attrs->end() ? ctx.state.coerceToPath(*i->pos, *i->value, pathset) : "???";
|
||||||
|
out << "«derivation " << drvPath << "»";
|
||||||
|
} catch (Error & e) {
|
||||||
|
out << describeError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value parseAndEval(EvalState & state, const std::string & expression, const std::string & path)
|
||||||
|
{
|
||||||
|
Value v{};
|
||||||
|
state.eval(state.parseExprFromString(expression, absPath(path)), v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path);
|
||||||
|
|
||||||
|
void printList(Context & ctx, Out & out, Value & v)
|
||||||
|
{
|
||||||
|
Out listOut(out, "[", "]", v.listSize());
|
||||||
|
for (unsigned int n = 0; n < v.listSize(); ++n) {
|
||||||
|
printValue(ctx, listOut, *v.listElems()[n], "");
|
||||||
|
listOut << Out::sep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path)
|
||||||
|
{
|
||||||
|
Out attrsOut(out, "{", "}", v.attrs->size());
|
||||||
|
for (const auto & a : v.attrs->lexicographicOrder()) {
|
||||||
|
std::string name = a->name;
|
||||||
|
attrsOut << name << " = ";
|
||||||
|
printValue(ctx, attrsOut, *a->value, appendPath(path, name));
|
||||||
|
attrsOut << ";" << Out::sep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void multiLineStringEscape(Out & out, const std::string & s)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < s.size(); i++) {
|
||||||
|
if (s[i - 1] == '$' && s[i] == '{') {
|
||||||
|
out << "''${";
|
||||||
|
i++;
|
||||||
|
} else if (s[i - 1] == '\'' && s[i] == '\'') {
|
||||||
|
out << "'''";
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
out << s[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == s.size()) {
|
||||||
|
out << s[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printMultiLineString(Out & out, const Value & v)
|
||||||
|
{
|
||||||
|
std::string s = v.string.s;
|
||||||
|
Out strOut(out, "''", "''", Out::MULTI_LINE);
|
||||||
|
std::string::size_type begin = 0;
|
||||||
|
while (begin < s.size()) {
|
||||||
|
std::string::size_type end = s.find('\n', begin);
|
||||||
|
if (end == std::string::npos) {
|
||||||
|
multiLineStringEscape(strOut, s.substr(begin, s.size() - begin));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
multiLineStringEscape(strOut, s.substr(begin, end - begin));
|
||||||
|
strOut << Out::sep;
|
||||||
|
begin = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (auto ex = std::get_if<std::exception_ptr>(&maybeValue)) {
|
||||||
|
std::rethrow_exception(*ex);
|
||||||
|
}
|
||||||
|
Value v = evaluateValue(ctx, std::get<Value>(maybeValue));
|
||||||
|
if (ctx.state.isDerivation(v)) {
|
||||||
|
describeDerivation(ctx, out, v);
|
||||||
|
} else if (v.isList()) {
|
||||||
|
printList(ctx, out, v);
|
||||||
|
} else if (v.type == tAttrs) {
|
||||||
|
printAttrs(ctx, out, v, path);
|
||||||
|
} else if (v.type == tString && std::string(v.string.s).find('\n') != std::string::npos) {
|
||||||
|
printMultiLineString(out, v);
|
||||||
|
} else {
|
||||||
|
ctx.state.forceValueDeep(v);
|
||||||
|
out << v;
|
||||||
|
}
|
||||||
|
} catch (ThrownError & e) {
|
||||||
|
if (e.msg() == "The option `" + path + "' is used but not defined.") {
|
||||||
|
// 93% of errors are this, and just letting this message through would be
|
||||||
|
// misleading. These values may or may not actually be "used" in the
|
||||||
|
// config. The thing throwing the error message assumes that if anything
|
||||||
|
// ever looks at this value, it is a "use" of this value. But here in
|
||||||
|
// nixos-option, we are looking at this value only to print it.
|
||||||
|
// In order to avoid implying that this undefined value is actually
|
||||||
|
// referenced, eat the underlying error message and emit "«not defined»".
|
||||||
|
out << "«not defined»";
|
||||||
|
} else {
|
||||||
|
out << describeError(e);
|
||||||
|
}
|
||||||
|
} catch (Error & e) {
|
||||||
|
out << describeError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printConfigValue(Context & ctx, Out & out, const std::string & path, std::variant<Value, std::exception_ptr> v)
|
||||||
|
{
|
||||||
|
out << path << " = ";
|
||||||
|
printValue(ctx, out, std::move(v), path);
|
||||||
|
out << ";\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void printAll(Context & ctx, Out & out)
|
||||||
|
{
|
||||||
|
mapOptions(
|
||||||
|
[&ctx, &out](const std::string & optionPath) {
|
||||||
|
mapConfigValuesInOption(
|
||||||
|
[&ctx, &out](const std::string & configPath, std::variant<Value, std::exception_ptr> v) {
|
||||||
|
printConfigValue(ctx, out, configPath, v);
|
||||||
|
},
|
||||||
|
optionPath, ctx);
|
||||||
|
},
|
||||||
|
ctx, ctx.optionsRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printAttr(Context & ctx, Out & out, const std::string & path, Value & root)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
printValue(ctx, out, *findAlongAttrPath(ctx.state, path, ctx.autoArgs, root), path);
|
||||||
|
} catch (Error & e) {
|
||||||
|
out << describeError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasExample(Context & ctx, Value & option)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
findAlongAttrPath(ctx.state, "example", ctx.autoArgs, option);
|
||||||
|
return true;
|
||||||
|
} catch (Error &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printOption(Context & ctx, Out & out, const std::string & path, Value & option)
|
||||||
|
{
|
||||||
|
out << "Value:\n";
|
||||||
|
printAttr(ctx, out, path, ctx.configRoot);
|
||||||
|
|
||||||
|
out << "\n\nDefault:\n";
|
||||||
|
printAttr(ctx, out, "default", option);
|
||||||
|
|
||||||
|
out << "\n\nType:\n";
|
||||||
|
printAttr(ctx, out, "type.description", option);
|
||||||
|
|
||||||
|
if (hasExample(ctx, option)) {
|
||||||
|
out << "\n\nExample:\n";
|
||||||
|
printAttr(ctx, out, "example", option);
|
||||||
|
}
|
||||||
|
|
||||||
|
out << "\n\nDescription:\n";
|
||||||
|
printAttr(ctx, out, "description", option);
|
||||||
|
|
||||||
|
out << "\n\nDeclared by:\n";
|
||||||
|
printAttr(ctx, out, "declarations", option);
|
||||||
|
|
||||||
|
out << "\n\nDefined by:\n";
|
||||||
|
printAttr(ctx, out, "files", option);
|
||||||
|
out << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void printListing(Out & out, Value & v)
|
||||||
|
{
|
||||||
|
out << "This attribute set contains:\n";
|
||||||
|
for (const auto & a : v.attrs->lexicographicOrder()) {
|
||||||
|
std::string name = a->name;
|
||||||
|
if (!name.empty() && name[0] != '_') {
|
||||||
|
out << name << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const auto & typeLookup = v.attrs->find(ctx.state.sType);
|
||||||
|
if (typeLookup == v.attrs->end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Value type = evaluateValue(ctx, *typeLookup->value);
|
||||||
|
if (type.type != tAttrs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto & nameLookup = type.attrs->find(ctx.state.sName);
|
||||||
|
if (nameLookup == type.attrs->end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Value name = evaluateValue(ctx, *nameLookup->value);
|
||||||
|
if (name.type != tString) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return name.string.s == soughtType;
|
||||||
|
} catch (Error &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAggregateOptionType(Context & ctx, Value & v)
|
||||||
|
{
|
||||||
|
return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf");
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeError(OptionPathError, EvalError);
|
||||||
|
|
||||||
|
Value getSubOptions(Context & ctx, Value & option)
|
||||||
|
{
|
||||||
|
Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option));
|
||||||
|
if (getSubOptions.type != tLambda) {
|
||||||
|
throw OptionPathError("Option's type.getSubOptions isn't a function");
|
||||||
|
}
|
||||||
|
Value emptyString{};
|
||||||
|
nix::mkString(emptyString, "");
|
||||||
|
Value v;
|
||||||
|
ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{});
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carefully walk an option path, looking for sub-options when a path walks past
|
||||||
|
// an option value.
|
||||||
|
Value findAlongOptionPath(Context & ctx, const std::string & path)
|
||||||
|
{
|
||||||
|
Strings tokens = parseAttrPath(path);
|
||||||
|
Value v = ctx.optionsRoot;
|
||||||
|
for (auto i = tokens.begin(); i != tokens.end(); i++) {
|
||||||
|
const auto & attr = *i;
|
||||||
|
try {
|
||||||
|
bool lastAttribute = std::next(i) == tokens.end();
|
||||||
|
v = evaluateValue(ctx, v);
|
||||||
|
if (attr.empty()) {
|
||||||
|
throw OptionPathError("empty attribute name");
|
||||||
|
}
|
||||||
|
if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) {
|
||||||
|
v = getSubOptions(ctx, v);
|
||||||
|
}
|
||||||
|
if (isOption(ctx, v) && isAggregateOptionType(ctx, v) && !lastAttribute) {
|
||||||
|
v = getSubOptions(ctx, v);
|
||||||
|
// Note that we've consumed attr, but didn't actually use it. This is the path component that's looked
|
||||||
|
// up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name".
|
||||||
|
} else if (v.type != tAttrs) {
|
||||||
|
throw OptionPathError("Value is %s while a set was expected", showType(v));
|
||||||
|
} else {
|
||||||
|
const auto & next = v.attrs->find(ctx.state.symbols.create(attr));
|
||||||
|
if (next == v.attrs->end()) {
|
||||||
|
throw OptionPathError("Attribute not found", attr, path);
|
||||||
|
}
|
||||||
|
v = *next->value;
|
||||||
|
}
|
||||||
|
} catch (OptionPathError & e) {
|
||||||
|
throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printOne(Context & ctx, Out & out, const std::string & path)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Value option = findAlongOptionPath(ctx, path);
|
||||||
|
option = evaluateValue(ctx, option);
|
||||||
|
if (isOption(ctx, option)) {
|
||||||
|
printOption(ctx, out, path, option);
|
||||||
|
} else {
|
||||||
|
printListing(out, option);
|
||||||
|
}
|
||||||
|
} catch (Error & e) {
|
||||||
|
std::cerr << "error: " << e.msg()
|
||||||
|
<< "\nAn error occurred while looking for attribute names. Are "
|
||||||
|
"you sure that '"
|
||||||
|
<< path << "' exists?\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char ** argv)
|
||||||
|
{
|
||||||
|
bool all = false;
|
||||||
|
std::string path = ".";
|
||||||
|
std::string optionsExpr = "(import <nixpkgs/nixos> {}).options";
|
||||||
|
std::string configExpr = "(import <nixpkgs/nixos> {}).config";
|
||||||
|
std::vector<std::string> args;
|
||||||
|
|
||||||
|
struct MyArgs : nix::LegacyArgs, nix::MixEvalArgs
|
||||||
|
{
|
||||||
|
using nix::LegacyArgs::LegacyArgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
MyArgs myArgs(nix::baseNameOf(argv[0]), [&](Strings::iterator & arg, const Strings::iterator & end) {
|
||||||
|
if (*arg == "--help") {
|
||||||
|
nix::showManPage("nixos-option");
|
||||||
|
} else if (*arg == "--version") {
|
||||||
|
nix::printVersion("nixos-option");
|
||||||
|
} else if (*arg == "--all") {
|
||||||
|
all = true;
|
||||||
|
} else if (*arg == "--path") {
|
||||||
|
path = nix::getArg(*arg, arg, end);
|
||||||
|
} else if (*arg == "--options_expr") {
|
||||||
|
optionsExpr = nix::getArg(*arg, arg, end);
|
||||||
|
} else if (*arg == "--config_expr") {
|
||||||
|
configExpr = nix::getArg(*arg, arg, end);
|
||||||
|
} else if (!arg->empty() && arg->at(0) == '-') {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
args.push_back(*arg);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
myArgs.parseCmdline(nix::argvToStrings(argc, argv));
|
||||||
|
|
||||||
|
nix::initPlugins();
|
||||||
|
nix::initGC();
|
||||||
|
nix::settings.readOnlyMode = true;
|
||||||
|
auto store = nix::openStore();
|
||||||
|
auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
|
||||||
|
|
||||||
|
Value optionsRoot = parseAndEval(*state, optionsExpr, path);
|
||||||
|
Value configRoot = parseAndEval(*state, configExpr, path);
|
||||||
|
|
||||||
|
Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot};
|
||||||
|
Out out(std::cout);
|
||||||
|
|
||||||
|
if (all) {
|
||||||
|
if (!args.empty()) {
|
||||||
|
throw UsageError("--all cannot be used with arguments");
|
||||||
|
}
|
||||||
|
printAll(ctx, out);
|
||||||
|
} else {
|
||||||
|
if (args.empty()) {
|
||||||
|
printOne(ctx, out, "");
|
||||||
|
}
|
||||||
|
for (const auto & arg : args) {
|
||||||
|
printOne(ctx, out, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.state.printStats();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -90,6 +90,11 @@ while [ "$#" -gt 0 ]; do
|
|||||||
targetHost="$1"
|
targetHost="$1"
|
||||||
shift 1
|
shift 1
|
||||||
;;
|
;;
|
||||||
|
--use-remote-sudo)
|
||||||
|
# note the trailing space
|
||||||
|
maybeSudo="sudo "
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "$0: unknown option \`$i'"
|
echo "$0: unknown option \`$i'"
|
||||||
exit 1
|
exit 1
|
||||||
@ -97,10 +102,6 @@ while [ "$#" -gt 0 ]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -n "$SUDO_USER" ]; then
|
|
||||||
maybeSudo="sudo "
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$buildHost" -a -n "$targetHost" ]; then
|
if [ -z "$buildHost" -a -n "$targetHost" ]; then
|
||||||
buildHost="$targetHost"
|
buildHost="$targetHost"
|
||||||
fi
|
fi
|
||||||
|
@ -41,10 +41,7 @@ let
|
|||||||
inherit (config.system.nixos-generate-config) configuration;
|
inherit (config.system.nixos-generate-config) configuration;
|
||||||
};
|
};
|
||||||
|
|
||||||
nixos-option = makeProg {
|
nixos-option = pkgs.callPackage ./nixos-option { };
|
||||||
name = "nixos-option";
|
|
||||||
src = ./nixos-option.sh;
|
|
||||||
};
|
|
||||||
|
|
||||||
nixos-version = makeProg {
|
nixos-version = makeProg {
|
||||||
name = "nixos-version";
|
name = "nixos-version";
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
[
|
[
|
||||||
./config/debug-info.nix
|
./config/debug-info.nix
|
||||||
./config/fonts/corefonts.nix
|
|
||||||
./config/fonts/fontconfig.nix
|
./config/fonts/fontconfig.nix
|
||||||
./config/fonts/fontconfig-penultimate.nix
|
./config/fonts/fontconfig-penultimate.nix
|
||||||
./config/fonts/fontconfig-ultimate.nix
|
|
||||||
./config/fonts/fontdir.nix
|
./config/fonts/fontdir.nix
|
||||||
./config/fonts/fonts.nix
|
./config/fonts/fonts.nix
|
||||||
./config/fonts/ghostscript.nix
|
./config/fonts/ghostscript.nix
|
||||||
@ -620,7 +618,6 @@
|
|||||||
./services/networking/iodine.nix
|
./services/networking/iodine.nix
|
||||||
./services/networking/iperf3.nix
|
./services/networking/iperf3.nix
|
||||||
./services/networking/ircd-hybrid/default.nix
|
./services/networking/ircd-hybrid/default.nix
|
||||||
./services/networking/jormungandr.nix
|
|
||||||
./services/networking/iwd.nix
|
./services/networking/iwd.nix
|
||||||
./services/networking/keepalived/default.nix
|
./services/networking/keepalived/default.nix
|
||||||
./services/networking/keybase.nix
|
./services/networking/keybase.nix
|
||||||
@ -813,8 +810,10 @@
|
|||||||
./services/web-apps/nexus.nix
|
./services/web-apps/nexus.nix
|
||||||
./services/web-apps/pgpkeyserver-lite.nix
|
./services/web-apps/pgpkeyserver-lite.nix
|
||||||
./services/web-apps/matomo.nix
|
./services/web-apps/matomo.nix
|
||||||
|
./services/web-apps/moinmoin.nix
|
||||||
./services/web-apps/restya-board.nix
|
./services/web-apps/restya-board.nix
|
||||||
./services/web-apps/tt-rss.nix
|
./services/web-apps/tt-rss.nix
|
||||||
|
./services/web-apps/trac.nix
|
||||||
./services/web-apps/selfoss.nix
|
./services/web-apps/selfoss.nix
|
||||||
./services/web-apps/shiori.nix
|
./services/web-apps/shiori.nix
|
||||||
./services/web-apps/virtlyst.nix
|
./services/web-apps/virtlyst.nix
|
||||||
@ -865,6 +864,7 @@
|
|||||||
./services/x11/hardware/multitouch.nix
|
./services/x11/hardware/multitouch.nix
|
||||||
./services/x11/hardware/synaptics.nix
|
./services/x11/hardware/synaptics.nix
|
||||||
./services/x11/hardware/wacom.nix
|
./services/x11/hardware/wacom.nix
|
||||||
|
./services/x11/hardware/digimend.nix
|
||||||
./services/x11/hardware/cmt.nix
|
./services/x11/hardware/cmt.nix
|
||||||
./services/x11/gdk-pixbuf.nix
|
./services/x11/gdk-pixbuf.nix
|
||||||
./services/x11/redshift.nix
|
./services/x11/redshift.nix
|
||||||
|
@ -115,6 +115,16 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
agentPKCS11Whitelist = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
example = "\${pkgs.opensc}/lib/opensc-pkcs11.so";
|
||||||
|
description = ''
|
||||||
|
A pattern-list of acceptable paths for PKCS#11 shared libraries
|
||||||
|
that may be used with the -s option to ssh-add.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
package = mkOption {
|
package = mkOption {
|
||||||
type = types.package;
|
type = types.package;
|
||||||
default = pkgs.openssh;
|
default = pkgs.openssh;
|
||||||
@ -241,6 +251,7 @@ in
|
|||||||
ExecStart =
|
ExecStart =
|
||||||
"${cfg.package}/bin/ssh-agent " +
|
"${cfg.package}/bin/ssh-agent " +
|
||||||
optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") +
|
optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") +
|
||||||
|
optionalString (cfg.agentPKCS11Whitelist != null) ("-P ${cfg.agentPKCS11Whitelist} ")
|
||||||
"-a %t/ssh-agent";
|
"-a %t/ssh-agent";
|
||||||
StandardOutput = "null";
|
StandardOutput = "null";
|
||||||
Type = "forking";
|
Type = "forking";
|
||||||
|
@ -234,6 +234,7 @@ with lib;
|
|||||||
(mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.")
|
(mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.")
|
||||||
(mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.")
|
(mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.")
|
||||||
(mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
|
(mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
|
||||||
|
(mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.")
|
||||||
|
|
||||||
# ZSH
|
# ZSH
|
||||||
(mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
|
(mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
|
||||||
@ -291,5 +292,14 @@ with lib;
|
|||||||
(opt: mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] ''
|
(opt: mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] ''
|
||||||
The prometheus exporters are now configured using `services.prometheus.exporters'.
|
The prometheus exporters are now configured using `services.prometheus.exporters'.
|
||||||
See the 18.03 release notes for more information.
|
See the 18.03 release notes for more information.
|
||||||
|
'' ))
|
||||||
|
|
||||||
|
++ (forEach [ "enable" "substitutions" "preset" ]
|
||||||
|
(opt: mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] ''
|
||||||
|
The fonts.fontconfig.ultimate module and configuration is obsolete.
|
||||||
|
The repository has since been archived and activity has ceased.
|
||||||
|
https://github.com/bohoomil/fontconfig-ultimate/issues/171.
|
||||||
|
No action should be needed for font configuration, as the fonts.fontconfig
|
||||||
|
module is already used by default.
|
||||||
'' ));
|
'' ));
|
||||||
}
|
}
|
||||||
|
@ -181,6 +181,7 @@ in {
|
|||||||
ProtectKernelModules = true;
|
ProtectKernelModules = true;
|
||||||
RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
|
RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
|
||||||
RestrictNamespaces = true;
|
RestrictNamespaces = true;
|
||||||
|
Restart = "always";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -407,6 +407,9 @@ in {
|
|||||||
"192.168.0.0/16"
|
"192.168.0.0/16"
|
||||||
"100.64.0.0/10"
|
"100.64.0.0/10"
|
||||||
"169.254.0.0/16"
|
"169.254.0.0/16"
|
||||||
|
"::1/128"
|
||||||
|
"fe80::/64"
|
||||||
|
"fc00::/7"
|
||||||
];
|
];
|
||||||
description = ''
|
description = ''
|
||||||
List of IP address CIDR ranges that the URL preview spider is denied
|
List of IP address CIDR ranges that the URL preview spider is denied
|
||||||
|
@ -62,20 +62,11 @@ in
|
|||||||
services.redmine = {
|
services.redmine = {
|
||||||
enable = mkEnableOption "Redmine";
|
enable = mkEnableOption "Redmine";
|
||||||
|
|
||||||
# default to the 4.x series not forcing major version upgrade of those on the 3.x series
|
|
||||||
package = mkOption {
|
package = mkOption {
|
||||||
type = types.package;
|
type = types.package;
|
||||||
default = if versionAtLeast config.system.stateVersion "19.03"
|
default = pkgs.redmine;
|
||||||
then pkgs.redmine_4
|
description = "Which Redmine package to use.";
|
||||||
else pkgs.redmine
|
example = "pkgs.redmine.override { ruby = pkgs.ruby_2_4; }";
|
||||||
;
|
|
||||||
defaultText = "pkgs.redmine";
|
|
||||||
description = ''
|
|
||||||
Which Redmine package to use. This defaults to version 3.x if
|
|
||||||
<literal>system.stateVersion < 19.03</literal> and version 4.x
|
|
||||||
otherwise.
|
|
||||||
'';
|
|
||||||
example = "pkgs.redmine_4.override { ruby = pkgs.ruby_2_4; }";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
user = mkOption {
|
user = mkOption {
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.services.jormungandr;
|
|
||||||
|
|
||||||
inherit (lib) mkEnableOption mkIf mkOption;
|
|
||||||
inherit (lib) optionalString types;
|
|
||||||
|
|
||||||
dataDir = "/var/lib/jormungandr";
|
|
||||||
|
|
||||||
# Default settings so far, as the service matures we will
|
|
||||||
# move these out as separate settings
|
|
||||||
configSettings = {
|
|
||||||
storage = dataDir;
|
|
||||||
p2p = {
|
|
||||||
public_address = "/ip4/127.0.0.1/tcp/8299";
|
|
||||||
topics_of_interest = {
|
|
||||||
messages = "high";
|
|
||||||
blocks = "high";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
rest = {
|
|
||||||
listen = "127.0.0.1:8607";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
configFile = if cfg.configFile == null then
|
|
||||||
pkgs.writeText "jormungandr.yaml" (builtins.toJSON configSettings)
|
|
||||||
else cfg.configFile;
|
|
||||||
|
|
||||||
in {
|
|
||||||
|
|
||||||
options = {
|
|
||||||
|
|
||||||
services.jormungandr = {
|
|
||||||
enable = mkEnableOption "jormungandr service";
|
|
||||||
|
|
||||||
configFile = mkOption {
|
|
||||||
type = types.nullOr types.path;
|
|
||||||
default = null;
|
|
||||||
example = "/var/lib/jormungandr/node.yaml";
|
|
||||||
description = ''
|
|
||||||
The path of the jormungandr blockchain configuration file in YAML format.
|
|
||||||
If no file is specified, a file is generated using the other options.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
secretFile = mkOption {
|
|
||||||
type = types.nullOr types.path;
|
|
||||||
default = null;
|
|
||||||
example = "/etc/secret/jormungandr.yaml";
|
|
||||||
description = ''
|
|
||||||
The path of the jormungandr blockchain secret node configuration file in
|
|
||||||
YAML format. Do not store this in nix store!
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
genesisBlockHash = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
example = "d70495af81ae8600aca3e642b2427327cb6001ec4d7a0037e96a00dabed163f9";
|
|
||||||
description = ''
|
|
||||||
Set the genesis block hash (the hash of the block0) so we can retrieve
|
|
||||||
the genesis block (and the blockchain configuration) from the existing
|
|
||||||
storage or from the network.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
genesisBlockFile = mkOption {
|
|
||||||
type = types.nullOr types.path;
|
|
||||||
default = null;
|
|
||||||
example = "/var/lib/jormungandr/block-0.bin";
|
|
||||||
description = ''
|
|
||||||
The path of the genesis block file if we are hosting it locally.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
|
||||||
|
|
||||||
systemd.services.jormungandr = {
|
|
||||||
description = "jormungandr server";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [ "network-online.target" ];
|
|
||||||
environment = {
|
|
||||||
RUST_BACKTRACE = "full";
|
|
||||||
};
|
|
||||||
serviceConfig = {
|
|
||||||
DynamicUser = true;
|
|
||||||
StateDirectory = baseNameOf dataDir;
|
|
||||||
ExecStart = ''
|
|
||||||
${pkgs.jormungandr}/bin/jormungandr --config ${configFile} \
|
|
||||||
${optionalString (cfg.secretFile != null) " --secret ${cfg.secretFile}"} \
|
|
||||||
${optionalString (cfg.genesisBlockHash != null) " --genesis-block-hash ${cfg.genesisBlockHash}"} \
|
|
||||||
${optionalString (cfg.genesisBlockFile != null) " --genesis-block ${cfg.genesisBlockFile}"}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@ -29,7 +29,7 @@ let
|
|||||||
iptables -w -t nat -N nixos-nat-post
|
iptables -w -t nat -N nixos-nat-post
|
||||||
|
|
||||||
# We can't match on incoming interface in POSTROUTING, so
|
# We can't match on incoming interface in POSTROUTING, so
|
||||||
# mark packets coming from the external interfaces.
|
# mark packets coming from the internal interfaces.
|
||||||
${concatMapStrings (iface: ''
|
${concatMapStrings (iface: ''
|
||||||
iptables -w -t nat -A nixos-nat-pre \
|
iptables -w -t nat -A nixos-nat-pre \
|
||||||
-i '${iface}' -j MARK --set-mark 1
|
-i '${iface}' -j MARK --set-mark 1
|
||||||
|
@ -456,15 +456,19 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Turn off NixOS' network management when networking is managed entirely by NetworkManager
|
# Turn off NixOS' network management when networking is managed entirely by NetworkManager
|
||||||
networking = (mkIf (!delegateWireless) {
|
networking = mkMerge [
|
||||||
|
(mkIf (!delegateWireless) {
|
||||||
useDHCP = false;
|
useDHCP = false;
|
||||||
# Use mkDefault to trigger the assertion about the conflict above
|
})
|
||||||
wireless.enable = mkDefault false;
|
|
||||||
}) // (mkIf cfg.enableStrongSwan {
|
(mkIf cfg.enableStrongSwan {
|
||||||
networkmanager.packages = [ pkgs.networkmanager_strongswan ];
|
networkmanager.packages = [ pkgs.networkmanager_strongswan ];
|
||||||
}) // (mkIf enableIwd {
|
})
|
||||||
|
|
||||||
|
(mkIf enableIwd {
|
||||||
wireless.iwd.enable = true;
|
wireless.iwd.enable = true;
|
||||||
});
|
})
|
||||||
|
];
|
||||||
|
|
||||||
security.polkit.extraConfig = polkitConf;
|
security.polkit.extraConfig = polkitConf;
|
||||||
|
|
||||||
|
@ -119,9 +119,8 @@ in
|
|||||||
};
|
};
|
||||||
users.groups.vault.gid = config.ids.gids.vault;
|
users.groups.vault.gid = config.ids.gids.vault;
|
||||||
|
|
||||||
systemd.tmpfiles.rules = optional (cfg.storagePath != null) [
|
systemd.tmpfiles.rules = optional (cfg.storagePath != null)
|
||||||
"d '${cfg.storagePath}' 0700 vault vault - -"
|
"d '${cfg.storagePath}' 0700 vault vault - -";
|
||||||
];
|
|
||||||
|
|
||||||
systemd.services.vault = {
|
systemd.services.vault = {
|
||||||
description = "Vault server daemon";
|
description = "Vault server daemon";
|
||||||
|
303
nixos/modules/services/web-apps/moinmoin.nix
Normal file
303
nixos/modules/services/web-apps/moinmoin.nix
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.moinmoin;
|
||||||
|
python = pkgs.python27;
|
||||||
|
pkg = python.pkgs.moinmoin;
|
||||||
|
dataDir = "/var/lib/moin";
|
||||||
|
usingGunicorn = cfg.webServer == "nginx-gunicorn" || cfg.webServer == "gunicorn";
|
||||||
|
usingNginx = cfg.webServer == "nginx-gunicorn";
|
||||||
|
user = "moin";
|
||||||
|
group = "moin";
|
||||||
|
|
||||||
|
uLit = s: ''u"${s}"'';
|
||||||
|
indentLines = n: str: concatMapStrings (line: "${fixedWidthString n " " " "}${line}\n") (splitString "\n" str);
|
||||||
|
|
||||||
|
moinCliWrapper = wikiIdent: pkgs.writeShellScriptBin "moin-${wikiIdent}" ''
|
||||||
|
${pkgs.su}/bin/su -s ${pkgs.runtimeShell} -c "${pkg}/bin/moin --config-dir=/var/lib/moin/${wikiIdent}/config $*" ${user}
|
||||||
|
'';
|
||||||
|
|
||||||
|
wikiConfig = wikiIdent: w: ''
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from MoinMoin.config import multiconfig, url_prefix_static
|
||||||
|
|
||||||
|
class Config(multiconfig.DefaultConfig):
|
||||||
|
${optionalString (w.webLocation != "/") ''
|
||||||
|
url_prefix_static = '${w.webLocation}' + url_prefix_static
|
||||||
|
''}
|
||||||
|
|
||||||
|
sitename = u'${w.siteName}'
|
||||||
|
page_front_page = u'${w.frontPage}'
|
||||||
|
|
||||||
|
data_dir = '${dataDir}/${wikiIdent}/data'
|
||||||
|
data_underlay_dir = '${dataDir}/${wikiIdent}/underlay'
|
||||||
|
|
||||||
|
language_default = u'${w.languageDefault}'
|
||||||
|
${optionalString (w.superUsers != []) ''
|
||||||
|
superuser = [${concatMapStringsSep ", " uLit w.superUsers}]
|
||||||
|
''}
|
||||||
|
|
||||||
|
${indentLines 4 w.extraConfig}
|
||||||
|
'';
|
||||||
|
wikiConfigFile = name: wiki: pkgs.writeText "${name}.py" (wikiConfig name wiki);
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.moinmoin = with types; {
|
||||||
|
enable = mkEnableOption "MoinMoin Wiki Engine";
|
||||||
|
|
||||||
|
webServer = mkOption {
|
||||||
|
type = enum [ "nginx-gunicorn" "gunicorn" "none" ];
|
||||||
|
default = "nginx-gunicorn";
|
||||||
|
example = "none";
|
||||||
|
description = ''
|
||||||
|
Which web server to use to serve the wiki.
|
||||||
|
Use <literal>none</literal> if you want to configure this yourself.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
gunicorn.workers = mkOption {
|
||||||
|
type = ints.positive;
|
||||||
|
default = 3;
|
||||||
|
example = 10;
|
||||||
|
description = ''
|
||||||
|
The number of worker processes for handling requests.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
wikis = mkOption {
|
||||||
|
type = attrsOf (submodule ({ name, ... }: {
|
||||||
|
options = {
|
||||||
|
siteName = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "Untitled Wiki";
|
||||||
|
example = "ExampleWiki";
|
||||||
|
description = ''
|
||||||
|
Short description of your wiki site, displayed below the logo on each page, and
|
||||||
|
used in RSS documents as the channel title.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
webHost = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "Host part of the wiki URL. If undefined, the name of the attribute set will be used.";
|
||||||
|
example = "wiki.example.org";
|
||||||
|
};
|
||||||
|
|
||||||
|
webLocation = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "/";
|
||||||
|
example = "/moin";
|
||||||
|
description = "Location part of the wiki URL.";
|
||||||
|
};
|
||||||
|
|
||||||
|
frontPage = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "LanguageSetup";
|
||||||
|
example = "FrontPage";
|
||||||
|
description = ''
|
||||||
|
Front page name. Set this to something like <literal>FrontPage</literal> once languages are
|
||||||
|
configured.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
superUsers = mkOption {
|
||||||
|
type = listOf str;
|
||||||
|
default = [];
|
||||||
|
example = [ "elvis" ];
|
||||||
|
description = ''
|
||||||
|
List of trusted user names with wiki system administration super powers.
|
||||||
|
|
||||||
|
Please note that accounts for these users need to be created using the <command>moin</command> command-line utility, e.g.:
|
||||||
|
<command>moin-<replaceable>WIKINAME</replaceable> account create --name=<replaceable>NAME</replaceable> --email=<replaceable>EMAIL</replaceable> --password=<replaceable>PASSWORD</replaceable></command>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
languageDefault = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "en";
|
||||||
|
example = "de";
|
||||||
|
description = "The ISO-639-1 name of the main wiki language. Languages that MoinMoin does not support are ignored.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = lines;
|
||||||
|
default = "";
|
||||||
|
example = ''
|
||||||
|
show_hosts = True
|
||||||
|
search_results_per_page = 100
|
||||||
|
acl_rights_default = u"Known:read,write,delete,revert All:read"
|
||||||
|
logo_string = u"<h2>\U0001f639</h2>"
|
||||||
|
theme_default = u"modernized"
|
||||||
|
|
||||||
|
user_checkbox_defaults = {'show_page_trail': 0, 'edit_on_doubleclick': 0}
|
||||||
|
navi_bar = [u'SomePage'] + multiconfig.DefaultConfig.navi_bar
|
||||||
|
actions_excluded = multiconfig.DefaultConfig.actions_excluded + ['newaccount']
|
||||||
|
|
||||||
|
mail_smarthost = "mail.example.org"
|
||||||
|
mail_from = u"Example.Org Wiki <wiki@example.org>"
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Additional configuration to be appended verbatim to this wiki's config.
|
||||||
|
|
||||||
|
See <link xlink:href='http://moinmo.in/HelpOnConfiguration' /> for documentation.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
webHost = mkDefault name;
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
example = literalExample ''
|
||||||
|
{
|
||||||
|
"mywiki" = {
|
||||||
|
siteName = "Example Wiki";
|
||||||
|
webHost = "wiki.example.org";
|
||||||
|
superUsers = [ "admin" ];
|
||||||
|
frontPage = "Index";
|
||||||
|
extraConfig = "page_category_regex = ur'(?P<all>(Category|Kategorie)(?P<key>(?!Template)\S+))'"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Configurations of the individual wikis. Attribute names must be valid Python
|
||||||
|
identifiers of the form <literal>[A-Za-z_][A-Za-z0-9_]*</literal>.
|
||||||
|
|
||||||
|
For every attribute <replaceable>WIKINAME</replaceable>, a helper script
|
||||||
|
moin-<replaceable>WIKINAME</replaceable> is created which runs the
|
||||||
|
<command>moin</command> command under the <literal>moin</literal> user (to avoid
|
||||||
|
file ownership issues) and with the right configuration directory passed to it.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
assertions = forEach (attrNames cfg.wikis) (wname:
|
||||||
|
{ assertion = builtins.match "[A-Za-z_][A-Za-z0-9_]*" wname != null;
|
||||||
|
message = "${wname} is not valid Python identifier";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
users.users = {
|
||||||
|
moin = {
|
||||||
|
description = "MoinMoin wiki";
|
||||||
|
home = dataDir;
|
||||||
|
group = group;
|
||||||
|
isSystemUser = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups = {
|
||||||
|
moin = {
|
||||||
|
members = mkIf usingNginx [ config.services.nginx.user ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = [ pkg ] ++ map moinCliWrapper (attrNames cfg.wikis);
|
||||||
|
|
||||||
|
systemd.services = mkIf usingGunicorn
|
||||||
|
(flip mapAttrs' cfg.wikis (wikiIdent: wiki:
|
||||||
|
nameValuePair "moin-${wikiIdent}"
|
||||||
|
{
|
||||||
|
description = "MoinMoin wiki ${wikiIdent} - gunicorn process";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "network.target" ];
|
||||||
|
restartIfChanged = true;
|
||||||
|
restartTriggers = [ (wikiConfigFile wikiIdent wiki) ];
|
||||||
|
|
||||||
|
environment = let
|
||||||
|
penv = python.buildEnv.override {
|
||||||
|
# setuptools: https://github.com/benoitc/gunicorn/issues/1716
|
||||||
|
extraLibs = [ python.pkgs.gevent python.pkgs.setuptools pkg ];
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}";
|
||||||
|
};
|
||||||
|
|
||||||
|
preStart = ''
|
||||||
|
umask 0007
|
||||||
|
rm -rf ${dataDir}/${wikiIdent}/underlay
|
||||||
|
cp -r ${pkg}/share/moin/underlay ${dataDir}/${wikiIdent}/
|
||||||
|
chmod -R u+w ${dataDir}/${wikiIdent}/underlay
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
User = user;
|
||||||
|
Group = group;
|
||||||
|
WorkingDirectory = "${dataDir}/${wikiIdent}";
|
||||||
|
ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \
|
||||||
|
--name gunicorn-${wikiIdent} \
|
||||||
|
--workers ${toString cfg.gunicorn.workers} \
|
||||||
|
--worker-class gevent \
|
||||||
|
--bind unix:/run/moin/${wikiIdent}/gunicorn.sock
|
||||||
|
'';
|
||||||
|
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "2s";
|
||||||
|
StartLimitIntervalSec = "30s";
|
||||||
|
|
||||||
|
StateDirectory = "moin/${wikiIdent}";
|
||||||
|
StateDirectoryMode = "0750";
|
||||||
|
RuntimeDirectory = "moin/${wikiIdent}";
|
||||||
|
RuntimeDirectoryMode = "0750";
|
||||||
|
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ProtectHome = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
PrivateNetwork = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
services.nginx = mkIf usingNginx {
|
||||||
|
enable = true;
|
||||||
|
virtualHosts = flip mapAttrs' cfg.wikis (name: w: nameValuePair w.webHost {
|
||||||
|
forceSSL = mkDefault true;
|
||||||
|
enableACME = mkDefault true;
|
||||||
|
locations."${w.webLocation}" = {
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Server $host;
|
||||||
|
|
||||||
|
proxy_pass http://unix:/run/moin/${name}/gunicorn.sock;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /run/moin 0750 ${user} ${group} - -"
|
||||||
|
"d ${dataDir} 0550 ${user} ${group} - -"
|
||||||
|
]
|
||||||
|
++ (concatLists (flip mapAttrsToList cfg.wikis (wikiIdent: wiki: [
|
||||||
|
"d ${dataDir}/${wikiIdent} 0750 ${user} ${group} - -"
|
||||||
|
"d ${dataDir}/${wikiIdent}/config 0550 ${user} ${group} - -"
|
||||||
|
"L+ ${dataDir}/${wikiIdent}/config/wikiconfig.py - - - - ${wikiConfigFile wikiIdent wiki}"
|
||||||
|
# needed in order to pass module name to gunicorn
|
||||||
|
"L+ ${dataDir}/${wikiIdent}/config/moin_wsgi.py - - - - ${pkg}/share/moin/server/moin.wsgi"
|
||||||
|
# seed data files
|
||||||
|
"C ${dataDir}/${wikiIdent}/data 0770 ${user} ${group} - ${pkg}/share/moin/data"
|
||||||
|
# fix nix store permissions
|
||||||
|
"Z ${dataDir}/${wikiIdent}/data 0770 ${user} ${group} - -"
|
||||||
|
])));
|
||||||
|
};
|
||||||
|
|
||||||
|
meta.maintainers = with lib.maintainers; [ b42 ];
|
||||||
|
}
|
79
nixos/modules/services/web-apps/trac.nix
Normal file
79
nixos/modules/services/web-apps/trac.nix
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.trac;
|
||||||
|
|
||||||
|
inherit (lib) mkEnableOption mkIf mkOption types;
|
||||||
|
|
||||||
|
in {
|
||||||
|
|
||||||
|
options = {
|
||||||
|
|
||||||
|
services.trac = {
|
||||||
|
enable = mkEnableOption "Trac service";
|
||||||
|
|
||||||
|
listen = {
|
||||||
|
ip = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "0.0.0.0";
|
||||||
|
description = ''
|
||||||
|
IP address that Trac should listen on.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8000;
|
||||||
|
description = ''
|
||||||
|
Listen port for Trac.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
dataDir = mkOption {
|
||||||
|
default = "/var/lib/trac";
|
||||||
|
type = types.path;
|
||||||
|
description = ''
|
||||||
|
The directory for storing the Trac data.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Open ports in the firewall for Trac.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
|
||||||
|
systemd.services.trac = {
|
||||||
|
description = "Trac server";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
DynamicUser = true;
|
||||||
|
StateDirectory = baseNameOf cfg.dataDir;
|
||||||
|
ExecStart = ''
|
||||||
|
${pkgs.trac}/bin/tracd -s \
|
||||||
|
-b ${toString cfg.listen.ip} \
|
||||||
|
-p ${toString cfg.listen.port} \
|
||||||
|
${cfg.dataDir}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
preStart = ''
|
||||||
|
if [ ! -e ${cfg.dataDir}/VERSION ]; then
|
||||||
|
${pkgs.trac}/bin/trac-admin ${cfg.dataDir} initenv Trac "sqlite:db/trac.db"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = mkIf cfg.openFirewall {
|
||||||
|
allowedTCPPorts = [ cfg.listen.port ];
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
@ -6,6 +6,8 @@ let
|
|||||||
|
|
||||||
mainCfg = config.services.httpd;
|
mainCfg = config.services.httpd;
|
||||||
|
|
||||||
|
runtimeDir = "/run/httpd";
|
||||||
|
|
||||||
httpd = mainCfg.package.out;
|
httpd = mainCfg.package.out;
|
||||||
|
|
||||||
httpdConf = mainCfg.configFile;
|
httpdConf = mainCfg.configFile;
|
||||||
@ -27,41 +29,26 @@ let
|
|||||||
|
|
||||||
listenToString = l: "${l.ip}:${toString l.port}";
|
listenToString = l: "${l.ip}:${toString l.port}";
|
||||||
|
|
||||||
extraModules = attrByPath ["extraModules"] [] mainCfg;
|
|
||||||
extraForeignModules = filter isAttrs extraModules;
|
|
||||||
extraApacheModules = filter isString extraModules;
|
|
||||||
|
|
||||||
allHosts = [mainCfg] ++ mainCfg.virtualHosts;
|
allHosts = [mainCfg] ++ mainCfg.virtualHosts;
|
||||||
|
|
||||||
enableSSL = any (vhost: vhost.enableSSL) allHosts;
|
enableSSL = any (vhost: vhost.enableSSL) allHosts;
|
||||||
|
|
||||||
|
# NOTE: generally speaking order of modules is very important
|
||||||
# Names of modules from ${httpd}/modules that we want to load.
|
modules =
|
||||||
apacheModules =
|
[ # required apache modules our httpd service cannot run without
|
||||||
[ # HTTP authentication mechanisms: basic and digest.
|
"authn_core" "authz_core"
|
||||||
"auth_basic" "auth_digest"
|
"log_config"
|
||||||
|
"mime" "autoindex" "negotiation" "dir"
|
||||||
# Authentication: is the user who he claims to be?
|
"alias" "rewrite"
|
||||||
"authn_file" "authn_dbm" "authn_anon" "authn_core"
|
"unixd" "slotmem_shm" "socache_shmcb"
|
||||||
|
|
||||||
# Authorization: is the user allowed access?
|
|
||||||
"authz_user" "authz_groupfile" "authz_host" "authz_core"
|
|
||||||
|
|
||||||
# Other modules.
|
|
||||||
"ext_filter" "include" "log_config" "env" "mime_magic"
|
|
||||||
"cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
|
|
||||||
"mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
|
|
||||||
"vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
|
|
||||||
"userdir" "alias" "rewrite" "proxy" "proxy_http"
|
|
||||||
"unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb"
|
|
||||||
"mpm_${mainCfg.multiProcessingModule}"
|
"mpm_${mainCfg.multiProcessingModule}"
|
||||||
|
|
||||||
# For compatibility with old configurations, the new module mod_access_compat is provided.
|
|
||||||
"access_compat"
|
|
||||||
]
|
]
|
||||||
++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
|
++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
|
||||||
++ optional enableSSL "ssl"
|
++ optional enableSSL "ssl"
|
||||||
++ extraApacheModules;
|
++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
|
||||||
|
++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
|
||||||
|
++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
|
||||||
|
++ mainCfg.extraModules;
|
||||||
|
|
||||||
|
|
||||||
allDenied = "Require all denied";
|
allDenied = "Require all denied";
|
||||||
@ -85,6 +72,7 @@ let
|
|||||||
|
|
||||||
|
|
||||||
browserHacks = ''
|
browserHacks = ''
|
||||||
|
<IfModule mod_setenvif.c>
|
||||||
BrowserMatch "Mozilla/2" nokeepalive
|
BrowserMatch "Mozilla/2" nokeepalive
|
||||||
BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
|
BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
|
||||||
BrowserMatch "RealPlayer 4\.0" force-response-1.0
|
BrowserMatch "RealPlayer 4\.0" force-response-1.0
|
||||||
@ -94,11 +82,12 @@ let
|
|||||||
BrowserMatch "^WebDrive" redirect-carefully
|
BrowserMatch "^WebDrive" redirect-carefully
|
||||||
BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
|
BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
|
||||||
BrowserMatch "^gnome-vfs" redirect-carefully
|
BrowserMatch "^gnome-vfs" redirect-carefully
|
||||||
|
</IfModule>
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
||||||
sslConf = ''
|
sslConf = ''
|
||||||
SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000)
|
SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
|
||||||
|
|
||||||
Mutex posixsem
|
Mutex posixsem
|
||||||
|
|
||||||
@ -239,13 +228,13 @@ let
|
|||||||
|
|
||||||
ServerRoot ${httpd}
|
ServerRoot ${httpd}
|
||||||
|
|
||||||
DefaultRuntimeDir ${mainCfg.stateDir}/runtime
|
DefaultRuntimeDir ${runtimeDir}/runtime
|
||||||
|
|
||||||
PidFile ${mainCfg.stateDir}/httpd.pid
|
PidFile ${runtimeDir}/httpd.pid
|
||||||
|
|
||||||
${optionalString (mainCfg.multiProcessingModule != "prefork") ''
|
${optionalString (mainCfg.multiProcessingModule != "prefork") ''
|
||||||
# mod_cgid requires this.
|
# mod_cgid requires this.
|
||||||
ScriptSock ${mainCfg.stateDir}/cgisock
|
ScriptSock ${runtimeDir}/cgisock
|
||||||
''}
|
''}
|
||||||
|
|
||||||
<IfModule prefork.c>
|
<IfModule prefork.c>
|
||||||
@ -264,13 +253,12 @@ let
|
|||||||
Group ${mainCfg.group}
|
Group ${mainCfg.group}
|
||||||
|
|
||||||
${let
|
${let
|
||||||
load = {name, path}: "LoadModule ${name}_module ${path}\n";
|
mkModule = module:
|
||||||
allModules = map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
|
if isString module then { name = module; path = "${httpd}/modules/mod_${module}.so"; }
|
||||||
++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
|
else if isAttrs module then { inherit (module) name path; }
|
||||||
++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
|
else throw "Expecting either a string or attribute set including a name and path.";
|
||||||
++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
|
in
|
||||||
++ extraForeignModules;
|
concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules))
|
||||||
in concatMapStrings load (unique allModules)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AddHandler type-map var
|
AddHandler type-map var
|
||||||
@ -337,6 +325,7 @@ in
|
|||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
(mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
|
(mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
|
||||||
|
(mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
|
||||||
];
|
];
|
||||||
|
|
||||||
###### interface
|
###### interface
|
||||||
@ -384,7 +373,12 @@ in
|
|||||||
extraModules = mkOption {
|
extraModules = mkOption {
|
||||||
type = types.listOf types.unspecified;
|
type = types.listOf types.unspecified;
|
||||||
default = [];
|
default = [];
|
||||||
example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
|
example = literalExample ''
|
||||||
|
[
|
||||||
|
"proxy_connect"
|
||||||
|
{ name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
|
||||||
|
]
|
||||||
|
'';
|
||||||
description = ''
|
description = ''
|
||||||
Additional Apache modules to be used. These can be
|
Additional Apache modules to be used. These can be
|
||||||
specified as a string in the case of modules distributed
|
specified as a string in the case of modules distributed
|
||||||
@ -431,16 +425,6 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
stateDir = mkOption {
|
|
||||||
type = types.path;
|
|
||||||
default = "/run/httpd";
|
|
||||||
description = ''
|
|
||||||
Directory for Apache's transient runtime state (such as PID
|
|
||||||
files). It is created automatically. Note that the default,
|
|
||||||
<filename>/run/httpd</filename>, is deleted at boot time.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualHosts = mkOption {
|
virtualHosts = mkOption {
|
||||||
type = types.listOf (types.submodule (
|
type = types.listOf (types.submodule (
|
||||||
{ options = import ./per-server-options.nix {
|
{ options = import ./per-server-options.nix {
|
||||||
@ -595,6 +579,28 @@ in
|
|||||||
date.timezone = "${config.time.timeZone}"
|
date.timezone = "${config.time.timeZone}"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
services.httpd.extraModules = mkBefore [
|
||||||
|
# HTTP authentication mechanisms: basic and digest.
|
||||||
|
"auth_basic" "auth_digest"
|
||||||
|
|
||||||
|
# Authentication: is the user who he claims to be?
|
||||||
|
"authn_file" "authn_dbm" "authn_anon"
|
||||||
|
|
||||||
|
# Authorization: is the user allowed access?
|
||||||
|
"authz_user" "authz_groupfile" "authz_host"
|
||||||
|
|
||||||
|
# Other modules.
|
||||||
|
"ext_filter" "include" "env" "mime_magic"
|
||||||
|
"cern_meta" "expires" "headers" "usertrack" "setenvif"
|
||||||
|
"dav" "status" "asis" "info" "dav_fs"
|
||||||
|
"vhost_alias" "imagemap" "actions" "speling"
|
||||||
|
"proxy" "proxy_http"
|
||||||
|
"cache" "cache_disk"
|
||||||
|
|
||||||
|
# For compatibility with old configurations, the new module mod_access_compat is provided.
|
||||||
|
"access_compat"
|
||||||
|
];
|
||||||
|
|
||||||
systemd.services.httpd =
|
systemd.services.httpd =
|
||||||
{ description = "Apache HTTPD";
|
{ description = "Apache HTTPD";
|
||||||
|
|
||||||
@ -611,12 +617,6 @@ in
|
|||||||
|
|
||||||
preStart =
|
preStart =
|
||||||
''
|
''
|
||||||
mkdir -m 0750 -p ${mainCfg.stateDir}
|
|
||||||
[ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
|
|
||||||
|
|
||||||
mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
|
|
||||||
[ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
|
|
||||||
|
|
||||||
mkdir -m 0700 -p ${mainCfg.logDir}
|
mkdir -m 0700 -p ${mainCfg.logDir}
|
||||||
|
|
||||||
# Get rid of old semaphores. These tend to accumulate across
|
# Get rid of old semaphores. These tend to accumulate across
|
||||||
@ -630,10 +630,13 @@ in
|
|||||||
serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
|
serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
|
||||||
serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
|
serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
|
||||||
serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
|
serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
|
||||||
|
serviceConfig.Group = mainCfg.group;
|
||||||
serviceConfig.Type = "forking";
|
serviceConfig.Type = "forking";
|
||||||
serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
|
serviceConfig.PIDFile = "${runtimeDir}/httpd.pid";
|
||||||
serviceConfig.Restart = "always";
|
serviceConfig.Restart = "always";
|
||||||
serviceConfig.RestartSec = "5s";
|
serviceConfig.RestartSec = "5s";
|
||||||
|
serviceConfig.RuntimeDirectory = "httpd httpd/runtime";
|
||||||
|
serviceConfig.RuntimeDirectoryMode = "0750";
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
43
nixos/modules/services/x11/hardware/digimend.nix
Normal file
43
nixos/modules/services/x11/hardware/digimend.nix
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
cfg = config.services.xserver.digimend;
|
||||||
|
|
||||||
|
pkg = config.boot.kernelPackages.digimend;
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
options = {
|
||||||
|
|
||||||
|
services.xserver.digimend = {
|
||||||
|
|
||||||
|
enable = mkOption {
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to enable the digimend drivers for Huion/XP-Pen/etc. tablets.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
|
||||||
|
# digimend drivers use xsetwacom and wacom X11 drivers
|
||||||
|
services.xserver.wacom.enable = true;
|
||||||
|
|
||||||
|
boot.extraModulePackages = [ pkg ];
|
||||||
|
|
||||||
|
environment.etc."X11/xorg.conf.d/50-digimend.conf".source =
|
||||||
|
"${pkg}/usr/share/X11/xorg.conf.d/50-digimend.conf";
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -23,24 +23,56 @@ let
|
|||||||
|
|
||||||
cfg = config.virtualisation;
|
cfg = config.virtualisation;
|
||||||
|
|
||||||
qemuGraphics = lib.optionalString (!cfg.graphics) "-nographic";
|
|
||||||
|
|
||||||
consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles;
|
consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles;
|
||||||
|
|
||||||
# XXX: This is very ugly and in the future we really should use attribute
|
driveOpts = { ... }: {
|
||||||
# sets to build ALL of the QEMU flags instead of this mixed mess of Nix
|
|
||||||
# expressions and shell script stuff.
|
options = {
|
||||||
mkDiskIfaceDriveFlag = idx: driveArgs: let
|
|
||||||
inherit (cfg.qemu) diskInterface;
|
file = mkOption {
|
||||||
# The drive identifier created by incrementing the index by one using the
|
type = types.str;
|
||||||
# shell.
|
description = "The file image used for this drive.";
|
||||||
drvId = "drive$((${idx} + 1))";
|
};
|
||||||
# NOTE: DO NOT shell escape, because this may contain shell variables.
|
|
||||||
commonArgs = "index=${idx},id=${drvId},${driveArgs}";
|
driveExtraOpts = mkOption {
|
||||||
isSCSI = diskInterface == "scsi";
|
type = types.attrsOf types.str;
|
||||||
devArgs = "${diskInterface}-hd,drive=${drvId}";
|
default = {};
|
||||||
args = "-drive ${commonArgs},if=none -device lsi53c895a -device ${devArgs}";
|
description = "Extra options passed to drive flag.";
|
||||||
in if isSCSI then args else "-drive ${commonArgs},if=${diskInterface}";
|
};
|
||||||
|
|
||||||
|
deviceExtraOpts = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = {};
|
||||||
|
description = "Extra options passed to device flag.";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }:
|
||||||
|
let
|
||||||
|
drvId = "drive${toString idx}";
|
||||||
|
mkKeyValue = generators.mkKeyValueDefault {} "=";
|
||||||
|
mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts);
|
||||||
|
driveOpts = mkOpts (driveExtraOpts // {
|
||||||
|
index = idx;
|
||||||
|
id = drvId;
|
||||||
|
"if" = "none";
|
||||||
|
inherit file;
|
||||||
|
});
|
||||||
|
deviceOpts = mkOpts (deviceExtraOpts // {
|
||||||
|
drive = drvId;
|
||||||
|
});
|
||||||
|
device =
|
||||||
|
if cfg.qemu.diskInterface == "scsi" then
|
||||||
|
"-device lsi53c895a -device scsi-hd,${deviceOpts}"
|
||||||
|
else
|
||||||
|
"-device virtio-blk-pci,${deviceOpts}";
|
||||||
|
in
|
||||||
|
"-drive ${driveOpts} ${device}";
|
||||||
|
|
||||||
|
drivesCmdLine = drives: concatStringsSep " " (imap1 driveCmdline drives);
|
||||||
|
|
||||||
# Shell script to start the VM.
|
# Shell script to start the VM.
|
||||||
startVM =
|
startVM =
|
||||||
@ -77,13 +109,11 @@ let
|
|||||||
''}
|
''}
|
||||||
|
|
||||||
cd $TMPDIR
|
cd $TMPDIR
|
||||||
idx=2
|
idx=0
|
||||||
extraDisks=""
|
|
||||||
${flip concatMapStrings cfg.emptyDiskImages (size: ''
|
${flip concatMapStrings cfg.emptyDiskImages (size: ''
|
||||||
if ! test -e "empty$idx.qcow2"; then
|
if ! test -e "empty$idx.qcow2"; then
|
||||||
${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
|
${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
|
||||||
fi
|
fi
|
||||||
extraDisks="$extraDisks ${mkDiskIfaceDriveFlag "$idx" "file=$(pwd)/empty$idx.qcow2,werror=report"}"
|
|
||||||
idx=$((idx + 1))
|
idx=$((idx + 1))
|
||||||
'')}
|
'')}
|
||||||
|
|
||||||
@ -97,21 +127,7 @@ let
|
|||||||
-virtfs local,path=/nix/store,security_model=none,mount_tag=store \
|
-virtfs local,path=/nix/store,security_model=none,mount_tag=store \
|
||||||
-virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \
|
-virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \
|
||||||
-virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \
|
-virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \
|
||||||
${if cfg.useBootLoader then ''
|
${drivesCmdLine config.virtualisation.qemu.drives} \
|
||||||
${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \
|
|
||||||
${mkDiskIfaceDriveFlag "1" "file=$TMPDIR/disk.img,media=disk"} \
|
|
||||||
${if cfg.useEFIBoot then ''
|
|
||||||
-pflash $TMPDIR/bios.bin \
|
|
||||||
'' else ''
|
|
||||||
''}
|
|
||||||
'' else ''
|
|
||||||
${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \
|
|
||||||
-kernel ${config.system.build.toplevel}/kernel \
|
|
||||||
-initrd ${config.system.build.toplevel}/initrd \
|
|
||||||
-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS" \
|
|
||||||
''} \
|
|
||||||
$extraDisks \
|
|
||||||
${qemuGraphics} \
|
|
||||||
${toString config.virtualisation.qemu.options} \
|
${toString config.virtualisation.qemu.options} \
|
||||||
$QEMU_OPTS \
|
$QEMU_OPTS \
|
||||||
"$@"
|
"$@"
|
||||||
@ -367,6 +383,12 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
drives =
|
||||||
|
mkOption {
|
||||||
|
type = types.listOf (types.submodule driveOpts);
|
||||||
|
description = "Drives passed to qemu.";
|
||||||
|
};
|
||||||
|
|
||||||
diskInterface =
|
diskInterface =
|
||||||
mkOption {
|
mkOption {
|
||||||
default = "virtio";
|
default = "virtio";
|
||||||
@ -476,8 +498,49 @@ in
|
|||||||
|
|
||||||
# FIXME: Consolidate this one day.
|
# FIXME: Consolidate this one day.
|
||||||
virtualisation.qemu.options = mkMerge [
|
virtualisation.qemu.options = mkMerge [
|
||||||
(mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ "-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0" ])
|
(mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
|
||||||
(mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [ "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" ])
|
"-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0"
|
||||||
|
])
|
||||||
|
(mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
|
||||||
|
"-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet"
|
||||||
|
])
|
||||||
|
(mkIf (!cfg.useBootLoader) [
|
||||||
|
"-kernel ${config.system.build.toplevel}/kernel"
|
||||||
|
"-initrd ${config.system.build.toplevel}/initrd"
|
||||||
|
''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"''
|
||||||
|
])
|
||||||
|
(mkIf cfg.useEFIBoot [
|
||||||
|
"-pflash $TMPDIR/bios.bin"
|
||||||
|
])
|
||||||
|
(mkIf (!cfg.graphics) [
|
||||||
|
"-nographic"
|
||||||
|
])
|
||||||
|
];
|
||||||
|
|
||||||
|
virtualisation.qemu.drives = mkMerge [
|
||||||
|
(mkIf cfg.useBootLoader [
|
||||||
|
{
|
||||||
|
file = "$NIX_DISK_IMAGE";
|
||||||
|
driveExtraOpts.cache = "writeback";
|
||||||
|
driveExtraOpts.werror = "report";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
file = "$TMPDIR/disk.img";
|
||||||
|
driveExtraOpts.media = "disk";
|
||||||
|
deviceExtraOpts.bootindex = "1";
|
||||||
|
}
|
||||||
|
])
|
||||||
|
(mkIf (!cfg.useBootLoader) [
|
||||||
|
{
|
||||||
|
file = "$NIX_DISK_IMAGE";
|
||||||
|
driveExtraOpts.cache = "writeback";
|
||||||
|
driveExtraOpts.werror = "report";
|
||||||
|
}
|
||||||
|
])
|
||||||
|
(imap0 (idx: _: {
|
||||||
|
file = "$(pwd)/empty${toString idx}.qcow2";
|
||||||
|
driveExtraOpts.werror = "report";
|
||||||
|
}) cfg.emptyDiskImages)
|
||||||
];
|
];
|
||||||
|
|
||||||
# Mount the host filesystem via 9P, and bind-mount the Nix store
|
# Mount the host filesystem via 9P, and bind-mount the Nix store
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
let
|
let
|
||||||
commonConfig = ./common/letsencrypt/common.nix;
|
commonConfig = ./common/letsencrypt/common.nix;
|
||||||
in import ./make-test.nix {
|
in import ./make-test-python.nix {
|
||||||
name = "acme";
|
name = "acme";
|
||||||
|
|
||||||
nodes = rec {
|
nodes = rec {
|
||||||
@ -90,39 +90,44 @@ in import ./make-test.nix {
|
|||||||
newServerSystem = nodes.webserver2.config.system.build.toplevel;
|
newServerSystem = nodes.webserver2.config.system.build.toplevel;
|
||||||
switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
|
switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
|
||||||
in
|
in
|
||||||
# Note, waitForUnit does not work for oneshot services that do not have RemainAfterExit=true,
|
# Note, wait_for_unit does not work for oneshot services that do not have RemainAfterExit=true,
|
||||||
# this is because a oneshot goes from inactive => activating => inactive, and never
|
# this is because a oneshot goes from inactive => activating => inactive, and never
|
||||||
# reaches the active state. To work around this, we create some mock target units which
|
# reaches the active state. To work around this, we create some mock target units which
|
||||||
# get pulled in by the oneshot units. The target units linger after activation, and hence we
|
# get pulled in by the oneshot units. The target units linger after activation, and hence we
|
||||||
# can use them to probe that a oneshot fired. It is a bit ugly, but it is the best we can do
|
# can use them to probe that a oneshot fired. It is a bit ugly, but it is the best we can do
|
||||||
''
|
''
|
||||||
$client->start;
|
client.start()
|
||||||
$letsencrypt->start;
|
letsencrypt.start()
|
||||||
$acmeStandalone->start;
|
acmeStandalone.start()
|
||||||
|
|
||||||
$letsencrypt->waitForUnit("default.target");
|
letsencrypt.wait_for_unit("default.target")
|
||||||
$letsencrypt->waitForUnit("pebble.service");
|
letsencrypt.wait_for_unit("pebble.service")
|
||||||
|
|
||||||
subtest "can request certificate with HTTPS-01 challenge", sub {
|
with subtest("can request certificate with HTTPS-01 challenge"):
|
||||||
$acmeStandalone->waitForUnit("default.target");
|
acmeStandalone.wait_for_unit("default.target")
|
||||||
$acmeStandalone->succeed("systemctl start acme-standalone.com.service");
|
acmeStandalone.succeed("systemctl start acme-standalone.com.service")
|
||||||
$acmeStandalone->waitForUnit("acme-finished-standalone.com.target");
|
acmeStandalone.wait_for_unit("acme-finished-standalone.com.target")
|
||||||
};
|
|
||||||
|
|
||||||
$client->waitForUnit("default.target");
|
client.wait_for_unit("default.target")
|
||||||
|
|
||||||
$client->succeed('curl https://acme-v02.api.letsencrypt.org:15000/roots/0 > /tmp/ca.crt');
|
client.succeed("curl https://acme-v02.api.letsencrypt.org:15000/roots/0 > /tmp/ca.crt")
|
||||||
$client->succeed('curl https://acme-v02.api.letsencrypt.org:15000/intermediate-keys/0 >> /tmp/ca.crt');
|
client.succeed(
|
||||||
|
"curl https://acme-v02.api.letsencrypt.org:15000/intermediate-keys/0 >> /tmp/ca.crt"
|
||||||
|
)
|
||||||
|
|
||||||
subtest "Can request certificate for nginx service", sub {
|
with subtest("Can request certificate for nginx service"):
|
||||||
$webserver->waitForUnit("acme-finished-a.example.com.target");
|
webserver.wait_for_unit("acme-finished-a.example.com.target")
|
||||||
$client->succeed('curl --cacert /tmp/ca.crt https://a.example.com/ | grep -qF "hello world"');
|
client.succeed(
|
||||||
};
|
"curl --cacert /tmp/ca.crt https://a.example.com/ | grep -qF 'hello world'"
|
||||||
|
)
|
||||||
|
|
||||||
subtest "Can add another certificate for nginx service", sub {
|
with subtest("Can add another certificate for nginx service"):
|
||||||
$webserver->succeed("/run/current-system/fine-tune/child-1/bin/switch-to-configuration test");
|
webserver.succeed(
|
||||||
$webserver->waitForUnit("acme-finished-b.example.com.target");
|
"/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"
|
||||||
$client->succeed('curl --cacert /tmp/ca.crt https://b.example.com/ | grep -qF "hello world"');
|
)
|
||||||
};
|
webserver.wait_for_unit("acme-finished-b.example.com.target")
|
||||||
|
client.succeed(
|
||||||
|
"curl --cacert /tmp/ca.crt https://b.example.com/ | grep -qF 'hello world'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,6 @@ in
|
|||||||
clickhouse = handleTest ./clickhouse.nix {};
|
clickhouse = handleTest ./clickhouse.nix {};
|
||||||
cloud-init = handleTest ./cloud-init.nix {};
|
cloud-init = handleTest ./cloud-init.nix {};
|
||||||
codimd = handleTest ./codimd.nix {};
|
codimd = handleTest ./codimd.nix {};
|
||||||
colord = handleTest ./colord.nix {};
|
|
||||||
containers-bridge = handleTest ./containers-bridge.nix {};
|
containers-bridge = handleTest ./containers-bridge.nix {};
|
||||||
containers-ephemeral = handleTest ./containers-ephemeral.nix {};
|
containers-ephemeral = handleTest ./containers-ephemeral.nix {};
|
||||||
containers-extra_veth = handleTest ./containers-extra_veth.nix {};
|
containers-extra_veth = handleTest ./containers-extra_veth.nix {};
|
||||||
@ -88,27 +87,20 @@ in
|
|||||||
firewall = handleTest ./firewall.nix {};
|
firewall = handleTest ./firewall.nix {};
|
||||||
fish = handleTest ./fish.nix {};
|
fish = handleTest ./fish.nix {};
|
||||||
flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
|
flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
|
||||||
flatpak = handleTest ./flatpak.nix {};
|
|
||||||
flatpak-builder = handleTest ./flatpak-builder.nix {};
|
|
||||||
fluentd = handleTest ./fluentd.nix {};
|
fluentd = handleTest ./fluentd.nix {};
|
||||||
fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
|
fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
|
||||||
fsck = handleTest ./fsck.nix {};
|
fsck = handleTest ./fsck.nix {};
|
||||||
fwupd = handleTestOn ["x86_64-linux"] ./fwupd.nix {}; # libsmbios is unsupported on aarch64
|
|
||||||
gdk-pixbuf = handleTest ./gdk-pixbuf.nix {};
|
|
||||||
gotify-server = handleTest ./gotify-server.nix {};
|
gotify-server = handleTest ./gotify-server.nix {};
|
||||||
gitea = handleTest ./gitea.nix {};
|
gitea = handleTest ./gitea.nix {};
|
||||||
gitlab = handleTest ./gitlab.nix {};
|
gitlab = handleTest ./gitlab.nix {};
|
||||||
gitolite = handleTest ./gitolite.nix {};
|
gitolite = handleTest ./gitolite.nix {};
|
||||||
gjs = handleTest ./gjs.nix {};
|
|
||||||
glib-networking = handleTest ./glib-networking.nix {};
|
|
||||||
glusterfs = handleTest ./glusterfs.nix {};
|
glusterfs = handleTest ./glusterfs.nix {};
|
||||||
gnome3-xorg = handleTest ./gnome3-xorg.nix {};
|
gnome3-xorg = handleTest ./gnome3-xorg.nix {};
|
||||||
gnome3 = handleTest ./gnome3.nix {};
|
gnome3 = handleTest ./gnome3.nix {};
|
||||||
gnome-photos = handleTest ./gnome-photos.nix {};
|
installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
|
||||||
gocd-agent = handleTest ./gocd-agent.nix {};
|
gocd-agent = handleTest ./gocd-agent.nix {};
|
||||||
gocd-server = handleTest ./gocd-server.nix {};
|
gocd-server = handleTest ./gocd-server.nix {};
|
||||||
google-oslogin = handleTest ./google-oslogin {};
|
google-oslogin = handleTest ./google-oslogin {};
|
||||||
graphene = handleTest ./graphene.nix {};
|
|
||||||
grafana = handleTest ./grafana.nix {};
|
grafana = handleTest ./grafana.nix {};
|
||||||
graphite = handleTest ./graphite.nix {};
|
graphite = handleTest ./graphite.nix {};
|
||||||
graylog = handleTest ./graylog.nix {};
|
graylog = handleTest ./graylog.nix {};
|
||||||
@ -135,7 +127,6 @@ in
|
|||||||
jackett = handleTest ./jackett.nix {};
|
jackett = handleTest ./jackett.nix {};
|
||||||
jellyfin = handleTest ./jellyfin.nix {};
|
jellyfin = handleTest ./jellyfin.nix {};
|
||||||
jenkins = handleTest ./jenkins.nix {};
|
jenkins = handleTest ./jenkins.nix {};
|
||||||
jormungandr = handleTest ./jormungandr.nix {};
|
|
||||||
kafka = handleTest ./kafka.nix {};
|
kafka = handleTest ./kafka.nix {};
|
||||||
kerberos = handleTest ./kerberos/default.nix {};
|
kerberos = handleTest ./kerberos/default.nix {};
|
||||||
kernel-latest = handleTest ./kernel-latest.nix {};
|
kernel-latest = handleTest ./kernel-latest.nix {};
|
||||||
@ -150,8 +141,6 @@ in
|
|||||||
latestKernel.login = handleTest ./login.nix { latestKernel = true; };
|
latestKernel.login = handleTest ./login.nix { latestKernel = true; };
|
||||||
ldap = handleTest ./ldap.nix {};
|
ldap = handleTest ./ldap.nix {};
|
||||||
leaps = handleTest ./leaps.nix {};
|
leaps = handleTest ./leaps.nix {};
|
||||||
libgdata = handleTest ./libgdata.nix {};
|
|
||||||
libxmlb = handleTest ./libxmlb.nix {};
|
|
||||||
lidarr = handleTest ./lidarr.nix {};
|
lidarr = handleTest ./lidarr.nix {};
|
||||||
lightdm = handleTest ./lightdm.nix {};
|
lightdm = handleTest ./lightdm.nix {};
|
||||||
limesurvey = handleTest ./limesurvey.nix {};
|
limesurvey = handleTest ./limesurvey.nix {};
|
||||||
@ -170,6 +159,7 @@ in
|
|||||||
minio = handleTest ./minio.nix {};
|
minio = handleTest ./minio.nix {};
|
||||||
minidlna = handleTest ./minidlna.nix {};
|
minidlna = handleTest ./minidlna.nix {};
|
||||||
misc = handleTest ./misc.nix {};
|
misc = handleTest ./misc.nix {};
|
||||||
|
moinmoin = handleTest ./moinmoin.nix {};
|
||||||
mongodb = handleTest ./mongodb.nix {};
|
mongodb = handleTest ./mongodb.nix {};
|
||||||
moodle = handleTest ./moodle.nix {};
|
moodle = handleTest ./moodle.nix {};
|
||||||
morty = handleTest ./morty.nix {};
|
morty = handleTest ./morty.nix {};
|
||||||
@ -216,7 +206,6 @@ in
|
|||||||
os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {};
|
os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {};
|
||||||
osquery = handleTest ./osquery.nix {};
|
osquery = handleTest ./osquery.nix {};
|
||||||
osrm-backend = handleTest ./osrm-backend.nix {};
|
osrm-backend = handleTest ./osrm-backend.nix {};
|
||||||
ostree = handleTest ./ostree.nix {};
|
|
||||||
overlayfs = handleTest ./overlayfs.nix {};
|
overlayfs = handleTest ./overlayfs.nix {};
|
||||||
packagekit = handleTest ./packagekit.nix {};
|
packagekit = handleTest ./packagekit.nix {};
|
||||||
pam-oath-login = handleTest ./pam-oath-login.nix {};
|
pam-oath-login = handleTest ./pam-oath-login.nix {};
|
||||||
@ -242,7 +231,6 @@ in
|
|||||||
prosodyMysql = handleTest ./xmpp/prosody-mysql.nix {};
|
prosodyMysql = handleTest ./xmpp/prosody-mysql.nix {};
|
||||||
proxy = handleTest ./proxy.nix {};
|
proxy = handleTest ./proxy.nix {};
|
||||||
quagga = handleTest ./quagga.nix {};
|
quagga = handleTest ./quagga.nix {};
|
||||||
quake3 = handleTest ./quake3.nix {};
|
|
||||||
rabbitmq = handleTest ./rabbitmq.nix {};
|
rabbitmq = handleTest ./rabbitmq.nix {};
|
||||||
radarr = handleTest ./radarr.nix {};
|
radarr = handleTest ./radarr.nix {};
|
||||||
radicale = handleTest ./radicale.nix {};
|
radicale = handleTest ./radicale.nix {};
|
||||||
@ -280,6 +268,7 @@ in
|
|||||||
tinydns = handleTest ./tinydns.nix {};
|
tinydns = handleTest ./tinydns.nix {};
|
||||||
tor = handleTest ./tor.nix {};
|
tor = handleTest ./tor.nix {};
|
||||||
transmission = handleTest ./transmission.nix {};
|
transmission = handleTest ./transmission.nix {};
|
||||||
|
trac = handleTest ./trac.nix {};
|
||||||
trezord = handleTest ./trezord.nix {};
|
trezord = handleTest ./trezord.nix {};
|
||||||
trickster = handleTest ./trickster.nix {};
|
trickster = handleTest ./trickster.nix {};
|
||||||
udisks2 = handleTest ./udisks2.nix {};
|
udisks2 = handleTest ./udisks2.nix {};
|
||||||
@ -291,7 +280,6 @@ in
|
|||||||
wireguard-generated = handleTest ./wireguard/generated.nix {};
|
wireguard-generated = handleTest ./wireguard/generated.nix {};
|
||||||
wordpress = handleTest ./wordpress.nix {};
|
wordpress = handleTest ./wordpress.nix {};
|
||||||
xautolock = handleTest ./xautolock.nix {};
|
xautolock = handleTest ./xautolock.nix {};
|
||||||
xdg-desktop-portal = handleTest ./xdg-desktop-portal.nix {};
|
|
||||||
xfce = handleTest ./xfce.nix {};
|
xfce = handleTest ./xfce.nix {};
|
||||||
xfce4-14 = handleTest ./xfce4-14.nix {};
|
xfce4-14 = handleTest ./xfce4-14.nix {};
|
||||||
xmonad = handleTest ./xmonad.nix {};
|
xmonad = handleTest ./xmonad.nix {};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ...} : {
|
import ./make-test-python.nix ({ pkgs, ...} : {
|
||||||
name = "ammonite";
|
name = "ammonite";
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
maintainers = [ nequissimus ];
|
maintainers = [ nequissimus ];
|
||||||
@ -13,8 +13,8 @@ import ./make-test.nix ({ pkgs, ...} : {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
$amm->succeed("amm -c 'val foo = 21; println(foo * 2)' | grep 42")
|
amm.succeed("amm -c 'val foo = 21; println(foo * 2)' | grep 42")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }:
|
import ./make-test-python.nix ({ pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
name = "atd";
|
name = "atd";
|
||||||
@ -14,18 +14,18 @@ import ./make-test.nix ({ pkgs, ... }:
|
|||||||
|
|
||||||
# "at" has a resolution of 1 minute
|
# "at" has a resolution of 1 minute
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
$machine->waitForUnit('atd.service'); # wait for atd to start
|
machine.wait_for_unit("atd.service") # wait for atd to start
|
||||||
$machine->fail("test -f ~root/at-1");
|
machine.fail("test -f ~root/at-1")
|
||||||
$machine->fail("test -f ~alice/at-1");
|
machine.fail("test -f ~alice/at-1")
|
||||||
|
|
||||||
$machine->succeed("echo 'touch ~root/at-1' | at now+1min");
|
machine.succeed("echo 'touch ~root/at-1' | at now+1min")
|
||||||
$machine->succeed("su - alice -c \"echo 'touch at-1' | at now+1min\"");
|
machine.succeed("su - alice -c \"echo 'touch at-1' | at now+1min\"")
|
||||||
|
|
||||||
$machine->succeed("sleep 1.5m");
|
machine.succeed("sleep 1.5m")
|
||||||
|
|
||||||
$machine->succeed("test -f ~root/at-1");
|
machine.succeed("test -f ~root/at-1")
|
||||||
$machine->succeed("test -f ~alice/at-1");
|
machine.succeed("test -f ~alice/at-1")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, lib, ... }:
|
import ./make-test-python.nix ({ pkgs, lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
name = "automysqlbackup";
|
name = "automysqlbackup";
|
||||||
@ -15,20 +15,24 @@ import ./make-test.nix ({ pkgs, lib, ... }:
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
# Need to have mysql started so that it can be populated with data.
|
# Need to have mysql started so that it can be populated with data.
|
||||||
$machine->waitForUnit("mysql.service");
|
machine.wait_for_unit("mysql.service")
|
||||||
|
|
||||||
# Wait for testdb to be fully populated (5 rows).
|
with subtest("Wait for testdb to be fully populated (5 rows)."):
|
||||||
$machine->waitUntilSucceeds("mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5");
|
machine.wait_until_succeeds(
|
||||||
|
"mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
|
||||||
|
)
|
||||||
|
|
||||||
# Do a backup and wait for it to start
|
with subtest("Do a backup and wait for it to start"):
|
||||||
$machine->startJob("automysqlbackup.service");
|
machine.start_job("automysqlbackup.service")
|
||||||
$machine->waitForJob("automysqlbackup.service");
|
machine.wait_for_job("automysqlbackup.service")
|
||||||
|
|
||||||
# wait for backup file and check that data appears in backup
|
with subtest("wait for backup file and check that data appears in backup"):
|
||||||
$machine->waitForFile("/var/backup/mysql/daily/testdb");
|
machine.wait_for_file("/var/backup/mysql/daily/testdb")
|
||||||
$machine->succeed("${pkgs.gzip}/bin/zcat /var/backup/mysql/daily/testdb/daily_testdb_*.sql.gz | grep hello");
|
machine.succeed(
|
||||||
|
"${pkgs.gzip}/bin/zcat /var/backup/mysql/daily/testdb/daily_testdb_*.sql.gz | grep hello"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Test whether `avahi-daemon' and `libnss-mdns' work as expected.
|
# Test whether `avahi-daemon' and `libnss-mdns' work as expected.
|
||||||
import ./make-test.nix ({ pkgs, ... } : {
|
import ./make-test-python.nix ({ pkgs, ... } : {
|
||||||
name = "avahi";
|
name = "avahi";
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
maintainers = [ eelco ];
|
maintainers = [ eelco ];
|
||||||
@ -23,45 +23,45 @@ import ./make-test.nix ({ pkgs, ... } : {
|
|||||||
two = cfg;
|
two = cfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript =
|
testScript = ''
|
||||||
'' startAll;
|
start_all()
|
||||||
|
|
||||||
# mDNS.
|
# mDNS.
|
||||||
$one->waitForUnit("network.target");
|
one.wait_for_unit("network.target")
|
||||||
$two->waitForUnit("network.target");
|
two.wait_for_unit("network.target")
|
||||||
|
|
||||||
$one->succeed("avahi-resolve-host-name one.local | tee out >&2");
|
one.succeed("avahi-resolve-host-name one.local | tee out >&2")
|
||||||
$one->succeed("test \"`cut -f1 < out`\" = one.local");
|
one.succeed('test "`cut -f1 < out`" = one.local')
|
||||||
$one->succeed("avahi-resolve-host-name two.local | tee out >&2");
|
one.succeed("avahi-resolve-host-name two.local | tee out >&2")
|
||||||
$one->succeed("test \"`cut -f1 < out`\" = two.local");
|
one.succeed('test "`cut -f1 < out`" = two.local')
|
||||||
|
|
||||||
$two->succeed("avahi-resolve-host-name one.local | tee out >&2");
|
two.succeed("avahi-resolve-host-name one.local | tee out >&2")
|
||||||
$two->succeed("test \"`cut -f1 < out`\" = one.local");
|
two.succeed('test "`cut -f1 < out`" = one.local')
|
||||||
$two->succeed("avahi-resolve-host-name two.local | tee out >&2");
|
two.succeed("avahi-resolve-host-name two.local | tee out >&2")
|
||||||
$two->succeed("test \"`cut -f1 < out`\" = two.local");
|
two.succeed('test "`cut -f1 < out`" = two.local')
|
||||||
|
|
||||||
# Basic DNS-SD.
|
# Basic DNS-SD.
|
||||||
$one->succeed("avahi-browse -r -t _workstation._tcp | tee out >&2");
|
one.succeed("avahi-browse -r -t _workstation._tcp | tee out >&2")
|
||||||
$one->succeed("test `wc -l < out` -gt 0");
|
one.succeed("test `wc -l < out` -gt 0")
|
||||||
$two->succeed("avahi-browse -r -t _workstation._tcp | tee out >&2");
|
two.succeed("avahi-browse -r -t _workstation._tcp | tee out >&2")
|
||||||
$two->succeed("test `wc -l < out` -gt 0");
|
two.succeed("test `wc -l < out` -gt 0")
|
||||||
|
|
||||||
# More DNS-SD.
|
# More DNS-SD.
|
||||||
$one->execute("avahi-publish -s \"This is a test\" _test._tcp 123 one=1 &");
|
one.execute('avahi-publish -s "This is a test" _test._tcp 123 one=1 &')
|
||||||
$one->sleep(5);
|
one.sleep(5)
|
||||||
$two->succeed("avahi-browse -r -t _test._tcp | tee out >&2");
|
two.succeed("avahi-browse -r -t _test._tcp | tee out >&2")
|
||||||
$two->succeed("test `wc -l < out` -gt 0");
|
two.succeed("test `wc -l < out` -gt 0")
|
||||||
|
|
||||||
# NSS-mDNS.
|
# NSS-mDNS.
|
||||||
$one->succeed("getent hosts one.local >&2");
|
one.succeed("getent hosts one.local >&2")
|
||||||
$one->succeed("getent hosts two.local >&2");
|
one.succeed("getent hosts two.local >&2")
|
||||||
$two->succeed("getent hosts one.local >&2");
|
two.succeed("getent hosts one.local >&2")
|
||||||
$two->succeed("getent hosts two.local >&2");
|
two.succeed("getent hosts two.local >&2")
|
||||||
|
|
||||||
# extra service definitions
|
# extra service definitions
|
||||||
$one->succeed("avahi-browse -r -t _ssh._tcp | tee out >&2");
|
one.succeed("avahi-browse -r -t _ssh._tcp | tee out >&2")
|
||||||
$one->succeed("test `wc -l < out` -gt 0");
|
one.succeed("test `wc -l < out` -gt 0")
|
||||||
$two->succeed("avahi-browse -r -t _ssh._tcp | tee out >&2");
|
two.succeed("avahi-browse -r -t _ssh._tcp | tee out >&2")
|
||||||
$two->succeed("test `wc -l < out` -gt 0");
|
two.succeed("test `wc -l < out` -gt 0")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import ./make-test.nix ({ pkgs, lib, ...} : {
|
import ./make-test-python.nix ({ pkgs, lib, ...} : {
|
||||||
name = "babeld";
|
name = "babeld";
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
maintainers = [ hexa ];
|
maintainers = [ hexa ];
|
||||||
@ -21,7 +21,7 @@ import ./make-test.nix ({ pkgs, lib, ...} : {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
localRouter = { pkgs, lib, ... }:
|
local_router = { pkgs, lib, ... }:
|
||||||
{
|
{
|
||||||
virtualisation.vlans = [ 10 20 ];
|
virtualisation.vlans = [ 10 20 ];
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ import ./make-test.nix ({ pkgs, lib, ...} : {
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
remoteRouter = { pkgs, lib, ... }:
|
remote_router = { pkgs, lib, ... }:
|
||||||
{
|
{
|
||||||
virtualisation.vlans = [ 20 30 ];
|
virtualisation.vlans = [ 20 30 ];
|
||||||
|
|
||||||
@ -124,25 +124,25 @@ import ./make-test.nix ({ pkgs, lib, ...} : {
|
|||||||
|
|
||||||
testScript =
|
testScript =
|
||||||
''
|
''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
$client->waitForUnit("network-online.target");
|
client.wait_for_unit("network-online.target")
|
||||||
$localRouter->waitForUnit("network-online.target");
|
local_router.wait_for_unit("network-online.target")
|
||||||
$remoteRouter->waitForUnit("network-online.target");
|
remote_router.wait_for_unit("network-online.target")
|
||||||
|
|
||||||
$localRouter->waitForUnit("babeld.service");
|
local_router.wait_for_unit("babeld.service")
|
||||||
$remoteRouter->waitForUnit("babeld.service");
|
remote_router.wait_for_unit("babeld.service")
|
||||||
|
|
||||||
$localRouter->waitUntilSucceeds("ip route get 192.168.30.1");
|
local_router.wait_until_succeeds("ip route get 192.168.30.1")
|
||||||
$localRouter->waitUntilSucceeds("ip route get 2001:db8:30::1");
|
local_router.wait_until_succeeds("ip route get 2001:db8:30::1")
|
||||||
|
|
||||||
$remoteRouter->waitUntilSucceeds("ip route get 192.168.10.1");
|
remote_router.wait_until_succeeds("ip route get 192.168.10.1")
|
||||||
$remoteRouter->waitUntilSucceeds("ip route get 2001:db8:10::1");
|
remote_router.wait_until_succeeds("ip route get 2001:db8:10::1")
|
||||||
|
|
||||||
$client->succeed("ping -c1 192.168.30.1");
|
client.succeed("ping -c1 192.168.30.1")
|
||||||
$client->succeed("ping -c1 2001:db8:30::1");
|
client.succeed("ping -c1 2001:db8:30::1")
|
||||||
|
|
||||||
$remoteRouter->succeed("ping -c1 192.168.10.2");
|
remote_router.succeed("ping -c1 192.168.10.2")
|
||||||
$remoteRouter->succeed("ping -c1 2001:db8:10::2");
|
remote_router.succeed("ping -c1 2001:db8:10::2")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }: {
|
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||||
name = "bcachefs";
|
name = "bcachefs";
|
||||||
meta.maintainers = with pkgs.stdenv.lib.maintainers; [ chiiruno ];
|
meta.maintainers = with pkgs.stdenv.lib.maintainers; [ chiiruno ];
|
||||||
|
|
||||||
@ -10,29 +10,25 @@ import ./make-test.nix ({ pkgs, ... }: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
$machine->succeed("modprobe bcachefs");
|
machine.succeed("modprobe bcachefs")
|
||||||
$machine->succeed("bcachefs version");
|
machine.succeed("bcachefs version")
|
||||||
$machine->succeed("ls /dev");
|
machine.succeed("ls /dev")
|
||||||
|
|
||||||
$machine->succeed(
|
machine.succeed(
|
||||||
"mkdir /tmp/mnt",
|
"mkdir /tmp/mnt",
|
||||||
|
|
||||||
"udevadm settle",
|
"udevadm settle",
|
||||||
"parted --script /dev/vdb mklabel msdos",
|
"parted --script /dev/vdb mklabel msdos",
|
||||||
"parted --script /dev/vdb -- mkpart primary 1024M -1s",
|
"parted --script /dev/vdb -- mkpart primary 1024M -1s",
|
||||||
"udevadm settle",
|
"udevadm settle",
|
||||||
|
|
||||||
# Due to #32279, we cannot use encryption for this test yet
|
# Due to #32279, we cannot use encryption for this test yet
|
||||||
# "echo password | bcachefs format --encrypted /dev/vdb1",
|
# "echo password | bcachefs format --encrypted /dev/vdb1",
|
||||||
# "echo password | bcachefs unlock /dev/vdb1",
|
# "echo password | bcachefs unlock /dev/vdb1",
|
||||||
"bcachefs format /dev/vdb1",
|
"bcachefs format /dev/vdb1",
|
||||||
"mount -t bcachefs /dev/vdb1 /tmp/mnt",
|
"mount -t bcachefs /dev/vdb1 /tmp/mnt",
|
||||||
"udevadm settle",
|
"udevadm settle",
|
||||||
|
|
||||||
"bcachefs fs usage /tmp/mnt",
|
"bcachefs fs usage /tmp/mnt",
|
||||||
|
|
||||||
"umount /tmp/mnt",
|
"umount /tmp/mnt",
|
||||||
"udevadm settle"
|
"udevadm settle",
|
||||||
);
|
)
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, lib, ... }:
|
import ./make-test-python.nix ({ pkgs, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
pythonEnv = pkgs.python3.withPackages (p: [p.beanstalkc]);
|
pythonEnv = pkgs.python3.withPackages (p: [p.beanstalkc]);
|
||||||
@ -34,12 +34,16 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
$machine->waitForUnit('beanstalkd.service');
|
machine.wait_for_unit("beanstalkd.service")
|
||||||
|
|
||||||
$machine->succeed("${produce}");
|
machine.succeed("${produce}")
|
||||||
$machine->succeed("${consume}") eq "this is a job\n" or die;
|
assert "this is a job\n" == machine.succeed(
|
||||||
$machine->succeed("${consume}") eq "this is another job\n" or die;
|
"${consume}"
|
||||||
|
)
|
||||||
|
assert "this is another job\n" == machine.succeed(
|
||||||
|
"${consume}"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix {
|
import ./make-test-python.nix {
|
||||||
name = "bind";
|
name = "bind";
|
||||||
|
|
||||||
machine = { pkgs, lib, ... }: {
|
machine = { pkgs, lib, ... }: {
|
||||||
@ -20,8 +20,8 @@ import ./make-test.nix {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
$machine->waitForUnit('bind.service');
|
machine.wait_for_unit("bind.service")
|
||||||
$machine->waitForOpenPort(53);
|
machine.wait_for_open_port(53)
|
||||||
$machine->succeed('host 192.168.0.1 127.0.0.1 | grep -qF ns.example.org');
|
machine.succeed("host 192.168.0.1 127.0.0.1 | grep -qF ns.example.org")
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
# which only works if the first client successfully uses the UPnP-IGD
|
# which only works if the first client successfully uses the UPnP-IGD
|
||||||
# protocol to poke a hole in the NAT.
|
# protocol to poke a hole in the NAT.
|
||||||
|
|
||||||
import ./make-test.nix ({ pkgs, ... }:
|
import ./make-test-python.nix ({ pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
@ -108,42 +108,56 @@ in
|
|||||||
testScript =
|
testScript =
|
||||||
{ nodes, ... }:
|
{ nodes, ... }:
|
||||||
''
|
''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
# Wait for network and miniupnpd.
|
# Wait for network and miniupnpd.
|
||||||
$router->waitForUnit("network-online.target");
|
router.wait_for_unit("network-online.target")
|
||||||
$router->waitForUnit("miniupnpd");
|
router.wait_for_unit("miniupnpd")
|
||||||
|
|
||||||
# Create the torrent.
|
# Create the torrent.
|
||||||
$tracker->succeed("mkdir /tmp/data");
|
tracker.succeed("mkdir /tmp/data")
|
||||||
$tracker->succeed("cp ${file} /tmp/data/test.tar.bz2");
|
tracker.succeed(
|
||||||
$tracker->succeed("transmission-create /tmp/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent");
|
"cp ${file} /tmp/data/test.tar.bz2"
|
||||||
$tracker->succeed("chmod 644 /tmp/test.torrent");
|
)
|
||||||
|
tracker.succeed(
|
||||||
|
"transmission-create /tmp/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
|
||||||
|
)
|
||||||
|
tracker.succeed("chmod 644 /tmp/test.torrent")
|
||||||
|
|
||||||
# Start the tracker. !!! use a less crappy tracker
|
# Start the tracker. !!! use a less crappy tracker
|
||||||
$tracker->waitForUnit("network-online.target");
|
tracker.wait_for_unit("network-online.target")
|
||||||
$tracker->waitForUnit("opentracker.service");
|
tracker.wait_for_unit("opentracker.service")
|
||||||
$tracker->waitForOpenPort(6969);
|
tracker.wait_for_open_port(6969)
|
||||||
|
|
||||||
# Start the initial seeder.
|
# Start the initial seeder.
|
||||||
$tracker->succeed("transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir /tmp/data");
|
tracker.succeed(
|
||||||
|
"transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir /tmp/data"
|
||||||
|
)
|
||||||
|
|
||||||
# Now we should be able to download from the client behind the NAT.
|
# Now we should be able to download from the client behind the NAT.
|
||||||
$tracker->waitForUnit("httpd");
|
tracker.wait_for_unit("httpd")
|
||||||
$client1->waitForUnit("network-online.target");
|
client1.wait_for_unit("network-online.target")
|
||||||
$client1->succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent --download-dir /tmp >&2 &");
|
client1.succeed(
|
||||||
$client1->waitForFile("/tmp/test.tar.bz2");
|
"transmission-remote --add http://${externalTrackerAddress}/test.torrent --download-dir /tmp >&2 &"
|
||||||
$client1->succeed("cmp /tmp/test.tar.bz2 ${file}");
|
)
|
||||||
|
client1.wait_for_file("/tmp/test.tar.bz2")
|
||||||
|
client1.succeed(
|
||||||
|
"cmp /tmp/test.tar.bz2 ${file}"
|
||||||
|
)
|
||||||
|
|
||||||
# Bring down the initial seeder.
|
# Bring down the initial seeder.
|
||||||
# $tracker->stopJob("transmission");
|
# tracker.stop_job("transmission")
|
||||||
|
|
||||||
# Now download from the second client. This can only succeed if
|
# Now download from the second client. This can only succeed if
|
||||||
# the first client created a NAT hole in the router.
|
# the first client created a NAT hole in the router.
|
||||||
$client2->waitForUnit("network-online.target");
|
client2.wait_for_unit("network-online.target")
|
||||||
$client2->succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht --download-dir /tmp >&2 &");
|
client2.succeed(
|
||||||
$client2->waitForFile("/tmp/test.tar.bz2");
|
"transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht --download-dir /tmp >&2 &"
|
||||||
$client2->succeed("cmp /tmp/test.tar.bz2 ${file}");
|
)
|
||||||
|
client2.wait_for_file("/tmp/test.tar.bz2")
|
||||||
|
client2.succeed(
|
||||||
|
"cmp /tmp/test.tar.bz2 ${file}"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }: {
|
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||||
name = "boot-stage1";
|
name = "boot-stage1";
|
||||||
|
|
||||||
machine = { config, pkgs, lib, ... }: {
|
machine = { config, pkgs, lib, ... }: {
|
||||||
@ -150,12 +150,12 @@ import ./make-test.nix ({ pkgs, ... }: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
$machine->waitForUnit("multi-user.target");
|
machine.wait_for_unit("multi-user.target")
|
||||||
$machine->succeed('test -s /run/canary2.pid');
|
machine.succeed("test -s /run/canary2.pid")
|
||||||
$machine->fail('pgrep -a canary1');
|
machine.fail("pgrep -a canary1")
|
||||||
$machine->fail('kill -0 $(< /run/canary2.pid)');
|
machine.fail("kill -0 $(< /run/canary2.pid)")
|
||||||
$machine->succeed('pgrep -a -f \'^@canary3$\''');
|
machine.succeed('pgrep -a -f "^@canary3$"')
|
||||||
$machine->succeed('pgrep -a -f \'^kcanary$\''');
|
machine.succeed('pgrep -a -f "^kcanary$"')
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta.maintainers = with pkgs.stdenv.lib.maintainers; [ aszlig ];
|
meta.maintainers = with pkgs.stdenv.lib.maintainers; [ aszlig ];
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
pkgs ? import ../.. { inherit system config; }
|
pkgs ? import ../.. { inherit system config; }
|
||||||
}:
|
}:
|
||||||
|
|
||||||
with import ../lib/testing.nix { inherit system pkgs; };
|
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||||
with pkgs.lib;
|
with pkgs.lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
@ -17,11 +17,11 @@ let
|
|||||||
];
|
];
|
||||||
}).config.system.build.isoImage;
|
}).config.system.build.isoImage;
|
||||||
|
|
||||||
perlAttrs = params: "{ ${concatStringsSep ", " (mapAttrsToList (name: param: "${name} => ${builtins.toJSON param}") params)} }";
|
pythonDict = params: "\n {\n ${concatStringsSep ",\n " (mapAttrsToList (name: param: "\"${name}\": \"${param}\"") params)},\n }\n";
|
||||||
|
|
||||||
makeBootTest = name: extraConfig:
|
makeBootTest = name: extraConfig:
|
||||||
let
|
let
|
||||||
machineConfig = perlAttrs ({ qemuFlags = "-m 768"; } // extraConfig);
|
machineConfig = pythonDict ({ qemuFlags = "-m 768"; } // extraConfig);
|
||||||
in
|
in
|
||||||
makeTest {
|
makeTest {
|
||||||
inherit iso;
|
inherit iso;
|
||||||
@ -29,16 +29,16 @@ let
|
|||||||
nodes = { };
|
nodes = { };
|
||||||
testScript =
|
testScript =
|
||||||
''
|
''
|
||||||
my $machine = createMachine(${machineConfig});
|
machine = create_machine(${machineConfig})
|
||||||
$machine->start;
|
machine.start()
|
||||||
$machine->waitForUnit("multi-user.target");
|
machine.wait_for_unit("multi-user.target")
|
||||||
$machine->succeed("nix verify -r --no-trust /run/current-system");
|
machine.succeed("nix verify -r --no-trust /run/current-system")
|
||||||
|
|
||||||
# Test whether the channel got installed correctly.
|
with subtest("Check whether the channel got installed correctly"):
|
||||||
$machine->succeed("nix-instantiate --dry-run '<nixpkgs>' -A hello");
|
machine.succeed("nix-instantiate --dry-run '<nixpkgs>' -A hello")
|
||||||
$machine->succeed("nix-env --dry-run -iA nixos.procps");
|
machine.succeed("nix-env --dry-run -iA nixos.procps")
|
||||||
|
|
||||||
$machine->shutdown;
|
machine.shutdown()
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ let
|
|||||||
config.system.build.netbootIpxeScript
|
config.system.build.netbootIpxeScript
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
machineConfig = perlAttrs ({
|
machineConfig = pythonDict ({
|
||||||
qemuFlags = "-boot order=n -m 2000";
|
qemuFlags = "-boot order=n -m 2000";
|
||||||
netBackendArgs = "tftp=${ipxeBootDir},bootfile=netboot.ipxe";
|
netBackendArgs = "tftp=${ipxeBootDir},bootfile=netboot.ipxe";
|
||||||
} // extraConfig);
|
} // extraConfig);
|
||||||
@ -68,12 +68,11 @@ let
|
|||||||
makeTest {
|
makeTest {
|
||||||
name = "boot-netboot-" + name;
|
name = "boot-netboot-" + name;
|
||||||
nodes = { };
|
nodes = { };
|
||||||
testScript =
|
testScript = ''
|
||||||
''
|
machine = create_machine(${machineConfig})
|
||||||
my $machine = createMachine(${machineConfig});
|
machine.start()
|
||||||
$machine->start;
|
machine.wait_for_unit("multi-user.target")
|
||||||
$machine->waitForUnit("multi-user.target");
|
machine.shutdown()
|
||||||
$machine->shutdown;
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }:
|
import ./make-test-python.nix ({ pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
passphrase = "supersecret";
|
passphrase = "supersecret";
|
||||||
@ -106,60 +106,70 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
$client->fail('test -d "${remoteRepo}"');
|
client.fail('test -d "${remoteRepo}"')
|
||||||
|
|
||||||
$client->succeed("cp ${privateKey} /root/id_ed25519");
|
client.succeed(
|
||||||
$client->succeed("chmod 0600 /root/id_ed25519");
|
"cp ${privateKey} /root/id_ed25519"
|
||||||
$client->succeed("cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly");
|
)
|
||||||
$client->succeed("chmod 0600 /root/id_ed25519.appendOnly");
|
client.succeed("chmod 0600 /root/id_ed25519")
|
||||||
|
client.succeed(
|
||||||
|
"cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly"
|
||||||
|
)
|
||||||
|
client.succeed("chmod 0600 /root/id_ed25519.appendOnly")
|
||||||
|
|
||||||
$client->succeed("mkdir -p ${dataDir}");
|
client.succeed("mkdir -p ${dataDir}")
|
||||||
$client->succeed("touch ${dataDir}/${excludeFile}");
|
client.succeed("touch ${dataDir}/${excludeFile}")
|
||||||
$client->succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}");
|
client.succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}")
|
||||||
|
|
||||||
subtest "local", sub {
|
with subtest("local"):
|
||||||
my $borg = "BORG_PASSPHRASE='${passphrase}' borg";
|
borg = "BORG_PASSPHRASE='${passphrase}' borg"
|
||||||
$client->systemctl("start --wait borgbackup-job-local");
|
client.systemctl("start --wait borgbackup-job-local")
|
||||||
$client->fail("systemctl is-failed borgbackup-job-local");
|
client.fail("systemctl is-failed borgbackup-job-local")
|
||||||
# Make sure exactly one archive has been created
|
# Make sure exactly one archive has been created
|
||||||
$client->succeed("c=\$($borg list '${localRepo}' | wc -l) && [[ \$c == '1' ]]");
|
assert int(client.succeed("{} list '${localRepo}' | wc -l".format(borg))) > 0
|
||||||
# Make sure excludeFile has been excluded
|
# Make sure excludeFile has been excluded
|
||||||
$client->fail("$borg list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'");
|
client.fail(
|
||||||
|
"{} list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'".format(borg)
|
||||||
|
)
|
||||||
# Make sure keepFile has the correct content
|
# Make sure keepFile has the correct content
|
||||||
$client->succeed("$borg extract '${localRepo}::${archiveName}'");
|
client.succeed("{} extract '${localRepo}::${archiveName}'".format(borg))
|
||||||
$client->succeed('c=$(cat ${dataDir}/${keepFile}) && [[ "$c" == "${keepFileData}" ]]');
|
assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}")
|
||||||
# Make sure the same is true when using `borg mount`
|
# Make sure the same is true when using `borg mount`
|
||||||
$client->succeed("mkdir -p /mnt/borg && $borg mount '${localRepo}::${archiveName}' /mnt/borg");
|
client.succeed(
|
||||||
$client->succeed('c=$(cat /mnt/borg/${dataDir}/${keepFile}) && [[ "$c" == "${keepFileData}" ]]');
|
"mkdir -p /mnt/borg && {} mount '${localRepo}::${archiveName}' /mnt/borg".format(
|
||||||
};
|
borg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert "${keepFileData}" in client.succeed(
|
||||||
|
"cat /mnt/borg/${dataDir}/${keepFile}"
|
||||||
|
)
|
||||||
|
|
||||||
subtest "remote", sub {
|
with subtest("remote"):
|
||||||
my $borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg";
|
borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg"
|
||||||
$server->waitForUnit("sshd.service");
|
server.wait_for_unit("sshd.service")
|
||||||
$client->waitForUnit("network.target");
|
client.wait_for_unit("network.target")
|
||||||
$client->systemctl("start --wait borgbackup-job-remote");
|
client.systemctl("start --wait borgbackup-job-remote")
|
||||||
$client->fail("systemctl is-failed borgbackup-job-remote");
|
client.fail("systemctl is-failed borgbackup-job-remote")
|
||||||
|
|
||||||
# Make sure we can't access repos other than the specified one
|
# Make sure we can't access repos other than the specified one
|
||||||
$client->fail("$borg list borg\@server:wrong");
|
client.fail("{} list borg\@server:wrong".format(borg))
|
||||||
|
|
||||||
# TODO: Make sure that data is actually deleted
|
# TODO: Make sure that data is actually deleted
|
||||||
};
|
|
||||||
|
|
||||||
subtest "remoteAppendOnly", sub {
|
with subtest("remoteAppendOnly"):
|
||||||
my $borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg";
|
borg = (
|
||||||
$server->waitForUnit("sshd.service");
|
"BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg"
|
||||||
$client->waitForUnit("network.target");
|
)
|
||||||
$client->systemctl("start --wait borgbackup-job-remoteAppendOnly");
|
server.wait_for_unit("sshd.service")
|
||||||
$client->fail("systemctl is-failed borgbackup-job-remoteAppendOnly");
|
client.wait_for_unit("network.target")
|
||||||
|
client.systemctl("start --wait borgbackup-job-remoteAppendOnly")
|
||||||
|
client.fail("systemctl is-failed borgbackup-job-remoteAppendOnly")
|
||||||
|
|
||||||
# Make sure we can't access repos other than the specified one
|
# Make sure we can't access repos other than the specified one
|
||||||
$client->fail("$borg list borg\@server:wrong");
|
client.fail("{} list borg\@server:wrong".format(borg))
|
||||||
|
|
||||||
# TODO: Make sure that data is not actually deleted
|
# TODO: Make sure that data is not actually deleted
|
||||||
};
|
|
||||||
|
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "colord";
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.colord.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -d '${pkgs.colord.installedTests}/share'");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ...} : {
|
import ./make-test-python.nix ({ pkgs, ...} : {
|
||||||
name = "emacs-daemon";
|
name = "emacs-daemon";
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
maintainers = [ ];
|
maintainers = [ ];
|
||||||
@ -21,25 +21,28 @@ import ./make-test.nix ({ pkgs, ...} : {
|
|||||||
environment.variables.TEST_SYSTEM_VARIABLE = "system variable";
|
environment.variables.TEST_SYSTEM_VARIABLE = "system variable";
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript =
|
testScript = ''
|
||||||
''
|
machine.wait_for_unit("multi-user.target")
|
||||||
$machine->waitForUnit("multi-user.target");
|
|
||||||
|
|
||||||
# checks that the EDITOR environment variable is set
|
# checks that the EDITOR environment variable is set
|
||||||
$machine->succeed("test \$(basename \"\$EDITOR\") = emacseditor");
|
machine.succeed('test $(basename "$EDITOR") = emacseditor')
|
||||||
|
|
||||||
# waits for the emacs service to be ready
|
# waits for the emacs service to be ready
|
||||||
$machine->waitUntilSucceeds("systemctl --user status emacs.service | grep 'Active: active'");
|
machine.wait_until_succeeds(
|
||||||
|
"systemctl --user status emacs.service | grep 'Active: active'"
|
||||||
|
)
|
||||||
|
|
||||||
# connects to the daemon
|
# connects to the daemon
|
||||||
$machine->succeed("emacsclient --create-frame \$EDITOR &");
|
machine.succeed("emacsclient --create-frame $EDITOR &")
|
||||||
|
|
||||||
# checks that Emacs shows the edited filename
|
# checks that Emacs shows the edited filename
|
||||||
$machine->waitForText("emacseditor");
|
machine.wait_for_text("emacseditor")
|
||||||
|
|
||||||
# makes sure environment variables are accessible from Emacs
|
# makes sure environment variables are accessible from Emacs
|
||||||
$machine->succeed("emacsclient --eval '(getenv \"TEST_SYSTEM_VARIABLE\")'") =~ /system variable/ or die;
|
machine.succeed(
|
||||||
|
"emacsclient --eval '(getenv \"TEST_SYSTEM_VARIABLE\")' | grep -q 'system variable'"
|
||||||
|
)
|
||||||
|
|
||||||
$machine->screenshot("emacsclient");
|
machine.screenshot("emacsclient")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }: {
|
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||||
name = "firefox";
|
name = "firefox";
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
maintainers = [ eelco shlevy ];
|
maintainers = [ eelco shlevy ];
|
||||||
@ -11,19 +11,27 @@ import ./make-test.nix ({ pkgs, ... }: {
|
|||||||
environment.systemPackages = [ pkgs.firefox pkgs.xdotool ];
|
environment.systemPackages = [ pkgs.firefox pkgs.xdotool ];
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript =
|
testScript = ''
|
||||||
''
|
machine.wait_for_x()
|
||||||
$machine->waitForX;
|
|
||||||
$machine->execute("xterm -e 'firefox file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' &");
|
with subtest("wait until Firefox has finished loading the Valgrind docs page"):
|
||||||
$machine->waitForWindow(qr/Valgrind/);
|
machine.execute(
|
||||||
$machine->sleep(40); # wait until Firefox has finished loading the page
|
"xterm -e 'firefox file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' &"
|
||||||
$machine->execute("xdotool key space"); # do I want to make Firefox the
|
)
|
||||||
# default browser? I just want to close the dialog
|
machine.wait_for_window("Valgrind")
|
||||||
$machine->sleep(2); # wait until Firefox hides the default browser window
|
machine.sleep(40)
|
||||||
$machine->execute("xdotool key F12");
|
|
||||||
$machine->sleep(10); # wait until Firefox draws the developer tool panel
|
with subtest("Close default browser prompt"):
|
||||||
$machine->succeed("xwininfo -root -tree | grep Valgrind");
|
machine.execute("xdotool key space")
|
||||||
$machine->screenshot("screen");
|
|
||||||
|
with subtest("Hide default browser window"):
|
||||||
|
machine.sleep(2)
|
||||||
|
machine.execute("xdotool key F12")
|
||||||
|
|
||||||
|
with subtest("wait until Firefox draws the developer tool panel"):
|
||||||
|
machine.sleep(10)
|
||||||
|
machine.succeed("xwininfo -root -tree | grep Valgrind")
|
||||||
|
machine.screenshot("screen")
|
||||||
'';
|
'';
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "flatpak-builder";
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.flatpak-builder.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
services.flatpak.enable = true;
|
|
||||||
xdg.portal.enable = true;
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing flatpak-builder ] ++ flatpak-builder.installedTestsDependencies;
|
|
||||||
virtualisation.diskSize = 2048;
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -d '${pkgs.flatpak-builder.installedTests}/share' --timeout 3600");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,26 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "flatpak";
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.flatpak.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
imports = [ ./common/x11.nix ];
|
|
||||||
services.xserver.desktopManager.gnome3.enable = true; # TODO: figure out minimal environment where the tests work
|
|
||||||
# common/x11.nix enables the auto display manager (lightdm)
|
|
||||||
services.xserver.displayManager.gdm.enable = false;
|
|
||||||
environment.gnome3.excludePackages = pkgs.gnome3.optionalPackages;
|
|
||||||
services.flatpak.enable = true;
|
|
||||||
environment.systemPackages = with pkgs; [ gnupg gnome-desktop-testing ostree python2 ];
|
|
||||||
virtualisation.memorySize = 2047;
|
|
||||||
virtualisation.diskSize = 1024;
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->waitForX();
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -d '${pkgs.flatpak.installedTests}/share' --timeout 3600");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,7 +1,12 @@
|
|||||||
import ./make-test.nix ({ lib, ... }:
|
import ./make-test-python.nix ({ lib, ... }:
|
||||||
{
|
{
|
||||||
name = "fontconfig-default-fonts";
|
name = "fontconfig-default-fonts";
|
||||||
|
|
||||||
|
meta.maintainers = with lib.maintainers; [
|
||||||
|
jtojnar
|
||||||
|
worldofpeace
|
||||||
|
];
|
||||||
|
|
||||||
machine = { config, pkgs, ... }: {
|
machine = { config, pkgs, ... }: {
|
||||||
fonts.enableDefaultFonts = true; # Background fonts
|
fonts.enableDefaultFonts = true; # Background fonts
|
||||||
fonts.fonts = with pkgs; [
|
fonts.fonts = with pkgs; [
|
||||||
@ -20,9 +25,9 @@ import ./make-test.nix ({ lib, ... }:
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
$machine->succeed("fc-match serif | grep '\"Gentium Plus\"'");
|
machine.succeed("fc-match serif | grep '\"Gentium Plus\"'")
|
||||||
$machine->succeed("fc-match sans-serif | grep '\"Cantarell\"'");
|
machine.succeed("fc-match sans-serif | grep '\"Cantarell\"'")
|
||||||
$machine->succeed("fc-match monospace | grep '\"Source Code Pro\"'");
|
machine.succeed("fc-match monospace | grep '\"Source Code Pro\"'")
|
||||||
$machine->succeed("fc-match emoji | grep '\"Twitter Color Emoji\"'");
|
machine.succeed("fc-match emoji | grep '\"Twitter Color Emoji\"'")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix {
|
import ./make-test-python.nix {
|
||||||
name = "fsck";
|
name = "fsck";
|
||||||
|
|
||||||
machine = { lib, ... }: {
|
machine = { lib, ... }: {
|
||||||
@ -14,16 +14,18 @@ import ./make-test.nix {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
$machine->waitForUnit('default.target');
|
machine.wait_for_unit("default.target")
|
||||||
|
|
||||||
subtest "root fs is fsckd", sub {
|
with subtest("root fs is fsckd"):
|
||||||
$machine->succeed('journalctl -b | grep "fsck.ext4.*/dev/vda"');
|
machine.succeed("journalctl -b | grep 'fsck.ext4.*/dev/vda'")
|
||||||
};
|
|
||||||
|
|
||||||
subtest "mnt fs is fsckd", sub {
|
with subtest("mnt fs is fsckd"):
|
||||||
$machine->succeed('journalctl -b | grep "fsck.*/dev/vdb.*clean"');
|
machine.succeed("journalctl -b | grep 'fsck.*/dev/vdb.*clean'")
|
||||||
$machine->succeed('grep "Requires=systemd-fsck@dev-vdb.service" /run/systemd/generator/mnt.mount');
|
machine.succeed(
|
||||||
$machine->succeed('grep "After=systemd-fsck@dev-vdb.service" /run/systemd/generator/mnt.mount');
|
"grep 'Requires=systemd-fsck@dev-vdb.service' /run/systemd/generator/mnt.mount"
|
||||||
};
|
)
|
||||||
|
machine.succeed(
|
||||||
|
"grep 'After=systemd-fsck@dev-vdb.service' /run/systemd/generator/mnt.mount"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }: {
|
|
||||||
name = "fwupd";
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.fwupd.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
services.fwupd.enable = true;
|
|
||||||
services.fwupd.blacklistPlugins = []; # don't blacklist test plugin
|
|
||||||
services.fwupd.enableTestRemote = true;
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
|
|
||||||
environment.variables.XDG_DATA_DIRS = [ "${pkgs.fwupd.installedTests}/share" ];
|
|
||||||
virtualisation.memorySize = 768;
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,21 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }: {
|
|
||||||
name = "gdk-pixbuf";
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.gdk-pixbuf.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
|
|
||||||
environment.variables.XDG_DATA_DIRS = [ "${pkgs.gdk-pixbuf.installedTests}/share" ];
|
|
||||||
|
|
||||||
# Tests allocate a lot of memory trying to exploit a CVE
|
|
||||||
# but qemu-system-i386 has a 2047M memory limit
|
|
||||||
virtualisation.memorySize = if pkgs.stdenv.isi686 then 2047 else 4096;
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -t 1800"); # increase timeout to 1800s
|
|
||||||
'';
|
|
||||||
})
|
|
@ -3,7 +3,7 @@
|
|||||||
pkgs ? import ../.. { inherit system config; }
|
pkgs ? import ../.. { inherit system config; }
|
||||||
}:
|
}:
|
||||||
|
|
||||||
with import ../lib/testing.nix { inherit system pkgs; };
|
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||||
with pkgs.lib;
|
with pkgs.lib;
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -18,11 +18,11 @@ with pkgs.lib;
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
$machine->waitForUnit('gitea.service');
|
machine.wait_for_unit("gitea.service")
|
||||||
$machine->waitForOpenPort('3000');
|
machine.wait_for_open_port(3000)
|
||||||
$machine->succeed("curl --fail http://localhost:3000/");
|
machine.succeed("curl --fail http://localhost:3000/")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,11 +37,11 @@ with pkgs.lib;
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
$machine->waitForUnit('gitea.service');
|
machine.wait_for_unit("gitea.service")
|
||||||
$machine->waitForOpenPort('3000');
|
machine.wait_for_open_port(3000)
|
||||||
$machine->succeed("curl --fail http://localhost:3000/");
|
machine.succeed("curl --fail http://localhost:3000/")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -56,12 +56,14 @@ with pkgs.lib;
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
$machine->waitForUnit('gitea.service');
|
machine.wait_for_unit("gitea.service")
|
||||||
$machine->waitForOpenPort('3000');
|
machine.wait_for_open_port(3000)
|
||||||
$machine->succeed("curl --fail http://localhost:3000/");
|
machine.succeed("curl --fail http://localhost:3000/")
|
||||||
$machine->succeed("curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. Please contact your site administrator.'");
|
machine.succeed(
|
||||||
|
"curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. Please contact your site administrator.'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }: {
|
|
||||||
name = "gjs";
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.gnome3.gjs.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
imports = [ ./common/x11.nix ];
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
|
|
||||||
environment.variables.XDG_DATA_DIRS = [ "${pkgs.gnome3.gjs.installedTests}/share" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->waitForX;
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,17 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "glib-networking";
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.glib-networking.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -d '${pkgs.glib-networking.installedTests}/share'");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,42 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, lib, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
|
|
||||||
# gsettings tool with access to gsettings-desktop-schemas
|
|
||||||
desktop-gsettings = with pkgs; stdenv.mkDerivation {
|
|
||||||
name = "desktop-gsettings";
|
|
||||||
dontUnpack = true;
|
|
||||||
nativeBuildInputs = [ glib wrapGAppsHook ];
|
|
||||||
buildInputs = [ gsettings-desktop-schemas ];
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
mkdir -p $out/bin
|
|
||||||
ln -s ${glib.bin}/bin/gsettings $out/bin/desktop-gsettings
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "gnome-photos";
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.gnome-photos.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
imports = [ ./common/x11.nix ];
|
|
||||||
programs.dconf.enable = true;
|
|
||||||
services.gnome3.at-spi2-core.enable = true; # needed for dogtail
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing desktop-gsettings ];
|
|
||||||
services.dbus.packages = with pkgs; [ gnome-photos ];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->waitForX;
|
|
||||||
# dogtail needs accessibility enabled
|
|
||||||
$machine->succeed("desktop-gsettings set org.gnome.desktop.interface toolkit-accessibility true 2>&1");
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -d '${pkgs.gnome-photos.installedTests}/share' 2>&1");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ lib, pkgs, ... }:
|
import ./make-test-python.nix ({ lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (lib) mkMerge nameValuePair maintainers;
|
inherit (lib) mkMerge nameValuePair maintainers;
|
||||||
@ -64,28 +64,34 @@ in {
|
|||||||
inherit nodes;
|
inherit nodes;
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll();
|
start_all()
|
||||||
|
|
||||||
subtest "Grafana sqlite", sub {
|
with subtest("Successful API query as admin user with sqlite db"):
|
||||||
$sqlite->waitForUnit("grafana.service");
|
sqlite.wait_for_unit("grafana.service")
|
||||||
$sqlite->waitForOpenPort(3000);
|
sqlite.wait_for_open_port(3000)
|
||||||
$sqlite->succeed("curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep -q testadmin\@localhost");
|
sqlite.succeed(
|
||||||
};
|
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep -q testadmin\@localhost"
|
||||||
|
)
|
||||||
|
sqlite.shutdown()
|
||||||
|
|
||||||
subtest "Grafana postgresql", sub {
|
with subtest("Successful API query as admin user with postgresql db"):
|
||||||
$postgresql->waitForUnit("grafana.service");
|
postgresql.wait_for_unit("grafana.service")
|
||||||
$postgresql->waitForUnit("postgresql.service");
|
postgresql.wait_for_unit("postgresql.service")
|
||||||
$postgresql->waitForOpenPort(3000);
|
postgresql.wait_for_open_port(3000)
|
||||||
$postgresql->waitForOpenPort(5432);
|
postgresql.wait_for_open_port(5432)
|
||||||
$postgresql->succeed("curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep -q testadmin\@localhost");
|
postgresql.succeed(
|
||||||
};
|
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep -q testadmin\@localhost"
|
||||||
|
)
|
||||||
|
postgresql.shutdown()
|
||||||
|
|
||||||
subtest "Grafana mysql", sub {
|
with subtest("Successful API query as admin user with mysql db"):
|
||||||
$mysql->waitForUnit("grafana.service");
|
mysql.wait_for_unit("grafana.service")
|
||||||
$mysql->waitForUnit("mysql.service");
|
mysql.wait_for_unit("mysql.service")
|
||||||
$mysql->waitForOpenPort(3000);
|
mysql.wait_for_open_port(3000)
|
||||||
$mysql->waitForOpenPort(3306);
|
mysql.wait_for_open_port(3306)
|
||||||
$mysql->succeed("curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep -q testadmin\@localhost");
|
mysql.succeed(
|
||||||
};
|
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep -q testadmin\@localhost"
|
||||||
|
)
|
||||||
|
mysql.shutdown()
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "graphene";
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.graphene.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -d '${pkgs.graphene.installedTests}/share'");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,4 +1,4 @@
|
|||||||
import ../make-test.nix ({ lib, ... }:
|
import ../make-test-python.nix ({ lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
name = "initrd-network-ssh";
|
name = "initrd-network-ssh";
|
||||||
@ -35,7 +35,8 @@ import ../make-test.nix ({ lib, ... }:
|
|||||||
client =
|
client =
|
||||||
{ config, ... }:
|
{ config, ... }:
|
||||||
{
|
{
|
||||||
environment.etc.knownHosts = {
|
environment.etc = {
|
||||||
|
knownHosts = {
|
||||||
text = concatStrings [
|
text = concatStrings [
|
||||||
"server,"
|
"server,"
|
||||||
"${toString (head (splitString " " (
|
"${toString (head (splitString " " (
|
||||||
@ -44,16 +45,21 @@ import ../make-test.nix ({ lib, ... }:
|
|||||||
"${readFile ./dropbear.pub}"
|
"${readFile ./dropbear.pub}"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
sshKey = {
|
||||||
|
source = ./openssh.priv; # dont use this anywhere else
|
||||||
|
mode = "0600";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
$client->waitForUnit("network.target");
|
client.wait_for_unit("network.target")
|
||||||
$client->copyFileFromHost("${./openssh.priv}","/etc/sshKey");
|
client.wait_until_succeeds("ping -c 1 server")
|
||||||
$client->succeed("chmod 0600 /etc/sshKey");
|
client.succeed(
|
||||||
$client->waitUntilSucceeds("ping -c 1 server");
|
"ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'touch /fnord'"
|
||||||
$client->succeed("ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'touch /fnord'");
|
)
|
||||||
$client->shutdown;
|
client.shutdown()
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
5
nixos/tests/installed-tests/colord.nix
Normal file
5
nixos/tests/installed-tests/colord.nix
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.colord;
|
||||||
|
}
|
80
nixos/tests/installed-tests/default.nix
Normal file
80
nixos/tests/installed-tests/default.nix
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# NixOS tests for gnome-desktop-testing-runner using software
|
||||||
|
# See https://wiki.gnome.org/Initiatives/GnomeGoals/InstalledTests
|
||||||
|
|
||||||
|
{ system ? builtins.currentSystem,
|
||||||
|
config ? {},
|
||||||
|
pkgs ? import ../../.. { inherit system config; }
|
||||||
|
}:
|
||||||
|
|
||||||
|
with import ../../lib/testing-python.nix { inherit system pkgs; };
|
||||||
|
with pkgs.lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
callInstalledTest = pkgs.newScope { inherit makeInstalledTest; };
|
||||||
|
|
||||||
|
makeInstalledTest =
|
||||||
|
{ # Package to test. Needs to have an installedTests output
|
||||||
|
tested
|
||||||
|
|
||||||
|
# Config to inject into machine
|
||||||
|
, testConfig ? {}
|
||||||
|
|
||||||
|
# Test script snippet to inject before gnome-desktop-testing-runner begins.
|
||||||
|
# This is useful for extra setup the environment may need before the runner begins.
|
||||||
|
, preTestScript ? ""
|
||||||
|
|
||||||
|
# Does test need X11?
|
||||||
|
, withX11 ? false
|
||||||
|
|
||||||
|
# Extra flags to pass to gnome-desktop-testing-runner.
|
||||||
|
, testRunnerFlags ? ""
|
||||||
|
}:
|
||||||
|
makeTest rec {
|
||||||
|
name = tested.name;
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
maintainers = tested.meta.maintainers;
|
||||||
|
};
|
||||||
|
|
||||||
|
machine = { ... }: {
|
||||||
|
imports = [
|
||||||
|
testConfig
|
||||||
|
] ++ optional withX11 ../common/x11.nix;
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript =
|
||||||
|
optionalString withX11 ''
|
||||||
|
machine.wait_for_x()
|
||||||
|
'' +
|
||||||
|
optionalString (preTestScript != "") ''
|
||||||
|
${preTestScript}
|
||||||
|
'' +
|
||||||
|
''
|
||||||
|
machine.succeed(
|
||||||
|
"gnome-desktop-testing-runner ${testRunnerFlags} -d '${tested.installedTests}/share'"
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
colord = callInstalledTest ./colord.nix {};
|
||||||
|
flatpak = callInstalledTest ./flatpak.nix {};
|
||||||
|
flatpak-builder = callInstalledTest ./flatpak-builder.nix {};
|
||||||
|
fwupd = callInstalledTest ./fwupd.nix {};
|
||||||
|
gcab = callInstalledTest ./gcab.nix {};
|
||||||
|
gdk-pixbuf = callInstalledTest ./gdk-pixbuf.nix {};
|
||||||
|
gjs = callInstalledTest ./gjs.nix {};
|
||||||
|
glib-networking = callInstalledTest ./glib-networking.nix {};
|
||||||
|
gnome-photos = callInstalledTest ./gnome-photos.nix {};
|
||||||
|
graphene = callInstalledTest ./graphene.nix {};
|
||||||
|
libgdata = callInstalledTest ./libgdata.nix {};
|
||||||
|
libxmlb = callInstalledTest ./libxmlb.nix {};
|
||||||
|
ostree = callInstalledTest ./ostree.nix {};
|
||||||
|
xdg-desktop-portal = callInstalledTest ./xdg-desktop-portal.nix {};
|
||||||
|
}
|
14
nixos/tests/installed-tests/flatpak-builder.nix
Normal file
14
nixos/tests/installed-tests/flatpak-builder.nix
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.flatpak-builder;
|
||||||
|
|
||||||
|
testConfig = {
|
||||||
|
services.flatpak.enable = true;
|
||||||
|
xdg.portal.enable = true;
|
||||||
|
environment.systemPackages = with pkgs; [ flatpak-builder ] ++ flatpak-builder.installedTestsDependencies;
|
||||||
|
virtualisation.diskSize = 2048;
|
||||||
|
};
|
||||||
|
|
||||||
|
testRunnerFlags = "--timeout 3600";
|
||||||
|
}
|
19
nixos/tests/installed-tests/flatpak.nix
Normal file
19
nixos/tests/installed-tests/flatpak.nix
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.flatpak;
|
||||||
|
withX11 = true;
|
||||||
|
|
||||||
|
testConfig = {
|
||||||
|
services.xserver.desktopManager.gnome3.enable = true; # TODO: figure out minimal environment where the tests work
|
||||||
|
# common/x11.nix enables the auto display manager (lightdm)
|
||||||
|
services.xserver.displayManager.gdm.enable = false;
|
||||||
|
services.gnome3.core-utilities.enable = false;
|
||||||
|
services.flatpak.enable = true;
|
||||||
|
environment.systemPackages = with pkgs; [ gnupg ostree python2 ];
|
||||||
|
virtualisation.memorySize = 2047;
|
||||||
|
virtualisation.diskSize = 1024;
|
||||||
|
};
|
||||||
|
|
||||||
|
testRunnerFlags = "--timeout 3600";
|
||||||
|
}
|
12
nixos/tests/installed-tests/fwupd.nix
Normal file
12
nixos/tests/installed-tests/fwupd.nix
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.fwupd;
|
||||||
|
|
||||||
|
testConfig = {
|
||||||
|
services.fwupd.enable = true;
|
||||||
|
services.fwupd.blacklistPlugins = []; # don't blacklist test plugin
|
||||||
|
services.fwupd.enableTestRemote = true;
|
||||||
|
virtualisation.memorySize = 768;
|
||||||
|
};
|
||||||
|
}
|
5
nixos/tests/installed-tests/gcab.nix
Normal file
5
nixos/tests/installed-tests/gcab.nix
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.gcab;
|
||||||
|
}
|
13
nixos/tests/installed-tests/gdk-pixbuf.nix
Normal file
13
nixos/tests/installed-tests/gdk-pixbuf.nix
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.gdk-pixbuf;
|
||||||
|
|
||||||
|
testConfig = {
|
||||||
|
# Tests allocate a lot of memory trying to exploit a CVE
|
||||||
|
# but qemu-system-i386 has a 2047M memory limit
|
||||||
|
virtualisation.memorySize = if pkgs.stdenv.isi686 then 2047 else 4096;
|
||||||
|
};
|
||||||
|
|
||||||
|
testRunnerFlags = "--timeout 1800";
|
||||||
|
}
|
6
nixos/tests/installed-tests/gjs.nix
Normal file
6
nixos/tests/installed-tests/gjs.nix
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.gjs;
|
||||||
|
withX11 = true;
|
||||||
|
}
|
5
nixos/tests/installed-tests/glib-networking.nix
Normal file
5
nixos/tests/installed-tests/glib-networking.nix
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.glib-networking;
|
||||||
|
}
|
35
nixos/tests/installed-tests/gnome-photos.nix
Normal file
35
nixos/tests/installed-tests/gnome-photos.nix
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.gnome-photos;
|
||||||
|
|
||||||
|
withX11 = true;
|
||||||
|
|
||||||
|
testConfig = {
|
||||||
|
programs.dconf.enable = true;
|
||||||
|
services.gnome3.at-spi2-core.enable = true; # needed for dogtail
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
# gsettings tool with access to gsettings-desktop-schemas
|
||||||
|
(stdenv.mkDerivation {
|
||||||
|
name = "desktop-gsettings";
|
||||||
|
dontUnpack = true;
|
||||||
|
nativeBuildInputs = [ glib wrapGAppsHook ];
|
||||||
|
buildInputs = [ gsettings-desktop-schemas ];
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
mkdir -p $out/bin
|
||||||
|
ln -s ${glib.bin}/bin/gsettings $out/bin/desktop-gsettings
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
];
|
||||||
|
services.dbus.packages = with pkgs; [ gnome-photos ];
|
||||||
|
};
|
||||||
|
|
||||||
|
preTestScript = ''
|
||||||
|
# dogtail needs accessibility enabled
|
||||||
|
machine.succeed(
|
||||||
|
"desktop-gsettings set org.gnome.desktop.interface toolkit-accessibility true 2>&1"
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
}
|
5
nixos/tests/installed-tests/graphene.nix
Normal file
5
nixos/tests/installed-tests/graphene.nix
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.graphene;
|
||||||
|
}
|
11
nixos/tests/installed-tests/libgdata.nix
Normal file
11
nixos/tests/installed-tests/libgdata.nix
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.libgdata;
|
||||||
|
|
||||||
|
testConfig = {
|
||||||
|
# # GLib-GIO-DEBUG: _g_io_module_get_default: Found default implementation dummy (GDummyTlsBackend) for ‘gio-tls-backend’
|
||||||
|
# Bail out! libgdata:ERROR:../gdata/tests/common.c:134:gdata_test_init: assertion failed (child_error == NULL): TLS support is not available (g-tls-error-quark, 0)
|
||||||
|
services.gnome3.glib-networking.enable = true;
|
||||||
|
};
|
||||||
|
}
|
5
nixos/tests/installed-tests/libxmlb.nix
Normal file
5
nixos/tests/installed-tests/libxmlb.nix
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.libxmlb;
|
||||||
|
}
|
23
nixos/tests/installed-tests/ostree.nix
Normal file
23
nixos/tests/installed-tests/ostree.nix
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{ pkgs, lib, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.ostree;
|
||||||
|
|
||||||
|
# TODO: Wrap/patch the tests directly in the package
|
||||||
|
testConfig = {
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
(python3.withPackages (p: with p; [ pyyaml ]))
|
||||||
|
gnupg
|
||||||
|
ostree
|
||||||
|
];
|
||||||
|
|
||||||
|
# for GJS tests
|
||||||
|
environment.variables.GI_TYPELIB_PATH = lib.makeSearchPath "lib/girepository-1.0" (with pkgs; [
|
||||||
|
gtk3
|
||||||
|
pango.out
|
||||||
|
ostree
|
||||||
|
gdk-pixbuf
|
||||||
|
atk
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
}
|
5
nixos/tests/installed-tests/xdg-desktop-portal.nix
Normal file
5
nixos/tests/installed-tests/xdg-desktop-portal.nix
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ pkgs, makeInstalledTest, ... }:
|
||||||
|
|
||||||
|
makeInstalledTest {
|
||||||
|
tested = pkgs.xdg-desktop-portal;
|
||||||
|
}
|
@ -1,77 +0,0 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }: {
|
|
||||||
name = "jormungandr";
|
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
|
||||||
maintainers = [ mmahut ];
|
|
||||||
};
|
|
||||||
|
|
||||||
nodes = {
|
|
||||||
# Testing the Byzantine Fault Tolerant protocol
|
|
||||||
bft = { ... }: {
|
|
||||||
environment.systemPackages = [ pkgs.jormungandr ];
|
|
||||||
services.jormungandr.enable = true;
|
|
||||||
services.jormungandr.genesisBlockFile = "/var/lib/jormungandr/block-0.bin";
|
|
||||||
services.jormungandr.secretFile = "/etc/secrets/jormungandr.yaml";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Testing the Ouroboros Genesis Praos protocol
|
|
||||||
genesis = { ... }: {
|
|
||||||
environment.systemPackages = [ pkgs.jormungandr ];
|
|
||||||
services.jormungandr.enable = true;
|
|
||||||
services.jormungandr.genesisBlockFile = "/var/lib/jormungandr/block-0.bin";
|
|
||||||
services.jormungandr.secretFile = "/etc/secrets/jormungandr.yaml";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
startAll;
|
|
||||||
|
|
||||||
## Testing BFT
|
|
||||||
# Let's wait for the StateDirectory
|
|
||||||
$bft->waitForFile("/var/lib/jormungandr/");
|
|
||||||
|
|
||||||
# First, we generate the genesis file for our new blockchain
|
|
||||||
$bft->succeed("jcli genesis init > /root/genesis.yaml");
|
|
||||||
|
|
||||||
# We need to generate our secret key
|
|
||||||
$bft->succeed("jcli key generate --type=Ed25519 > /root/key.prv");
|
|
||||||
|
|
||||||
# We include the secret key into our services.jormungandr.secretFile
|
|
||||||
$bft->succeed("mkdir -p /etc/secrets");
|
|
||||||
$bft->succeed("echo -e \"bft:\\n signing_key:\" \$(cat /root/key.prv) > /etc/secrets/jormungandr.yaml");
|
|
||||||
|
|
||||||
# After that, we generate our public key from it
|
|
||||||
$bft->succeed("cat /root/key.prv | jcli key to-public > /root/key.pub");
|
|
||||||
|
|
||||||
# We add our public key as a consensus leader in the genesis configration file
|
|
||||||
$bft->succeed("sed -ie \"s/ed25519_pk1vvwp2s0n5jl5f4xcjurp2e92sj2awehkrydrlas4vgqr7xzt33jsadha32/\$(cat /root/key.pub)/\" /root/genesis.yaml");
|
|
||||||
|
|
||||||
# Now we can generate the genesis block from it
|
|
||||||
$bft->succeed("jcli genesis encode --input /root/genesis.yaml --output /var/lib/jormungandr/block-0.bin");
|
|
||||||
|
|
||||||
# We should have everything to start the service now
|
|
||||||
$bft->succeed("systemctl restart jormungandr");
|
|
||||||
$bft->waitForUnit("jormungandr.service");
|
|
||||||
|
|
||||||
# Now we can test if we are able to reach the REST API
|
|
||||||
$bft->waitUntilSucceeds("curl -L http://localhost:8607/api/v0/node/stats | grep uptime");
|
|
||||||
|
|
||||||
## Testing Genesis
|
|
||||||
# Let's wait for the StateDirectory
|
|
||||||
$genesis->waitForFile("/var/lib/jormungandr/");
|
|
||||||
|
|
||||||
# Bootstraping the configuration
|
|
||||||
$genesis->succeed("jormungandr-bootstrap -g -p 8607 -s 1");
|
|
||||||
|
|
||||||
# Moving generated files in place
|
|
||||||
$genesis->succeed("mkdir -p /etc/secrets");
|
|
||||||
$genesis->succeed("mv pool-secret1.yaml /etc/secrets/jormungandr.yaml");
|
|
||||||
$genesis->succeed("mv block-0.bin /var/lib/jormungandr/");
|
|
||||||
|
|
||||||
# We should have everything to start the service now
|
|
||||||
$genesis->succeed("systemctl restart jormungandr");
|
|
||||||
$genesis->waitForUnit("jormungandr.service");
|
|
||||||
|
|
||||||
# Now we can create and delegate an account
|
|
||||||
$genesis->succeed("./create-account-and-delegate.sh | tee -a /tmp/delegate.log");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, lib, ...} :
|
import ./make-test-python.nix ({ pkgs, lib, ...} :
|
||||||
let
|
let
|
||||||
common = {
|
common = {
|
||||||
networking.firewall.enable = false;
|
networking.firewall.enable = false;
|
||||||
@ -30,6 +30,10 @@ let
|
|||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
name = "knot";
|
name = "knot";
|
||||||
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
|
maintainers = [ hexa ];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
nodes = {
|
nodes = {
|
||||||
master = { lib, ... }: {
|
master = { lib, ... }: {
|
||||||
@ -161,37 +165,35 @@ in {
|
|||||||
slave4 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv4.addresses).address;
|
slave4 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv4.addresses).address;
|
||||||
slave6 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv6.addresses).address;
|
slave6 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv6.addresses).address;
|
||||||
in ''
|
in ''
|
||||||
startAll;
|
import re
|
||||||
|
|
||||||
$client->waitForUnit("network.target");
|
start_all()
|
||||||
$master->waitForUnit("knot.service");
|
|
||||||
$slave->waitForUnit("knot.service");
|
|
||||||
|
|
||||||
sub assertResponse {
|
client.wait_for_unit("network.target")
|
||||||
my ($knot, $query_type, $query, $expected) = @_;
|
master.wait_for_unit("knot.service")
|
||||||
my $out = $client->succeed("khost -t $query_type $query $knot");
|
slave.wait_for_unit("knot.service")
|
||||||
$client->log("$knot replies with: $out");
|
|
||||||
chomp $out;
|
|
||||||
die "DNS query for $query ($query_type) against $knot gave '$out' instead of '$expected'"
|
|
||||||
if ($out !~ $expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ("${master4}", "${master6}", "${slave4}", "${slave6}") {
|
|
||||||
subtest $_, sub {
|
|
||||||
assertResponse($_, "SOA", "example.com", qr/start of authority.*?noc\.example\.com/);
|
|
||||||
assertResponse($_, "A", "example.com", qr/has no [^ ]+ record/);
|
|
||||||
assertResponse($_, "AAAA", "example.com", qr/has no [^ ]+ record/);
|
|
||||||
|
|
||||||
assertResponse($_, "A", "www.example.com", qr/address 192.0.2.1$/);
|
def test(host, query_type, query, pattern):
|
||||||
assertResponse($_, "AAAA", "www.example.com", qr/address 2001:db8::1$/);
|
out = client.succeed(f"khost -t {query_type} {query} {host}").strip()
|
||||||
|
client.log(f"{host} replied with: {out}")
|
||||||
|
assert re.search(pattern, out), f'Did not match "{pattern}"'
|
||||||
|
|
||||||
assertResponse($_, "NS", "sub.example.com", qr/nameserver is ns\d\.example\.com.$/);
|
|
||||||
assertResponse($_, "A", "sub.example.com", qr/address 192.0.2.2$/);
|
|
||||||
assertResponse($_, "AAAA", "sub.example.com", qr/address 2001:db8::2$/);
|
|
||||||
|
|
||||||
assertResponse($_, "RRSIG", "www.example.com", qr/RR set signature is/);
|
for host in ("${master4}", "${master6}", "${slave4}", "${slave6}"):
|
||||||
assertResponse($_, "DNSKEY", "example.com", qr/DNSSEC key is/);
|
with subtest(f"Interrogate {host}"):
|
||||||
};
|
test(host, "SOA", "example.com", r"start of authority.*noc\.example\.com\.")
|
||||||
}
|
test(host, "A", "example.com", r"has no [^ ]+ record")
|
||||||
|
test(host, "AAAA", "example.com", r"has no [^ ]+ record")
|
||||||
|
|
||||||
|
test(host, "A", "www.example.com", r"address 192.0.2.1$")
|
||||||
|
test(host, "AAAA", "www.example.com", r"address 2001:db8::1$")
|
||||||
|
|
||||||
|
test(host, "NS", "sub.example.com", r"nameserver is ns\d\.example\.com.$")
|
||||||
|
test(host, "A", "sub.example.com", r"address 192.0.2.2$")
|
||||||
|
test(host, "AAAA", "sub.example.com", r"address 2001:db8::2$")
|
||||||
|
|
||||||
|
test(host, "RRSIG", "www.example.com", r"RR set signature is")
|
||||||
|
test(host, "DNSKEY", "example.com", r"DNSSEC key is")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "libgdata";
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.libgdata.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
|
|
||||||
# # GLib-GIO-DEBUG: _g_io_module_get_default: Found default implementation dummy (GDummyTlsBackend) for ‘gio-tls-backend’
|
|
||||||
# Bail out! libgdata:ERROR:../gdata/tests/common.c:134:gdata_test_init: assertion failed (child_error == NULL): TLS support is not available (g-tls-error-quark, 0)
|
|
||||||
services.gnome3.glib-networking.enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -d '${pkgs.libgdata.installedTests}/share'");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,17 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "libxmlb";
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.libxmlb.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -d '${pkgs.libxmlb.installedTests}/share'");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, latestKernel ? false, ... }:
|
import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
name = "login";
|
name = "login";
|
||||||
@ -12,62 +12,48 @@ import ./make-test.nix ({ pkgs, latestKernel ? false, ... }:
|
|||||||
sound.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
|
sound.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript =
|
testScript = ''
|
||||||
''
|
machine.wait_for_unit("multi-user.target")
|
||||||
$machine->waitForUnit('multi-user.target');
|
machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
|
||||||
$machine->waitUntilSucceeds("pgrep -f 'agetty.*tty1'");
|
machine.screenshot("postboot")
|
||||||
$machine->screenshot("postboot");
|
|
||||||
|
|
||||||
subtest "create user", sub {
|
with subtest("create user"):
|
||||||
$machine->succeed("useradd -m alice");
|
machine.succeed("useradd -m alice")
|
||||||
$machine->succeed("(echo foobar; echo foobar) | passwd alice");
|
machine.succeed("(echo foobar; echo foobar) | passwd alice")
|
||||||
};
|
|
||||||
|
|
||||||
# Check whether switching VTs works.
|
with subtest("Check whether switching VTs works"):
|
||||||
subtest "virtual console switching", sub {
|
machine.fail("pgrep -f 'agetty.*tty2'")
|
||||||
$machine->fail("pgrep -f 'agetty.*tty2'");
|
machine.send_key("alt-f2")
|
||||||
$machine->sendKeys("alt-f2");
|
machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
|
||||||
$machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]");
|
machine.wait_for_unit("getty@tty2.service")
|
||||||
$machine->waitForUnit('getty@tty2.service');
|
machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
|
||||||
$machine->waitUntilSucceeds("pgrep -f 'agetty.*tty2'");
|
|
||||||
};
|
|
||||||
|
|
||||||
# Log in as alice on a virtual console.
|
with subtest("Log in as alice on a virtual console"):
|
||||||
subtest "virtual console login", sub {
|
machine.wait_until_tty_matches(2, "login: ")
|
||||||
$machine->waitUntilTTYMatches(2, "login: ");
|
machine.send_chars("alice\n")
|
||||||
$machine->sendChars("alice\n");
|
machine.wait_until_tty_matches(2, "login: alice")
|
||||||
$machine->waitUntilTTYMatches(2, "login: alice");
|
machine.wait_until_succeeds("pgrep login")
|
||||||
$machine->waitUntilSucceeds("pgrep login");
|
machine.wait_until_tty_matches(2, "Password: ")
|
||||||
$machine->waitUntilTTYMatches(2, "Password: ");
|
machine.send_chars("foobar\n")
|
||||||
$machine->sendChars("foobar\n");
|
machine.wait_until_succeeds("pgrep -u alice bash")
|
||||||
$machine->waitUntilSucceeds("pgrep -u alice bash");
|
machine.send_chars("touch done\n")
|
||||||
$machine->sendChars("touch done\n");
|
machine.wait_for_file("/home/alice/done")
|
||||||
$machine->waitForFile("/home/alice/done");
|
|
||||||
};
|
|
||||||
|
|
||||||
# Check whether systemd gives and removes device ownership as
|
with subtest("Systemd gives and removes device ownership as needed"):
|
||||||
# needed.
|
machine.succeed("getfacl /dev/snd/timer | grep -q alice")
|
||||||
subtest "device permissions", sub {
|
machine.send_key("alt-f1")
|
||||||
$machine->succeed("getfacl -p /dev/snd/timer | grep -q alice");
|
machine.wait_until_succeeds("[ $(fgconsole) = 1 ]")
|
||||||
$machine->sendKeys("alt-f1");
|
machine.fail("getfacl /dev/snd/timer | grep -q alice")
|
||||||
$machine->waitUntilSucceeds("[ \$(fgconsole) = 1 ]");
|
machine.succeed("chvt 2")
|
||||||
$machine->fail("getfacl -p /dev/snd/timer | grep -q alice");
|
machine.wait_until_succeeds("getfacl /dev/snd/timer | grep -q alice")
|
||||||
$machine->succeed("chvt 2");
|
|
||||||
$machine->waitUntilSucceeds("getfacl -p /dev/snd/timer | grep -q alice");
|
|
||||||
};
|
|
||||||
|
|
||||||
# Log out.
|
with subtest("Virtual console logout"):
|
||||||
subtest "virtual console logout", sub {
|
machine.send_chars("exit\n")
|
||||||
$machine->sendChars("exit\n");
|
machine.wait_until_fails("pgrep -u alice bash")
|
||||||
$machine->waitUntilFails("pgrep -u alice bash");
|
machine.screenshot("mingetty")
|
||||||
$machine->screenshot("mingetty");
|
|
||||||
};
|
|
||||||
|
|
||||||
# Check whether ctrl-alt-delete works.
|
with subtest("Check whether ctrl-alt-delete works"):
|
||||||
subtest "ctrl-alt-delete", sub {
|
machine.send_key("ctrl-alt-delete")
|
||||||
$machine->sendKeys("ctrl-alt-delete");
|
machine.wait_for_shutdown()
|
||||||
$machine->waitForShutdown;
|
|
||||||
};
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ lib, pkgs, ... }:
|
import ./make-test-python.nix ({ lib, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
name = "loki";
|
name = "loki";
|
||||||
@ -26,12 +26,14 @@ import ./make-test.nix ({ lib, pkgs, ... }:
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
$machine->start;
|
machine.start
|
||||||
$machine->waitForUnit("loki.service");
|
machine.wait_for_unit("loki.service")
|
||||||
$machine->waitForUnit("promtail.service");
|
machine.wait_for_unit("promtail.service")
|
||||||
$machine->waitForOpenPort(3100);
|
machine.wait_for_open_port(3100)
|
||||||
$machine->waitForOpenPort(9080);
|
machine.wait_for_open_port(9080)
|
||||||
$machine->succeed("echo 'Loki Ingestion Test' > /var/log/testlog");
|
machine.succeed("echo 'Loki Ingestion Test' > /var/log/testlog")
|
||||||
$machine->waitUntilSucceeds("${pkgs.grafana-loki}/bin/logcli --addr='http://localhost:3100' query --no-labels '{job=\"varlogs\",filename=\"/var/log/testlog\"}' | grep -q 'Loki Ingestion Test'");
|
machine.wait_until_succeeds(
|
||||||
|
"${pkgs.grafana-loki}/bin/logcli --addr='http://localhost:3100' query --no-labels '{job=\"varlogs\",filename=\"/var/log/testlog\"}' | grep -q 'Loki Ingestion Test'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
9
nixos/tests/make-test-python.nix
Normal file
9
nixos/tests/make-test-python.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
f: {
|
||||||
|
system ? builtins.currentSystem,
|
||||||
|
pkgs ? import ../.. { inherit system; config = {}; },
|
||||||
|
...
|
||||||
|
} @ args:
|
||||||
|
|
||||||
|
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||||
|
|
||||||
|
makeTest (if pkgs.lib.isFunction f then f (args // { inherit pkgs; inherit (pkgs) lib; }) else f)
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... } : let
|
import ./make-test-python.nix ({ pkgs, ... } : let
|
||||||
|
|
||||||
|
|
||||||
runWithOpenSSL = file: cmd: pkgs.runCommand file {
|
runWithOpenSSL = file: cmd: pkgs.runCommand file {
|
||||||
@ -55,13 +55,17 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
$serverpostgres->waitForUnit("matrix-synapse.service");
|
serverpostgres.wait_for_unit("matrix-synapse.service")
|
||||||
$serverpostgres->waitUntilSucceeds("curl -L --cacert ${ca_pem} https://localhost:8448/");
|
serverpostgres.wait_until_succeeds(
|
||||||
$serverpostgres->requireActiveUnit("postgresql.service");
|
"curl -L --cacert ${ca_pem} https://localhost:8448/"
|
||||||
$serversqlite->waitForUnit("matrix-synapse.service");
|
)
|
||||||
$serversqlite->waitUntilSucceeds("curl -L --cacert ${ca_pem} https://localhost:8448/");
|
serverpostgres.require_unit_state("postgresql.service")
|
||||||
$serversqlite->mustSucceed("[ -e /var/lib/matrix-synapse/homeserver.db ]");
|
serversqlite.wait_for_unit("matrix-synapse.service")
|
||||||
|
serversqlite.wait_until_succeeds(
|
||||||
|
"curl -L --cacert ${ca_pem} https://localhost:8448/"
|
||||||
|
)
|
||||||
|
serversqlite.succeed("[ -e /var/lib/matrix-synapse/homeserver.db ]")
|
||||||
'';
|
'';
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }: {
|
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||||
name = "metabase";
|
name = "metabase";
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
maintainers = [ mmahut ];
|
maintainers = [ mmahut ];
|
||||||
@ -12,9 +12,9 @@ import ./make-test.nix ({ pkgs, ... }: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
$machine->waitForUnit("metabase.service");
|
machine.wait_for_unit("metabase.service")
|
||||||
$machine->waitForOpenPort(3000);
|
machine.wait_for_open_port(3000)
|
||||||
$machine->waitUntilSucceeds("curl -L http://localhost:3000/setup | grep Metabase");
|
machine.wait_until_succeeds("curl -L http://localhost:3000/setup | grep Metabase")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
24
nixos/tests/moinmoin.nix
Normal file
24
nixos/tests/moinmoin.nix
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import ./make-test.nix ({ pkgs, lib, ... }: {
|
||||||
|
name = "moinmoin";
|
||||||
|
meta.maintainers = [ ]; # waiting for https://github.com/NixOS/nixpkgs/pull/65397
|
||||||
|
|
||||||
|
machine =
|
||||||
|
{ ... }:
|
||||||
|
{ services.moinmoin.enable = true;
|
||||||
|
services.moinmoin.wikis.ExampleWiki.superUsers = [ "admin" ];
|
||||||
|
services.moinmoin.wikis.ExampleWiki.webHost = "localhost";
|
||||||
|
|
||||||
|
services.nginx.virtualHosts.localhost.enableACME = false;
|
||||||
|
services.nginx.virtualHosts.localhost.forceSSL = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
startAll;
|
||||||
|
|
||||||
|
$machine->waitForUnit('moin-ExampleWiki.service');
|
||||||
|
$machine->waitForUnit('nginx.service');
|
||||||
|
$machine->waitForFile('/run/moin/ExampleWiki/gunicorn.sock');
|
||||||
|
$machine->succeed('curl -L http://localhost/') =~ /If you have just installed/ or die;
|
||||||
|
$machine->succeed('moin-ExampleWiki account create --name=admin --email=admin@example.com --password=foo 2>&1') =~ /status success/ or die;
|
||||||
|
'';
|
||||||
|
})
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, lib, ... }: {
|
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||||
name = "moodle";
|
name = "moodle";
|
||||||
meta.maintainers = [ lib.maintainers.aanderse ];
|
meta.maintainers = [ lib.maintainers.aanderse ];
|
||||||
|
|
||||||
@ -15,8 +15,8 @@ import ./make-test.nix ({ pkgs, lib, ... }: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
$machine->waitForUnit('phpfpm-moodle.service');
|
machine.wait_for_unit("phpfpm-moodle.service")
|
||||||
$machine->succeed('curl http://localhost/') =~ /You are not logged in/ or die;
|
machine.wait_until_succeeds("curl http://localhost/ | grep 'You are not logged in'")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }:
|
import ./make-test-python.nix ({ pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
name = "morty";
|
name = "morty";
|
||||||
@ -22,11 +22,9 @@ import ./make-test.nix ({ pkgs, ... }:
|
|||||||
testScript =
|
testScript =
|
||||||
{ ... }:
|
{ ... }:
|
||||||
''
|
''
|
||||||
$mortyProxyWithKey->waitForUnit("default.target");
|
mortyProxyWithKey.wait_for_unit("default.target")
|
||||||
|
mortyProxyWithKey.wait_for_open_port(3001)
|
||||||
$mortyProxyWithKey->waitForOpenPort(3001);
|
mortyProxyWithKey.succeed("curl -L 127.0.0.1:3001 | grep MortyProxy")
|
||||||
$mortyProxyWithKey->succeed("curl -L 127.0.0.1:3001 | grep MortyProxy");
|
|
||||||
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ lib, ... } : {
|
import ./make-test-python.nix ({ lib, ... } : {
|
||||||
name = "nixos-generate-config";
|
name = "nixos-generate-config";
|
||||||
meta.maintainers = with lib.maintainers; [ basvandijk ];
|
meta.maintainers = with lib.maintainers; [ basvandijk ];
|
||||||
machine = {
|
machine = {
|
||||||
@ -11,14 +11,16 @@ import ./make-test.nix ({ lib, ... } : {
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
$machine->waitForUnit("multi-user.target");
|
machine.wait_for_unit("multi-user.target")
|
||||||
$machine->succeed("nixos-generate-config");
|
machine.succeed("nixos-generate-config")
|
||||||
|
|
||||||
# Test if the configuration really is overridden
|
# Test if the configuration really is overridden
|
||||||
$machine->succeed("grep 'OVERRIDDEN' /etc/nixos/configuration.nix");
|
machine.succeed("grep 'OVERRIDDEN' /etc/nixos/configuration.nix")
|
||||||
|
|
||||||
# Test of if the Perl variable $bootLoaderConfig is spliced correctly:
|
# Test of if the Perl variable $bootLoaderConfig is spliced correctly:
|
||||||
$machine->succeed("grep 'boot\\.loader\\.grub\\.enable = true;' /etc/nixos/configuration.nix");
|
machine.succeed(
|
||||||
|
"grep 'boot\\.loader\\.grub\\.enable = true;' /etc/nixos/configuration.nix"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }:
|
import ./make-test-python.nix ({ pkgs, ... }:
|
||||||
|
|
||||||
let inherit (import ./ssh-keys.nix pkgs)
|
let inherit (import ./ssh-keys.nix pkgs)
|
||||||
snakeOilPrivateKey snakeOilPublicKey;
|
snakeOilPrivateKey snakeOilPublicKey;
|
||||||
@ -58,47 +58,55 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
my $key=`${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f key -N ""`;
|
server.wait_for_unit("sshd")
|
||||||
|
|
||||||
$server->waitForUnit("sshd");
|
with subtest("manual-authkey"):
|
||||||
|
client.succeed("mkdir -m 700 /root/.ssh")
|
||||||
|
client.succeed(
|
||||||
|
'${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""'
|
||||||
|
)
|
||||||
|
public_key = client.succeed(
|
||||||
|
"${pkgs.openssh}/bin/ssh-keygen -y -f /root/.ssh/id_ed25519"
|
||||||
|
)
|
||||||
|
public_key = public_key.strip()
|
||||||
|
client.succeed("chmod 600 /root/.ssh/id_ed25519")
|
||||||
|
|
||||||
subtest "manual-authkey", sub {
|
server.succeed("mkdir -m 700 /root/.ssh")
|
||||||
$server->succeed("mkdir -m 700 /root/.ssh");
|
server.succeed("echo '{}' > /root/.ssh/authorized_keys".format(public_key))
|
||||||
$server->copyFileFromHost("key.pub", "/root/.ssh/authorized_keys");
|
server_lazy.succeed("mkdir -m 700 /root/.ssh")
|
||||||
$server_lazy->succeed("mkdir -m 700 /root/.ssh");
|
server_lazy.succeed("echo '{}' > /root/.ssh/authorized_keys".format(public_key))
|
||||||
$server_lazy->copyFileFromHost("key.pub", "/root/.ssh/authorized_keys");
|
|
||||||
|
|
||||||
$client->succeed("mkdir -m 700 /root/.ssh");
|
client.wait_for_unit("network.target")
|
||||||
$client->copyFileFromHost("key", "/root/.ssh/id_ed25519");
|
client.succeed(
|
||||||
$client->succeed("chmod 600 /root/.ssh/id_ed25519");
|
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2"
|
||||||
|
)
|
||||||
|
client.succeed(
|
||||||
|
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024"
|
||||||
|
)
|
||||||
|
|
||||||
$client->waitForUnit("network.target");
|
client.succeed(
|
||||||
$client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2");
|
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'echo hello world' >&2"
|
||||||
$client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024");
|
)
|
||||||
|
client.succeed(
|
||||||
|
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'ulimit -l' | grep 1024"
|
||||||
|
)
|
||||||
|
|
||||||
$client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'echo hello world' >&2");
|
with subtest("configured-authkey"):
|
||||||
$client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'ulimit -l' | grep 1024");
|
client.succeed(
|
||||||
|
"cat ${snakeOilPrivateKey} > privkey.snakeoil"
|
||||||
|
)
|
||||||
|
client.succeed("chmod 600 privkey.snakeoil")
|
||||||
|
client.succeed(
|
||||||
|
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server true"
|
||||||
|
)
|
||||||
|
client.succeed(
|
||||||
|
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server_lazy true"
|
||||||
|
)
|
||||||
|
|
||||||
};
|
with subtest("localhost-only"):
|
||||||
|
server_localhost_only.succeed("ss -nlt | grep '127.0.0.1:22'")
|
||||||
subtest "configured-authkey", sub {
|
server_localhost_only_lazy.succeed("ss -nlt | grep '127.0.0.1:22'")
|
||||||
$client->succeed("cat ${snakeOilPrivateKey} > privkey.snakeoil");
|
|
||||||
$client->succeed("chmod 600 privkey.snakeoil");
|
|
||||||
$client->succeed("ssh -o UserKnownHostsFile=/dev/null" .
|
|
||||||
" -o StrictHostKeyChecking=no -i privkey.snakeoil" .
|
|
||||||
" server true");
|
|
||||||
|
|
||||||
$client->succeed("ssh -o UserKnownHostsFile=/dev/null" .
|
|
||||||
" -o StrictHostKeyChecking=no -i privkey.snakeoil" .
|
|
||||||
" server_lazy true");
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
subtest "localhost-only", sub {
|
|
||||||
$server_localhost_only->succeed("ss -nlt | grep '127.0.0.1:22'");
|
|
||||||
$server_localhost_only_lazy->succeed("ss -nlt | grep '127.0.0.1:22'");
|
|
||||||
}
|
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
# run installed tests
|
|
||||||
import ./make-test.nix ({ pkgs, lib, ... }: {
|
|
||||||
name = "ostree";
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
maintainers = pkgs.ostree.meta.maintainers;
|
|
||||||
};
|
|
||||||
|
|
||||||
# TODO: Wrap/patch the tests directly in the package
|
|
||||||
machine = { pkgs, ... }: {
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
gnome-desktop-testing ostree gnupg (python3.withPackages (p: with p; [ pyyaml ]))
|
|
||||||
];
|
|
||||||
|
|
||||||
environment.variables.GI_TYPELIB_PATH = lib.makeSearchPath "lib/girepository-1.0" (with pkgs; [ gtk3 pango.out ostree gdk-pixbuf atk ]); # for GJS tests
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
$machine->succeed("gnome-desktop-testing-runner -d ${pkgs.ostree.installedTests}/share");
|
|
||||||
'';
|
|
||||||
})
|
|
@ -1,4 +1,4 @@
|
|||||||
import ./make-test.nix ({ pkgs, ... }: {
|
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||||
name = "packagekit";
|
name = "packagekit";
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
maintainers = [ peterhoeg ];
|
maintainers = [ peterhoeg ];
|
||||||
@ -13,12 +13,14 @@ import ./make-test.nix ({ pkgs, ... }: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
startAll;
|
start_all()
|
||||||
|
|
||||||
# send a dbus message to activate the service
|
# send a dbus message to activate the service
|
||||||
$machine->succeed("dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.PackageKit /org/freedesktop/PackageKit org.freedesktop.DBus.Introspectable.Introspect");
|
machine.succeed(
|
||||||
|
"dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.PackageKit /org/freedesktop/PackageKit org.freedesktop.DBus.Introspectable.Introspect"
|
||||||
|
)
|
||||||
|
|
||||||
# so now it should be running
|
# so now it should be running
|
||||||
$machine->succeed("systemctl is-active packagekit.service");
|
machine.wait_for_unit("packagekit.service")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
import ./make-test.nix ({ pkgs, lib, ...}:
|
import ./make-test-python.nix ({ pkgs, lib, ...}:
|
||||||
let
|
|
||||||
test = with pkgs; runCommand "patch-test" {
|
|
||||||
nativeBuildInputs = [ pgjwt ];
|
|
||||||
}
|
|
||||||
''
|
|
||||||
sed -e '12 i CREATE EXTENSION pgcrypto;\nCREATE EXTENSION pgtap;\nSET search_path TO tap,public;' ${pgjwt.src}/test.sql > $out;
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
with pkgs; {
|
with pkgs; {
|
||||||
name = "pgjwt";
|
name = "pgjwt";
|
||||||
meta = with lib.maintainers; {
|
meta = with lib.maintainers; {
|
||||||
@ -29,9 +22,13 @@ with pkgs; {
|
|||||||
pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}";
|
pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}";
|
||||||
in
|
in
|
||||||
''
|
''
|
||||||
startAll;
|
start_all()
|
||||||
$master->waitForUnit("postgresql");
|
master.wait_for_unit("postgresql")
|
||||||
$master->copyFileFromHost("${test}","/tmp/test.sql");
|
master.succeed(
|
||||||
$master->succeed("${pkgs.sudo}/bin/sudo -u ${sqlSU} PGOPTIONS=--search_path=tap,public ${pgProve}/bin/pg_prove -d postgres -v -f /tmp/test.sql");
|
"${pkgs.gnused}/bin/sed -e '12 i CREATE EXTENSION pgcrypto;\\nCREATE EXTENSION pgtap;\\nSET search_path TO tap,public;' ${pgjwt.src}/test.sql > /tmp/test.sql"
|
||||||
|
)
|
||||||
|
master.succeed(
|
||||||
|
"${pkgs.sudo}/bin/sudo -u ${sqlSU} PGOPTIONS=--search_path=tap,public ${pgProve}/bin/pg_prove -d postgres -v -f /tmp/test.sql"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
pkgs ? import ../.. { inherit system config; }
|
pkgs ? import ../.. { inherit system config; }
|
||||||
}:
|
}:
|
||||||
|
|
||||||
with import ../lib/testing.nix { inherit system pkgs; };
|
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||||
with pkgs.lib;
|
with pkgs.lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
@ -40,29 +40,33 @@ let
|
|||||||
backupName = if backup-all then "all" else "postgres";
|
backupName = if backup-all then "all" else "postgres";
|
||||||
backupService = if backup-all then "postgresqlBackup" else "postgresqlBackup-postgres";
|
backupService = if backup-all then "postgresqlBackup" else "postgresqlBackup-postgres";
|
||||||
in ''
|
in ''
|
||||||
sub check_count {
|
def check_count(statement, lines):
|
||||||
my ($select, $nlines) = @_;
|
return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format(
|
||||||
return 'test $(sudo -u postgres psql postgres -tAc "' . $select . '"|wc -l) -eq ' . $nlines;
|
statement, lines
|
||||||
}
|
)
|
||||||
|
|
||||||
|
|
||||||
|
machine.start()
|
||||||
|
machine.wait_for_unit("postgresql")
|
||||||
|
|
||||||
$machine->start;
|
|
||||||
$machine->waitForUnit("postgresql");
|
|
||||||
# postgresql should be available just after unit start
|
# postgresql should be available just after unit start
|
||||||
$machine->succeed("cat ${test-sql} | sudo -u postgres psql");
|
machine.succeed(
|
||||||
$machine->shutdown; # make sure that postgresql survive restart (bug #1735)
|
"cat ${test-sql} | sudo -u postgres psql"
|
||||||
sleep(2);
|
)
|
||||||
$machine->start;
|
machine.shutdown() # make sure that postgresql survive restart (bug #1735)
|
||||||
$machine->waitForUnit("postgresql");
|
time.sleep(2)
|
||||||
$machine->fail(check_count("SELECT * FROM sth;", 3));
|
machine.start()
|
||||||
$machine->succeed(check_count("SELECT * FROM sth;", 5));
|
machine.wait_for_unit("postgresql")
|
||||||
$machine->fail(check_count("SELECT * FROM sth;", 4));
|
machine.fail(check_count("SELECT * FROM sth;", 3))
|
||||||
$machine->succeed(check_count("SELECT xpath(\'/test/text()\', doc) FROM xmltest;", 1));
|
machine.succeed(check_count("SELECT * FROM sth;", 5))
|
||||||
|
machine.fail(check_count("SELECT * FROM sth;", 4))
|
||||||
|
machine.succeed(check_count("SELECT xpath('/test/text()', doc) FROM xmltest;", 1))
|
||||||
|
|
||||||
# Check backup service
|
# Check backup service
|
||||||
$machine->succeed("systemctl start ${backupService}.service");
|
machine.succeed("systemctl start ${backupService}.service")
|
||||||
$machine->succeed("zcat /var/backup/postgresql/${backupName}.sql.gz | grep '<test>ok</test>'");
|
machine.succeed("zcat /var/backup/postgresql/${backupName}.sql.gz | grep '<test>ok</test>'")
|
||||||
$machine->succeed("stat -c '%a' /var/backup/postgresql/${backupName}.sql.gz | grep 600");
|
machine.succeed("stat -c '%a' /var/backup/postgresql/${backupName}.sql.gz | grep 600")
|
||||||
$machine->shutdown;
|
machine.shutdown()
|
||||||
'';
|
'';
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -4,12 +4,10 @@
|
|||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (import ../lib/testing.nix { inherit system pkgs; }) makeTest;
|
inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
|
||||||
inherit (pkgs.lib) concatStringsSep maintainers mapAttrs mkMerge
|
inherit (pkgs.lib) concatStringsSep maintainers mapAttrs mkMerge
|
||||||
removeSuffix replaceChars singleton splitString;
|
removeSuffix replaceChars singleton splitString;
|
||||||
|
|
||||||
escape' = str: replaceChars [''"'' "$" "\n"] [''\\\"'' "\\$" ""] str;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The attrset `exporterTests` contains one attribute
|
* The attrset `exporterTests` contains one attribute
|
||||||
* for each exporter test. Each of these attributes
|
* for each exporter test. Each of these attributes
|
||||||
@ -33,9 +31,9 @@ let
|
|||||||
* services.<metricProvider>.enable = true;
|
* services.<metricProvider>.enable = true;
|
||||||
* };
|
* };
|
||||||
* exporterTest = ''
|
* exporterTest = ''
|
||||||
* waitForUnit("prometheus-<exporterName>-exporter.service");
|
* wait_for_unit("prometheus-<exporterName>-exporter.service")
|
||||||
* waitForOpenPort("1234");
|
* wait_for_open_port("1234")
|
||||||
* succeed("curl -sSf 'localhost:1234/metrics'");
|
* succeed("curl -sSf 'localhost:1234/metrics'")
|
||||||
* '';
|
* '';
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
@ -49,11 +47,11 @@ let
|
|||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* testScript = ''
|
* testScript = ''
|
||||||
* $<exporterName>->start();
|
* <exporterName>.start()
|
||||||
* $<exporterName>->waitForUnit("prometheus-<exporterName>-exporter.service");
|
* <exporterName>.wait_for_unit("prometheus-<exporterName>-exporter.service")
|
||||||
* $<exporterName>->waitForOpenPort("1234");
|
* <exporterName>.wait_for_open_port("1234")
|
||||||
* $<exporterName>->succeed("curl -sSf 'localhost:1234/metrics'");
|
* <exporterName>.succeed("curl -sSf 'localhost:1234/metrics'")
|
||||||
* $<exporterName>->shutdown();
|
* <exporterName>.shutdown()
|
||||||
* '';
|
* '';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -72,9 +70,11 @@ let
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-bind-exporter.service");
|
wait_for_unit("prometheus-bind-exporter.service")
|
||||||
waitForOpenPort(9119);
|
wait_for_open_port(9119)
|
||||||
succeed("curl -sSf http://localhost:9119/metrics | grep -q 'bind_query_recursions_total 0'");
|
succeed(
|
||||||
|
"curl -sSf http://localhost:9119/metrics | grep -q 'bind_query_recursions_total 0'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,9 +89,11 @@ let
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-blackbox-exporter.service");
|
wait_for_unit("prometheus-blackbox-exporter.service")
|
||||||
waitForOpenPort(9115);
|
wait_for_open_port(9115)
|
||||||
succeed("curl -sSf 'http://localhost:9115/probe?target=localhost&module=icmp_v6' | grep -q 'probe_success 1'");
|
succeed(
|
||||||
|
"curl -sSf 'http://localhost:9115/probe?target=localhost&module=icmp_v6' | grep -q 'probe_success 1'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,7 +102,7 @@ let
|
|||||||
enable = true;
|
enable = true;
|
||||||
extraFlags = [ "--web.collectd-push-path /collectd" ];
|
extraFlags = [ "--web.collectd-push-path /collectd" ];
|
||||||
};
|
};
|
||||||
exporterTest =let postData = escape' ''
|
exporterTest = let postData = replaceChars [ "\n" ] [ "" ] ''
|
||||||
[{
|
[{
|
||||||
"values":[23],
|
"values":[23],
|
||||||
"dstypes":["gauge"],
|
"dstypes":["gauge"],
|
||||||
@ -108,13 +110,21 @@ let
|
|||||||
"interval":1000,
|
"interval":1000,
|
||||||
"host":"testhost",
|
"host":"testhost",
|
||||||
"plugin":"testplugin",
|
"plugin":"testplugin",
|
||||||
"time":$(date +%s)
|
"time":DATE
|
||||||
}]
|
}]
|
||||||
''; in ''
|
''; in ''
|
||||||
waitForUnit("prometheus-collectd-exporter.service");
|
wait_for_unit("prometheus-collectd-exporter.service")
|
||||||
waitForOpenPort(9103);
|
wait_for_open_port(9103)
|
||||||
succeed("curl -sSfH 'Content-Type: application/json' -X POST --data \"${postData}\" localhost:9103/collectd");
|
succeed(
|
||||||
succeed("curl -sSf localhost:9103/metrics | grep -q 'collectd_testplugin_gauge{instance=\"testhost\"} 23'");
|
'echo \'${postData}\'> /tmp/data.json'
|
||||||
|
)
|
||||||
|
succeed('sed -ie "s DATE $(date +%s) " /tmp/data.json')
|
||||||
|
succeed(
|
||||||
|
"curl -sSfH 'Content-Type: application/json' -X POST --data @/tmp/data.json localhost:9103/collectd"
|
||||||
|
)
|
||||||
|
succeed(
|
||||||
|
"curl -sSf localhost:9103/metrics | grep -q 'collectd_testplugin_gauge{instance=\"testhost\"} 23'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -127,9 +137,9 @@ let
|
|||||||
services.dnsmasq.enable = true;
|
services.dnsmasq.enable = true;
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-dnsmasq-exporter.service");
|
wait_for_unit("prometheus-dnsmasq-exporter.service")
|
||||||
waitForOpenPort(9153);
|
wait_for_open_port(9153)
|
||||||
succeed("curl -sSf http://localhost:9153/metrics | grep -q 'dnsmasq_leases 0'");
|
succeed("curl -sSf http://localhost:9153/metrics | grep -q 'dnsmasq_leases 0'")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -144,9 +154,11 @@ let
|
|||||||
services.dovecot2.enable = true;
|
services.dovecot2.enable = true;
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-dovecot-exporter.service");
|
wait_for_unit("prometheus-dovecot-exporter.service")
|
||||||
waitForOpenPort(9166);
|
wait_for_open_port(9166)
|
||||||
succeed("curl -sSf http://localhost:9166/metrics | grep -q 'dovecot_up{scope=\"global\"} 1'");
|
succeed(
|
||||||
|
"curl -sSf http://localhost:9166/metrics | grep -q 'dovecot_up{scope=\"global\"} 1'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -155,9 +167,11 @@ let
|
|||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-fritzbox-exporter.service");
|
wait_for_unit("prometheus-fritzbox-exporter.service")
|
||||||
waitForOpenPort(9133);
|
wait_for_open_port(9133)
|
||||||
succeed("curl -sSf http://localhost:9133/metrics | grep -q 'fritzbox_exporter_collect_errors 0'");
|
succeed(
|
||||||
|
"curl -sSf http://localhost:9133/metrics | grep -q 'fritzbox_exporter_collect_errors 0'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -180,11 +194,11 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("nginx.service");
|
wait_for_unit("nginx.service")
|
||||||
waitForOpenPort(80);
|
wait_for_open_port(80)
|
||||||
waitForUnit("prometheus-json-exporter.service");
|
wait_for_unit("prometheus-json-exporter.service")
|
||||||
waitForOpenPort(7979);
|
wait_for_open_port(7979)
|
||||||
succeed("curl -sSf localhost:7979/metrics | grep -q 'json_test_metric 1'");
|
succeed("curl -sSf localhost:7979/metrics | grep -q 'json_test_metric 1'")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -222,10 +236,12 @@ let
|
|||||||
users.users.mailexporter.isSystemUser = true;
|
users.users.mailexporter.isSystemUser = true;
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("postfix.service")
|
wait_for_unit("postfix.service")
|
||||||
waitForUnit("prometheus-mail-exporter.service")
|
wait_for_unit("prometheus-mail-exporter.service")
|
||||||
waitForOpenPort(9225)
|
wait_for_open_port(9225)
|
||||||
waitUntilSucceeds("curl -sSf http://localhost:9225/metrics | grep -q 'mail_deliver_success{configname=\"testserver\"} 1'")
|
wait_until_succeeds(
|
||||||
|
"curl -sSf http://localhost:9225/metrics | grep -q 'mail_deliver_success{configname=\"testserver\"} 1'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -256,9 +272,9 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("nginx.service")
|
wait_for_unit("nginx.service")
|
||||||
waitForUnit("prometheus-nextcloud-exporter.service")
|
wait_for_unit("prometheus-nextcloud-exporter.service")
|
||||||
waitForOpenPort(9205)
|
wait_for_open_port(9205)
|
||||||
succeed("curl -sSf http://localhost:9205/metrics | grep -q 'nextcloud_up 1'")
|
succeed("curl -sSf http://localhost:9205/metrics | grep -q 'nextcloud_up 1'")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
@ -275,9 +291,9 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("nginx.service")
|
wait_for_unit("nginx.service")
|
||||||
waitForUnit("prometheus-nginx-exporter.service")
|
wait_for_unit("prometheus-nginx-exporter.service")
|
||||||
waitForOpenPort(9113)
|
wait_for_open_port(9113)
|
||||||
succeed("curl -sSf http://localhost:9113/metrics | grep -q 'nginx_up 1'")
|
succeed("curl -sSf http://localhost:9113/metrics | grep -q 'nginx_up 1'")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
@ -287,9 +303,11 @@ let
|
|||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-node-exporter.service");
|
wait_for_unit("prometheus-node-exporter.service")
|
||||||
waitForOpenPort(9100);
|
wait_for_open_port(9100)
|
||||||
succeed("curl -sSf http://localhost:9100/metrics | grep -q 'node_exporter_build_info{.\\+} 1'");
|
succeed(
|
||||||
|
"curl -sSf http://localhost:9100/metrics | grep -q 'node_exporter_build_info{.\\+} 1'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -301,9 +319,11 @@ let
|
|||||||
services.postfix.enable = true;
|
services.postfix.enable = true;
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-postfix-exporter.service");
|
wait_for_unit("prometheus-postfix-exporter.service")
|
||||||
waitForOpenPort(9154);
|
wait_for_open_port(9154)
|
||||||
succeed("curl -sSf http://localhost:9154/metrics | grep -q 'postfix_smtpd_connects_total 0'");
|
succeed(
|
||||||
|
"curl -sSf http://localhost:9154/metrics | grep -q 'postfix_smtpd_connects_total 0'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -316,18 +336,24 @@ let
|
|||||||
services.postgresql.enable = true;
|
services.postgresql.enable = true;
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-postgres-exporter.service");
|
wait_for_unit("prometheus-postgres-exporter.service")
|
||||||
waitForOpenPort(9187);
|
wait_for_open_port(9187)
|
||||||
waitForUnit("postgresql.service");
|
wait_for_unit("postgresql.service")
|
||||||
succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_exporter_last_scrape_error 0'");
|
succeed(
|
||||||
succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_up 1'");
|
"curl -sSf http://localhost:9187/metrics | grep -q 'pg_exporter_last_scrape_error 0'"
|
||||||
systemctl("stop postgresql.service");
|
)
|
||||||
succeed("curl -sSf http://localhost:9187/metrics | grep -qv 'pg_exporter_last_scrape_error 0'");
|
succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_up 1'")
|
||||||
succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_up 0'");
|
systemctl("stop postgresql.service")
|
||||||
systemctl("start postgresql.service");
|
succeed(
|
||||||
waitForUnit("postgresql.service");
|
"curl -sSf http://localhost:9187/metrics | grep -qv 'pg_exporter_last_scrape_error 0'"
|
||||||
succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_exporter_last_scrape_error 0'");
|
)
|
||||||
succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_up 1'");
|
succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_up 0'")
|
||||||
|
systemctl("start postgresql.service")
|
||||||
|
wait_for_unit("postgresql.service")
|
||||||
|
succeed(
|
||||||
|
"curl -sSf http://localhost:9187/metrics | grep -q 'pg_exporter_last_scrape_error 0'"
|
||||||
|
)
|
||||||
|
succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_up 1'")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -339,11 +365,13 @@ let
|
|||||||
services.rspamd.enable = true;
|
services.rspamd.enable = true;
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("rspamd.service");
|
wait_for_unit("rspamd.service")
|
||||||
waitForUnit("prometheus-rspamd-exporter.service");
|
wait_for_unit("prometheus-rspamd-exporter.service")
|
||||||
waitForOpenPort(11334);
|
wait_for_open_port(11334)
|
||||||
waitForOpenPort(7980);
|
wait_for_open_port(7980)
|
||||||
waitUntilSucceeds("curl -sSf localhost:7980/metrics | grep -q 'rspamd_scanned{host=\"rspamd\"} 0'");
|
wait_until_succeeds(
|
||||||
|
"curl -sSf localhost:7980/metrics | grep -q 'rspamd_scanned{host=\"rspamd\"} 0'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -356,9 +384,9 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-snmp-exporter.service");
|
wait_for_unit("prometheus-snmp-exporter.service")
|
||||||
waitForOpenPort(9116);
|
wait_for_open_port(9116)
|
||||||
succeed("curl -sSf localhost:9116/metrics | grep -q 'snmp_request_errors_total 0'");
|
succeed("curl -sSf localhost:9116/metrics | grep -q 'snmp_request_errors_total 0'")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -377,11 +405,11 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("nginx.service");
|
wait_for_unit("nginx.service")
|
||||||
waitForOpenPort(80);
|
wait_for_open_port(80)
|
||||||
waitForUnit("prometheus-surfboard-exporter.service");
|
wait_for_unit("prometheus-surfboard-exporter.service")
|
||||||
waitForOpenPort(9239);
|
wait_for_open_port(9239)
|
||||||
succeed("curl -sSf localhost:9239/metrics | grep -q 'surfboard_up 1'");
|
succeed("curl -sSf localhost:9239/metrics | grep -q 'surfboard_up 1'")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -396,11 +424,11 @@ let
|
|||||||
services.tor.controlPort = 9051;
|
services.tor.controlPort = 9051;
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("tor.service");
|
wait_for_unit("tor.service")
|
||||||
waitForOpenPort(9051);
|
wait_for_open_port(9051)
|
||||||
waitForUnit("prometheus-tor-exporter.service");
|
wait_for_unit("prometheus-tor-exporter.service")
|
||||||
waitForOpenPort(9130);
|
wait_for_open_port(9130)
|
||||||
succeed("curl -sSf localhost:9130/metrics | grep -q 'tor_version{.\\+} 1'");
|
succeed("curl -sSf localhost:9130/metrics | grep -q 'tor_version{.\\+} 1'")
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -426,10 +454,12 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-varnish-exporter.service");
|
wait_for_unit("prometheus-varnish-exporter.service")
|
||||||
waitForOpenPort(6081);
|
wait_for_open_port(6081)
|
||||||
waitForOpenPort(9131);
|
wait_for_open_port(9131)
|
||||||
succeed("curl -sSf http://localhost:9131/metrics | grep -q 'varnish_up 1'");
|
succeed(
|
||||||
|
"curl -sSf http://localhost:9131/metrics | grep -q 'varnish_up 1'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -451,9 +481,11 @@ let
|
|||||||
systemd.services.prometheus-wireguard-exporter.after = [ "wireguard-wg0.service" ];
|
systemd.services.prometheus-wireguard-exporter.after = [ "wireguard-wg0.service" ];
|
||||||
};
|
};
|
||||||
exporterTest = ''
|
exporterTest = ''
|
||||||
waitForUnit("prometheus-wireguard-exporter.service");
|
wait_for_unit("prometheus-wireguard-exporter.service")
|
||||||
waitForOpenPort(9586);
|
wait_for_open_port(9586)
|
||||||
waitUntilSucceeds("curl -sSf http://localhost:9586/metrics | grep '${snakeoil.peer1.publicKey}'");
|
wait_until_succeeds(
|
||||||
|
"curl -sSf http://localhost:9586/metrics | grep '${snakeoil.peer1.publicKey}'"
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -466,11 +498,13 @@ mapAttrs (exporter: testConfig: (makeTest {
|
|||||||
} testConfig.metricProvider or {}];
|
} testConfig.metricProvider or {}];
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
${"$"+exporter}->start();
|
${exporter}.start()
|
||||||
${concatStringsSep " " (map (line: ''
|
${concatStringsSep "\n" (map (line:
|
||||||
${"$"+exporter}->${line};
|
if (builtins.substring 0 1 line == " " || builtins.substring 0 1 line == ")")
|
||||||
'') (splitString "\n" (removeSuffix "\n" testConfig.exporterTest)))}
|
then line
|
||||||
${"$"+exporter}->shutdown();
|
else "${exporter}.${line}"
|
||||||
|
) (splitString "\n" (removeSuffix "\n" testConfig.exporterTest)))}
|
||||||
|
${exporter}.shutdown()
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = with maintainers; {
|
meta = with maintainers; {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user