Merge remote-tracking branch 'origin/master' into haskell-updates
This commit is contained in:
commit
24cd70f14a
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
@ -195,10 +195,11 @@
|
||||
/pkgs/top-level/php-packages.nix @NixOS/php
|
||||
|
||||
# Podman, CRI-O modules and related
|
||||
/nixos/modules/virtualisation/containers.nix @NixOS/podman
|
||||
/nixos/modules/virtualisation/cri-o.nix @NixOS/podman
|
||||
/nixos/modules/virtualisation/podman.nix @NixOS/podman
|
||||
/nixos/tests/podman.nix @NixOS/podman
|
||||
/nixos/modules/virtualisation/containers.nix @NixOS/podman @zowoq
|
||||
/nixos/modules/virtualisation/cri-o.nix @NixOS/podman @zowoq
|
||||
/nixos/modules/virtualisation/podman.nix @NixOS/podman @zowoq
|
||||
/nixos/tests/cri-o.nix @NixOS/podman @zowoq
|
||||
/nixos/tests/podman.nix @NixOS/podman @zowoq
|
||||
|
||||
# Blockchains
|
||||
/pkgs/applications/blockchains @mmahut
|
||||
|
21
.github/workflows/pending-clear.yml
vendored
Normal file
21
.github/workflows/pending-clear.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: "clear pending status"
|
||||
|
||||
on:
|
||||
check_suite:
|
||||
types: [ completed ]
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: clear pending status
|
||||
if: github.repository_owner == 'NixOS' && github.event.check_suite.app.name == 'OfBorg'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-d '{"state": "success", "target_url": " ", "description": " ", "context": "Wait for ofborg"}' \
|
||||
"https://api.github.com/repos/NixOS/nixpkgs/statuses/${{ github.event.check_suite.head_sha }}"
|
20
.github/workflows/pending-set.yml
vendored
Normal file
20
.github/workflows/pending-set.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: "set pending status"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: set pending status
|
||||
if: github.repository_owner == 'NixOS'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-d '{"state": "failure", "target_url": " ", "description": "This failed status will be cleared when ofborg finishes eval.", "context": "Wait for ofborg"}' \
|
||||
"https://api.github.com/repos/NixOS/nixpkgs/statuses/${{ github.event.pull_request.head.sha }}"
|
@ -17,7 +17,6 @@ pkgs.runCommandNoCC "nixpkgs-lib-tests" {
|
||||
export TEST_ROOT=$(pwd)/test-tmp
|
||||
export NIX_BUILD_HOOK=
|
||||
export NIX_CONF_DIR=$TEST_ROOT/etc
|
||||
export NIX_DB_DIR=$TEST_ROOT/db
|
||||
export NIX_LOCALSTATE_DIR=$TEST_ROOT/var
|
||||
export NIX_LOG_DIR=$TEST_ROOT/var/log/nix
|
||||
export NIX_STATE_DIR=$TEST_ROOT/var/nix
|
||||
|
@ -254,6 +254,12 @@
|
||||
githubId = 732652;
|
||||
name = "Andreas Herrmann";
|
||||
};
|
||||
ahrzb = {
|
||||
email = "ahrzb5@gmail.com";
|
||||
github = "ahrzb";
|
||||
githubId = 5220438;
|
||||
name = "AmirHossein Roozbahani";
|
||||
};
|
||||
ahuzik = {
|
||||
email = "ales.guzik@gmail.com";
|
||||
github = "alesguzik";
|
||||
@ -466,6 +472,12 @@
|
||||
githubId = 858965;
|
||||
name = "Andrew Morsillo";
|
||||
};
|
||||
andehen = {
|
||||
email = "git@andehen.net";
|
||||
github = "andehen";
|
||||
githubId = 754494;
|
||||
name = "Anders Asheim Hennum";
|
||||
};
|
||||
andersk = {
|
||||
email = "andersk@mit.edu";
|
||||
github = "andersk";
|
||||
@ -2705,6 +2717,12 @@
|
||||
githubId = 857308;
|
||||
name = "Joe Hermaszewski";
|
||||
};
|
||||
extends = {
|
||||
email = "sharosari@gmail.com";
|
||||
github = "ImExtends";
|
||||
githubId = 55919390;
|
||||
name = "Vincent VILLIAUMEY";
|
||||
};
|
||||
eyjhb = {
|
||||
email = "eyjhbb@gmail.com";
|
||||
github = "eyJhb";
|
||||
@ -3343,6 +3361,12 @@
|
||||
githubId = 131599;
|
||||
name = "Martin Weinelt";
|
||||
};
|
||||
hh = {
|
||||
email = "hh@m-labs.hk";
|
||||
github = "HarryMakes";
|
||||
githubId = 66358631;
|
||||
name = "Harry Ho";
|
||||
};
|
||||
hhm = {
|
||||
email = "heehooman+nixpkgs@gmail.com";
|
||||
github = "hhm0";
|
||||
@ -3715,6 +3739,12 @@
|
||||
}];
|
||||
name = "Jiri Daněk";
|
||||
};
|
||||
jdbaldry = {
|
||||
email = "jack.baldry@grafana.com";
|
||||
github = "jdbaldry";
|
||||
githubId = 4599384;
|
||||
name = "Jack Baldry";
|
||||
};
|
||||
jdehaas = {
|
||||
email = "qqlq@nullptr.club";
|
||||
github = "jeroendehaas";
|
||||
@ -3835,6 +3865,12 @@
|
||||
githubId = 51518420;
|
||||
name = "jitwit";
|
||||
};
|
||||
jjjollyjim = {
|
||||
email = "jamie@kwiius.com";
|
||||
github = "JJJollyjim";
|
||||
githubId = 691552;
|
||||
name = "Jamie McClymont";
|
||||
};
|
||||
jk = {
|
||||
email = "hello+nixpkgs@j-k.io";
|
||||
github = "06kellyjac";
|
||||
@ -6719,6 +6755,12 @@
|
||||
githubId = 37715;
|
||||
name = "Brian McKenna";
|
||||
};
|
||||
purcell = {
|
||||
email = "steve@sanityinc.com";
|
||||
github = "purcell";
|
||||
githubId = 5636;
|
||||
name = "Steve Purcell";
|
||||
};
|
||||
puzzlewolf = {
|
||||
email = "nixos@nora.pink";
|
||||
github = "puzzlewolf";
|
||||
@ -6755,6 +6797,12 @@
|
||||
githubId = 115877;
|
||||
name = "Kenny Shen";
|
||||
};
|
||||
quentini = {
|
||||
email = "quentini@airmail.cc";
|
||||
github = "QuentinI";
|
||||
githubId = 18196237;
|
||||
name = "Quentin Inkling";
|
||||
};
|
||||
qyliss = {
|
||||
email = "hi@alyssa.is";
|
||||
github = "alyssais";
|
||||
@ -8133,6 +8181,12 @@
|
||||
githubId = 863327;
|
||||
name = "Tyler Benster";
|
||||
};
|
||||
tcbravo = {
|
||||
email = "tomas.bravo@protonmail.ch";
|
||||
github = "tcbravo";
|
||||
githubId = 66133083;
|
||||
name = "Tomas Bravo";
|
||||
};
|
||||
tckmn = {
|
||||
email = "andy@tck.mn";
|
||||
github = "tckmn";
|
||||
@ -8523,6 +8577,12 @@
|
||||
githubId = 699403;
|
||||
name = "Tomas Vestelind";
|
||||
};
|
||||
tviti = {
|
||||
email = "tviti@hawaii.edu";
|
||||
github = "tviti";
|
||||
githubId = 2251912;
|
||||
name = "Taylor Viti";
|
||||
};
|
||||
tvorog = {
|
||||
email = "marszaripov@gmail.com";
|
||||
github = "tvorog";
|
||||
@ -9132,6 +9192,16 @@
|
||||
fingerprint = "85F8 E850 F8F2 F823 F934 535B EC50 6589 9AEA AF4C";
|
||||
}];
|
||||
};
|
||||
yusdacra = {
|
||||
email = "y.bera003.06@protonmail.com";
|
||||
github = "yusdacra";
|
||||
githubId = 19897088;
|
||||
name = "Yusuf Bera Ertan";
|
||||
keys = [{
|
||||
longkeyid = "rsa2048/0x61807181F60EFCB2";
|
||||
fingerprint = "9270 66BD 8125 A45B 4AC4 0326 6180 7181 F60E FCB2";
|
||||
}];
|
||||
};
|
||||
yvesf = {
|
||||
email = "yvesf+nix@xapek.org";
|
||||
github = "yvesf";
|
||||
@ -9404,4 +9474,20 @@
|
||||
github = "fzakaria";
|
||||
githubId = 605070;
|
||||
};
|
||||
yevhenshymotiuk = {
|
||||
name = "Yevhen Shymotiuk";
|
||||
email = "yevhenshymotiuk@gmail.com";
|
||||
github = "yevhenshymotiuk";
|
||||
githubId = 44244245;
|
||||
};
|
||||
hmenke = {
|
||||
name = "Henri Menke";
|
||||
email = "henri@henrimenke.de";
|
||||
github = "hmenke";
|
||||
githubId = 1903556;
|
||||
keys = [{
|
||||
longkeyid = "rsa4096/0xD65C9AFB4C224DA3";
|
||||
fingerprint = "F1C5 760E 45B9 9A44 72E9 6BFB D65C 9AFB 4C22 4DA3";
|
||||
}];
|
||||
};
|
||||
}
|
||||
|
@ -70,35 +70,12 @@ Platform Vendor Advanced Micro Devices, Inc.</screen>
|
||||
Core Next</link> (GCN) GPUs are supported through the
|
||||
<package>rocm-opencl-icd</package> package. Adding this package to
|
||||
<xref linkend="opt-hardware.opengl.extraPackages"/> enables OpenCL
|
||||
support. However, OpenCL Image support is provided through the
|
||||
non-free <package>rocm-runtime-ext</package> package. This package can
|
||||
be added to the same configuration option, but requires that
|
||||
<varname>allowUnfree</varname> option is is enabled for nixpkgs. Full
|
||||
OpenCL support on supported AMD GPUs is thus enabled as follows:
|
||||
support:
|
||||
|
||||
<programlisting><xref linkend="opt-hardware.opengl.extraPackages"/> = [
|
||||
rocm-opencl-icd
|
||||
rocm-runtime-ext
|
||||
];</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
It is also possible to use the OpenCL Image extension without a
|
||||
system-wide installation of the <package>rocm-runtime-ext</package>
|
||||
package by setting the <varname>ROCR_EXT_DIR</varname> environment
|
||||
variable to the directory that contains the extension:
|
||||
|
||||
<screen><prompt>$</prompt> export \
|
||||
ROCR_EXT_DIR=`nix-build '<nixpkgs>' --no-out-link -A rocm-runtime-ext`/lib/rocm-runtime-ext</screen>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
With either approach, you can verify that OpenCL Image support
|
||||
is indeed working with the <command>clinfo</command> command:
|
||||
|
||||
<screen><prompt>$</prompt> clinfo | grep Image
|
||||
Image support Yes</screen>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section xml:id="sec-gpu-accel-opencl-intel">
|
||||
|
@ -136,7 +136,7 @@
|
||||
<filename>/mnt</filename>:
|
||||
</para>
|
||||
<screen>
|
||||
# nixos-enter /mnt
|
||||
# nixos-enter --root /mnt
|
||||
</screen>
|
||||
<para>
|
||||
Run a shell command:
|
||||
|
@ -128,7 +128,7 @@ GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'localhost' WITH GRANT OPTION;
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Two new option <link linkend="opt-documentation.man.generateCaches">documentation.man.generateCaches</link>
|
||||
The new option <link linkend="opt-documentation.man.generateCaches">documentation.man.generateCaches</link>
|
||||
has been added to automatically generate the <literal>man-db</literal> caches, which are needed by utilities
|
||||
like <command>whatis</command> and <command>apropos</command>. The caches are generated during the build of
|
||||
the NixOS configuration: since this can be expensive when a large number of packages are installed, the
|
||||
@ -137,7 +137,7 @@ GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'localhost' WITH GRANT OPTION;
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>services.postfix.sslCACert</varname> was replaced by <varname>services.postfix.tlsTrustedAuthorities</varname> which now defaults to system certifcate authorities.
|
||||
<varname>services.postfix.sslCACert</varname> was replaced by <varname>services.postfix.tlsTrustedAuthorities</varname> which now defaults to system certificate authorities.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
@ -156,6 +156,54 @@ GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'localhost' WITH GRANT OPTION;
|
||||
Support for built-in LCDs in various pieces of Logitech hardware (keyboards and USB speakers). <varname>hardware.logitech.lcd.enable</varname> enables support for all hardware supported by the g15daemon project.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Zabbix now defaults to 5.0, updated from 4.4. Please carefully read through
|
||||
<link xlink:href="https://www.zabbix.com/documentation/current/manual/installation/upgrade/sources">the upgrade guide</link>
|
||||
and apply any changes required. Be sure to take special note of the section on
|
||||
<link xlink:href="https://www.zabbix.com/documentation/current/manual/installation/upgrade_notes_500#enabling_extended_range_of_numeric_float_values">enabling extended range of numeric (float) values</link>
|
||||
as you will need to apply this database migration manually.
|
||||
</para>
|
||||
<para>
|
||||
If you are using Zabbix Server with a MySQL or MariaDB database you should note that using a character set of <literal>utf8</literal> and a collate of <literal>utf8_bin</literal> has become mandatory with
|
||||
this release. See the upstream <link xlink:href="https://support.zabbix.com/browse/ZBX-17357">issue</link> for further discussion. Before upgrading you should check the character set and collation used by
|
||||
your database and ensure they are correct:
|
||||
<programlisting>
|
||||
SELECT
|
||||
default_character_set_name,
|
||||
default_collation_name
|
||||
FROM
|
||||
information_schema.schemata
|
||||
WHERE
|
||||
schema_name = 'zabbix';
|
||||
</programlisting>
|
||||
If these values are not correct you should take a backup of your database and convert the character set and collation as required. Here is an
|
||||
<link xlink:href="https://www.zabbix.com/forum/zabbix-help/396573-reinstall-after-upgrade?p=396891#post396891">example</link> of how to do so, taken from
|
||||
the Zabbix forums:
|
||||
<programlisting>
|
||||
ALTER DATABASE `zabbix` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
|
||||
|
||||
-- the following will produce a list of SQL commands you should subsequently execute
|
||||
SELECT CONCAT("ALTER TABLE ", TABLE_NAME," CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;") AS ExecuteTheString
|
||||
FROM information_schema.`COLUMNS`
|
||||
WHERE table_schema = "zabbix" AND COLLATION_NAME = "utf8_general_ci";
|
||||
</programlisting>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The NixOS module system now supports freeform modules as a mix between <literal>types.attrsOf</literal> and <literal>types.submodule</literal>. These allow you to explicitly declare a subset of options while still permitting definitions without an associated option. See <xref linkend='sec-freeform-modules'/> for how to use them.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The GRUB module gained support for basic password protection, which
|
||||
allows to restrict non-default entries in the boot menu to one or more
|
||||
users. The users and passwords are defined via the option
|
||||
<option>boot.loader.grub.users</option>.
|
||||
Note: Password support is only avaiable in GRUB version 2.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
@ -199,7 +247,7 @@ GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'localhost' WITH GRANT OPTION;
|
||||
in the source tree for downloaded modules instead of using go's <link
|
||||
xlink:href="https://golang.org/cmd/go/#hdr-Module_proxy_protocol">module
|
||||
proxy protocol</link>. This storage format is simpler and therefore less
|
||||
likekly to break with future versions of go. As a result
|
||||
likely to break with future versions of go. As a result
|
||||
<literal>buildGoModule</literal> switched from
|
||||
<literal>modSha256</literal> to the <literal>vendorSha256</literal>
|
||||
attribute to pin fetched version data.
|
||||
@ -211,7 +259,7 @@ GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'localhost' WITH GRANT OPTION;
|
||||
<link xlink:href="https://grafana.com/docs/grafana/latest/guides/whats-new-in-v6-4/">deprecated in Grafana</link>
|
||||
and the <package>phantomjs</package> project is
|
||||
<link xlink:href="https://github.com/ariya/phantomjs/issues/15344#issue-302015362">currently unmaintained</link>.
|
||||
It can still be enabled by providing <literal>phantomJsSupport = true</literal> to the package instanciation:
|
||||
It can still be enabled by providing <literal>phantomJsSupport = true</literal> to the package instantiation:
|
||||
<programlisting>{
|
||||
services.grafana.package = pkgs.grafana.overrideAttrs (oldAttrs: rec {
|
||||
phantomJsSupport = false;
|
||||
@ -223,7 +271,7 @@ GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'localhost' WITH GRANT OPTION;
|
||||
<para>
|
||||
The <link linkend="opt-services.supybot.enable">supybot</link> module now uses <literal>/var/lib/supybot</literal>
|
||||
as its default <link linkend="opt-services.supybot.stateDir">stateDir</link> path if <literal>stateVersion</literal>
|
||||
is 20.09 or higher. It also enables number of
|
||||
is 20.09 or higher. It also enables a number of
|
||||
<link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Sandboxing">systemd sandboxing options</link>
|
||||
which may possibly interfere with some plugins. If this is the case you can disable the options through attributes in
|
||||
<option>systemd.services.supybot.serviceConfig</option>.
|
||||
@ -697,6 +745,13 @@ CREATE ROLE postgres LOGIN SUPERUSER;
|
||||
The USBGuard module now removes options and instead hardcodes values for <literal>IPCAccessControlFiles</literal>, <literal>ruleFiles</literal>, and <literal>auditFilePath</literal>. Audit logs can be found in the journal.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The NixOS module system now evaluates option definitions more strictly, allowing it to detect a larger set of problems.
|
||||
As a result, what previously evaluated may not do so anymore.
|
||||
See <link xlink:href="https://github.com/NixOS/nixpkgs/pull/82743#issuecomment-674520472">the PR that changed this</link> for more info.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
@ -915,9 +970,18 @@ services.transmission.settings.rpc-bind-address = "0.0.0.0";
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Nginx module <literal>nginxModules.fastcgi-cache-purge</literal> renamed to official name <literal>nginxModules.cache-purge</literal>.
|
||||
Nginx module <literal>nginxModules.ngx_aws_auth</literal> renamed to official name <literal>nginxModules.aws-auth</literal>.
|
||||
The packages <package>perl</package>, <package>rsync</package> and <package>strace</package> were removed from <option>systemPackages</option>. If you need them, install them again with <code><xref linkend="opt-environment.systemPackages"/> = with pkgs; [ perl rsync strace ];</code> in your <filename>configuration.nix</filename>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The <literal>undervolt</literal> option no longer needs to apply its
|
||||
settings every 30s. If they still become undone, open an issue and restore
|
||||
the previous behaviour using <literal>undervolt.useTimer</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
</section>
|
||||
|
@ -24,11 +24,11 @@
|
||||
check ? true
|
||||
, prefix ? []
|
||||
, lib ? import ../../lib
|
||||
, extraModules ? let e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH";
|
||||
in if e == "" then [] else [(import e)]
|
||||
}:
|
||||
|
||||
let extraArgs_ = extraArgs; pkgs_ = pkgs;
|
||||
extraModules = let e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH";
|
||||
in if e == "" then [] else [(import e)];
|
||||
in
|
||||
|
||||
let
|
||||
|
@ -22,9 +22,9 @@ rec {
|
||||
else throw "Unknown QEMU serial device for system '${pkgs.stdenv.hostPlatform.system}'";
|
||||
|
||||
qemuBinary = qemuPkg: {
|
||||
x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu host";
|
||||
x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu max";
|
||||
armv7l-linux = "${qemuPkg}/bin/qemu-system-arm -enable-kvm -machine virt -cpu host";
|
||||
aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -enable-kvm -machine virt,gic-version=host -cpu host";
|
||||
x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu host";
|
||||
x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu max";
|
||||
}.${pkgs.stdenv.hostPlatform.system} or "${qemuPkg}/bin/qemu-kvm";
|
||||
}
|
||||
|
@ -1,19 +1,13 @@
|
||||
#! /somewhere/python3
|
||||
from contextlib import contextmanager, _GeneratorContextManager
|
||||
from queue import Queue, Empty
|
||||
from typing import Tuple, Any, Callable, Dict, Iterator, Optional, List
|
||||
from xml.sax.saxutils import XMLGenerator
|
||||
import queue
|
||||
import io
|
||||
import _thread
|
||||
import argparse
|
||||
import atexit
|
||||
import base64
|
||||
import codecs
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import ptpython.repl
|
||||
import pty
|
||||
import queue
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
@ -21,9 +15,12 @@ import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import _thread
|
||||
import time
|
||||
import traceback
|
||||
import unicodedata
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple
|
||||
|
||||
import ptpython.repl
|
||||
|
||||
CHAR_TO_KEY = {
|
||||
"A": "shift-a",
|
||||
@ -88,13 +85,17 @@ CHAR_TO_KEY = {
|
||||
")": "shift-0x0B",
|
||||
}
|
||||
|
||||
# Forward references
|
||||
log: "Logger"
|
||||
# Forward reference
|
||||
machines: "List[Machine]"
|
||||
|
||||
logging.basicConfig(format="%(message)s")
|
||||
logger = logging.getLogger("test-driver")
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
def eprint(*args: object, **kwargs: Any) -> None:
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
class MachineLogAdapter(logging.LoggerAdapter):
|
||||
def process(self, msg: str, kwargs: Any) -> Tuple[str, Any]:
|
||||
return f"{self.extra['machine']}: {msg}", kwargs
|
||||
|
||||
|
||||
def make_command(args: list) -> str:
|
||||
@ -102,8 +103,7 @@ def make_command(args: list) -> str:
|
||||
|
||||
|
||||
def create_vlan(vlan_nr: str) -> Tuple[str, str, "subprocess.Popen[bytes]", Any]:
|
||||
global log
|
||||
log.log("starting VDE switch for network {}".format(vlan_nr))
|
||||
logger.info(f"starting VDE switch for network {vlan_nr}")
|
||||
vde_socket = tempfile.mkdtemp(
|
||||
prefix="nixos-test-vde-", suffix="-vde{}.ctl".format(vlan_nr)
|
||||
)
|
||||
@ -142,70 +142,6 @@ def retry(fn: Callable) -> None:
|
||||
raise Exception("action timed out")
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self) -> None:
|
||||
self.logfile = os.environ.get("LOGFILE", "/dev/null")
|
||||
self.logfile_handle = codecs.open(self.logfile, "wb")
|
||||
self.xml = XMLGenerator(self.logfile_handle, encoding="utf-8")
|
||||
self.queue: "Queue[Dict[str, str]]" = Queue()
|
||||
|
||||
self.xml.startDocument()
|
||||
self.xml.startElement("logfile", attrs={})
|
||||
|
||||
def close(self) -> None:
|
||||
self.xml.endElement("logfile")
|
||||
self.xml.endDocument()
|
||||
self.logfile_handle.close()
|
||||
|
||||
def sanitise(self, message: str) -> str:
|
||||
return "".join(ch for ch in message if unicodedata.category(ch)[0] != "C")
|
||||
|
||||
def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str:
|
||||
if "machine" in attributes:
|
||||
return "{}: {}".format(attributes["machine"], message)
|
||||
return message
|
||||
|
||||
def log_line(self, message: str, attributes: Dict[str, str]) -> None:
|
||||
self.xml.startElement("line", attributes)
|
||||
self.xml.characters(message)
|
||||
self.xml.endElement("line")
|
||||
|
||||
def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
|
||||
eprint(self.maybe_prefix(message, attributes))
|
||||
self.drain_log_queue()
|
||||
self.log_line(message, attributes)
|
||||
|
||||
def enqueue(self, message: Dict[str, str]) -> None:
|
||||
self.queue.put(message)
|
||||
|
||||
def drain_log_queue(self) -> None:
|
||||
try:
|
||||
while True:
|
||||
item = self.queue.get_nowait()
|
||||
attributes = {"machine": item["machine"], "type": "serial"}
|
||||
self.log_line(self.sanitise(item["msg"]), attributes)
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
@contextmanager
|
||||
def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
|
||||
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: Dict[str, Any]) -> None:
|
||||
if "name" in args:
|
||||
@ -235,8 +171,8 @@ class Machine:
|
||||
self.pid: Optional[int] = None
|
||||
self.socket = None
|
||||
self.monitor: Optional[socket.socket] = None
|
||||
self.logger: Logger = args["log"]
|
||||
self.allow_reboot = args.get("allowReboot", False)
|
||||
self.logger = MachineLogAdapter(logger, extra=dict(machine=self.name))
|
||||
|
||||
@staticmethod
|
||||
def create_startcommand(args: Dict[str, str]) -> str:
|
||||
@ -292,14 +228,6 @@ class Machine:
|
||||
def is_up(self) -> bool:
|
||||
return self.booted and self.connected
|
||||
|
||||
def log(self, msg: str) -> None:
|
||||
self.logger.log(msg, {"machine": self.name})
|
||||
|
||||
def nested(self, msg: str, attrs: Dict[str, str] = {}) -> _GeneratorContextManager:
|
||||
my_attrs = {"machine": self.name}
|
||||
my_attrs.update(attrs)
|
||||
return self.logger.nested(msg, my_attrs)
|
||||
|
||||
def wait_for_monitor_prompt(self) -> str:
|
||||
assert self.monitor is not None
|
||||
answer = ""
|
||||
@ -314,7 +242,7 @@ class Machine:
|
||||
|
||||
def send_monitor_command(self, command: str) -> str:
|
||||
message = ("{}\n".format(command)).encode()
|
||||
self.log("sending monitor command: {}".format(command))
|
||||
self.logger.info(f"sending monitor command: {command}")
|
||||
assert self.monitor is not None
|
||||
self.monitor.send(message)
|
||||
return self.wait_for_monitor_prompt()
|
||||
@ -381,16 +309,19 @@ class Machine:
|
||||
return self.execute("systemctl {}".format(q))
|
||||
|
||||
def require_unit_state(self, unit: str, require_state: str = "active") -> None:
|
||||
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)
|
||||
+ "'{}' but it is in state ‘{}’".format(require_state, state)
|
||||
)
|
||||
self.logger.info(
|
||||
f"checking if unit ‘{unit}’ has reached state '{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)
|
||||
+ "'{}' but it is in state ‘{}’".format(require_state, state)
|
||||
)
|
||||
|
||||
def log(self, message: str) -> None:
|
||||
self.logger.info(message)
|
||||
|
||||
def execute(self, command: str) -> Tuple[int, str]:
|
||||
self.connect()
|
||||
@ -414,25 +345,26 @@ class Machine:
|
||||
"""Execute each command and check that it succeeds."""
|
||||
output = ""
|
||||
for command in commands:
|
||||
with self.nested("must succeed: {}".format(command)):
|
||||
(status, out) = self.execute(command)
|
||||
if status != 0:
|
||||
self.log("output: {}".format(out))
|
||||
raise Exception(
|
||||
"command `{}` failed (exit code {})".format(command, status)
|
||||
)
|
||||
output += out
|
||||
self.logger.info(f"must succeed: {command}")
|
||||
(status, out) = self.execute(command)
|
||||
if status != 0:
|
||||
self.logger.info(f"output: {out}")
|
||||
raise Exception(
|
||||
"command `{}` failed (exit code {})".format(command, status)
|
||||
)
|
||||
output += out
|
||||
return output
|
||||
|
||||
def fail(self, *commands: str) -> None:
|
||||
def fail(self, *commands: str) -> str:
|
||||
"""Execute each command and check that it fails."""
|
||||
output = ""
|
||||
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)
|
||||
)
|
||||
self.logger.info(f"must fail: {command}")
|
||||
(status, out) = self.execute(command)
|
||||
if status == 0:
|
||||
raise Exception("command `{}` unexpectedly succeeded".format(command))
|
||||
output += out
|
||||
return output
|
||||
|
||||
def wait_until_succeeds(self, command: str) -> str:
|
||||
"""Wait until a command returns success and return its output.
|
||||
@ -445,9 +377,9 @@ class Machine:
|
||||
status, output = self.execute(command)
|
||||
return status == 0
|
||||
|
||||
with self.nested("waiting for success: {}".format(command)):
|
||||
retry(check_success)
|
||||
return output
|
||||
self.logger.info(f"waiting for success: {command}")
|
||||
retry(check_success)
|
||||
return output
|
||||
|
||||
def wait_until_fails(self, command: str) -> str:
|
||||
"""Wait until a command returns failure.
|
||||
@ -460,21 +392,21 @@ class Machine:
|
||||
status, output = self.execute(command)
|
||||
return status != 0
|
||||
|
||||
with self.nested("waiting for failure: {}".format(command)):
|
||||
retry(check_failure)
|
||||
return output
|
||||
self.logger.info(f"waiting for failure: {command}")
|
||||
retry(check_failure)
|
||||
return output
|
||||
|
||||
def wait_for_shutdown(self) -> None:
|
||||
if not self.booted:
|
||||
return
|
||||
|
||||
with self.nested("waiting for the VM to power off"):
|
||||
sys.stdout.flush()
|
||||
self.process.wait()
|
||||
self.logger.info("waiting for the VM to power off")
|
||||
sys.stdout.flush()
|
||||
self.process.wait()
|
||||
|
||||
self.pid = None
|
||||
self.booted = False
|
||||
self.connected = False
|
||||
self.pid = None
|
||||
self.booted = False
|
||||
self.connected = False
|
||||
|
||||
def get_tty_text(self, tty: str) -> str:
|
||||
status, output = self.execute(
|
||||
@ -492,19 +424,19 @@ class Machine:
|
||||
def tty_matches(last: bool) -> bool:
|
||||
text = self.get_tty_text(tty)
|
||||
if last:
|
||||
self.log(
|
||||
self.logger.info(
|
||||
f"Last chance to match /{regexp}/ on TTY{tty}, "
|
||||
f"which currently contains: {text}"
|
||||
)
|
||||
return len(matcher.findall(text)) > 0
|
||||
|
||||
with self.nested("waiting for {} to appear on tty {}".format(regexp, tty)):
|
||||
retry(tty_matches)
|
||||
self.logger.info(f"waiting for {regexp} to appear on tty {tty}")
|
||||
retry(tty_matches)
|
||||
|
||||
def send_chars(self, chars: List[str]) -> None:
|
||||
with self.nested("sending keys ‘{}‘".format(chars)):
|
||||
for char in chars:
|
||||
self.send_key(char)
|
||||
self.logger.info(f"sending keys ‘{chars}‘")
|
||||
for char in chars:
|
||||
self.send_key(char)
|
||||
|
||||
def wait_for_file(self, filename: str) -> None:
|
||||
"""Waits until the file exists in machine's file system."""
|
||||
@ -513,16 +445,16 @@ class Machine:
|
||||
status, _ = self.execute("test -e {}".format(filename))
|
||||
return status == 0
|
||||
|
||||
with self.nested("waiting for file ‘{}‘".format(filename)):
|
||||
retry(check_file)
|
||||
self.logger.info(f"waiting for file ‘{filename}‘")
|
||||
retry(check_file)
|
||||
|
||||
def wait_for_open_port(self, port: int) -> None:
|
||||
def port_is_open(_: Any) -> bool:
|
||||
status, _ = self.execute("nc -z localhost {}".format(port))
|
||||
return status == 0
|
||||
|
||||
with self.nested("waiting for TCP port {}".format(port)):
|
||||
retry(port_is_open)
|
||||
self.logger.info(f"waiting for TCP port {port}")
|
||||
retry(port_is_open)
|
||||
|
||||
def wait_for_closed_port(self, port: int) -> None:
|
||||
def port_is_closed(_: Any) -> bool:
|
||||
@ -544,17 +476,17 @@ class Machine:
|
||||
if self.connected:
|
||||
return
|
||||
|
||||
with self.nested("waiting for the VM to finish booting"):
|
||||
self.start()
|
||||
self.logger.info("waiting for the VM to finish booting")
|
||||
self.start()
|
||||
|
||||
tic = time.time()
|
||||
self.shell.recv(1024)
|
||||
# TODO: Timeout
|
||||
toc = time.time()
|
||||
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
|
||||
self.logger.info("connected to guest root shell")
|
||||
self.logger.info(f"(connecting took {toc - tic:.2f} seconds)")
|
||||
self.connected = True
|
||||
|
||||
def screenshot(self, filename: str) -> None:
|
||||
out_dir = os.environ.get("out", os.getcwd())
|
||||
@ -563,15 +495,12 @@ class Machine:
|
||||
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")
|
||||
self.logger.info(f"making screenshot {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 copy_from_host_via_shell(self, source: str, target: str) -> None:
|
||||
"""Copy a file from the host into the guest by piping it over the
|
||||
@ -647,20 +576,18 @@ class Machine:
|
||||
|
||||
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))
|
||||
self.logger.info("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)
|
||||
)
|
||||
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")
|
||||
return ret.stdout.decode("utf-8")
|
||||
|
||||
def wait_for_text(self, regex: str) -> None:
|
||||
def screen_matches(last: bool) -> bool:
|
||||
@ -668,15 +595,15 @@ class Machine:
|
||||
matches = re.search(regex, text) is not None
|
||||
|
||||
if last and not matches:
|
||||
self.log("Last OCR attempt failed. Text was: {}".format(text))
|
||||
self.logger.info(f"Last OCR attempt failed. Text was: {text}")
|
||||
|
||||
return matches
|
||||
|
||||
with self.nested("waiting for {} to appear on screen".format(regex)):
|
||||
retry(screen_matches)
|
||||
self.logger.info(f"waiting for {regex} to appear on screen")
|
||||
retry(screen_matches)
|
||||
|
||||
def wait_for_console_text(self, regex: str) -> None:
|
||||
self.log("waiting for {} to appear on console".format(regex))
|
||||
self.logger.info(f"waiting for {regex} to appear on console")
|
||||
# Buffer the console output, this is needed
|
||||
# to match multiline regexes.
|
||||
console = io.StringIO()
|
||||
@ -699,7 +626,7 @@ class Machine:
|
||||
if self.booted:
|
||||
return
|
||||
|
||||
self.log("starting vm")
|
||||
self.logger.info("starting vm")
|
||||
|
||||
def create_socket(path: str) -> socket.socket:
|
||||
if os.path.exists(path):
|
||||
@ -756,7 +683,7 @@ class Machine:
|
||||
|
||||
# Store last serial console lines for use
|
||||
# of wait_for_console_text
|
||||
self.last_lines: Queue = Queue()
|
||||
self.last_lines: queue.Queue = queue.Queue()
|
||||
|
||||
def process_serial_output() -> None:
|
||||
assert self.process.stdout is not None
|
||||
@ -764,8 +691,7 @@ class Machine:
|
||||
# Ignore undecodable bytes that may occur in boot menus
|
||||
line = _line.decode(errors="ignore").replace("\r", "").rstrip()
|
||||
self.last_lines.put(line)
|
||||
eprint("{} # {}".format(self.name, line))
|
||||
self.logger.enqueue({"msg": line, "machine": self.name})
|
||||
self.logger.info(line)
|
||||
|
||||
_thread.start_new_thread(process_serial_output, ())
|
||||
|
||||
@ -774,10 +700,10 @@ class Machine:
|
||||
self.pid = self.process.pid
|
||||
self.booted = True
|
||||
|
||||
self.log("QEMU running (pid {})".format(self.pid))
|
||||
self.logger.info(f"QEMU running (pid {self.pid})")
|
||||
|
||||
def cleanup_statedir(self) -> None:
|
||||
self.log("delete the VM state directory")
|
||||
self.logger.info("delete the VM state directory")
|
||||
if os.path.isfile(self.state_dir):
|
||||
shutil.rmtree(self.state_dir)
|
||||
|
||||
@ -792,7 +718,7 @@ class Machine:
|
||||
if not self.booted:
|
||||
return
|
||||
|
||||
self.log("forced crash")
|
||||
self.logger.info("forced crash")
|
||||
self.send_monitor_command("quit")
|
||||
self.wait_for_shutdown()
|
||||
|
||||
@ -812,8 +738,8 @@ class Machine:
|
||||
status, _ = self.execute("[ -e /tmp/.X11-unix/X0 ]")
|
||||
return status == 0
|
||||
|
||||
with self.nested("waiting for the X11 server"):
|
||||
retry(check_x)
|
||||
self.logger.info("waiting for the X11 server")
|
||||
retry(check_x)
|
||||
|
||||
def get_window_names(self) -> List[str]:
|
||||
return self.succeed(
|
||||
@ -826,15 +752,14 @@ class Machine:
|
||||
def window_is_visible(last_try: bool) -> bool:
|
||||
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)
|
||||
self.logger.info(
|
||||
f"Last chance to match {regexp} on the window list, "
|
||||
+ f"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)
|
||||
self.logger.info("Waiting for a window to appear")
|
||||
retry(window_is_visible)
|
||||
|
||||
def sleep(self, secs: int) -> None:
|
||||
time.sleep(secs)
|
||||
@ -862,23 +787,22 @@ class Machine:
|
||||
|
||||
def create_machine(args: Dict[str, Any]) -> Machine:
|
||||
global log
|
||||
args["log"] = log
|
||||
args["redirectSerial"] = os.environ.get("USE_SERIAL", "0") == "1"
|
||||
return Machine(args)
|
||||
|
||||
|
||||
def start_all() -> None:
|
||||
global machines
|
||||
with log.nested("starting all VMs"):
|
||||
for machine in machines:
|
||||
machine.start()
|
||||
logger.info("starting all VMs")
|
||||
for machine in machines:
|
||||
machine.start()
|
||||
|
||||
|
||||
def join_all() -> None:
|
||||
global machines
|
||||
with log.nested("waiting for all VMs to finish"):
|
||||
for machine in machines:
|
||||
machine.wait_for_shutdown()
|
||||
logger.info("waiting for all VMs to finish")
|
||||
for machine in machines:
|
||||
machine.wait_for_shutdown()
|
||||
|
||||
|
||||
def test_script() -> None:
|
||||
@ -889,13 +813,12 @@ def run_tests() -> None:
|
||||
global machines
|
||||
tests = os.environ.get("tests", None)
|
||||
if tests is not None:
|
||||
with log.nested("running the VM test script"):
|
||||
try:
|
||||
exec(tests, globals())
|
||||
except Exception as e:
|
||||
eprint("error: ")
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
logger.info("running the VM test script")
|
||||
try:
|
||||
exec(tests, globals())
|
||||
except Exception:
|
||||
logging.exception("error:")
|
||||
sys.exit(1)
|
||||
else:
|
||||
ptpython.repl.embed(locals(), globals())
|
||||
|
||||
@ -908,18 +831,19 @@ def run_tests() -> None:
|
||||
|
||||
@contextmanager
|
||||
def subtest(name: str) -> Iterator[None]:
|
||||
with log.nested(name):
|
||||
try:
|
||||
yield
|
||||
return True
|
||||
except Exception as e:
|
||||
log.log(f'Test "{name}" failed with error: "{e}"')
|
||||
raise e
|
||||
logger.info(name)
|
||||
try:
|
||||
yield
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.info(f'Test "{name}" failed with error: "{e}"')
|
||||
raise e
|
||||
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main() -> None:
|
||||
global machines
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument(
|
||||
"-K",
|
||||
@ -929,8 +853,6 @@ if __name__ == "__main__":
|
||||
)
|
||||
(cli_args, vm_scripts) = arg_parser.parse_known_args()
|
||||
|
||||
log = Logger()
|
||||
|
||||
vlan_nrs = list(dict.fromkeys(os.environ.get("VLANS", "").split()))
|
||||
vde_sockets = [create_vlan(v) for v in vlan_nrs]
|
||||
for nr, vde_socket, _, _ in vde_sockets:
|
||||
@ -941,23 +863,27 @@ if __name__ == "__main__":
|
||||
if not cli_args.keep_vm_state:
|
||||
machine.cleanup_statedir()
|
||||
machine_eval = [
|
||||
"{0} = machines[{1}]".format(m.name, idx) for idx, m in enumerate(machines)
|
||||
"global {0}; {0} = machines[{1}]".format(m.name, idx)
|
||||
for idx, m in enumerate(machines)
|
||||
]
|
||||
exec("\n".join(machine_eval))
|
||||
|
||||
@atexit.register
|
||||
def clean_up() -> None:
|
||||
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.terminate()
|
||||
log.close()
|
||||
logger.info("cleaning up")
|
||||
for machine in machines:
|
||||
if machine.pid is None:
|
||||
continue
|
||||
logger.info(f"killing {machine.name} (pid {machine.pid})")
|
||||
machine.process.kill()
|
||||
for _, _, process, _ in vde_sockets:
|
||||
process.terminate()
|
||||
|
||||
tic = time.time()
|
||||
run_tests()
|
||||
toc = time.time()
|
||||
print("test script finished in {:.2f}s".format(toc - tic))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@ -62,7 +62,7 @@ rec {
|
||||
''
|
||||
mkdir -p $out
|
||||
|
||||
LOGFILE=/dev/null tests='exec(os.environ["testScript"])' ${driver}/bin/nixos-test-driver
|
||||
tests='exec(os.environ["testScript"])' ${driver}/bin/nixos-test-driver
|
||||
|
||||
for i in */xchg/coverage-data; do
|
||||
mkdir -p $out/coverage-data
|
||||
|
@ -29,7 +29,7 @@ log() {
|
||||
echo "$@" >&2
|
||||
}
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
if [ "$#" -ne 1 ]; then
|
||||
log "Usage: ./upload-amazon-image.sh IMAGE_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
@ -321,7 +321,7 @@ in
|
||||
monetdb = 290;
|
||||
restic = 291;
|
||||
openvpn = 292;
|
||||
meguca = 293;
|
||||
# meguca = 293; # removed 2020-08-21
|
||||
yarn = 294;
|
||||
hdfs = 295;
|
||||
mapred = 296;
|
||||
@ -622,7 +622,7 @@ in
|
||||
monetdb = 290;
|
||||
restic = 291;
|
||||
openvpn = 292;
|
||||
meguca = 293;
|
||||
# meguca = 293; # removed 2020-08-21
|
||||
yarn = 294;
|
||||
hdfs = 295;
|
||||
mapred = 296;
|
||||
|
@ -127,7 +127,7 @@ in {
|
||||
{ LOCATE_PATH = cfg.output;
|
||||
};
|
||||
|
||||
warnings = optional (isMLocate && cfg.localuser != null) "mlocate does not support searching as user other than root"
|
||||
warnings = optional (isMLocate && cfg.localuser != null) "mlocate does not support the services.locate.localuser option; updatedb will run as root. (Silence with services.locate.localuser = null.)"
|
||||
++ optional (isFindutils && cfg.pruneNames != []) "findutils locate does not support pruning by directory component"
|
||||
++ optional (isFindutils && cfg.pruneBindMounts) "findutils locate does not support skipping bind mounts";
|
||||
|
||||
|
@ -178,8 +178,6 @@ in
|
||||
type = types.nullOr types.attrs; # TODO utilize lib.systems.parsedPlatform
|
||||
default = null;
|
||||
example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
|
||||
defaultText = literalExample
|
||||
''(import "''${nixos}/../lib").lib.systems.examples.aarch64-multiplatform'';
|
||||
description = ''
|
||||
Specifies the platform for which NixOS should be
|
||||
built. Specify this only if it is different from
|
||||
|
@ -300,6 +300,7 @@
|
||||
./services/desktops/dleyna-renderer.nix
|
||||
./services/desktops/dleyna-server.nix
|
||||
./services/desktops/pantheon/files.nix
|
||||
./services/desktops/espanso.nix
|
||||
./services/desktops/flatpak.nix
|
||||
./services/desktops/geoclue2.nix
|
||||
./services/desktops/gsignond.nix
|
||||
@ -886,7 +887,6 @@
|
||||
./services/web-servers/lighttpd/collectd.nix
|
||||
./services/web-servers/lighttpd/default.nix
|
||||
./services/web-servers/lighttpd/gitweb.nix
|
||||
./services/web-servers/meguca.nix
|
||||
./services/web-servers/mighttpd2.nix
|
||||
./services/web-servers/minio.nix
|
||||
./services/web-servers/molly-brown.nix
|
||||
|
@ -1,7 +1,7 @@
|
||||
# A profile with most (vanilla) hardening options enabled by default,
|
||||
# potentially at the cost of features and performance.
|
||||
|
||||
{ lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
@ -27,6 +27,9 @@ with lib;
|
||||
|
||||
security.forcePageTableIsolation = mkDefault true;
|
||||
|
||||
# This is required by podman to run containers in rootless mode.
|
||||
security.unprivilegedUsernsClone = mkDefault config.virtualisation.containers.enable;
|
||||
|
||||
security.virtualisation.flushL1DataCache = mkDefault "always";
|
||||
|
||||
security.apparmor.enable = mkDefault true;
|
||||
|
@ -48,6 +48,7 @@ with lib;
|
||||
instead, or any other display manager in NixOS as they all support auto-login.
|
||||
'')
|
||||
(mkRemovedOptionModule [ "services" "dnscrypt-proxy" ] "Use services.dnscrypt-proxy2 instead")
|
||||
(mkRemovedOptionModule [ "services" "meguca" ] "Use meguca has been removed from nixpkgs")
|
||||
(mkRemovedOptionModule ["hardware" "brightnessctl" ] ''
|
||||
The brightnessctl module was removed because newer versions of
|
||||
brightnessctl don't require the udev rules anymore (they can use the
|
||||
|
@ -150,6 +150,14 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
extraLegoFlags = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Additional global flags to pass to all lego commands.
|
||||
'';
|
||||
};
|
||||
|
||||
extraLegoRenewFlags = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
@ -157,6 +165,14 @@ let
|
||||
Additional flags to pass to lego renew.
|
||||
'';
|
||||
};
|
||||
|
||||
extraLegoRunFlags = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Additional flags to pass to lego run.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -313,9 +329,10 @@ in
|
||||
++ optionals (data.dnsProvider != null && !data.dnsPropagationCheck) [ "--dns.disable-cp" ]
|
||||
++ concatLists (mapAttrsToList (name: root: [ "-d" name ]) data.extraDomains)
|
||||
++ (if data.dnsProvider != null then [ "--dns" data.dnsProvider ] else [ "--http" "--http.webroot" data.webroot ])
|
||||
++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)];
|
||||
++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)]
|
||||
++ data.extraLegoFlags;
|
||||
certOpts = optionals data.ocspMustStaple [ "--must-staple" ];
|
||||
runOpts = escapeShellArgs (globalOpts ++ [ "run" ] ++ certOpts);
|
||||
runOpts = escapeShellArgs (globalOpts ++ [ "run" ] ++ certOpts ++ data.extraLegoRunFlags);
|
||||
renewOpts = escapeShellArgs (globalOpts ++
|
||||
[ "renew" "--days" (toString cfg.validMinDays) ] ++
|
||||
certOpts ++ data.extraLegoRenewFlags);
|
||||
|
@ -51,7 +51,7 @@ in
|
||||
};
|
||||
|
||||
secretKeyFile = mkOption {
|
||||
type = types.path;
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
A file containing your secret key. The security of your Duo application is tied to the security of your secret key.
|
||||
|
@ -27,6 +27,16 @@ with lib;
|
||||
'';
|
||||
};
|
||||
|
||||
security.unprivilegedUsernsClone = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
When disabled, unprivileged users will not be able to create new namespaces.
|
||||
By default unprivileged user namespaces are disabled.
|
||||
This option only works in a hardened profile.
|
||||
'';
|
||||
};
|
||||
|
||||
security.protectKernelImage = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
@ -115,6 +125,10 @@ with lib;
|
||||
];
|
||||
})
|
||||
|
||||
(mkIf config.security.unprivilegedUsernsClone {
|
||||
boot.kernel.sysctl."kernel.unprivileged_userns_clone" = mkDefault true;
|
||||
})
|
||||
|
||||
(mkIf config.security.protectKernelImage {
|
||||
# Disable hibernation (allows replacing the running kernel)
|
||||
boot.kernelParams = [ "nohibernate" ];
|
||||
|
@ -47,7 +47,7 @@ in {
|
||||
enable = mkEnableOption "Icecast server";
|
||||
|
||||
hostname = mkOption {
|
||||
type = types.str;
|
||||
type = types.nullOr types.str;
|
||||
description = "DNS name or IP address that will be used for the stream directory lookups or possibily the playlist generation if a Host header is not provided.";
|
||||
default = config.networking.domain;
|
||||
};
|
||||
|
25
nixos/modules/services/desktops/espanso.nix
Normal file
25
nixos/modules/services/desktops/espanso.nix
Normal file
@ -0,0 +1,25 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let cfg = config.services.espanso;
|
||||
in {
|
||||
meta = { maintainers = with lib.maintainers; [ numkem ]; };
|
||||
|
||||
options = {
|
||||
services.espanso = { enable = options.mkEnableOption "Espanso"; };
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.user.services.espanso = {
|
||||
description = "Espanso daemon";
|
||||
path = with pkgs; [ espanso libnotify xclip ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.espanso}/bin/espanso daemon";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
wantedBy = [ "default.target" ];
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.espanso ];
|
||||
};
|
||||
}
|
@ -15,7 +15,7 @@ let
|
||||
jupyterhubConfig = pkgs.writeText "jupyterhub_config.py" ''
|
||||
c.JupyterHub.bind_url = "http://${cfg.host}:${toString cfg.port}"
|
||||
|
||||
c.JupyterHub.authentication_class = "${cfg.authentication}"
|
||||
c.JupyterHub.authenticator_class = "${cfg.authentication}"
|
||||
c.JupyterHub.spawner_class = "${cfg.spawner}"
|
||||
|
||||
c.SystemdSpawner.default_url = '/lab'
|
||||
|
@ -12,7 +12,7 @@ in{
|
||||
|
||||
config = mkOption {
|
||||
default = null;
|
||||
type = types.lines;
|
||||
type = types.nullOr types.lines;
|
||||
description = "Fancontrol configuration file content. See <citerefentry><refentrytitle>pwmconfig</refentrytitle><manvolnum>8</manvolnum></citerefentry> from the lm_sensors package.";
|
||||
example = ''
|
||||
# Configuration file generated by pwmconfig
|
||||
|
@ -103,6 +103,17 @@ in
|
||||
The temperature target on battery power in Celsius degrees.
|
||||
'';
|
||||
};
|
||||
|
||||
useTimer = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to set a timer that applies the undervolt settings every 30s.
|
||||
This will cause spam in the journal but might be required for some
|
||||
hardware under specific conditions.
|
||||
Enable this if your undervolt settings don't hold.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
@ -114,6 +125,11 @@ in
|
||||
path = [ pkgs.undervolt ];
|
||||
|
||||
description = "Intel Undervolting Service";
|
||||
|
||||
# Apply undervolt on boot, nixos generation switch and resume
|
||||
wantedBy = [ "multi-user.target" "post-resume.target" ];
|
||||
after = [ "post-resume.target" ]; # Not sure why but it won't work without this
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
Restart = "no";
|
||||
@ -121,7 +137,7 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.undervolt = {
|
||||
systemd.timers.undervolt = mkIf cfg.useTimer {
|
||||
description = "Undervolt timer to ensure voltage settings are always applied";
|
||||
partOf = [ "undervolt.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
@ -5,54 +5,93 @@ with lib;
|
||||
let
|
||||
cfg = config.services.logrotate;
|
||||
|
||||
pathOptions = {
|
||||
pathOpts = {
|
||||
options = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable log rotation for this path. This can be used to explicitly disable
|
||||
logging that has been configured by NixOS.
|
||||
'';
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
type = types.str;
|
||||
description = "The path to log files to be rotated";
|
||||
description = ''
|
||||
The path to log files to be rotated.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = "The user account to use for rotation";
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The user account to use for rotation.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
description = "The group to use for rotation";
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The group to use for rotation.
|
||||
'';
|
||||
};
|
||||
|
||||
frequency = mkOption {
|
||||
type = types.enum [
|
||||
"daily" "weekly" "monthly" "yearly"
|
||||
];
|
||||
type = types.enum [ "daily" "weekly" "monthly" "yearly" ];
|
||||
default = "daily";
|
||||
description = "How often to rotate the logs";
|
||||
description = ''
|
||||
How often to rotate the logs.
|
||||
'';
|
||||
};
|
||||
|
||||
keep = mkOption {
|
||||
type = types.int;
|
||||
default = 20;
|
||||
description = "How many rotations to keep";
|
||||
description = ''
|
||||
How many rotations to keep.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Extra logrotate config options for this path";
|
||||
description = ''
|
||||
Extra logrotate config options for this path. Refer to
|
||||
<link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
|
||||
'';
|
||||
};
|
||||
|
||||
priority = mkOption {
|
||||
type = types.int;
|
||||
default = 1000;
|
||||
description = ''
|
||||
Order of this logrotate block in relation to the others. The semantics are
|
||||
the same as with `lib.mkOrder`. Smaller values have a greater priority.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pathConfig = options: ''
|
||||
"${options.path}" {
|
||||
su ${options.user} ${options.group}
|
||||
${options.frequency}
|
||||
config.extraConfig = ''
|
||||
missingok
|
||||
notifempty
|
||||
rotate ${toString options.keep}
|
||||
${options.extraConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
mkConf = pathOpts: ''
|
||||
# generated by NixOS using the `services.logrotate.paths.${pathOpts.name}` attribute set
|
||||
"${pathOpts.path}" {
|
||||
${optionalString (pathOpts.user != null || pathOpts.group != null) "su ${pathOpts.user} ${pathOpts.group}"}
|
||||
${pathOpts.frequency}
|
||||
rotate ${toString pathOpts.keep}
|
||||
${pathOpts.extraConfig}
|
||||
}
|
||||
'';
|
||||
|
||||
configFile = pkgs.writeText "logrotate.conf" (
|
||||
(concatStringsSep "\n" ((map pathConfig cfg.paths) ++ [cfg.extraConfig]))
|
||||
);
|
||||
paths = sortProperties (mapAttrsToList (name: pathOpts: pathOpts // { name = name; }) (filterAttrs (_: pathOpts: pathOpts.enable) cfg.paths));
|
||||
configFile = pkgs.writeText "logrotate.conf" (concatStringsSep "\n" ((map mkConf paths) ++ [ cfg.extraConfig ]));
|
||||
|
||||
in
|
||||
{
|
||||
@ -65,41 +104,66 @@ in
|
||||
enable = mkEnableOption "the logrotate systemd service";
|
||||
|
||||
paths = mkOption {
|
||||
type = types.listOf (types.submodule pathOptions);
|
||||
default = [];
|
||||
description = "List of attribute sets with paths to rotate";
|
||||
example = {
|
||||
"/var/log/myapp/*.log" = {
|
||||
user = "myuser";
|
||||
group = "mygroup";
|
||||
rotate = "weekly";
|
||||
keep = 5;
|
||||
};
|
||||
};
|
||||
type = with types; attrsOf (submodule pathOpts);
|
||||
default = {};
|
||||
description = ''
|
||||
Attribute set of paths to rotate. The order each block appears in the generated configuration file
|
||||
can be controlled by the <link linkend="opt-services.logrotate.paths._name_.priority">priority</link> option
|
||||
using the same semantics as `lib.mkOrder`. Smaller values have a greater priority.
|
||||
'';
|
||||
example = literalExample ''
|
||||
{
|
||||
httpd = {
|
||||
path = "/var/log/httpd/*.log";
|
||||
user = config.services.httpd.user;
|
||||
group = config.services.httpd.group;
|
||||
keep = 7;
|
||||
};
|
||||
|
||||
myapp = {
|
||||
path = "/var/log/myapp/*.log";
|
||||
user = "myuser";
|
||||
group = "mygroup";
|
||||
frequency = "weekly";
|
||||
keep = 5;
|
||||
priority = 1;
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Extra contents to add to the logrotate config file.
|
||||
See https://linux.die.net/man/8/logrotate
|
||||
Extra contents to append to the logrotate configuration file. Refer to
|
||||
<link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.logrotate = {
|
||||
description = "Logrotate Service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
startAt = "*-*-* *:05:00";
|
||||
assertions = mapAttrsToList (name: pathOpts:
|
||||
{ assertion = (pathOpts.user != null) == (pathOpts.group != null);
|
||||
message = ''
|
||||
If either of `services.logrotate.paths.${name}.user` or `services.logrotate.paths.${name}.group` are specified then *both* must be specified.
|
||||
'';
|
||||
}
|
||||
) cfg.paths;
|
||||
|
||||
serviceConfig.Restart = "no";
|
||||
serviceConfig.User = "root";
|
||||
systemd.services.logrotate = {
|
||||
description = "Logrotate Service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
startAt = "*-*-* *:05:00";
|
||||
script = ''
|
||||
exec ${pkgs.logrotate}/sbin/logrotate ${configFile}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Restart = "no";
|
||||
User = "root";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -4,13 +4,9 @@ with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.logstash;
|
||||
pluginPath = lib.concatStringsSep ":" cfg.plugins;
|
||||
havePluginPath = lib.length cfg.plugins > 0;
|
||||
ops = lib.optionalString;
|
||||
verbosityFlag = "--log.level " + cfg.logLevel;
|
||||
|
||||
pluginsPath = "--path.plugins ${pluginPath}";
|
||||
|
||||
logstashConf = pkgs.writeText "logstash.conf" ''
|
||||
input {
|
||||
${cfg.inputConfig}
|
||||
@ -173,7 +169,7 @@ in
|
||||
ExecStart = concatStringsSep " " (filter (s: stringLength s != 0) [
|
||||
"${cfg.package}/bin/logstash"
|
||||
"-w ${toString cfg.filterWorkers}"
|
||||
(ops havePluginPath pluginsPath)
|
||||
(concatMapStringsSep " " (x: "--path.plugins ${x}") cfg.plugins)
|
||||
"${verbosityFlag}"
|
||||
"-f ${logstashConf}"
|
||||
"--path.settings ${logstashSettingsDir}"
|
||||
|
@ -1,4 +1,4 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{ options, config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
@ -83,11 +83,11 @@ let
|
||||
)
|
||||
|
||||
(
|
||||
optionalString (cfg.mailboxes != []) ''
|
||||
optionalString (cfg.mailboxes != {}) ''
|
||||
protocol imap {
|
||||
namespace inbox {
|
||||
inbox=yes
|
||||
${concatStringsSep "\n" (map mailboxConfig cfg.mailboxes)}
|
||||
${concatStringsSep "\n" (map mailboxConfig (attrValues cfg.mailboxes))}
|
||||
}
|
||||
}
|
||||
''
|
||||
@ -131,12 +131,13 @@ let
|
||||
special_use = \${toString mailbox.specialUse}
|
||||
'' + "}";
|
||||
|
||||
mailboxes = { ... }: {
|
||||
mailboxes = { name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.nullOr (types.strMatching ''[^"]+'');
|
||||
type = types.strMatching ''[^"]+'';
|
||||
example = "Spam";
|
||||
default = null;
|
||||
default = name;
|
||||
readOnly = true;
|
||||
description = "The name of the mailbox.";
|
||||
};
|
||||
auto = mkOption {
|
||||
@ -335,19 +336,11 @@ in
|
||||
};
|
||||
|
||||
mailboxes = mkOption {
|
||||
type = with types; let m = submodule mailboxes; in either (listOf m) (attrsOf m);
|
||||
type = with types; coercedTo
|
||||
(listOf unspecified)
|
||||
(list: listToAttrs (map (entry: { name = entry.name; value = removeAttrs entry ["name"]; }) list))
|
||||
(attrsOf (submodule mailboxes));
|
||||
default = {};
|
||||
apply = x:
|
||||
if isList x then warn "Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.03!" x
|
||||
else mapAttrsToList (name: value:
|
||||
if value.name != null
|
||||
then throw ''
|
||||
When specifying dovecot2 mailboxes as attributes, declaring
|
||||
a `name'-attribute is prohibited! The name ${value.name} should
|
||||
be the attribute key!
|
||||
''
|
||||
else value // { inherit name; }
|
||||
) x;
|
||||
example = literalExample ''
|
||||
{
|
||||
Spam = { specialUse = "Junk"; auto = "create"; };
|
||||
@ -471,6 +464,10 @@ in
|
||||
|
||||
environment.systemPackages = [ dovecotPkg ];
|
||||
|
||||
warnings = mkIf (any isList options.services.dovecot2.mailboxes.definitions) [
|
||||
"Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.03! See the release notes for more info for migration."
|
||||
];
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != [];
|
||||
|
@ -172,7 +172,7 @@ in {
|
||||
};
|
||||
|
||||
database = mkOption {
|
||||
type = types.str;
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Database name to store sms data";
|
||||
};
|
||||
|
@ -50,6 +50,12 @@ in
|
||||
description = "Parse and interpret emoji tags";
|
||||
};
|
||||
|
||||
h1-title = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Use the first h1 as page title";
|
||||
};
|
||||
|
||||
branch = mkOption {
|
||||
type = types.str;
|
||||
default = "master";
|
||||
@ -102,6 +108,7 @@ in
|
||||
--ref ${cfg.branch} \
|
||||
${optionalString cfg.mathjax "--mathjax"} \
|
||||
${optionalString cfg.emoji "--emoji"} \
|
||||
${optionalString cfg.h1-title "--h1-title"} \
|
||||
${optionalString (cfg.allowUploads != null) "--allow-uploads ${cfg.allowUploads}"} \
|
||||
${cfg.stateDir}
|
||||
'';
|
||||
|
@ -68,8 +68,8 @@ in
|
||||
plugins = mkOption {
|
||||
default = plugins: [];
|
||||
defaultText = "plugins: []";
|
||||
example = literalExample "plugins: [ m3d-fio ]";
|
||||
description = "Additional plugins.";
|
||||
example = literalExample "plugins: with plugins; [ m33-fio stlviewer ]";
|
||||
description = "Additional plugins to be used. Available plugins are passed through the plugins input.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
|
@ -29,13 +29,15 @@ in {
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.ssm-agent = {
|
||||
users.extraUsers.ssm-user = {};
|
||||
|
||||
inherit (cfg.package.meta) description;
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
path = [ fake-lsb-release ];
|
||||
path = [ fake-lsb-release pkgs.coreutils ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/agent";
|
||||
ExecStart = "${cfg.package}/bin/amazon-ssm-agent";
|
||||
KillMode = "process";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "15min";
|
||||
|
@ -69,7 +69,7 @@ in {
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
system.nssModules = pkgs.sssd;
|
||||
system.nssModules = [ pkgs.sssd ];
|
||||
system.nssDatabases = {
|
||||
group = [ "sss" ];
|
||||
passwd = [ "sss" ];
|
||||
@ -92,4 +92,6 @@ in {
|
||||
services.openssh.authorizedKeysCommand = "/etc/ssh/authorized_keys_command";
|
||||
services.openssh.authorizedKeysCommandUser = "nobody";
|
||||
})];
|
||||
|
||||
meta.maintainers = with maintainers; [ bbigras ];
|
||||
}
|
||||
|
@ -4,19 +4,29 @@ with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.monit;
|
||||
extraConfig = pkgs.writeText "monitConfig" cfg.extraConfig;
|
||||
in
|
||||
|
||||
{
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "monit" "config" ] ["services" "monit" "extraConfig" ])
|
||||
];
|
||||
|
||||
options.services.monit = {
|
||||
|
||||
enable = mkEnableOption "Monit";
|
||||
|
||||
config = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "monitrc content";
|
||||
configFiles = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [];
|
||||
description = "List of paths to be included in the monitrc file";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Additional monit config as string";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
@ -24,7 +34,7 @@ in
|
||||
environment.systemPackages = [ pkgs.monit ];
|
||||
|
||||
environment.etc.monitrc = {
|
||||
text = cfg.config;
|
||||
text = concatMapStringsSep "\n" (path: "include ${path}") (cfg.configFiles ++ [extraConfig]);
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
|
@ -46,7 +46,7 @@ let
|
||||
cmdlineArgs = cfg.extraFlags ++ [
|
||||
"--storage.tsdb.path=${workingDir}/data/"
|
||||
"--config.file=${prometheusYml}"
|
||||
"--web.listen-address=${cfg.listenAddress}"
|
||||
"--web.listen-address=${cfg.listenAddress}:${builtins.toString cfg.port}"
|
||||
"--alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
|
||||
"--alertmanager.timeout=${toString cfg.alertmanagerTimeout}s"
|
||||
] ++
|
||||
@ -489,9 +489,17 @@ in {
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 9090;
|
||||
description = ''
|
||||
Port to listen on.
|
||||
'';
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0:9090";
|
||||
default = "0.0.0.0";
|
||||
description = ''
|
||||
Address to listen on for the web interface, API, and telemetry.
|
||||
'';
|
||||
@ -619,6 +627,21 @@ in {
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
( let
|
||||
legacy = builtins.match "(.*):(.*)" cfg.listenAddress;
|
||||
in {
|
||||
assertion = legacy == null;
|
||||
message = ''
|
||||
Do not specify the port for Prometheus to listen on in the
|
||||
listenAddress option; use the port option instead:
|
||||
services.prometheus.listenAddress = ${builtins.elemAt legacy 0};
|
||||
services.prometheus.port = ${builtins.elemAt legacy 1};
|
||||
'';
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
users.groups.prometheus.gid = config.ids.gids.prometheus;
|
||||
users.users.prometheus = {
|
||||
description = "Prometheus daemon user";
|
||||
|
@ -20,7 +20,7 @@ let
|
||||
${pkgs.coreutils}/bin/cat << EOF
|
||||
From: smartd on ${host} <${nm.sender}>
|
||||
To: undisclosed-recipients:;
|
||||
Subject: SMART error on $SMARTD_DEVICESTRING: $SMARTD_FAILTYPE
|
||||
Subject: $SMARTD_SUBJECT
|
||||
|
||||
$SMARTD_FULLMESSAGE
|
||||
EOF
|
||||
@ -239,11 +239,7 @@ in
|
||||
|
||||
systemd.services.smartd = {
|
||||
description = "S.M.A.R.T. Daemon";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
path = [ pkgs.nettools ]; # for hostname and dnsdomanname calls in smartd
|
||||
|
||||
serviceConfig.ExecStart = "${pkgs.smartmontools}/sbin/smartd ${lib.concatStringsSep " " cfg.extraOptions} --no-fork --configfile=${smartdConf}";
|
||||
};
|
||||
|
||||
|
@ -5,8 +5,8 @@ let
|
||||
pgsql = config.services.postgresql;
|
||||
mysql = config.services.mysql;
|
||||
|
||||
inherit (lib) mkDefault mkEnableOption mkIf mkMerge mkOption;
|
||||
inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
|
||||
inherit (lib) mkAfter mkDefault mkEnableOption mkIf mkMerge mkOption;
|
||||
inherit (lib) attrValues concatMapStringsSep getName literalExample optional optionalAttrs optionalString types;
|
||||
inherit (lib.generators) toKeyValue;
|
||||
|
||||
user = "zabbix";
|
||||
@ -232,14 +232,15 @@ in
|
||||
services.mysql = optionalAttrs mysqlLocal {
|
||||
enable = true;
|
||||
package = mkDefault pkgs.mariadb;
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
ensureUsers = [
|
||||
{ name = cfg.database.user;
|
||||
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.mysql.postStart = mkAfter (optionalString mysqlLocal ''
|
||||
( echo "CREATE DATABASE IF NOT EXISTS \`${cfg.database.name}\` CHARACTER SET utf8 COLLATE utf8_bin;"
|
||||
echo "CREATE USER IF NOT EXISTS '${cfg.database.user}'@'localhost' IDENTIFIED WITH ${if (getName config.services.mysql.package == getName pkgs.mariadb) then "unix_socket" else "auth_socket"};"
|
||||
echo "GRANT ALL PRIVILEGES ON \`${cfg.database.name}\`.* TO '${cfg.database.user}'@'localhost';"
|
||||
) | ${config.services.mysql.package}/bin/mysql -N
|
||||
'');
|
||||
|
||||
services.postgresql = optionalAttrs pgsqlLocal {
|
||||
enable = true;
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
|
@ -5,8 +5,8 @@ let
|
||||
pgsql = config.services.postgresql;
|
||||
mysql = config.services.mysql;
|
||||
|
||||
inherit (lib) mkDefault mkEnableOption mkIf mkMerge mkOption;
|
||||
inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
|
||||
inherit (lib) mkAfter mkDefault mkEnableOption mkIf mkMerge mkOption;
|
||||
inherit (lib) attrValues concatMapStringsSep getName literalExample optional optionalAttrs optionalString types;
|
||||
inherit (lib.generators) toKeyValue;
|
||||
|
||||
user = "zabbix";
|
||||
@ -220,14 +220,15 @@ in
|
||||
services.mysql = optionalAttrs mysqlLocal {
|
||||
enable = true;
|
||||
package = mkDefault pkgs.mariadb;
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
ensureUsers = [
|
||||
{ name = cfg.database.user;
|
||||
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.mysql.postStart = mkAfter (optionalString mysqlLocal ''
|
||||
( echo "CREATE DATABASE IF NOT EXISTS \`${cfg.database.name}\` CHARACTER SET utf8 COLLATE utf8_bin;"
|
||||
echo "CREATE USER IF NOT EXISTS '${cfg.database.user}'@'localhost' IDENTIFIED WITH ${if (getName config.services.mysql.package == getName pkgs.mariadb) then "unix_socket" else "auth_socket"};"
|
||||
echo "GRANT ALL PRIVILEGES ON \`${cfg.database.name}\`.* TO '${cfg.database.user}'@'localhost';"
|
||||
) | ${config.services.mysql.package}/bin/mysql -N
|
||||
'');
|
||||
|
||||
services.postgresql = optionalAttrs pgsqlLocal {
|
||||
enable = true;
|
||||
ensureDatabases = [ cfg.database.name ];
|
||||
|
@ -83,14 +83,14 @@ in {
|
||||
};
|
||||
|
||||
dataStorageSpace = mkOption {
|
||||
type = types.str;
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/data/storage";
|
||||
description = "Directory for data storage.";
|
||||
};
|
||||
|
||||
metadataStorageSpace = mkOption {
|
||||
type = types.str;
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/data/meta";
|
||||
description = "Directory for meta data storage.";
|
||||
|
@ -87,7 +87,7 @@ in
|
||||
};
|
||||
|
||||
rpc.password = mkOption {
|
||||
type = types.str;
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Password for RPC connections.
|
||||
|
@ -89,7 +89,7 @@ in
|
||||
};
|
||||
|
||||
rpc.password = mkOption {
|
||||
type = types.str;
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Password for RPC connections.
|
||||
|
@ -11,8 +11,13 @@ let
|
||||
method = cfg.encryptionMethod;
|
||||
mode = cfg.mode;
|
||||
user = "nobody";
|
||||
fast_open = true;
|
||||
} // optionalAttrs (cfg.password != null) { password = cfg.password; };
|
||||
fast_open = cfg.fastOpen;
|
||||
} // optionalAttrs (cfg.plugin != null) {
|
||||
plugin = cfg.plugin;
|
||||
plugin_opts = cfg.pluginOpts;
|
||||
} // optionalAttrs (cfg.password != null) {
|
||||
password = cfg.password;
|
||||
};
|
||||
|
||||
configFile = pkgs.writeText "shadowsocks.json" (builtins.toJSON opts);
|
||||
|
||||
@ -74,6 +79,14 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
fastOpen = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
use TCP fast-open
|
||||
'';
|
||||
};
|
||||
|
||||
encryptionMethod = mkOption {
|
||||
type = types.str;
|
||||
default = "chacha20-ietf-poly1305";
|
||||
@ -82,6 +95,23 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
plugin = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "\${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin";
|
||||
description = ''
|
||||
SIP003 plugin for shadowsocks
|
||||
'';
|
||||
};
|
||||
|
||||
pluginOpts = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "server;host=example.com";
|
||||
description = ''
|
||||
Options to pass to the plugin if one was specified
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
@ -99,7 +129,7 @@ in
|
||||
description = "shadowsocks-libev Daemon";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.shadowsocks-libev ] ++ optional (cfg.passwordFile != null) pkgs.jq;
|
||||
path = [ pkgs.shadowsocks-libev cfg.plugin ] ++ optional (cfg.passwordFile != null) pkgs.jq;
|
||||
serviceConfig.PrivateTmp = true;
|
||||
script = ''
|
||||
${optionalString (cfg.passwordFile != null) ''
|
||||
|
@ -233,6 +233,9 @@ in {
|
||||
path = [ pkgs.wpa_supplicant ];
|
||||
|
||||
script = ''
|
||||
if [ -f /etc/wpa_supplicant.conf -a "/etc/wpa_supplicant.conf" != "${configFile}" ]
|
||||
then echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
|
||||
fi
|
||||
iface_args="-s -u -D${cfg.driver} -c ${configFile}"
|
||||
${if ifaces == [] then ''
|
||||
for i in $(cd /sys/class/net && echo *); do
|
||||
|
@ -52,6 +52,14 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
lockMessage = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Message to show on physlock login terminal.
|
||||
'';
|
||||
};
|
||||
|
||||
lockOn = {
|
||||
|
||||
suspend = mkOption {
|
||||
@ -111,7 +119,7 @@ in
|
||||
++ cfg.lockOn.extraTargets;
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
ExecStart = "${pkgs.physlock}/bin/physlock -d${optionalString cfg.disableSysRq "s"}";
|
||||
ExecStart = "${pkgs.physlock}/bin/physlock -d${optionalString cfg.disableSysRq "s"}${optionalString (cfg.lockMessage != "") " -p \"${cfg.lockMessage}\""}";
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -77,7 +77,6 @@ in {
|
||||
// Paths
|
||||
WOSendMail = "/run/wrappers/bin/sendmail";
|
||||
SOGoMailSpoolPath = "/var/lib/sogo/spool";
|
||||
SOGoZipPath = "${pkgs.zip}/bin/zip";
|
||||
// Enable CSRF protection
|
||||
SOGoXSRFValidationEnabled = YES;
|
||||
// Remove dates from log (jornald does that)
|
||||
|
@ -661,6 +661,25 @@ in
|
||||
pkg
|
||||
];
|
||||
|
||||
services.logrotate = optionalAttrs (cfg.logFormat != "none") {
|
||||
enable = mkDefault true;
|
||||
paths.httpd = {
|
||||
path = "${cfg.logDir}/*.log";
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
frequency = "daily";
|
||||
keep = 28;
|
||||
extraConfig = ''
|
||||
sharedscripts
|
||||
compress
|
||||
delaycompress
|
||||
postrotate
|
||||
systemctl reload httpd.service > /dev/null 2>/dev/null || true
|
||||
endscript
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
services.httpd.phpOptions =
|
||||
''
|
||||
; Needed for PHP's mail() function.
|
||||
|
@ -1,174 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.meguca;
|
||||
postgres = config.services.postgresql;
|
||||
in with lib; {
|
||||
options.services.meguca = {
|
||||
enable = mkEnableOption "meguca";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/meguca";
|
||||
example = "/home/okina/meguca";
|
||||
description = "Location where meguca stores it's database and links.";
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.str;
|
||||
default = "meguca";
|
||||
example = "dumbpass";
|
||||
description = "Password for the meguca database.";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/run/keys/meguca-password-file";
|
||||
example = "/home/okina/meguca/keys/pass";
|
||||
description = "Password file for the meguca database.";
|
||||
};
|
||||
|
||||
reverseProxy = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "192.168.1.5";
|
||||
description = "Reverse proxy IP.";
|
||||
};
|
||||
|
||||
sslCertificate = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/home/okina/meguca/ssl.cert";
|
||||
description = "Path to the SSL certificate.";
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "127.0.0.1:8000";
|
||||
description = "Listen on a specific IP address and port.";
|
||||
};
|
||||
|
||||
cacheSize = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
example = 256;
|
||||
description = "Cache size in MB.";
|
||||
};
|
||||
|
||||
postgresArgs = mkOption {
|
||||
type = types.str;
|
||||
example = "user=meguca password=dumbpass dbname=meguca sslmode=disable";
|
||||
description = "Postgresql connection arguments.";
|
||||
};
|
||||
|
||||
postgresArgsFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/run/keys/meguca-postgres-args";
|
||||
example = "/home/okina/meguca/keys/postgres";
|
||||
description = "Postgresql connection arguments file.";
|
||||
};
|
||||
|
||||
compressTraffic = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Compress all traffic with gzip.";
|
||||
};
|
||||
|
||||
assumeReverseProxy = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Assume the server is behind a reverse proxy, when resolving client IPs.";
|
||||
};
|
||||
|
||||
httpsOnly = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Serve and listen only through HTTPS.";
|
||||
};
|
||||
|
||||
videoPaths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [];
|
||||
example = [ "/home/okina/Videos/tehe_pero.webm" ];
|
||||
description = "Videos that will be symlinked into www/videos.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
security.sudo.enable = cfg.enable;
|
||||
services.postgresql.enable = cfg.enable;
|
||||
services.postgresql.package = pkgs.postgresql_11;
|
||||
services.meguca.passwordFile = mkDefault (pkgs.writeText "meguca-password-file" cfg.password);
|
||||
services.meguca.postgresArgsFile = mkDefault (pkgs.writeText "meguca-postgres-args" cfg.postgresArgs);
|
||||
services.meguca.postgresArgs = mkDefault "user=meguca password=${cfg.password} dbname=meguca sslmode=disable";
|
||||
|
||||
systemd.services.meguca = {
|
||||
description = "meguca";
|
||||
after = [ "network.target" "postgresql.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = ''
|
||||
# Ensure folder exists or create it and links and permissions are correct
|
||||
mkdir -p ${escapeShellArg cfg.dataDir}/www
|
||||
rm -rf ${escapeShellArg cfg.dataDir}/www/videos
|
||||
ln -sf ${pkgs.meguca}/share/meguca/www/* ${escapeShellArg cfg.dataDir}/www
|
||||
unlink ${escapeShellArg cfg.dataDir}/www/videos
|
||||
mkdir -p ${escapeShellArg cfg.dataDir}/www/videos
|
||||
|
||||
for vid in ${escapeShellArg cfg.videoPaths}; do
|
||||
ln -sf $vid ${escapeShellArg cfg.dataDir}/www/videos
|
||||
done
|
||||
|
||||
chmod 750 ${escapeShellArg cfg.dataDir}
|
||||
chown -R meguca:meguca ${escapeShellArg cfg.dataDir}
|
||||
|
||||
# Ensure the database is correct or create it
|
||||
${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createuser \
|
||||
-SDR meguca || true
|
||||
${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createdb \
|
||||
-T template0 -E UTF8 -O meguca meguca || true
|
||||
${pkgs.sudo}/bin/sudo -u meguca ${postgres.package}/bin/psql \
|
||||
-c "ALTER ROLE meguca WITH PASSWORD '$(cat ${escapeShellArg cfg.passwordFile})';" || true
|
||||
'';
|
||||
|
||||
script = ''
|
||||
cd ${escapeShellArg cfg.dataDir}
|
||||
|
||||
${pkgs.meguca}/bin/meguca -d "$(cat ${escapeShellArg cfg.postgresArgsFile})"''
|
||||
+ optionalString (cfg.reverseProxy != null) " -R ${cfg.reverseProxy}"
|
||||
+ optionalString (cfg.sslCertificate != null) " -S ${cfg.sslCertificate}"
|
||||
+ optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}"
|
||||
+ optionalString (cfg.cacheSize != null) " -c ${toString cfg.cacheSize}"
|
||||
+ optionalString (cfg.compressTraffic) " -g"
|
||||
+ optionalString (cfg.assumeReverseProxy) " -r"
|
||||
+ optionalString (cfg.httpsOnly) " -s" + " start";
|
||||
|
||||
serviceConfig = {
|
||||
PermissionsStartOnly = true;
|
||||
Type = "forking";
|
||||
User = "meguca";
|
||||
Group = "meguca";
|
||||
ExecStop = "${pkgs.meguca}/bin/meguca stop";
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
groups.meguca.gid = config.ids.gids.meguca;
|
||||
|
||||
users.meguca = {
|
||||
description = "meguca server service user";
|
||||
home = cfg.dataDir;
|
||||
createHome = true;
|
||||
group = "meguca";
|
||||
uid = config.ids.uids.meguca;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "meguca" "baseDir" ] [ "services" "meguca" "dataDir" ])
|
||||
];
|
||||
|
||||
meta.maintainers = with maintainers; [ chiiruno ];
|
||||
}
|
@ -61,7 +61,8 @@ in
|
||||
"--kill"
|
||||
] ++ cfg.extraOptions);
|
||||
ExecStop = "${pkgs.procps}/bin/pkill imwheel";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 3;
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -641,7 +641,7 @@ in
|
||||
credential = mkOption {
|
||||
default = null;
|
||||
example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
|
||||
type = types.str;
|
||||
type = types.nullOr types.str;
|
||||
description = "The FIDO2 credential ID.";
|
||||
};
|
||||
|
||||
|
@ -378,12 +378,14 @@ mountFS() {
|
||||
|
||||
mkdir -p "/mnt-root$mountPoint"
|
||||
|
||||
# For CIFS mounts, retry a few times before giving up.
|
||||
# For ZFS and CIFS mounts, retry a few times before giving up.
|
||||
# We do this for ZFS as a workaround for issue NixOS/nixpkgs#25383.
|
||||
local n=0
|
||||
while true; do
|
||||
mount "/mnt-root$mountPoint" && break
|
||||
if [ "$fsType" != cifs -o "$n" -ge 10 ]; then fail; break; fi
|
||||
if [ \( "$fsType" != cifs -a "$fsType" != zfs \) -o "$n" -ge 10 ]; then fail; break; fi
|
||||
echo "retrying..."
|
||||
sleep 1
|
||||
n=$((n + 1))
|
||||
done
|
||||
|
||||
|
@ -36,7 +36,7 @@ let
|
||||
set -euo pipefail
|
||||
|
||||
declare -A seen
|
||||
declare -a left
|
||||
left=()
|
||||
|
||||
patchelf="${pkgs.buildPackages.patchelf}/bin/patchelf"
|
||||
|
||||
@ -48,7 +48,7 @@ let
|
||||
done
|
||||
}
|
||||
|
||||
add_needed $1
|
||||
add_needed "$1"
|
||||
|
||||
while [ ''${#left[@]} -ne 0 ]; do
|
||||
next=''${left[0]}
|
||||
@ -87,7 +87,9 @@ let
|
||||
# copy what we need. Instead of using statically linked binaries,
|
||||
# we just copy what we need from Glibc and use patchelf to make it
|
||||
# work.
|
||||
extraUtils = pkgs.runCommandCC "extra-utils"
|
||||
extraUtils = let
|
||||
# Use lvm2 without udev support, which is the same lvm2 we already have in the closure anyways
|
||||
lvm2 = pkgs.lvm2.override { udev = null; }; in pkgs.runCommandCC "extra-utils"
|
||||
{ nativeBuildInputs = [pkgs.buildPackages.nukeReferences];
|
||||
allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd
|
||||
}
|
||||
@ -111,8 +113,8 @@ let
|
||||
copy_bin_and_libs ${pkgs.utillinux}/sbin/blkid
|
||||
|
||||
# Copy dmsetup and lvm.
|
||||
copy_bin_and_libs ${getBin pkgs.lvm2}/bin/dmsetup
|
||||
copy_bin_and_libs ${getBin pkgs.lvm2}/bin/lvm
|
||||
copy_bin_and_libs ${getBin lvm2}/bin/dmsetup
|
||||
copy_bin_and_libs ${getBin lvm2}/bin/lvm
|
||||
|
||||
# Add RAID mdadm tool.
|
||||
copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm
|
||||
|
@ -25,7 +25,7 @@ let
|
||||
"nss-lookup.target"
|
||||
"nss-user-lookup.target"
|
||||
"time-sync.target"
|
||||
#"cryptsetup.target"
|
||||
"cryptsetup.target"
|
||||
"sigpwr.target"
|
||||
"timers.target"
|
||||
"paths.target"
|
||||
|
@ -1129,7 +1129,6 @@ in
|
||||
++ optionals config.networking.wireless.enable [
|
||||
pkgs.wirelesstools # FIXME: obsolete?
|
||||
pkgs.iw
|
||||
pkgs.rfkill
|
||||
]
|
||||
++ bridgeStp;
|
||||
|
||||
|
@ -110,6 +110,7 @@ in
|
||||
'';
|
||||
|
||||
environment.etc."cni/net.d/10-crio-bridge.conf".source = copyFile "${pkgs.cri-o-unwrapped.src}/contrib/cni/10-crio-bridge.conf";
|
||||
environment.etc."cni/net.d/99-loopback.conf".source = copyFile "${pkgs.cri-o-unwrapped.src}/contrib/cni/99-loopback.conf";
|
||||
|
||||
# Enable common /etc/containers configuration
|
||||
virtualisation.containers.enable = true;
|
||||
|
@ -1,134 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
with builtins;
|
||||
|
||||
let
|
||||
cfg = config.virtualisation;
|
||||
|
||||
sanitizeImageName = image: replaceStrings ["/"] ["-"] image.imageName;
|
||||
hash = drv: head (split "-" (baseNameOf drv.outPath));
|
||||
# The label of an ext4 FS is limited to 16 bytes
|
||||
labelFromImage = image: substring 0 16 (hash image);
|
||||
|
||||
# The Docker image is loaded and some files from /var/lib/docker/
|
||||
# are written into a qcow image.
|
||||
preload = image: pkgs.vmTools.runInLinuxVM (
|
||||
pkgs.runCommand "docker-preload-image-${sanitizeImageName image}" {
|
||||
buildInputs = with pkgs; [ docker e2fsprogs utillinux curl kmod ];
|
||||
preVM = pkgs.vmTools.createEmptyImage {
|
||||
size = cfg.dockerPreloader.qcowSize;
|
||||
fullName = "docker-deamon-image.qcow2";
|
||||
};
|
||||
}
|
||||
''
|
||||
mkfs.ext4 /dev/vda
|
||||
e2label /dev/vda ${labelFromImage image}
|
||||
mkdir -p /var/lib/docker
|
||||
mount -t ext4 /dev/vda /var/lib/docker
|
||||
|
||||
modprobe overlay
|
||||
|
||||
# from https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount
|
||||
mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
|
||||
cd /sys/fs/cgroup
|
||||
for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do
|
||||
mkdir -p $sys
|
||||
if ! mountpoint -q $sys; then
|
||||
if ! mount -n -t cgroup -o $sys cgroup $sys; then
|
||||
rmdir $sys || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
dockerd -H tcp://127.0.0.1:5555 -H unix:///var/run/docker.sock &
|
||||
|
||||
until $(curl --output /dev/null --silent --connect-timeout 2 http://127.0.0.1:5555); do
|
||||
printf '.'
|
||||
sleep 1
|
||||
done
|
||||
|
||||
docker load -i ${image}
|
||||
|
||||
kill %1
|
||||
find /var/lib/docker/ -maxdepth 1 -mindepth 1 -not -name "image" -not -name "overlay2" | xargs rm -rf
|
||||
'');
|
||||
|
||||
preloadedImages = map preload cfg.dockerPreloader.images;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options.virtualisation.dockerPreloader = {
|
||||
images = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.package;
|
||||
description =
|
||||
''
|
||||
A list of Docker images to preload (in the /var/lib/docker directory).
|
||||
'';
|
||||
};
|
||||
qcowSize = mkOption {
|
||||
default = 1024;
|
||||
type = types.int;
|
||||
description =
|
||||
''
|
||||
The size (MB) of qcow files.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.dockerPreloader.images != []) {
|
||||
assertions = [{
|
||||
# If docker.storageDriver is null, Docker choose the storage
|
||||
# driver. So, in this case, we cannot be sure overlay2 is used.
|
||||
assertion = cfg.docker.storageDriver == "overlay2"
|
||||
|| cfg.docker.storageDriver == "overlay"
|
||||
|| cfg.docker.storageDriver == null;
|
||||
message = "The Docker image Preloader only works with overlay2 storage driver!";
|
||||
}];
|
||||
|
||||
virtualisation.qemu.options =
|
||||
map (path: "-drive if=virtio,file=${path}/disk-image.qcow2,readonly,media=cdrom,format=qcow2")
|
||||
preloadedImages;
|
||||
|
||||
|
||||
# All attached QCOW files are mounted and their contents are linked
|
||||
# to /var/lib/docker/ in order to make image available.
|
||||
systemd.services.docker-preloader = {
|
||||
description = "Preloaded Docker images";
|
||||
wantedBy = ["docker.service"];
|
||||
after = ["network.target"];
|
||||
path = with pkgs; [ mount rsync jq ];
|
||||
script = ''
|
||||
mkdir -p /var/lib/docker/overlay2/l /var/lib/docker/image/overlay2
|
||||
echo '{}' > /tmp/repositories.json
|
||||
|
||||
for i in ${concatStringsSep " " (map labelFromImage cfg.dockerPreloader.images)}; do
|
||||
mkdir -p /mnt/docker-images/$i
|
||||
|
||||
# The ext4 label is limited to 16 bytes
|
||||
mount /dev/disk/by-label/$(echo $i | cut -c1-16) -o ro,noload /mnt/docker-images/$i
|
||||
|
||||
find /mnt/docker-images/$i/overlay2/ -maxdepth 1 -mindepth 1 -not -name l\
|
||||
-exec ln -s '{}' /var/lib/docker/overlay2/ \;
|
||||
cp -P /mnt/docker-images/$i/overlay2/l/* /var/lib/docker/overlay2/l/
|
||||
|
||||
rsync -a /mnt/docker-images/$i/image/ /var/lib/docker/image/
|
||||
|
||||
# Accumulate image definitions
|
||||
cp /tmp/repositories.json /tmp/repositories.json.tmp
|
||||
jq -s '.[0] * .[1]' \
|
||||
/tmp/repositories.json.tmp \
|
||||
/mnt/docker-images/$i/image/overlay2/repositories.json \
|
||||
> /tmp/repositories.json
|
||||
done
|
||||
|
||||
mv /tmp/repositories.json /var/lib/docker/image/overlay2/repositories.json
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -32,7 +32,7 @@ in
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
type = types.nullOr types.package;
|
||||
default = config.boot.kernelPackages.prl-tools;
|
||||
defaultText = "config.boot.kernelPackages.prl-tools";
|
||||
example = literalExample "config.boot.kernelPackages.prl-tools";
|
||||
|
@ -264,7 +264,6 @@ in
|
||||
{
|
||||
imports = [
|
||||
../profiles/qemu-guest.nix
|
||||
./docker-preloader.nix
|
||||
];
|
||||
|
||||
options = {
|
||||
|
@ -34,6 +34,7 @@ in
|
||||
bind = handleTest ./bind.nix {};
|
||||
bitcoind = handleTest ./bitcoind.nix {};
|
||||
bittorrent = handleTest ./bittorrent.nix {};
|
||||
bitwarden = handleTest ./bitwarden.nix {};
|
||||
blockbook-frontend = handleTest ./blockbook-frontend.nix {};
|
||||
buildkite-agents = handleTest ./buildkite-agents.nix {};
|
||||
boot = handleTestOn ["x86_64-linux"] ./boot.nix {}; # syslinux is unsupported on aarch64
|
||||
@ -65,11 +66,13 @@ in
|
||||
containers-macvlans = handleTest ./containers-macvlans.nix {};
|
||||
containers-physical_interfaces = handleTest ./containers-physical_interfaces.nix {};
|
||||
containers-portforward = handleTest ./containers-portforward.nix {};
|
||||
containers-reloadable = handleTest ./containers-reloadable.nix {};
|
||||
containers-restart_networking = handleTest ./containers-restart_networking.nix {};
|
||||
containers-tmpfs = handleTest ./containers-tmpfs.nix {};
|
||||
convos = handleTest ./convos.nix {};
|
||||
corerad = handleTest ./corerad.nix {};
|
||||
couchdb = handleTest ./couchdb.nix {};
|
||||
cri-o = handleTestOn ["x86_64-linux"] ./cri-o.nix {};
|
||||
deluge = handleTest ./deluge.nix {};
|
||||
dhparams = handleTest ./dhparams.nix {};
|
||||
dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {};
|
||||
@ -78,15 +81,13 @@ in
|
||||
docker = handleTestOn ["x86_64-linux"] ./docker.nix {};
|
||||
oci-containers = handleTestOn ["x86_64-linux"] ./oci-containers.nix {};
|
||||
docker-edge = handleTestOn ["x86_64-linux"] ./docker-edge.nix {};
|
||||
docker-preloader = handleTestOn ["x86_64-linux"] ./docker-preloader.nix {};
|
||||
docker-registry = handleTest ./docker-registry.nix {};
|
||||
docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {};
|
||||
docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {};
|
||||
documize = handleTest ./documize.nix {};
|
||||
dokuwiki = handleTest ./dokuwiki.nix {};
|
||||
dovecot = handleTest ./dovecot.nix {};
|
||||
# ec2-config doesn't work in a sandbox as the simulated ec2 instance needs network access
|
||||
#ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
|
||||
ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
|
||||
ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
|
||||
ecryptfs = handleTest ./ecryptfs.nix {};
|
||||
ejabberd = handleTest ./xmpp/ejabberd.nix {};
|
||||
@ -306,6 +307,7 @@ in
|
||||
sanoid = handleTest ./sanoid.nix {};
|
||||
sddm = handleTest ./sddm.nix {};
|
||||
service-runner = handleTest ./service-runner.nix {};
|
||||
shadowsocks = handleTest ./shadowsocks.nix {};
|
||||
shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {};
|
||||
shiori = handleTest ./shiori.nix {};
|
||||
signal-desktop = handleTest ./signal-desktop.nix {};
|
||||
@ -320,6 +322,7 @@ in
|
||||
spike = handleTest ./spike.nix {};
|
||||
sonarr = handleTest ./sonarr.nix {};
|
||||
sslh = handleTest ./sslh.nix {};
|
||||
sssd = handleTestOn ["x86_64-linux"] ./sssd.nix {};
|
||||
strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
|
||||
sudo = handleTest ./sudo.nix {};
|
||||
switchTest = handleTest ./switch-test.nix {};
|
||||
|
188
nixos/tests/bitwarden.nix
Normal file
188
nixos/tests/bitwarden.nix
Normal file
@ -0,0 +1,188 @@
|
||||
{ system ? builtins.currentSystem
|
||||
, config ? { }
|
||||
, pkgs ? import ../.. { inherit system config; }
|
||||
}:
|
||||
|
||||
# These tests will:
|
||||
# * Set up a bitwarden-rs server
|
||||
# * Have Firefox use the web vault to create an account, log in, and save a password to the valut
|
||||
# * Have the bw cli log in and read that password from the vault
|
||||
#
|
||||
# Note that Firefox must be on the same machine as the server for WebCrypto APIs to be available (or HTTPS must be configured)
|
||||
#
|
||||
# The same tests should work without modification on the official bitwarden server, if we ever package that.
|
||||
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
with pkgs.lib;
|
||||
let
|
||||
backends = [ "sqlite" "mysql" "postgresql" ];
|
||||
|
||||
dbPassword = "please_dont_hack";
|
||||
|
||||
userEmail = "meow@example.com";
|
||||
userPassword = "also_super_secret_ZJWpBKZi668QGt"; # Must be complex to avoid interstitial warning on the signup page
|
||||
|
||||
storedPassword = "seeeecret";
|
||||
|
||||
makeBitwardenTest = backend: makeTest {
|
||||
name = "bitwarden_rs-${backend}";
|
||||
meta = {
|
||||
maintainers = with pkgs.stdenv.lib.maintainers; [ jjjollyjim ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
server = { pkgs, ... }:
|
||||
let backendConfig = {
|
||||
mysql = {
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
initialScript = pkgs.writeText "mysql-init.sql" ''
|
||||
CREATE DATABASE bitwarden;
|
||||
CREATE USER 'bitwardenuser'@'localhost' IDENTIFIED BY '${dbPassword}';
|
||||
GRANT ALL ON `bitwarden`.* TO 'bitwardenuser'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
'';
|
||||
package = pkgs.mysql;
|
||||
};
|
||||
|
||||
services.bitwarden_rs.config.databaseUrl = "mysql://bitwardenuser:${dbPassword}@localhost/bitwarden";
|
||||
|
||||
systemd.services.bitwarden_rs.after = [ "mysql.service" ];
|
||||
};
|
||||
|
||||
postgresql = {
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
initialScript = pkgs.writeText "postgresql-init.sql" ''
|
||||
CREATE DATABASE bitwarden;
|
||||
CREATE USER bitwardenuser WITH PASSWORD '${dbPassword}';
|
||||
GRANT ALL PRIVILEGES ON DATABASE bitwarden TO bitwardenuser;
|
||||
'';
|
||||
};
|
||||
|
||||
services.bitwarden_rs.config.databaseUrl = "postgresql://bitwardenuser:${dbPassword}@localhost/bitwarden";
|
||||
|
||||
systemd.services.bitwarden_rs.after = [ "postgresql.service" ];
|
||||
};
|
||||
|
||||
sqlite = { };
|
||||
};
|
||||
in
|
||||
mkMerge [
|
||||
backendConfig.${backend}
|
||||
{
|
||||
services.bitwarden_rs = {
|
||||
enable = true;
|
||||
dbBackend = backend;
|
||||
config.rocketPort = 80;
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
|
||||
environment.systemPackages =
|
||||
let
|
||||
testRunner = pkgs.writers.writePython3Bin "test-runner"
|
||||
{
|
||||
libraries = [ pkgs.python3Packages.selenium ];
|
||||
} ''
|
||||
from selenium.webdriver import Firefox
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
options = Options()
|
||||
options.add_argument('--headless')
|
||||
driver = Firefox(options=options)
|
||||
|
||||
driver.implicitly_wait(20)
|
||||
driver.get('http://localhost/#/register')
|
||||
|
||||
wait = WebDriverWait(driver, 10)
|
||||
|
||||
wait.until(EC.title_contains("Create Account"))
|
||||
|
||||
driver.find_element_by_css_selector('input#email').send_keys(
|
||||
'${userEmail}'
|
||||
)
|
||||
driver.find_element_by_css_selector('input#name').send_keys(
|
||||
'A Cat'
|
||||
)
|
||||
driver.find_element_by_css_selector('input#masterPassword').send_keys(
|
||||
'${userPassword}'
|
||||
)
|
||||
driver.find_element_by_css_selector('input#masterPasswordRetype').send_keys(
|
||||
'${userPassword}'
|
||||
)
|
||||
|
||||
driver.find_element_by_xpath("//button[contains(., 'Submit')]").click()
|
||||
|
||||
wait.until_not(EC.title_contains("Create Account"))
|
||||
|
||||
driver.find_element_by_css_selector('input#masterPassword').send_keys(
|
||||
'${userPassword}'
|
||||
)
|
||||
driver.find_element_by_xpath("//button[contains(., 'Log In')]").click()
|
||||
|
||||
wait.until(EC.title_contains("My Vault"))
|
||||
|
||||
driver.find_element_by_xpath("//button[contains(., 'Add Item')]").click()
|
||||
|
||||
driver.find_element_by_css_selector('input#name').send_keys(
|
||||
'secrets'
|
||||
)
|
||||
driver.find_element_by_css_selector('input#loginPassword').send_keys(
|
||||
'${storedPassword}'
|
||||
)
|
||||
|
||||
driver.find_element_by_xpath("//button[contains(., 'Save')]").click()
|
||||
'';
|
||||
in
|
||||
[ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
|
||||
|
||||
virtualisation.memorySize = 768;
|
||||
}
|
||||
];
|
||||
|
||||
client = { pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [ pkgs.bitwarden-cli ];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
server.wait_for_unit("bitwarden_rs.service")
|
||||
server.wait_for_open_port(80)
|
||||
|
||||
with subtest("configure the cli"):
|
||||
client.succeed("bw --nointeraction config server http://server")
|
||||
|
||||
with subtest("can't login to nonexistant account"):
|
||||
client.fail(
|
||||
"bw --nointeraction --raw login ${userEmail} ${userPassword}"
|
||||
)
|
||||
|
||||
with subtest("use the web interface to sign up, log in, and save a password"):
|
||||
server.succeed("PYTHONUNBUFFERED=1 test-runner | systemd-cat -t test-runner")
|
||||
|
||||
with subtest("log in with the cli"):
|
||||
key = client.succeed(
|
||||
"bw --nointeraction --raw login ${userEmail} ${userPassword}"
|
||||
).strip()
|
||||
|
||||
with subtest("sync with the cli"):
|
||||
client.succeed(f"bw --nointeraction --raw --session {key} sync -f")
|
||||
|
||||
with subtest("get the password with the cli"):
|
||||
password = client.succeed(
|
||||
f"bw --nointeraction --raw --session {key} list items | ${pkgs.jq}/bin/jq -r .[].login.password"
|
||||
)
|
||||
assert password.strip() == "${storedPassword}"
|
||||
'';
|
||||
};
|
||||
in
|
||||
builtins.listToAttrs (
|
||||
map
|
||||
(backend: { name = backend; value = makeBitwardenTest backend; })
|
||||
backends
|
||||
)
|
@ -20,30 +20,44 @@ with pkgs.lib;
|
||||
in makeTest {
|
||||
name = "ec2-" + name;
|
||||
nodes = {};
|
||||
testScript =
|
||||
''
|
||||
my $imageDir = ($ENV{'TMPDIR'} // "/tmp") . "/vm-state-machine";
|
||||
mkdir $imageDir, 0700;
|
||||
my $diskImage = "$imageDir/machine.qcow2";
|
||||
system("qemu-img create -f qcow2 -o backing_file=${image} $diskImage") == 0 or die;
|
||||
system("qemu-img resize $diskImage 10G") == 0 or die;
|
||||
testScript = ''
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# Note: we use net=169.0.0.0/8 rather than
|
||||
# net=169.254.0.0/16 to prevent dhcpcd from getting horribly
|
||||
# confused. (It would get a DHCP lease in the 169.254.*
|
||||
# range, which it would then configure and prompty delete
|
||||
# again when it deletes link-local addresses.) Ideally we'd
|
||||
# turn off the DHCP server, but qemu does not have an option
|
||||
# to do that.
|
||||
my $startCommand = "qemu-kvm -m 1024";
|
||||
$startCommand .= " -device virtio-net-pci,netdev=vlan0";
|
||||
$startCommand .= " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'";
|
||||
$startCommand .= " -drive file=$diskImage,if=virtio,werror=report";
|
||||
$startCommand .= " \$QEMU_OPTS";
|
||||
image_dir = os.path.join(
|
||||
os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine"
|
||||
)
|
||||
os.makedirs(image_dir, mode=0o700, exist_ok=True)
|
||||
disk_image = os.path.join(image_dir, "machine.qcow2")
|
||||
subprocess.check_call(
|
||||
[
|
||||
"qemu-img",
|
||||
"create",
|
||||
"-f",
|
||||
"qcow2",
|
||||
"-o",
|
||||
"backing_file=${image}",
|
||||
disk_image,
|
||||
]
|
||||
)
|
||||
subprocess.check_call(["qemu-img", "resize", disk_image, "10G"])
|
||||
|
||||
my $machine = createMachine({ startCommand => $startCommand });
|
||||
# Note: we use net=169.0.0.0/8 rather than
|
||||
# net=169.254.0.0/16 to prevent dhcpcd from getting horribly
|
||||
# confused. (It would get a DHCP lease in the 169.254.*
|
||||
# range, which it would then configure and prompty delete
|
||||
# again when it deletes link-local addresses.) Ideally we'd
|
||||
# turn off the DHCP server, but qemu does not have an option
|
||||
# to do that.
|
||||
start_command = (
|
||||
"qemu-kvm -m 1024"
|
||||
+ " -device virtio-net-pci,netdev=vlan0"
|
||||
+ " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'"
|
||||
+ f" -drive file={disk_image},if=virtio,werror=report"
|
||||
+ " $QEMU_OPTS"
|
||||
)
|
||||
|
||||
${script}
|
||||
'';
|
||||
machine = create_machine({"startCommand": start_command})
|
||||
'' + script;
|
||||
};
|
||||
}
|
||||
|
@ -9,13 +9,13 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
# prevent make-test.nix to change IP
|
||||
# prevent make-test-python.nix to change IP
|
||||
networking.interfaces = {
|
||||
eth1.ipv4.addresses = lib.mkOverride 0 [ ];
|
||||
};
|
||||
};
|
||||
in {
|
||||
name = "cotnainers-reloadable";
|
||||
name = "containers-reloadable";
|
||||
meta = with pkgs.stdenv.lib.maintainers; {
|
||||
maintainers = [ danbst ];
|
||||
};
|
||||
|
19
nixos/tests/cri-o.nix
Normal file
19
nixos/tests/cri-o.nix
Normal file
@ -0,0 +1,19 @@
|
||||
# This test runs CRI-O and verifies via critest
|
||||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "cri-o";
|
||||
maintainers = with pkgs.stdenv.lib.maintainers; teams.podman.members;
|
||||
|
||||
nodes = {
|
||||
crio = {
|
||||
virtualisation.cri-o.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
crio.wait_for_unit("crio.service")
|
||||
crio.succeed(
|
||||
"critest --ginkgo.focus='Runtime info' --runtime-endpoint unix:///var/run/crio/crio.sock"
|
||||
)
|
||||
'';
|
||||
})
|
@ -1,27 +0,0 @@
|
||||
import ./make-test.nix ({ pkgs, ...} : {
|
||||
name = "docker-preloader";
|
||||
meta = with pkgs.stdenv.lib.maintainers; {
|
||||
maintainers = [ lewo ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
docker =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
virtualisation.docker.enable = true;
|
||||
virtualisation.dockerPreloader.images = [ pkgs.dockerTools.examples.nix pkgs.dockerTools.examples.bash ];
|
||||
|
||||
services.openssh.enable = true;
|
||||
services.openssh.permitRootLogin = "yes";
|
||||
services.openssh.extraConfig = "PermitEmptyPasswords yes";
|
||||
users.extraUsers.root.password = "";
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
startAll;
|
||||
|
||||
$docker->waitForUnit("sockets.target");
|
||||
$docker->succeed("docker run nix nix-store --version");
|
||||
$docker->succeed("docker run bash bash --version");
|
||||
'';
|
||||
})
|
@ -3,58 +3,58 @@
|
||||
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 import common/ec2.nix { inherit makeTest pkgs; };
|
||||
|
||||
let
|
||||
imageCfg =
|
||||
(import ../lib/eval-config.nix {
|
||||
inherit system;
|
||||
modules = [
|
||||
../maintainers/scripts/ec2/amazon-image.nix
|
||||
../modules/testing/test-instrumentation.nix
|
||||
../modules/profiles/qemu-guest.nix
|
||||
{ ec2.hvm = true;
|
||||
imageCfg = (import ../lib/eval-config.nix {
|
||||
inherit system;
|
||||
modules = [
|
||||
../maintainers/scripts/ec2/amazon-image.nix
|
||||
../modules/testing/test-instrumentation.nix
|
||||
../modules/profiles/qemu-guest.nix
|
||||
{
|
||||
ec2.hvm = true;
|
||||
|
||||
# Hack to make the partition resizing work in QEMU.
|
||||
boot.initrd.postDeviceCommands = mkBefore
|
||||
''
|
||||
ln -s vda /dev/xvda
|
||||
ln -s vda1 /dev/xvda1
|
||||
'';
|
||||
# Hack to make the partition resizing work in QEMU.
|
||||
boot.initrd.postDeviceCommands = mkBefore ''
|
||||
ln -s vda /dev/xvda
|
||||
ln -s vda1 /dev/xvda1
|
||||
'';
|
||||
|
||||
# Needed by nixos-rebuild due to the lack of network
|
||||
# access. Determined by trial and error.
|
||||
system.extraDependencies =
|
||||
with pkgs; (
|
||||
[
|
||||
# Needed for a nixos-rebuild.
|
||||
busybox
|
||||
stdenv
|
||||
stdenvNoCC
|
||||
mkinitcpio-nfs-utils
|
||||
unionfs-fuse
|
||||
cloud-utils
|
||||
desktop-file-utils
|
||||
texinfo
|
||||
libxslt.bin
|
||||
xorg.lndir
|
||||
# Needed by nixos-rebuild due to the lack of network
|
||||
# access. Determined by trial and error.
|
||||
system.extraDependencies = with pkgs; ( [
|
||||
# Needed for a nixos-rebuild.
|
||||
busybox
|
||||
cloud-utils
|
||||
desktop-file-utils
|
||||
libxslt.bin
|
||||
mkinitcpio-nfs-utils
|
||||
stdenv
|
||||
stdenvNoCC
|
||||
texinfo
|
||||
unionfs-fuse
|
||||
xorg.lndir
|
||||
|
||||
# These are used in the configure-from-userdata tests
|
||||
# for EC2. Httpd and valgrind are requested by the
|
||||
# configuration.
|
||||
apacheHttpd apacheHttpd.doc apacheHttpd.man valgrind.doc
|
||||
]
|
||||
);
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
# These are used in the configure-from-userdata tests
|
||||
# for EC2. Httpd and valgrind are requested by the
|
||||
# configuration.
|
||||
apacheHttpd
|
||||
apacheHttpd.doc
|
||||
apacheHttpd.man
|
||||
valgrind.doc
|
||||
]);
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
image = "${imageCfg.system.build.amazonImage}/${imageCfg.amazonImage.name}.vhd";
|
||||
|
||||
sshKeys = import ./ssh-keys.nix pkgs;
|
||||
snakeOilPrivateKey = sshKeys.snakeOilPrivateKey.text;
|
||||
snakeOilPrivateKeyFile = pkgs.writeText "private-key" snakeOilPrivateKey;
|
||||
snakeOilPublicKey = sshKeys.snakeOilPublicKey;
|
||||
|
||||
in {
|
||||
@ -68,43 +68,47 @@ in {
|
||||
SSH_HOST_ED25519_KEY:${replaceStrings ["\n"] ["|"] snakeOilPrivateKey}
|
||||
'';
|
||||
script = ''
|
||||
$machine->start;
|
||||
$machine->waitForFile("/etc/ec2-metadata/user-data");
|
||||
$machine->waitForUnit("sshd.service");
|
||||
machine.start()
|
||||
machine.wait_for_file("/etc/ec2-metadata/user-data")
|
||||
machine.wait_for_unit("sshd.service")
|
||||
|
||||
$machine->succeed("grep unknown /etc/ec2-metadata/ami-manifest-path");
|
||||
machine.succeed("grep unknown /etc/ec2-metadata/ami-manifest-path")
|
||||
|
||||
# We have no keys configured on the client side yet, so this should fail
|
||||
$machine->fail("ssh -o BatchMode=yes localhost exit");
|
||||
machine.fail("ssh -o BatchMode=yes localhost exit")
|
||||
|
||||
# Let's install our client private key
|
||||
$machine->succeed("mkdir -p ~/.ssh");
|
||||
machine.succeed("mkdir -p ~/.ssh")
|
||||
|
||||
$machine->succeed("echo '${snakeOilPrivateKey}' > ~/.ssh/id_ed25519");
|
||||
$machine->succeed("chmod 600 ~/.ssh/id_ed25519");
|
||||
machine.copy_from_host_via_shell(
|
||||
"${snakeOilPrivateKeyFile}", "~/.ssh/id_ed25519"
|
||||
)
|
||||
machine.succeed("chmod 600 ~/.ssh/id_ed25519")
|
||||
|
||||
# We haven't configured the host key yet, so this should still fail
|
||||
$machine->fail("ssh -o BatchMode=yes localhost exit");
|
||||
machine.fail("ssh -o BatchMode=yes localhost exit")
|
||||
|
||||
# Add the host key; ssh should finally succeed
|
||||
$machine->succeed("echo localhost,127.0.0.1 ${snakeOilPublicKey} > ~/.ssh/known_hosts");
|
||||
$machine->succeed("ssh -o BatchMode=yes localhost exit");
|
||||
machine.succeed(
|
||||
"echo localhost,127.0.0.1 ${snakeOilPublicKey} > ~/.ssh/known_hosts"
|
||||
)
|
||||
machine.succeed("ssh -o BatchMode=yes localhost exit")
|
||||
|
||||
# Test whether the root disk was resized.
|
||||
my $blocks = $machine->succeed("stat -c %b -f /");
|
||||
my $bsize = $machine->succeed("stat -c %S -f /");
|
||||
my $size = $blocks * $bsize;
|
||||
die "wrong free space $size" if $size < 9.7 * 1024 * 1024 * 1024 || $size > 10 * 1024 * 1024 * 1024;
|
||||
blocks, block_size = map(int, machine.succeed("stat -c %b:%S -f /").split(":"))
|
||||
GB = 1024 ** 3
|
||||
assert 9.7 * GB <= blocks * block_size <= 10 * GB
|
||||
|
||||
# Just to make sure resizing is idempotent.
|
||||
$machine->shutdown;
|
||||
$machine->start;
|
||||
$machine->waitForFile("/etc/ec2-metadata/user-data");
|
||||
machine.shutdown()
|
||||
machine.start()
|
||||
machine.wait_for_file("/etc/ec2-metadata/user-data")
|
||||
'';
|
||||
};
|
||||
|
||||
boot-ec2-config = makeEc2Test {
|
||||
name = "config-userdata";
|
||||
meta.broken = true; # amazon-init wants to download from the internet while building the system
|
||||
inherit image;
|
||||
sshPublicKey = snakeOilPublicKey;
|
||||
|
||||
@ -133,17 +137,17 @@ in {
|
||||
}
|
||||
'';
|
||||
script = ''
|
||||
$machine->start;
|
||||
machine.start()
|
||||
|
||||
# amazon-init must succeed. if it fails, make the test fail
|
||||
# immediately instead of timing out in waitForFile.
|
||||
$machine->waitForUnit('amazon-init.service');
|
||||
# immediately instead of timing out in wait_for_file.
|
||||
machine.wait_for_unit("amazon-init.service")
|
||||
|
||||
$machine->waitForFile("/etc/testFile");
|
||||
$machine->succeed("cat /etc/testFile | grep -q 'whoa'");
|
||||
machine.wait_for_file("/etc/testFile")
|
||||
assert "whoa" in machine.succeed("cat /etc/testFile")
|
||||
|
||||
$machine->waitForUnit("httpd.service");
|
||||
$machine->succeed("curl http://localhost | grep Valgrind");
|
||||
machine.wait_for_unit("httpd.service")
|
||||
assert "Valgrind" in machine.succeed("curl http://localhost")
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
@ -23,6 +23,13 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
|
||||
services.xserver.desktopManager.gnome3.enable = true;
|
||||
services.xserver.desktopManager.gnome3.debug = true;
|
||||
|
||||
environment.systemPackages = [
|
||||
(pkgs.makeAutostartItem {
|
||||
name = "org.gnome.Terminal";
|
||||
package = pkgs.gnome3.gnome-terminal;
|
||||
})
|
||||
];
|
||||
|
||||
virtualisation.memorySize = 1024;
|
||||
};
|
||||
|
||||
@ -65,9 +72,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
|
||||
)
|
||||
|
||||
with subtest("Open Gnome Terminal"):
|
||||
machine.succeed(
|
||||
"${gnomeTerminalCommand}"
|
||||
)
|
||||
# correct output should be (true, '"gnome-terminal-server"')
|
||||
machine.wait_until_succeeds(
|
||||
"${wmClass} | grep -q 'gnome-terminal-server'"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import ./make-test.nix ({ pkgs, latestKernel ? false, ... } : {
|
||||
import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... } : {
|
||||
name = "hardened";
|
||||
meta = with pkgs.stdenv.lib.maintainers; {
|
||||
maintainers = [ joachifm ];
|
||||
@ -47,84 +47,88 @@ import ./make-test.nix ({ pkgs, latestKernel ? false, ... } : {
|
||||
};
|
||||
in
|
||||
''
|
||||
$machine->waitForUnit("multi-user.target");
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
|
||||
with subtest("AppArmor profiles are loaded"):
|
||||
machine.succeed("systemctl status apparmor.service")
|
||||
|
||||
subtest "apparmor-loaded", sub {
|
||||
$machine->succeed("systemctl status apparmor.service");
|
||||
};
|
||||
|
||||
# AppArmor securityfs
|
||||
subtest "apparmor-securityfs", sub {
|
||||
$machine->succeed("mountpoint -q /sys/kernel/security");
|
||||
$machine->succeed("cat /sys/kernel/security/apparmor/profiles");
|
||||
};
|
||||
with subtest("AppArmor securityfs is mounted"):
|
||||
machine.succeed("mountpoint -q /sys/kernel/security")
|
||||
machine.succeed("cat /sys/kernel/security/apparmor/profiles")
|
||||
|
||||
|
||||
# Test loading out-of-tree modules
|
||||
subtest "extra-module-packages", sub {
|
||||
$machine->succeed("grep -Fq wireguard /proc/modules");
|
||||
};
|
||||
with subtest("Out-of-tree modules can be loaded"):
|
||||
machine.succeed("grep -Fq wireguard /proc/modules")
|
||||
|
||||
|
||||
# Test hidepid
|
||||
subtest "hidepid", sub {
|
||||
$machine->succeed("grep -Fq hidepid=2 /proc/mounts");
|
||||
with subtest("hidepid=2 option is applied and works"):
|
||||
machine.succeed("grep -Fq hidepid=2 /proc/mounts")
|
||||
# cannot use pgrep -u here, it segfaults when access to process info is denied
|
||||
$machine->succeed("[ `su - sybil -c 'ps --no-headers --user root | wc -l'` = 0 ]");
|
||||
$machine->succeed("[ `su - alice -c 'ps --no-headers --user root | wc -l'` != 0 ]");
|
||||
};
|
||||
machine.succeed("[ `su - sybil -c 'ps --no-headers --user root | wc -l'` = 0 ]")
|
||||
machine.succeed("[ `su - alice -c 'ps --no-headers --user root | wc -l'` != 0 ]")
|
||||
|
||||
|
||||
# Test kernel module hardening
|
||||
subtest "lock-modules", sub {
|
||||
with subtest("No more kernel modules can be loaded"):
|
||||
# note: this better a be module we normally wouldn't load ...
|
||||
$machine->fail("modprobe dccp");
|
||||
};
|
||||
machine.fail("modprobe dccp")
|
||||
|
||||
|
||||
# Test userns
|
||||
subtest "userns", sub {
|
||||
$machine->succeed("unshare --user true");
|
||||
$machine->fail("su -l alice -c 'unshare --user true'");
|
||||
};
|
||||
with subtest("User namespaces are restricted"):
|
||||
machine.succeed("unshare --user true")
|
||||
machine.fail("su -l alice -c 'unshare --user true'")
|
||||
|
||||
|
||||
# Test dmesg restriction
|
||||
subtest "dmesg", sub {
|
||||
$machine->fail("su -l alice -c dmesg");
|
||||
};
|
||||
with subtest("Regular users cannot access dmesg"):
|
||||
machine.fail("su -l alice -c dmesg")
|
||||
|
||||
|
||||
# Test access to kcore
|
||||
subtest "kcore", sub {
|
||||
$machine->fail("cat /proc/kcore");
|
||||
};
|
||||
with subtest("Kcore is inaccessible as root"):
|
||||
machine.fail("cat /proc/kcore")
|
||||
|
||||
|
||||
# Test deferred mount
|
||||
subtest "mount", sub {
|
||||
$machine->fail("mountpoint -q /efi"); # was deferred
|
||||
$machine->execute("mkdir -p /efi");
|
||||
$machine->succeed("mount /dev/disk/by-label/EFISYS /efi");
|
||||
$machine->succeed("mountpoint -q /efi"); # now mounted
|
||||
};
|
||||
with subtest("Deferred mounts work"):
|
||||
machine.fail("mountpoint -q /efi") # was deferred
|
||||
machine.execute("mkdir -p /efi")
|
||||
machine.succeed("mount /dev/disk/by-label/EFISYS /efi")
|
||||
machine.succeed("mountpoint -q /efi") # now mounted
|
||||
|
||||
|
||||
# Test Nix dæmon usage
|
||||
subtest "nix-daemon", sub {
|
||||
$machine->fail("su -l nobody -s /bin/sh -c 'nix ping-store'");
|
||||
$machine->succeed("su -l alice -c 'nix ping-store'") =~ "OK";
|
||||
};
|
||||
with subtest("nix-daemon cannot be used by all users"):
|
||||
machine.fail("su -l nobody -s /bin/sh -c 'nix ping-store'")
|
||||
machine.succeed("su -l alice -c 'nix ping-store'")
|
||||
|
||||
|
||||
# Test kernel image protection
|
||||
subtest "kernelimage", sub {
|
||||
$machine->fail("systemctl hibernate");
|
||||
$machine->fail("systemctl kexec");
|
||||
};
|
||||
with subtest("The kernel image is protected"):
|
||||
machine.fail("systemctl hibernate")
|
||||
machine.fail("systemctl kexec")
|
||||
|
||||
|
||||
# Test hardened memory allocator
|
||||
sub runMallocTestProg {
|
||||
my ($progName, $errorText) = @_;
|
||||
my $text = "fatal allocator error: " . $errorText;
|
||||
$machine->fail("${hardened-malloc-tests}/bin/" . $progName) =~ $text;
|
||||
};
|
||||
def runMallocTestProg(prog_name, error_text):
|
||||
text = "fatal allocator error: " + error_text
|
||||
if not text in machine.fail(
|
||||
"${hardened-malloc-tests}/bin/"
|
||||
+ prog_name
|
||||
+ " 2>&1"
|
||||
):
|
||||
raise Exception("Hardened malloc does not work for {}".format(error_text))
|
||||
|
||||
subtest "hardenedmalloc", sub {
|
||||
runMallocTestProg("double_free_large", "invalid free");
|
||||
runMallocTestProg("unaligned_free_small", "invalid unaligned free");
|
||||
runMallocTestProg("write_after_free_small", "detected write after free");
|
||||
};
|
||||
|
||||
with subtest("The hardened memory allocator works"):
|
||||
runMallocTestProg("double_free_large", "invalid free")
|
||||
runMallocTestProg("unaligned_free_small", "invalid unaligned free")
|
||||
runMallocTestProg("write_after_free_small", "detected write after free")
|
||||
'';
|
||||
})
|
||||
|
@ -1,15 +1,16 @@
|
||||
import ../make-test.nix ({ pkgs, ...} : {
|
||||
import ../make-test-python.nix ({ pkgs, ...} : {
|
||||
name = "test-hocker-fetchdocker";
|
||||
meta = with pkgs.stdenv.lib.maintainers; {
|
||||
maintainers = [ ixmatus ];
|
||||
broken = true; # tries to download from registry-1.docker.io - how did this ever work?
|
||||
};
|
||||
|
||||
machine = import ./machine.nix;
|
||||
|
||||
testScript = ''
|
||||
startAll;
|
||||
start_all()
|
||||
|
||||
$machine->waitForUnit("sockets.target");
|
||||
$machine->waitUntilSucceeds("docker run registry-1.docker.io/v2/library/hello-world:latest");
|
||||
machine.wait_for_unit("sockets.target")
|
||||
machine.wait_until_succeeds("docker run registry-1.docker.io/v2/library/hello-world:latest")
|
||||
'';
|
||||
})
|
||||
|
@ -74,7 +74,7 @@ let
|
||||
throw "Non-EFI boot methods are only supported on i686 / x86_64"
|
||||
else ''
|
||||
def assemble_qemu_flags():
|
||||
flags = "-cpu host"
|
||||
flags = "-cpu max"
|
||||
${if system == "x86_64-linux"
|
||||
then ''flags += " -m 768"''
|
||||
else ''flags += " -m 512 -enable-kvm -machine virt,gic-version=host"''
|
||||
@ -317,6 +317,7 @@ let
|
||||
texinfo
|
||||
unionfs-fuse
|
||||
xorg.lndir
|
||||
(lvm2.override { udev = null; }) # for initrd (extra-utils)
|
||||
|
||||
# add curl so that rather than seeing the test attempt to download
|
||||
# curl's tarball, we see what it's trying to download
|
||||
@ -799,7 +800,7 @@ in {
|
||||
"btrfs subvol create /mnt/badpath/boot",
|
||||
"btrfs subvol create /mnt/nixos",
|
||||
"btrfs subvol set-default "
|
||||
+ "$(btrfs subvol list /mnt | grep 'nixos' | awk '{print \$2}') /mnt",
|
||||
+ "$(btrfs subvol list /mnt | grep 'nixos' | awk '{print $2}') /mnt",
|
||||
"umount /mnt",
|
||||
"mount -o defaults LABEL=root /mnt",
|
||||
"mkdir -p /mnt/badpath/boot", # Help ensure the detection mechanism
|
||||
|
@ -3,30 +3,30 @@
|
||||
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 import common/ec2.nix { inherit makeTest pkgs; };
|
||||
|
||||
let
|
||||
image =
|
||||
(import ../lib/eval-config.nix {
|
||||
inherit system;
|
||||
modules = [
|
||||
../maintainers/scripts/openstack/openstack-image.nix
|
||||
../modules/testing/test-instrumentation.nix
|
||||
../modules/profiles/qemu-guest.nix
|
||||
{
|
||||
# Needed by nixos-rebuild due to lack of network access.
|
||||
system.extraDependencies = with pkgs; [
|
||||
stdenv
|
||||
];
|
||||
}
|
||||
];
|
||||
}).config.system.build.openstackImage + "/nixos.qcow2";
|
||||
image = (import ../lib/eval-config.nix {
|
||||
inherit system;
|
||||
modules = [
|
||||
../maintainers/scripts/openstack/openstack-image.nix
|
||||
../modules/testing/test-instrumentation.nix
|
||||
../modules/profiles/qemu-guest.nix
|
||||
{
|
||||
# Needed by nixos-rebuild due to lack of network access.
|
||||
system.extraDependencies = with pkgs; [
|
||||
stdenv
|
||||
];
|
||||
}
|
||||
];
|
||||
}).config.system.build.openstackImage + "/nixos.qcow2";
|
||||
|
||||
sshKeys = import ./ssh-keys.nix pkgs;
|
||||
snakeOilPrivateKey = sshKeys.snakeOilPrivateKey.text;
|
||||
snakeOilPrivateKeyFile = pkgs.writeText "private-key" snakeOilPrivateKey;
|
||||
snakeOilPublicKey = sshKeys.snakeOilPublicKey;
|
||||
|
||||
in {
|
||||
@ -39,32 +39,36 @@ in {
|
||||
SSH_HOST_ED25519_KEY:${replaceStrings ["\n"] ["|"] snakeOilPrivateKey}
|
||||
'';
|
||||
script = ''
|
||||
$machine->start;
|
||||
$machine->waitForFile("/etc/ec2-metadata/user-data");
|
||||
$machine->waitForUnit("sshd.service");
|
||||
machine.start()
|
||||
machine.wait_for_file("/etc/ec2-metadata/user-data")
|
||||
machine.wait_for_unit("sshd.service")
|
||||
|
||||
$machine->succeed("grep unknown /etc/ec2-metadata/ami-manifest-path");
|
||||
machine.succeed("grep unknown /etc/ec2-metadata/ami-manifest-path")
|
||||
|
||||
# We have no keys configured on the client side yet, so this should fail
|
||||
$machine->fail("ssh -o BatchMode=yes localhost exit");
|
||||
machine.fail("ssh -o BatchMode=yes localhost exit")
|
||||
|
||||
# Let's install our client private key
|
||||
$machine->succeed("mkdir -p ~/.ssh");
|
||||
machine.succeed("mkdir -p ~/.ssh")
|
||||
|
||||
$machine->succeed("echo '${snakeOilPrivateKey}' > ~/.ssh/id_ed25519");
|
||||
$machine->succeed("chmod 600 ~/.ssh/id_ed25519");
|
||||
machine.copy_from_host_via_shell(
|
||||
"${snakeOilPrivateKeyFile}", "~/.ssh/id_ed25519"
|
||||
)
|
||||
machine.succeed("chmod 600 ~/.ssh/id_ed25519")
|
||||
|
||||
# We haven't configured the host key yet, so this should still fail
|
||||
$machine->fail("ssh -o BatchMode=yes localhost exit");
|
||||
machine.fail("ssh -o BatchMode=yes localhost exit")
|
||||
|
||||
# Add the host key; ssh should finally succeed
|
||||
$machine->succeed("echo localhost,127.0.0.1 ${snakeOilPublicKey} > ~/.ssh/known_hosts");
|
||||
$machine->succeed("ssh -o BatchMode=yes localhost exit");
|
||||
machine.succeed(
|
||||
"echo localhost,127.0.0.1 ${snakeOilPublicKey} > ~/.ssh/known_hosts"
|
||||
)
|
||||
machine.succeed("ssh -o BatchMode=yes localhost exit")
|
||||
|
||||
# Just to make sure resizing is idempotent.
|
||||
$machine->shutdown;
|
||||
$machine->start;
|
||||
$machine->waitForFile("/etc/ec2-metadata/user-data");
|
||||
machine.shutdown()
|
||||
machine.start()
|
||||
machine.wait_for_file("/etc/ec2-metadata/user-data")
|
||||
'';
|
||||
};
|
||||
|
||||
@ -86,9 +90,9 @@ in {
|
||||
}
|
||||
'';
|
||||
script = ''
|
||||
$machine->start;
|
||||
$machine->waitForFile("/etc/testFile");
|
||||
$machine->succeed("cat /etc/testFile | grep -q 'whoa'");
|
||||
machine.start()
|
||||
machine.wait_for_file("/etc/testFile")
|
||||
assert "whoa" in machine.succeed("cat /etc/testFile")
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import ./make-test.nix ({pkgs, lib, ...}:
|
||||
import ./make-test-python.nix ({pkgs, lib, ...}:
|
||||
let
|
||||
# A filesystem image with a (presumably) bootable debian
|
||||
debianImage = pkgs.vmTools.diskImageFuns.debian9i386 {
|
||||
@ -34,9 +34,6 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
# options to add the disk to the test vm
|
||||
QEMU_OPTS = "-drive index=2,file=${debianImage}/disk-image.qcow2,read-only,if=virtio";
|
||||
|
||||
# a part of the configuration of the test vm
|
||||
simpleConfig = {
|
||||
boot.loader.grub = {
|
||||
@ -71,7 +68,7 @@ in {
|
||||
machine = { config, pkgs, ... }: (simpleConfig // {
|
||||
imports = [ ../modules/profiles/installation-device.nix
|
||||
../modules/profiles/base.nix ];
|
||||
virtualisation.memorySize = 1024;
|
||||
virtualisation.memorySize = 1300;
|
||||
# The test cannot access the network, so any packages
|
||||
# nixos-rebuild needs must be included in the VM.
|
||||
system.extraDependencies = with pkgs;
|
||||
@ -99,22 +96,28 @@ in {
|
||||
|
||||
testScript = ''
|
||||
# hack to add the secondary disk
|
||||
$machine->{startCommand} = "QEMU_OPTS=\"\$QEMU_OPTS \"${lib.escapeShellArg QEMU_OPTS} ".$machine->{startCommand};
|
||||
os.environ[
|
||||
"QEMU_OPTS"
|
||||
] = "-drive index=2,file=${debianImage}/disk-image.qcow2,read-only,if=virtio"
|
||||
|
||||
$machine->start;
|
||||
$machine->succeed("udevadm settle");
|
||||
$machine->waitForUnit("multi-user.target");
|
||||
machine.start()
|
||||
machine.succeed("udevadm settle")
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
print(machine.succeed("lsblk"))
|
||||
|
||||
# check that os-prober works standalone
|
||||
$machine->succeed("${pkgs.os-prober}/bin/os-prober | grep /dev/vdb1");
|
||||
machine.succeed(
|
||||
"${pkgs.os-prober}/bin/os-prober | grep /dev/vdb1"
|
||||
)
|
||||
|
||||
# rebuild and test that debian is available in the grub menu
|
||||
$machine->succeed("nixos-generate-config");
|
||||
$machine->copyFileFromHost(
|
||||
machine.succeed("nixos-generate-config")
|
||||
machine.copy_from_host(
|
||||
"${configFile}",
|
||||
"/etc/nixos/configuration.nix");
|
||||
$machine->succeed("nixos-rebuild boot >&2");
|
||||
"/etc/nixos/configuration.nix",
|
||||
)
|
||||
machine.succeed("nixos-rebuild boot >&2")
|
||||
|
||||
$machine->succeed("egrep 'menuentry.*debian' /boot/grub/grub.cfg");
|
||||
machine.succeed("egrep 'menuentry.*debian' /boot/grub/grub.cfg")
|
||||
'';
|
||||
})
|
||||
|
@ -1,103 +1,111 @@
|
||||
{ system ? builtins.currentSystem
|
||||
, config ? { }
|
||||
, pkgs ? import ../.. { inherit system config; } }:
|
||||
|
||||
with import ../lib/testing.nix { inherit system pkgs; };
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
makePostgresqlWalReceiverTest = subTestName: postgresqlPackage: let
|
||||
# Makes a test for a PostgreSQL package, given by name and looked up from `pkgs`.
|
||||
makePostgresqlWalReceiverTest = postgresqlPackage:
|
||||
{
|
||||
name = postgresqlPackage;
|
||||
value =
|
||||
import ./make-test-python.nix ({ pkgs, lib, ... }: let
|
||||
|
||||
postgresqlDataDir = "/var/db/postgresql/test";
|
||||
replicationUser = "wal_receiver_user";
|
||||
replicationSlot = "wal_receiver_slot";
|
||||
replicationConn = "postgresql://${replicationUser}@localhost";
|
||||
baseBackupDir = "/tmp/pg_basebackup";
|
||||
walBackupDir = "/tmp/pg_wal";
|
||||
atLeast12 = versionAtLeast postgresqlPackage.version "12.0";
|
||||
restoreCommand = ''
|
||||
restore_command = 'cp ${walBackupDir}/%f %p'
|
||||
'';
|
||||
|
||||
recoveryFile = if atLeast12
|
||||
then pkgs.writeTextDir "recovery.signal" ""
|
||||
else pkgs.writeTextDir "recovery.conf" "${restoreCommand}";
|
||||
|
||||
in makeTest {
|
||||
name = "postgresql-wal-receiver-${subTestName}";
|
||||
meta.maintainers = with maintainers; [ pacien ];
|
||||
|
||||
machine = { ... }: {
|
||||
# Needed because this test uses a non-default 'services.postgresql.dataDir'.
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/db/postgresql 0700 postgres postgres"
|
||||
];
|
||||
services.postgresql = {
|
||||
package = postgresqlPackage;
|
||||
enable = true;
|
||||
dataDir = postgresqlDataDir;
|
||||
extraConfig = ''
|
||||
wal_level = archive # alias for replica on pg >= 9.6
|
||||
max_wal_senders = 10
|
||||
max_replication_slots = 10
|
||||
'' + optionalString atLeast12 ''
|
||||
${restoreCommand}
|
||||
recovery_end_command = 'touch recovery.done'
|
||||
pkg = pkgs."${postgresqlPackage}";
|
||||
postgresqlDataDir = "/var/lib/postgresql/${pkg.psqlSchema}";
|
||||
replicationUser = "wal_receiver_user";
|
||||
replicationSlot = "wal_receiver_slot";
|
||||
replicationConn = "postgresql://${replicationUser}@localhost";
|
||||
baseBackupDir = "/tmp/pg_basebackup";
|
||||
walBackupDir = "/tmp/pg_wal";
|
||||
atLeast12 = lib.versionAtLeast pkg.version "12.0";
|
||||
restoreCommand = ''
|
||||
restore_command = 'cp ${walBackupDir}/%f %p'
|
||||
'';
|
||||
authentication = ''
|
||||
host replication ${replicationUser} all trust
|
||||
'';
|
||||
initialScript = pkgs.writeText "init.sql" ''
|
||||
create user ${replicationUser} replication;
|
||||
select * from pg_create_physical_replication_slot('${replicationSlot}');
|
||||
'';
|
||||
};
|
||||
|
||||
services.postgresqlWalReceiver.receivers.main = {
|
||||
inherit postgresqlPackage;
|
||||
connection = replicationConn;
|
||||
slot = replicationSlot;
|
||||
directory = walBackupDir;
|
||||
};
|
||||
# This is only to speedup test, it isn't time racing. Service is set to autorestart always,
|
||||
# default 60sec is fine for real system, but is too much for a test
|
||||
systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = mkForce 5;
|
||||
recoveryFile = if atLeast12
|
||||
then pkgs.writeTextDir "recovery.signal" ""
|
||||
else pkgs.writeTextDir "recovery.conf" "${restoreCommand}";
|
||||
|
||||
in {
|
||||
name = "postgresql-wal-receiver-${postgresqlPackage}";
|
||||
meta.maintainers = with lib.maintainers; [ pacien ];
|
||||
|
||||
machine = { ... }: {
|
||||
services.postgresql = {
|
||||
package = pkg;
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
wal_level = archive # alias for replica on pg >= 9.6
|
||||
max_wal_senders = 10
|
||||
max_replication_slots = 10
|
||||
'' + lib.optionalString atLeast12 ''
|
||||
${restoreCommand}
|
||||
recovery_end_command = 'touch recovery.done'
|
||||
'';
|
||||
authentication = ''
|
||||
host replication ${replicationUser} all trust
|
||||
'';
|
||||
initialScript = pkgs.writeText "init.sql" ''
|
||||
create user ${replicationUser} replication;
|
||||
select * from pg_create_physical_replication_slot('${replicationSlot}');
|
||||
'';
|
||||
};
|
||||
|
||||
services.postgresqlWalReceiver.receivers.main = {
|
||||
postgresqlPackage = pkg;
|
||||
connection = replicationConn;
|
||||
slot = replicationSlot;
|
||||
directory = walBackupDir;
|
||||
};
|
||||
# This is only to speedup test, it isn't time racing. Service is set to autorestart always,
|
||||
# default 60sec is fine for real system, but is too much for a test
|
||||
systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = lib.mkForce 5;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
# make an initial base backup
|
||||
machine.wait_for_unit("postgresql")
|
||||
machine.wait_for_unit("postgresql-wal-receiver-main")
|
||||
# WAL receiver healthchecks PG every 5 seconds, so let's be sure they have connected each other
|
||||
# required only for 9.4
|
||||
machine.sleep(5)
|
||||
machine.succeed(
|
||||
"${pkg}/bin/pg_basebackup --dbname=${replicationConn} --pgdata=${baseBackupDir}"
|
||||
)
|
||||
|
||||
# create a dummy table with 100 records
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql --command='create table dummy as select * from generate_series(1, 100) as val;'"
|
||||
)
|
||||
|
||||
# stop postgres and destroy data
|
||||
machine.systemctl("stop postgresql")
|
||||
machine.systemctl("stop postgresql-wal-receiver-main")
|
||||
machine.succeed("rm -r ${postgresqlDataDir}/{base,global,pg_*}")
|
||||
|
||||
# restore the base backup
|
||||
machine.succeed(
|
||||
"cp -r ${baseBackupDir}/* ${postgresqlDataDir} && chown postgres:postgres -R ${postgresqlDataDir}"
|
||||
)
|
||||
|
||||
# prepare WAL and recovery
|
||||
machine.succeed("chmod a+rX -R ${walBackupDir}")
|
||||
machine.execute(
|
||||
"for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done"
|
||||
) # make use of partial segments too
|
||||
machine.succeed(
|
||||
"cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*"
|
||||
)
|
||||
|
||||
# replay WAL
|
||||
machine.systemctl("start postgresql")
|
||||
machine.wait_for_file("${postgresqlDataDir}/recovery.done")
|
||||
machine.systemctl("restart postgresql")
|
||||
machine.wait_for_unit("postgresql")
|
||||
|
||||
# check that our records have been restored
|
||||
machine.succeed(
|
||||
"test $(sudo -u postgres psql --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100"
|
||||
)
|
||||
'';
|
||||
});
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
# make an initial base backup
|
||||
$machine->waitForUnit('postgresql');
|
||||
$machine->waitForUnit('postgresql-wal-receiver-main');
|
||||
# WAL receiver healthchecks PG every 5 seconds, so let's be sure they have connected each other
|
||||
# required only for 9.4
|
||||
$machine->sleep(5);
|
||||
$machine->succeed('${postgresqlPackage}/bin/pg_basebackup --dbname=${replicationConn} --pgdata=${baseBackupDir}');
|
||||
|
||||
# create a dummy table with 100 records
|
||||
$machine->succeed('sudo -u postgres psql --command="create table dummy as select * from generate_series(1, 100) as val;"');
|
||||
|
||||
# stop postgres and destroy data
|
||||
$machine->systemctl('stop postgresql');
|
||||
$machine->systemctl('stop postgresql-wal-receiver-main');
|
||||
$machine->succeed('rm -r ${postgresqlDataDir}/{base,global,pg_*}');
|
||||
|
||||
# restore the base backup
|
||||
$machine->succeed('cp -r ${baseBackupDir}/* ${postgresqlDataDir} && chown postgres:postgres -R ${postgresqlDataDir}');
|
||||
|
||||
# prepare WAL and recovery
|
||||
$machine->succeed('chmod a+rX -R ${walBackupDir}');
|
||||
$machine->execute('for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done'); # make use of partial segments too
|
||||
$machine->succeed('cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*');
|
||||
|
||||
# replay WAL
|
||||
$machine->systemctl('start postgresql');
|
||||
$machine->waitForFile('${postgresqlDataDir}/recovery.done');
|
||||
$machine->systemctl('restart postgresql');
|
||||
$machine->waitForUnit('postgresql');
|
||||
|
||||
# check that our records have been restored
|
||||
$machine->succeed('test $(sudo -u postgres psql --pset="pager=off" --tuples-only --command="select count(distinct val) from dummy;") -eq 100');
|
||||
'';
|
||||
};
|
||||
|
||||
in mapAttrs makePostgresqlWalReceiverTest (import ../../pkgs/servers/sql/postgresql pkgs)
|
||||
# Maps the generic function over all attributes of PostgreSQL packages
|
||||
in builtins.listToAttrs (map makePostgresqlWalReceiverTest (builtins.attrNames (import ../../pkgs/servers/sql/postgresql { })))
|
||||
|
@ -158,7 +158,10 @@ in import ./make-test-python.nix {
|
||||
|
||||
s3 = { pkgs, ... } : {
|
||||
# Minio requires at least 1GiB of free disk space to run.
|
||||
virtualisation.diskSize = 2 * 1024;
|
||||
virtualisation = {
|
||||
diskSize = 2 * 1024;
|
||||
memorySize = 1024;
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ minioPort ];
|
||||
|
||||
services.minio = {
|
||||
@ -235,7 +238,7 @@ in import ./make-test-python.nix {
|
||||
# Test if the Thanos bucket command is able to retrieve blocks from the S3 bucket
|
||||
# and check if the blocks have the correct labels:
|
||||
store.succeed(
|
||||
"thanos bucket ls "
|
||||
"thanos tools bucket ls "
|
||||
+ "--objstore.config-file=${nodes.store.config.services.thanos.store.objstore.config-file} "
|
||||
+ "--output=json | "
|
||||
+ "jq .thanos.labels.some_label | "
|
||||
|
80
nixos/tests/shadowsocks.nix
Normal file
80
nixos/tests/shadowsocks.nix
Normal file
@ -0,0 +1,80 @@
|
||||
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||
name = "shadowsocks";
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ hmenke ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
server = {
|
||||
boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = "192.168.0.1"; prefixLength = 24; }
|
||||
];
|
||||
networking.firewall.rejectPackets = true;
|
||||
networking.firewall.allowedTCPPorts = [ 8488 ];
|
||||
networking.firewall.allowedUDPPorts = [ 8488 ];
|
||||
services.shadowsocks = {
|
||||
enable = true;
|
||||
encryptionMethod = "chacha20-ietf-poly1305";
|
||||
password = "pa$$w0rd";
|
||||
localAddress = [ "0.0.0.0" ];
|
||||
port = 8488;
|
||||
fastOpen = false;
|
||||
mode = "tcp_and_udp";
|
||||
plugin = "${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin";
|
||||
pluginOpts = "server;host=nixos.org";
|
||||
};
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts.server = {
|
||||
locations."/".root = pkgs.writeTextDir "index.html" "It works!";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
client = {
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.eth1.ipv4.addresses = [
|
||||
{ address = "192.168.0.2"; prefixLength = 24; }
|
||||
];
|
||||
systemd.services.shadowsocks-client = {
|
||||
description = "connect to shadowsocks";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = with pkgs; [
|
||||
shadowsocks-libev
|
||||
shadowsocks-v2ray-plugin
|
||||
];
|
||||
script = ''
|
||||
exec ss-local \
|
||||
-s 192.168.0.1 \
|
||||
-p 8488 \
|
||||
-l 1080 \
|
||||
-k 'pa$$w0rd' \
|
||||
-m chacha20-ietf-poly1305 \
|
||||
-a nobody \
|
||||
--plugin "${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin" \
|
||||
--plugin-opts "host=nixos.org"
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
server.wait_for_unit("shadowsocks-libev.service")
|
||||
client.wait_for_unit("shadowsocks-client.service")
|
||||
|
||||
client.fail(
|
||||
"${pkgs.curl}/bin/curl 192.168.0.1:80"
|
||||
)
|
||||
|
||||
msg = client.succeed(
|
||||
"${pkgs.curl}/bin/curl --socks5 localhost:1080 192.168.0.1:80"
|
||||
)
|
||||
assert msg == "It works!", "Could not connect through shadowsocks"
|
||||
'';
|
||||
}
|
||||
)
|
17
nixos/tests/sssd.nix
Normal file
17
nixos/tests/sssd.nix
Normal file
@ -0,0 +1,17 @@
|
||||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
|
||||
{
|
||||
name = "sssd";
|
||||
meta = with pkgs.stdenv.lib.maintainers; {
|
||||
maintainers = [ bbigras ];
|
||||
};
|
||||
machine = { pkgs, ... }: {
|
||||
services.sssd.enable = true;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_unit("sssd.service")
|
||||
'';
|
||||
})
|
@ -31,7 +31,7 @@ import ./make-test-python.nix ({pkgs, ...}: {
|
||||
firewall.enable = false;
|
||||
interfaces.eth1.ipv4.addresses = lib.mkForce []; # no need for legacy IP
|
||||
interfaces.eth1.ipv6.addresses = lib.mkForce [
|
||||
{ address = "2001:DB8::"; prefixLength = 64; }
|
||||
{ address = "2001:DB8::1"; prefixLength = 64; }
|
||||
];
|
||||
};
|
||||
|
||||
@ -260,7 +260,7 @@ import ./make-test-python.nix ({pkgs, ...}: {
|
||||
client.wait_until_succeeds("ping -6 -c 1 FD42::1")
|
||||
|
||||
# the global IP of the ISP router should still not be a reachable
|
||||
router.fail("ping -6 -c 1 2001:DB8::")
|
||||
router.fail("ping -6 -c 1 2001:DB8::1")
|
||||
|
||||
# Once we have internal connectivity boot up the ISP
|
||||
isp.start()
|
||||
@ -273,11 +273,11 @@ import ./make-test-python.nix ({pkgs, ...}: {
|
||||
|
||||
# wait until the uplink interface has a good status
|
||||
router.wait_for_unit("network-online.target")
|
||||
router.wait_until_succeeds("ping -6 -c1 2001:DB8::")
|
||||
router.wait_until_succeeds("ping -6 -c1 2001:DB8::1")
|
||||
|
||||
# shortly after that the client should have received it's global IPv6
|
||||
# address and thus be able to ping the ISP
|
||||
client.wait_until_succeeds("ping -6 -c1 2001:DB8::")
|
||||
client.wait_until_succeeds("ping -6 -c1 2001:DB8::1")
|
||||
|
||||
# verify that we got a globally scoped address in eth1 from the
|
||||
# documentation prefix
|
||||
|
@ -4,7 +4,10 @@ import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
machine = { lib, ... }: {
|
||||
imports = [ common/user-account.nix common/x11.nix ];
|
||||
|
||||
virtualisation.emptyDiskImages = [ 512 ];
|
||||
virtualisation.emptyDiskImages = [ 512 512 ];
|
||||
virtualisation.memorySize = 1024;
|
||||
|
||||
environment.systemPackages = [ pkgs.cryptsetup ];
|
||||
|
||||
fileSystems = lib.mkVMOverride {
|
||||
"/test-x-initrd-mount" = {
|
||||
@ -144,5 +147,25 @@ import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
assert "RuntimeWatchdogUSec=30s" in output
|
||||
assert "RebootWatchdogUSec=10m" in output
|
||||
assert "KExecWatchdogUSec=5m" in output
|
||||
|
||||
# Test systemd cryptsetup support
|
||||
with subtest("systemd successfully reads /etc/crypttab and unlocks volumes"):
|
||||
# create a luks volume and put a filesystem on it
|
||||
machine.succeed(
|
||||
"echo -n supersecret | cryptsetup luksFormat -q /dev/vdc -",
|
||||
"echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vdc foo",
|
||||
"mkfs.ext3 /dev/mapper/foo",
|
||||
)
|
||||
|
||||
# create a keyfile and /etc/crypttab
|
||||
machine.succeed("echo -n supersecret > /var/lib/luks-keyfile")
|
||||
machine.succeed("chmod 600 /var/lib/luks-keyfile")
|
||||
machine.succeed("echo 'luks1 /dev/vdc /var/lib/luks-keyfile luks' > /etc/crypttab")
|
||||
|
||||
# after a reboot, systemd should unlock the volume and we should be able to mount it
|
||||
machine.shutdown()
|
||||
machine.succeed("systemctl status systemd-cryptsetup@luks1.service")
|
||||
machine.succeed("mkdir -p /tmp/luks1")
|
||||
machine.succeed("mount /dev/mapper/luks1 /tmp/luks1")
|
||||
'';
|
||||
})
|
||||
|
@ -9,6 +9,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 9091 ];
|
||||
|
||||
security.apparmor.enable = true;
|
||||
|
||||
services.transmission.enable = true;
|
||||
};
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
assert use64bitGuest -> useKvmNestedVirt;
|
||||
|
||||
with import ../lib/testing.nix { inherit system pkgs; };
|
||||
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
@ -91,13 +91,15 @@ let
|
||||
(isYes "SERIAL_8250_CONSOLE")
|
||||
(isYes "SERIAL_8250")
|
||||
];
|
||||
|
||||
networking.usePredictableInterfaceNames = false;
|
||||
};
|
||||
|
||||
mkLog = logfile: tag: let
|
||||
rotated = map (i: "${logfile}.${toString i}") (range 1 9);
|
||||
all = concatMapStringsSep " " (f: "\"${f}\"") ([logfile] ++ rotated);
|
||||
logcmd = "tail -F ${all} 2> /dev/null | logger -t \"${tag}\"";
|
||||
in optionalString debug "$machine->execute(ru '${logcmd} & disown');";
|
||||
in if debug then "machine.execute(ru('${logcmd} & disown'))" else "pass";
|
||||
|
||||
testVM = vmName: vmScript: let
|
||||
cfg = (import ../lib/eval-config.nix {
|
||||
@ -204,96 +206,105 @@ let
|
||||
};
|
||||
|
||||
testSubs = ''
|
||||
my ${"$" + name}_sharepath = '${sharePath}';
|
||||
|
||||
sub checkRunning_${name} {
|
||||
my $cmd = 'VBoxManage list runningvms | grep -q "^\"${name}\""';
|
||||
my ($status, $out) = $machine->execute(ru $cmd);
|
||||
return $status == 0;
|
||||
}
|
||||
|
||||
sub cleanup_${name} {
|
||||
$machine->execute(ru "VBoxManage controlvm ${name} poweroff")
|
||||
if checkRunning_${name};
|
||||
$machine->succeed("rm -rf ${sharePath}");
|
||||
$machine->succeed("mkdir -p ${sharePath}");
|
||||
$machine->succeed("chown alice.users ${sharePath}");
|
||||
}
|
||||
${name}_sharepath = "${sharePath}"
|
||||
|
||||
sub createVM_${name} {
|
||||
vbm("createvm --name ${name} ${createFlags}");
|
||||
vbm("modifyvm ${name} ${vmFlags}");
|
||||
vbm("setextradata ${name} VBoxInternal/PDM/HaltOnReset 1");
|
||||
vbm("storagectl ${name} ${controllerFlags}");
|
||||
vbm("storageattach ${name} ${diskFlags}");
|
||||
vbm("sharedfolder add ${name} ${sharedFlags}");
|
||||
vbm("sharedfolder add ${name} ${nixstoreFlags}");
|
||||
cleanup_${name};
|
||||
|
||||
${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"}
|
||||
}
|
||||
def check_running_${name}():
|
||||
cmd = "VBoxManage list runningvms | grep -q '^\"${name}\"'"
|
||||
(status, _) = machine.execute(ru(cmd))
|
||||
return status == 0
|
||||
|
||||
sub destroyVM_${name} {
|
||||
cleanup_${name};
|
||||
vbm("unregistervm ${name} --delete");
|
||||
}
|
||||
|
||||
sub waitForVMBoot_${name} {
|
||||
$machine->execute(ru(
|
||||
'set -e; i=0; '.
|
||||
'while ! test -e ${sharePath}/boot-done; do '.
|
||||
'sleep 10; i=$(($i + 10)); [ $i -le 3600 ]; '.
|
||||
'VBoxManage list runningvms | grep -q "^\"${name}\""; '.
|
||||
'done'
|
||||
));
|
||||
}
|
||||
def cleanup_${name}():
|
||||
if check_running_${name}():
|
||||
machine.execute(ru("VBoxManage controlvm ${name} poweroff"))
|
||||
machine.succeed("rm -rf ${sharePath}")
|
||||
machine.succeed("mkdir -p ${sharePath}")
|
||||
machine.succeed("chown alice.users ${sharePath}")
|
||||
|
||||
sub waitForIP_${name} ($) {
|
||||
my $property = "/VirtualBox/GuestInfo/Net/$_[0]/V4/IP";
|
||||
my $getip = "VBoxManage guestproperty get ${name} $property | ".
|
||||
"sed -n -e 's/^Value: //p'";
|
||||
my $ip = $machine->succeed(ru(
|
||||
'for i in $(seq 1000); do '.
|
||||
'if ipaddr="$('.$getip.')" && [ -n "$ipaddr" ]; then '.
|
||||
'echo "$ipaddr"; exit 0; '.
|
||||
'fi; '.
|
||||
'sleep 1; '.
|
||||
'done; '.
|
||||
'echo "Could not get IPv4 address for ${name}!" >&2; '.
|
||||
'exit 1'
|
||||
));
|
||||
chomp $ip;
|
||||
return $ip;
|
||||
}
|
||||
|
||||
sub waitForStartup_${name} {
|
||||
for (my $i = 0; $i <= 120; $i += 10) {
|
||||
$machine->sleep(10);
|
||||
return if checkRunning_${name};
|
||||
eval { $_[0]->() } if defined $_[0];
|
||||
}
|
||||
die "VirtualBox VM didn't start up within 2 minutes";
|
||||
}
|
||||
def create_vm_${name}():
|
||||
# fmt: off
|
||||
vbm(f"createvm --name ${name} ${createFlags}")
|
||||
vbm(f"modifyvm ${name} ${vmFlags}")
|
||||
vbm(f"setextradata ${name} VBoxInternal/PDM/HaltOnReset 1")
|
||||
vbm(f"storagectl ${name} ${controllerFlags}")
|
||||
vbm(f"storageattach ${name} ${diskFlags}")
|
||||
vbm(f"sharedfolder add ${name} ${sharedFlags}")
|
||||
vbm(f"sharedfolder add ${name} ${nixstoreFlags}")
|
||||
cleanup_${name}()
|
||||
|
||||
sub waitForShutdown_${name} {
|
||||
for (my $i = 0; $i <= 120; $i += 10) {
|
||||
$machine->sleep(10);
|
||||
return unless checkRunning_${name};
|
||||
}
|
||||
die "VirtualBox VM didn't shut down within 2 minutes";
|
||||
}
|
||||
${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"}
|
||||
# fmt: on
|
||||
|
||||
sub shutdownVM_${name} {
|
||||
$machine->succeed(ru "touch ${sharePath}/shutdown");
|
||||
$machine->execute(
|
||||
'set -e; i=0; '.
|
||||
'while test -e ${sharePath}/shutdown '.
|
||||
' -o -e ${sharePath}/boot-done; do '.
|
||||
'sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; '.
|
||||
'done'
|
||||
);
|
||||
waitForShutdown_${name};
|
||||
}
|
||||
|
||||
def destroy_vm_${name}():
|
||||
cleanup_${name}()
|
||||
vbm("unregistervm ${name} --delete")
|
||||
|
||||
|
||||
def wait_for_vm_boot_${name}():
|
||||
machine.execute(
|
||||
ru(
|
||||
"set -e; i=0; "
|
||||
"while ! test -e ${sharePath}/boot-done; do "
|
||||
"sleep 10; i=$(($i + 10)); [ $i -le 3600 ]; "
|
||||
"VBoxManage list runningvms | grep -q '^\"${name}\"'; "
|
||||
"done"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def wait_for_ip_${name}(interface):
|
||||
property = f"/VirtualBox/GuestInfo/Net/{interface}/V4/IP"
|
||||
# fmt: off
|
||||
getip = f"VBoxManage guestproperty get ${name} {property} | sed -n -e 's/^Value: //p'"
|
||||
# fmt: on
|
||||
|
||||
ip = machine.succeed(
|
||||
ru(
|
||||
"for i in $(seq 1000); do "
|
||||
f'if ipaddr="$({getip})" && [ -n "$ipaddr" ]; then '
|
||||
'echo "$ipaddr"; exit 0; '
|
||||
"fi; "
|
||||
"sleep 1; "
|
||||
"done; "
|
||||
"echo 'Could not get IPv4 address for ${name}!' >&2; "
|
||||
"exit 1"
|
||||
)
|
||||
).strip()
|
||||
return ip
|
||||
|
||||
|
||||
def wait_for_startup_${name}(nudge=lambda: None):
|
||||
for _ in range(0, 130, 10):
|
||||
machine.sleep(10)
|
||||
if check_running_${name}():
|
||||
return
|
||||
nudge()
|
||||
raise Exception("VirtualBox VM didn't start up within 2 minutes")
|
||||
|
||||
|
||||
def wait_for_shutdown_${name}():
|
||||
for _ in range(0, 130, 10):
|
||||
machine.sleep(10)
|
||||
if not check_running_${name}():
|
||||
return
|
||||
raise Exception("VirtualBox VM didn't shut down within 2 minutes")
|
||||
|
||||
|
||||
def shutdown_vm_${name}():
|
||||
machine.succeed(ru("touch ${sharePath}/shutdown"))
|
||||
machine.execute(
|
||||
"set -e; i=0; "
|
||||
"while test -e ${sharePath}/shutdown "
|
||||
" -o -e ${sharePath}/boot-done; do "
|
||||
"sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; "
|
||||
"done"
|
||||
)
|
||||
wait_for_shutdown_${name}()
|
||||
'';
|
||||
};
|
||||
|
||||
@ -364,26 +375,31 @@ let
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
sub ru ($) {
|
||||
my $esc = $_[0] =~ s/'/'\\${"'"}'/gr;
|
||||
return "su - alice -c '$esc'";
|
||||
}
|
||||
|
||||
sub vbm {
|
||||
$machine->succeed(ru("VBoxManage ".$_[0]));
|
||||
};
|
||||
|
||||
sub removeUUIDs {
|
||||
return join("\n", grep { $_ !~ /^UUID:/ } split(/\n/, $_[0]))."\n";
|
||||
}
|
||||
|
||||
from shlex import quote
|
||||
${concatStrings (mapAttrsToList (_: getAttr "testSubs") vms)}
|
||||
|
||||
$machine->waitForX;
|
||||
def ru(cmd: str) -> str:
|
||||
return f"su - alice -c {quote(cmd)}"
|
||||
|
||||
|
||||
def vbm(cmd: str) -> str:
|
||||
return machine.succeed(ru(f"VBoxManage {cmd}"))
|
||||
|
||||
|
||||
def remove_uuids(output: str) -> str:
|
||||
return "\n".join(
|
||||
[line for line in (output or "").splitlines() if not line.startswith("UUID:")]
|
||||
)
|
||||
|
||||
|
||||
machine.wait_for_x()
|
||||
|
||||
# fmt: off
|
||||
${mkLog "$HOME/.config/VirtualBox/VBoxSVC.log" "HOST-SVC"}
|
||||
# fmt: on
|
||||
|
||||
${testScript}
|
||||
# (keep black happy)
|
||||
'';
|
||||
|
||||
meta = with pkgs.stdenv.lib.maintainers; {
|
||||
@ -393,133 +409,129 @@ let
|
||||
|
||||
unfreeTests = mapAttrs (mkVBoxTest true vboxVMsWithExtpack) {
|
||||
enable-extension-pack = ''
|
||||
createVM_testExtensionPack;
|
||||
vbm("startvm testExtensionPack");
|
||||
waitForStartup_testExtensionPack;
|
||||
$machine->screenshot("cli_started");
|
||||
waitForVMBoot_testExtensionPack;
|
||||
$machine->screenshot("cli_booted");
|
||||
create_vm_testExtensionPack()
|
||||
vbm("startvm testExtensionPack")
|
||||
wait_for_startup_testExtensionPack()
|
||||
machine.screenshot("cli_started")
|
||||
wait_for_vm_boot_testExtensionPack()
|
||||
machine.screenshot("cli_booted")
|
||||
|
||||
$machine->nest("Checking for privilege escalation", sub {
|
||||
$machine->fail("test -e '/root/VirtualBox VMs'");
|
||||
$machine->fail("test -e '/root/.config/VirtualBox'");
|
||||
$machine->succeed("test -e '/home/alice/VirtualBox VMs'");
|
||||
});
|
||||
with machine.nested("Checking for privilege escalation"):
|
||||
machine.fail("test -e '/root/VirtualBox VMs'")
|
||||
machine.fail("test -e '/root/.config/VirtualBox'")
|
||||
machine.succeed("test -e '/home/alice/VirtualBox VMs'")
|
||||
|
||||
shutdownVM_testExtensionPack;
|
||||
destroyVM_testExtensionPack;
|
||||
shutdown_vm_testExtensionPack()
|
||||
destroy_vm_testExtensionPack()
|
||||
'';
|
||||
};
|
||||
|
||||
in mapAttrs (mkVBoxTest false vboxVMs) {
|
||||
simple-gui = ''
|
||||
createVM_simple;
|
||||
$machine->succeed(ru "VirtualBox &");
|
||||
$machine->waitUntilSucceeds(
|
||||
ru "xprop -name 'Oracle VM VirtualBox Manager'"
|
||||
);
|
||||
$machine->sleep(5);
|
||||
$machine->screenshot("gui_manager_started");
|
||||
# Home to select Tools, down to move to the VM, enter to start it.
|
||||
$machine->sendKeys("home");
|
||||
$machine->sendKeys("down");
|
||||
$machine->sendKeys("ret");
|
||||
$machine->screenshot("gui_manager_sent_startup");
|
||||
waitForStartup_simple (sub {
|
||||
$machine->sendKeys("home");
|
||||
$machine->sendKeys("down");
|
||||
$machine->sendKeys("ret");
|
||||
});
|
||||
$machine->screenshot("gui_started");
|
||||
waitForVMBoot_simple;
|
||||
$machine->screenshot("gui_booted");
|
||||
shutdownVM_simple;
|
||||
$machine->sleep(5);
|
||||
$machine->screenshot("gui_stopped");
|
||||
$machine->sendKeys("ctrl-q");
|
||||
$machine->sleep(5);
|
||||
$machine->screenshot("gui_manager_stopped");
|
||||
destroyVM_simple;
|
||||
def send_vm_startup():
|
||||
machine.send_key("home")
|
||||
machine.send_key("down")
|
||||
machine.send_key("ret")
|
||||
|
||||
|
||||
create_vm_simple()
|
||||
machine.succeed(ru("VirtualBox &"))
|
||||
machine.wait_until_succeeds(ru("xprop -name 'Oracle VM VirtualBox Manager'"))
|
||||
machine.sleep(5)
|
||||
machine.screenshot("gui_manager_started")
|
||||
send_vm_startup()
|
||||
machine.screenshot("gui_manager_sent_startup")
|
||||
wait_for_startup_simple(send_vm_startup)
|
||||
machine.screenshot("gui_started")
|
||||
wait_for_vm_boot_simple()
|
||||
machine.screenshot("gui_booted")
|
||||
shutdown_vm_simple()
|
||||
machine.sleep(5)
|
||||
machine.screenshot("gui_stopped")
|
||||
machine.send_key("ctrl-q")
|
||||
machine.sleep(5)
|
||||
machine.screenshot("gui_manager_stopped")
|
||||
destroy_vm_simple()
|
||||
'';
|
||||
|
||||
simple-cli = ''
|
||||
createVM_simple;
|
||||
vbm("startvm simple");
|
||||
waitForStartup_simple;
|
||||
$machine->screenshot("cli_started");
|
||||
waitForVMBoot_simple;
|
||||
$machine->screenshot("cli_booted");
|
||||
create_vm_simple()
|
||||
vbm("startvm simple")
|
||||
wait_for_startup_simple()
|
||||
machine.screenshot("cli_started")
|
||||
wait_for_vm_boot_simple()
|
||||
machine.screenshot("cli_booted")
|
||||
|
||||
$machine->nest("Checking for privilege escalation", sub {
|
||||
$machine->fail("test -e '/root/VirtualBox VMs'");
|
||||
$machine->fail("test -e '/root/.config/VirtualBox'");
|
||||
$machine->succeed("test -e '/home/alice/VirtualBox VMs'");
|
||||
});
|
||||
with machine.nested("Checking for privilege escalation"):
|
||||
machine.fail("test -e '/root/VirtualBox VMs'")
|
||||
machine.fail("test -e '/root/.config/VirtualBox'")
|
||||
machine.succeed("test -e '/home/alice/VirtualBox VMs'")
|
||||
|
||||
shutdownVM_simple;
|
||||
destroyVM_simple;
|
||||
shutdown_vm_simple()
|
||||
destroy_vm_simple()
|
||||
'';
|
||||
|
||||
headless = ''
|
||||
createVM_headless;
|
||||
$machine->succeed(ru("VBoxHeadless --startvm headless & disown %1"));
|
||||
waitForStartup_headless;
|
||||
waitForVMBoot_headless;
|
||||
shutdownVM_headless;
|
||||
destroyVM_headless;
|
||||
create_vm_headless()
|
||||
machine.succeed(ru("VBoxHeadless --startvm headless & disown %1"))
|
||||
wait_for_startup_headless()
|
||||
wait_for_vm_boot_headless()
|
||||
shutdown_vm_headless()
|
||||
destroy_vm_headless()
|
||||
'';
|
||||
|
||||
host-usb-permissions = ''
|
||||
my $userUSB = removeUUIDs vbm("list usbhost");
|
||||
print STDERR $userUSB;
|
||||
my $rootUSB = removeUUIDs $machine->succeed("VBoxManage list usbhost");
|
||||
print STDERR $rootUSB;
|
||||
user_usb = remove_uuids(vbm("list usbhost"))
|
||||
print(user_usb, file=sys.stderr)
|
||||
root_usb = remove_uuids(machine.succeed("VBoxManage list usbhost"))
|
||||
print(root_usb, file=sys.stderr)
|
||||
|
||||
die "USB host devices differ for root and normal user"
|
||||
if $userUSB ne $rootUSB;
|
||||
die "No USB host devices found" if $userUSB =~ /<none>/;
|
||||
if user_usb != root_usb:
|
||||
raise Exception("USB host devices differ for root and normal user")
|
||||
if "<none>" in user_usb:
|
||||
raise Exception("No USB host devices found")
|
||||
'';
|
||||
|
||||
systemd-detect-virt = ''
|
||||
createVM_detectvirt;
|
||||
vbm("startvm detectvirt");
|
||||
waitForStartup_detectvirt;
|
||||
waitForVMBoot_detectvirt;
|
||||
shutdownVM_detectvirt;
|
||||
my $result = $machine->succeed("cat '$detectvirt_sharepath/result'");
|
||||
chomp $result;
|
||||
destroyVM_detectvirt;
|
||||
die "systemd-detect-virt returned \"$result\" instead of \"oracle\""
|
||||
if $result ne "oracle";
|
||||
create_vm_detectvirt()
|
||||
vbm("startvm detectvirt")
|
||||
wait_for_startup_detectvirt()
|
||||
wait_for_vm_boot_detectvirt()
|
||||
shutdown_vm_detectvirt()
|
||||
result = machine.succeed(f"cat '{detectvirt_sharepath}/result'").strip()
|
||||
destroy_vm_detectvirt()
|
||||
if result != "oracle":
|
||||
raise Exception(f'systemd-detect-virt returned "{result}" instead of "oracle"')
|
||||
'';
|
||||
|
||||
net-hostonlyif = ''
|
||||
createVM_test1;
|
||||
createVM_test2;
|
||||
create_vm_test1()
|
||||
create_vm_test2()
|
||||
|
||||
vbm("startvm test1");
|
||||
waitForStartup_test1;
|
||||
waitForVMBoot_test1;
|
||||
vbm("startvm test1")
|
||||
wait_for_startup_test1()
|
||||
wait_for_vm_boot_test1()
|
||||
|
||||
vbm("startvm test2");
|
||||
waitForStartup_test2;
|
||||
waitForVMBoot_test2;
|
||||
vbm("startvm test2")
|
||||
wait_for_startup_test2()
|
||||
wait_for_vm_boot_test2()
|
||||
|
||||
$machine->screenshot("net_booted");
|
||||
machine.screenshot("net_booted")
|
||||
|
||||
my $test1IP = waitForIP_test1 1;
|
||||
my $test2IP = waitForIP_test2 1;
|
||||
test1_ip = wait_for_ip_test1(1)
|
||||
test2_ip = wait_for_ip_test2(1)
|
||||
|
||||
$machine->succeed("echo '$test2IP' | nc -N '$test1IP' 1234");
|
||||
$machine->succeed("echo '$test1IP' | nc -N '$test2IP' 1234");
|
||||
machine.succeed(f"echo '{test2_ip}' | nc -N '{test1_ip}' 1234")
|
||||
machine.succeed(f"echo '{test1_ip}' | nc -N '{test2_ip}' 1234")
|
||||
|
||||
$machine->waitUntilSucceeds("nc -N '$test1IP' 5678 < /dev/null >&2");
|
||||
$machine->waitUntilSucceeds("nc -N '$test2IP' 5678 < /dev/null >&2");
|
||||
machine.wait_until_succeeds(f"nc -N '{test1_ip}' 5678 < /dev/null >&2")
|
||||
machine.wait_until_succeeds(f"nc -N '{test2_ip}' 5678 < /dev/null >&2")
|
||||
|
||||
shutdownVM_test1;
|
||||
shutdownVM_test2;
|
||||
shutdown_vm_test1()
|
||||
shutdown_vm_test2()
|
||||
|
||||
destroyVM_test1;
|
||||
destroyVM_test2;
|
||||
destroy_vm_test1()
|
||||
destroy_vm_test2()
|
||||
'';
|
||||
} // (if enableUnfree then unfreeTests else {})
|
||||
|
26
pkgs/applications/audio/ashuffle/default.nix
Normal file
26
pkgs/applications/audio/ashuffle/default.nix
Normal file
@ -0,0 +1,26 @@
|
||||
{ stdenv, fetchFromGitHub, cmake, pkg-config, mpd_clientlib, meson, ninja }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "ashuffle";
|
||||
version = "3.4.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "joshkunz";
|
||||
repo = "ashuffle";
|
||||
rev = "v${version}";
|
||||
sha256 = "09q6lwgc1dc8bg1mb9js9qz3xcsxph3548nxzvyb4v8111gixrp7";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
dontUseCmakeConfigure = true;
|
||||
nativeBuildInputs = [ cmake pkg-config meson ninja ];
|
||||
buildInputs = [ mpd_clientlib ];
|
||||
|
||||
meta = with stdenv.lib; {
|
||||
homepage = "https://github.com/joshkunz/ashuffle";
|
||||
description = "Automatic library-wide shuffle for mpd";
|
||||
maintainers = [ maintainers.tcbravo ];
|
||||
platforms = platforms.unix;
|
||||
license = licenses.mit;
|
||||
};
|
||||
}
|
@ -17,7 +17,7 @@ stdenv.mkDerivation rec {
|
||||
# When updating, please check if https://github.com/csound/csound/issues/1078
|
||||
# has been fixed in the new version so we can use the normal fluidsynth
|
||||
# version and remove fluidsynth 1.x from nixpkgs again.
|
||||
version = "6.13.0";
|
||||
version = "6.14.0";
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
@ -27,7 +27,7 @@ stdenv.mkDerivation rec {
|
||||
owner = "csound";
|
||||
repo = "csound";
|
||||
rev = version;
|
||||
sha256 = "14822ybqyp31z18gky2y9zadr9dkbhabg97y139py73w7v3af1bh";
|
||||
sha256 = "1sr9knfhbm2m0wpkjq2l5n471vnl51wy4p6j4m95zqybimzb4s2j";
|
||||
};
|
||||
|
||||
cmakeFlags = [ "-DBUILD_CSOUND_AC=0" ] # fails to find Score.hpp
|
||||
|
@ -7,13 +7,13 @@
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "ft2-clone";
|
||||
version = "1.26";
|
||||
version = "1.28";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "8bitbubsy";
|
||||
repo = "ft2-clone";
|
||||
rev = "v${version}";
|
||||
sha256 = "0fqb4415qy2nwjz7ahi43nk795ifswb2b37sc7p5n9m4yc8h53wv";
|
||||
sha256 = "1hbcl89cpx9bsafxrjyfx6vrbs4h3lnzmqm12smcvdg8ksfgzj0d";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ cmake ];
|
||||
|
@ -1,5 +1,16 @@
|
||||
{ stdenv, fetchFromGitHub , xorg, freetype, alsaLib, curl, libjack2
|
||||
, lv2, pkgconfig, libGLU, libGL }:
|
||||
{ stdenv
|
||||
, fetchFromGitHub
|
||||
, fetchpatch
|
||||
, xorg
|
||||
, freetype
|
||||
, alsaLib
|
||||
, curl
|
||||
, libjack2
|
||||
, lv2
|
||||
, pkgconfig
|
||||
, libGLU
|
||||
, libGL
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
version = "0.9.0";
|
||||
@ -20,7 +31,15 @@
|
||||
|
||||
CXXFLAGS = "-DHAVE_LROUND";
|
||||
|
||||
patchPhase = ''
|
||||
patches = [
|
||||
# gcc9 compatibility https://github.com/mtytel/helm/pull/233
|
||||
(fetchpatch {
|
||||
url = "https://github.com/mtytel/helm/commit/cb611a80bd5a36d31bfc31212ebbf79aa86c6f08.patch";
|
||||
sha256 = "1i2289srcfz17c3zzab6f51aznzdj62kk53l4afr32bkjh9s4ixk";
|
||||
})
|
||||
];
|
||||
|
||||
prePatch = ''
|
||||
sed -i 's|usr/||g' Makefile
|
||||
'';
|
||||
|
||||
|
28
pkgs/applications/audio/lv2-cpp-tools/default.nix
Normal file
28
pkgs/applications/audio/lv2-cpp-tools/default.nix
Normal file
@ -0,0 +1,28 @@
|
||||
{ stdenv, fetchzip, pkgconfig, lv2, gtkmm2, boost }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "lv2-cpp-tools";
|
||||
version = "1.0.5";
|
||||
|
||||
src = fetchzip {
|
||||
url = "http://deb.debian.org/debian/pool/main/l/lv2-c++-tools/lv2-c++-tools_${version}.orig.tar.bz2";
|
||||
sha256 = "039bq7d7s2bhfcnlsfq0mqxr9a9iqwg5bwcpxfi24c6yl6krydsi";
|
||||
};
|
||||
|
||||
preConfigure = ''
|
||||
sed -r 's,/bin/bash,${stdenv.shell},g' -i ./configure
|
||||
sed -r 's,/sbin/ldconfig,ldconfig,g' -i ./Makefile.template
|
||||
'';
|
||||
|
||||
nativeBuildInputs = [ pkgconfig ];
|
||||
|
||||
buildInputs = [ lv2 gtkmm2 boost ];
|
||||
|
||||
meta = with stdenv.lib; {
|
||||
homepage = "http://ll-plugins.nongnu.org/hacking.html";
|
||||
description = "Tools and libraries that may come in handy when writing LV2 plugins in C++";
|
||||
license = licenses.gpl3;
|
||||
maintainers = [ maintainers.michalrus ];
|
||||
platforms = platforms.linux;
|
||||
};
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
{ stdenv, fetchFromGitHub, cmake, eigen, libav_all }:
|
||||
{ stdenv, fetchFromGitHub, cmake, eigen, libav }:
|
||||
stdenv.mkDerivation {
|
||||
pname = "musly";
|
||||
version = "unstable-2017-04-26";
|
||||
@ -9,7 +9,7 @@ stdenv.mkDerivation {
|
||||
sha256 = "1q42wvdwy2pac7bhfraqqj2czw7w2m33ms3ifjl8phm7d87i8825";
|
||||
};
|
||||
nativeBuildInputs = [ cmake ];
|
||||
buildInputs = [ eigen (libav_all.override { vaapiSupport = stdenv.isLinux; }).libav_11 ];
|
||||
buildInputs = [ eigen (libav.override { vaapiSupport = stdenv.isLinux; }) ];
|
||||
fixupPhase = if stdenv.isDarwin then ''
|
||||
install_name_tool -change libmusly.dylib $out/lib/libmusly.dylib $out/bin/musly
|
||||
install_name_tool -change libmusly_resample.dylib $out/lib/libmusly_resample.dylib $out/bin/musly
|
||||
|
@ -10,13 +10,13 @@ assert pcreSupport -> pcre != null;
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "ncmpc";
|
||||
version = "0.38";
|
||||
version = "0.39";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "MusicPlayerDaemon";
|
||||
repo = "ncmpc";
|
||||
rev = "v${version}";
|
||||
sha256 = "1kidpd1xrfax3v31q93r9g9b7jd841476q47wgd94h1a86b70gs9";
|
||||
sha256 = "08xrcinfm1a7hjycf8la7gnsxbp3six70ks987dr7j42kd42irfq";
|
||||
};
|
||||
|
||||
buildInputs = [ glib ncurses mpd_clientlib boost ]
|
||||
|
@ -12,13 +12,13 @@ let
|
||||
;
|
||||
in pythonPackages.buildPythonApplication rec {
|
||||
pname = "picard";
|
||||
version = "2.4.1";
|
||||
version = "2.4.2";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "metabrainz";
|
||||
repo = pname;
|
||||
rev = "release-${version}";
|
||||
sha256 = "0s4jmcg1n6ayxf7x0amq67rgn6y127h98s2k4fcna6n9477krrwf";
|
||||
sha256 = "0sbccsisk9w0gnblvhg7wk1c5ydppldjbvaa0zhl3yrid5a363ah";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ gettext qt5.wrapQtAppsHook qt5.qtbase ]
|
||||
|
@ -4,11 +4,11 @@
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
name = "snd-20.2";
|
||||
name = "snd-20.3";
|
||||
|
||||
src = fetchurl {
|
||||
url = "mirror://sourceforge/snd/${name}.tar.gz";
|
||||
sha256 = "0ip4sfyxqlbghlggipmvvqjqs1a7qas0zcmzw8d1nwg6krjkfj0r";
|
||||
sha256 = "016slh34gb6qqb38m8k9yg48rbhc5p12084szcwvanhh5v7fc7mk";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ pkgconfig ];
|
||||
|
36
pkgs/applications/audio/talentedhack/default.nix
Normal file
36
pkgs/applications/audio/talentedhack/default.nix
Normal file
@ -0,0 +1,36 @@
|
||||
{ stdenv, fetchFromGitHub, lv2, fftwFloat, pkgconfig }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "talentedhack";
|
||||
version = "1.86";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "jeremysalwen";
|
||||
repo = "talentedhack";
|
||||
rev = "v${version}";
|
||||
sha256 = "0kwvayalysmk7y49jq0k16al252md8d45z58hphzsksmyz6148bx";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ pkgconfig ];
|
||||
|
||||
buildInputs = [ lv2 fftwFloat ];
|
||||
|
||||
# To avoid name clashes, plugins should be compiled with symbols hidden, except for `lv2_descriptor`:
|
||||
preConfigure = ''
|
||||
sed -r 's/^CFLAGS.*$/\0 -fvisibility=hidden/' -i Makefile
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
d=$out/lib/lv2/talentedhack.lv2
|
||||
mkdir -p $d
|
||||
cp *.so *.ttl $d
|
||||
'';
|
||||
|
||||
meta = with stdenv.lib; {
|
||||
homepage = "https://github.com/jeremysalwen/TalentedHack";
|
||||
description = "LV2 port of Autotalent pitch correction plugin";
|
||||
license = licenses.gpl3;
|
||||
maintainers = [ maintainers.michalrus ];
|
||||
platforms = platforms.linux;
|
||||
};
|
||||
}
|
89
pkgs/applications/audio/virtual-ans/default.nix
Normal file
89
pkgs/applications/audio/virtual-ans/default.nix
Normal file
@ -0,0 +1,89 @@
|
||||
{ stdenv
|
||||
, fetchzip
|
||||
, libX11
|
||||
, libXi
|
||||
, libGL
|
||||
, alsaLib
|
||||
, SDL2
|
||||
, autoPatchelfHook
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "virtual-ans";
|
||||
version = "3.0.2c";
|
||||
|
||||
src = fetchzip {
|
||||
url = "https://warmplace.ru/soft/ans/virtual_ans-${version}.zip";
|
||||
sha256 = "03r1v3l7rd59dakr7ndvgsqchv00ppkvi6sslgf1ng07r3rsvb1n";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
autoPatchelfHook
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
stdenv.cc.cc.lib
|
||||
libX11
|
||||
libXi
|
||||
libGL
|
||||
alsaLib
|
||||
SDL2
|
||||
];
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -R ./* $out/
|
||||
|
||||
# Remove all executables except for current architecture
|
||||
ls -1d $out/START* | grep -v ${startScript} | xargs rm -rf
|
||||
ls -1d $out/bin/pixilang_linux* | grep -v ${linuxExecutable} | xargs rm -rf
|
||||
|
||||
# Start script performs relative search for resources, so it cannot be moved
|
||||
# to bin directory
|
||||
ln -s $out/${startScript} $out/bin/virtual-ans
|
||||
'';
|
||||
|
||||
startScript = if stdenv.isx86_32 then "START_LINUX_X86"
|
||||
else if stdenv.isx86_64 then "START_LINUX_X86_64"
|
||||
#else if stdenv.isDarwin then "START_MACOS.app" # disabled because I cannot test on Darwin
|
||||
else abort "Unsupported platform: ${stdenv.platform.kernelArch}.";
|
||||
|
||||
linuxExecutable = if stdenv.isx86_32 then "pixilang_linux_x86"
|
||||
else if stdenv.isx86_64 then "pixilang_linux_x86_64"
|
||||
else "";
|
||||
|
||||
meta = with stdenv.lib; {
|
||||
description = "Photoelectronic microtonal/spectral musical instrument";
|
||||
longDescription = ''
|
||||
Virtual ANS is a software simulator of the unique Russian synthesizer ANS
|
||||
- photoelectronic musical instrument created by Evgeny Murzin from 1938 to
|
||||
1958. The ANS made it possible to draw music in the form of a spectrogram
|
||||
(sonogram), without live instruments and performers. It was used by
|
||||
Stanislav Kreichi, Alfred Schnittke, Edward Artemiev and other Soviet
|
||||
composers in their experimental works. You can also hear the sound of the
|
||||
ANS in Andrei Tarkovsky's movies Solaris, The Mirror, Stalker.
|
||||
|
||||
The simulator extends the capabilities of the original instrument. Now
|
||||
it's a full-featured graphics editor where you can convert sound into an
|
||||
image, load and play pictures, draw microtonal/spectral music and create
|
||||
some unusual deep atmospheric sounds. This app is for everyone who loves
|
||||
experiments and is looking for something new.
|
||||
|
||||
Key features:
|
||||
|
||||
+ unlimited number of pure tone generators;
|
||||
+ powerful sonogram editor - you can draw the spectrum and play it at the same time;
|
||||
+ any sound (from a WAV file or a Microphone/Line-in) can be converted to image (sonogram) and vice versa;
|
||||
+ support for MIDI devices;
|
||||
+ polyphonic synth mode with MIDI mapping;
|
||||
+ supported file formats: WAV, AIFF, PNG, JPEG, GIF;
|
||||
+ supported sound systems: ASIO, DirectSound, MME, ALSA, OSS, JACK, Audiobus, IAA.
|
||||
'';
|
||||
homepage = "https://warmplace.ru/soft/ans/";
|
||||
license = licenses.free;
|
||||
# I cannot test the Darwin version, so I'll leave it disabled
|
||||
platforms = [ "x86_64-linux" "i686-linux" ];
|
||||
maintainers = with maintainers; [ jacg ];
|
||||
};
|
||||
|
||||
}
|
27
pkgs/applications/audio/vocproc/default.nix
Normal file
27
pkgs/applications/audio/vocproc/default.nix
Normal file
@ -0,0 +1,27 @@
|
||||
{ stdenv, fetchzip, pkgconfig, lvtk, lv2, fftw, lv2-cpp-tools, gtkmm2 }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "vocproc";
|
||||
version = "0.2.1";
|
||||
|
||||
src = fetchzip {
|
||||
url = "https://hyperglitch.com/files/vocproc/${pname}-${version}.default.tar.gz";
|
||||
sha256 = "07a1scyz14mg2jdbw6fpv4qg91zsw61qqii64n9qbnny9d5pn8n2";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ pkgconfig ];
|
||||
|
||||
buildInputs = [ lv2 fftw lv2-cpp-tools gtkmm2 ];
|
||||
|
||||
makeFlags = [
|
||||
"INSTALL_DIR=$(out)/lib/lv2"
|
||||
];
|
||||
|
||||
meta = with stdenv.lib; {
|
||||
homepage = "https://hyperglitch.com/dev/VocProc";
|
||||
description = "An LV2 plugin for pitch shifting (with or without formant correction), vocoding, automatic pitch correction and harmonizing of singing voice (harmonizer)";
|
||||
license = licenses.gpl2;
|
||||
maintainers = [ maintainers.michalrus ];
|
||||
platforms = platforms.linux;
|
||||
};
|
||||
}
|
@ -7,13 +7,13 @@ with stdenv.lib;
|
||||
mkDerivation rec {
|
||||
|
||||
name = "bitcoin" + (toString (optional (!withGui) "d")) + "-abc-" + version;
|
||||
version = "0.21.12";
|
||||
version = "0.21.13";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "bitcoin-ABC";
|
||||
repo = "bitcoin-abc";
|
||||
rev = "v${version}";
|
||||
sha256 = "1mad3aqfwrxi06135nf8hv13d67nilmxpx4dw5vjcy1zi3lljj1j";
|
||||
sha256 = "1x8xcdi1vcskggk9bqkwr3ah4vi9b7sj2h8hf7spac6dvz8lmzav";
|
||||
};
|
||||
|
||||
patches = [ ./fix-bitcoin-qt-build.patch ];
|
||||
|
@ -4,11 +4,11 @@
|
||||
with stdenv.lib;
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "clightning";
|
||||
version = "0.9.0";
|
||||
version = "0.9.0-1";
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://github.com/ElementsProject/lightning/releases/download/v${version}/clightning-v${version}.zip";
|
||||
sha256 = "11ig5bqxvhx82gq9nl7c5iqaf3x8xbwfx7cf2318pyqdimz4r1v6";
|
||||
sha256 = "01cwcrqysqsrf96bbbj0grm8j5m46a3acgwy0kzxdx05jdzld9sc";
|
||||
};
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "ergo";
|
||||
version = "3.3.0";
|
||||
version = "3.3.1";
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://github.com/ergoplatform/ergo/releases/download/v${version}/ergo-${version}.jar";
|
||||
sha256 = "1lja4ba6bm1jk0lh2ra5v8i5g3f1gy7mk2b3yrx1w7x02ll9gr06";
|
||||
sha256 = "1qr1vfb6mhm2hxl2ksydkhadm7phadn93lwm3f9zni01plk56bb5";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ makeWrapper ];
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "go-ethereum";
|
||||
version = "1.9.19";
|
||||
version = "1.9.20";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "ethereum";
|
||||
repo = pname;
|
||||
rev = "v${version}";
|
||||
sha256 = "08wf7qklk31dky2z0l2j9vbyr8721gkvy4dsc60afwrwihwd8lrp";
|
||||
sha256 = "031cbl8yqw5g5yrm5h1x8s5ckdw2xkym46009l579zvafn2vcnj7";
|
||||
};
|
||||
|
||||
runVend = true;
|
||||
@ -42,6 +42,6 @@ buildGoModule rec {
|
||||
homepage = "https://geth.ethereum.org/";
|
||||
description = "Official golang implementation of the Ethereum protocol";
|
||||
license = with licenses; [ lgpl3 gpl3 ];
|
||||
maintainers = with maintainers; [ adisbladis lionello xrelkd ];
|
||||
maintainers = with maintainers; [ adisbladis lionello xrelkd RaghavSood ];
|
||||
};
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ assert stdenv.isDarwin -> IOKit != null;
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "monero";
|
||||
version = "0.16.0.1";
|
||||
version = "0.16.0.3";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "monero-project";
|
||||
repo = "monero";
|
||||
rev = "v${version}";
|
||||
sha256 = "0n2cviqm8radpynx70fc0819k1xknjc58cvb4whlc49ilyvh8ky6";
|
||||
sha256 = "1r9x3712vhb24dxxirfiwj5f9x0h4m7x0ngiiavf5983dfdlgz33";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
|
@ -19,9 +19,9 @@ let
|
||||
sha256Hash = "11lkwcbzdl86cyz4lci65cx9z5jjhrc4z40maqx2r5hw1xka9290";
|
||||
};
|
||||
latestVersion = { # canary & dev
|
||||
version = "4.2.0.5"; # "Android Studio 4.2 Canary 5"
|
||||
build = "201.6682321";
|
||||
sha256Hash = "076q6d7kmi0wcsqak7n6ggp1qns4xj1134xcpdzb92qk3dmg3wrh";
|
||||
version = "4.2.0.7"; # "Android Studio 4.2 Canary 7"
|
||||
build = "201.6720134";
|
||||
sha256Hash = "1c9s6rd0z596qr7hbil5rl3fqby7c8h7ma52d1qj5rxra73k77nz";
|
||||
};
|
||||
in {
|
||||
# Attributes are named by their corresponding release channels
|
||||
|
@ -1,18 +1,18 @@
|
||||
{ stdenv, pkgconfig, qt5, fetchFromGitHub }:
|
||||
|
||||
with qt5;
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
version = "0.10.0";
|
||||
{ stdenv, mkDerivation, pkgconfig, qmake, qttools, qtbase, qtsvg, qtx11extras, fetchFromGitHub }:
|
||||
mkDerivation rec {
|
||||
pname = "featherpad";
|
||||
version = "0.10.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "tsujan";
|
||||
repo = "FeatherPad";
|
||||
rev = "V${version}";
|
||||
sha256 = "1wrbs6kni9s3x39cckm9kzpglryxn5vyarilvh9pafbzpc6rc57p";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ qmake pkgconfig qttools ];
|
||||
buildInputs = [ qtbase qtsvg qtx11extras ];
|
||||
|
||||
meta = with stdenv.lib; {
|
||||
description = "Lightweight Qt5 Plain-Text Editor for Linux";
|
||||
homepage = "https://github.com/tsujan/FeatherPad";
|
||||
|
27
pkgs/applications/editors/kakoune/plugins/case.kak.nix
Normal file
27
pkgs/applications/editors/kakoune/plugins/case.kak.nix
Normal file
@ -0,0 +1,27 @@
|
||||
{ stdenv, fetchFromGitLab }:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "case.kak";
|
||||
version = "unstable-2020-04-06";
|
||||
|
||||
src = fetchFromGitLab {
|
||||
owner = "FlyingWombat";
|
||||
repo = "case.kak";
|
||||
rev = "6f1511820aa3abfa118e0f856118adc8113e2185";
|
||||
sha256 = "002njrlwgakqgp74wivbppr9qyn57dn4n5bxkr6k6nglk9qndwdp";
|
||||
};
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/share/kak/autoload/plugins
|
||||
cp -r rc/case.kak $out/share/kak/autoload/plugins
|
||||
'';
|
||||
|
||||
meta = with stdenv.lib; {
|
||||
description = "Ease navigation between opened buffers in Kakoune";
|
||||
homepage = "https://gitlab.com/FlyingWombat/case.kak";
|
||||
license = licenses.unlicense;
|
||||
maintainers = with maintainers; [ eraserhd ];
|
||||
platform = platforms.all;
|
||||
};
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user