Merge ee2477b0b5 into haskell-updates

This commit is contained in:
nixpkgs-ci[bot]
2025-05-30 00:23:29 +00:00
committed by GitHub
2185 changed files with 49853 additions and 49691 deletions
+1 -1
View File
@@ -162,7 +162,7 @@
- any-glob-to-any-file:
- doc/languages-frameworks/gnome.section.md
- nixos/modules/services/desktops/gnome/**/*
- nixos/modules/services/x11/desktop-managers/gnome.nix
- nixos/modules/services/desktop-managers/gnome.nix
- nixos/tests/gnome-xorg.nix
- nixos/tests/gnome.nix
- pkgs/desktops/gnome/**/*
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
filter: blob:none
filter: tree:0
path: trusted
- name: Check cherry-picks
@@ -28,4 +28,4 @@ jobs:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
./trusted/maintainers/scripts/check-cherry-picks.sh "$BASE_SHA" "$HEAD_SHA"
./trusted/ci/check-cherry-picks.sh "$BASE_SHA" "$HEAD_SHA"
+10
View File
@@ -165,6 +165,13 @@ nixos/modules/installer/tools/nix-fallback-paths.nix @NixOS/nix-team @raitobeza
## common-updater-scripts
/pkgs/common-updater/scripts/update-source-version @jtojnar
# Android tools, libraries, and environments
/pkgs/development/android* @NixOS/android
/pkgs/development/mobile/android* @NixOS/android
/pkgs/applications/editors/android-studio* @NixOS/android
/doc/languages-frameworks/android* @NixOS/android
/pkgs/by-name/an/android* @NixOS/android
# Python-related code and docs
/doc/languages-frameworks/python.section.md @mweinelt @natsukium
/maintainers/scripts/update-python-libraries @mweinelt @natsukium
@@ -470,6 +477,9 @@ pkgs/development/interpreters/erlang/ @NixOS/beam
pkgs/development/interpreters/elixir/ @NixOS/beam
pkgs/development/interpreters/lfe/ @NixOS/beam
# Authelia
pkgs/servers/authelia/ @06kellyjac @dit7ya @nicomem
# OctoDNS
pkgs/by-name/oc/octodns/ @anthonyroussel
@@ -1,17 +1,25 @@
#!/usr/bin/env bash
# Find alleged cherry-picks
set -e
set -eo pipefail
if [ $# != "2" ] ; then
echo "usage: check-cherry-picks.sh base_rev head_rev"
exit 2
fi
PICKABLE_BRANCHES=${PICKABLE_BRANCHES:-master staging release-??.?? staging-??.??}
# Make sure we are inside the nixpkgs repo, even when called from outside
cd "$(dirname "${BASH_SOURCE[0]}")"
PICKABLE_BRANCHES="master release-??.?? staging-??.?? haskell-updates python-updates"
problem=0
while read new_commit_sha ; do
# Not everyone calls their remote "origin"
remote="$(git remote -v | grep -i 'NixOS/nixpkgs' | head -n1 | cut -f1 || true)"
commits="$(git rev-list --reverse "$1..$2")"
while read -r new_commit_sha ; do
if [ -z "$new_commit_sha" ] ; then
continue # skip empty lines
fi
@@ -26,30 +34,47 @@ while read new_commit_sha ; do
original_commit_sha=$(
git rev-list --max-count=1 --format=format:%B "$new_commit_sha" \
| grep -Ei -m1 "cherry.*[0-9a-f]{40}" \
| grep -Eoi -m1 '[0-9a-f]{40}'
| grep -Eoi -m1 '[0-9a-f]{40}' || true
)
if [ "$?" != "0" ] ; then
echo " ? Couldn't locate original commit hash in message"
[ "$GITHUB_ACTIONS" = 'true' ] && echo ::endgroup::
if [ -z "$original_commit_sha" ] ; then
if [ "$GITHUB_ACTIONS" = 'true' ] ; then
echo ::endgroup::
echo -n "::error ::"
else
echo -n " ✘ "
fi
echo "Couldn't locate original commit hash in message"
echo "Note this should not necessarily be treated as a hard fail, but a reviewer's attention should" \
"be drawn to it and github actions have no way of doing that but to raise a 'failure'"
problem=1
continue
fi
set -f # prevent pathname expansion of patterns
for branch_pattern in $PICKABLE_BRANCHES ; do
for pattern in $PICKABLE_BRANCHES ; do
set +f # re-enable pathname expansion
# Reverse sorting by refname and taking one match only means we can only backport
# from unstable and the latest stable. That makes sense, because even right after
# branch-off, when we have two supported stable branches, we only ever want to cherry-pick
# **to** the older one, but never **from** it.
# This makes the job significantly faster in the case when commits can't be found,
# because it doesn't need to iterate through 20+ branches, which all need to be fetched.
branches="$(git for-each-ref --sort=-refname --format="%(refname)" \
"refs/remotes/${remote:-origin}/$pattern" | head -n1)"
while read -r picked_branch ; do
if git merge-base --is-ancestor "$original_commit_sha" "$picked_branch" ; then
echo "$original_commit_sha present in branch $picked_branch"
range_diff_common='git range-diff
range_diff_common='git --no-pager range-diff
--no-notes
--creation-factor=100
'"$original_commit_sha~..$original_commit_sha"'
'"$new_commit_sha~..$new_commit_sha"'
'
if $range_diff_common --no-color | grep -E '^ {4}[+-]{2}' > /dev/null ; then
if $range_diff_common --no-color 2> /dev/null | grep -E '^ {4}[+-]{2}' > /dev/null ; then
if [ "$GITHUB_ACTIONS" = 'true' ] ; then
echo ::endgroup::
echo -n "::warning ::"
@@ -72,11 +97,7 @@ while read new_commit_sha ; do
# move on to next commit
continue 3
fi
done <<< "$(
git for-each-ref \
--format="%(refname)" \
"refs/remotes/origin/$branch_pattern"
)"
done <<< "$branches"
done
if [ "$GITHUB_ACTIONS" = 'true' ] ; then
@@ -88,10 +109,6 @@ while read new_commit_sha ; do
echo "$original_commit_sha not found in any pickable branch"
problem=1
done <<< "$(
git rev-list \
-E -i --grep="cherry.*[0-9a-f]{40}" --reverse \
"$1..$2"
)"
done <<< "$commits"
exit $problem
+3 -1
View File
@@ -73,7 +73,9 @@ let
# The number of attributes per chunk, see ./README.md for more info.
chunkSize,
checkMeta ? true,
includeBroken ? true,
# Don't try to eval packages marked as broken.
includeBroken ? false,
# Whether to just evaluate a single chunk for quick testing
quickTest ? false,
}:
+4
View File
@@ -795,6 +795,10 @@ Additionally, the following optional arguments can be given:
: Clone the entire repository as opposing to just creating a shallow clone.
This implies `leaveDotGit`.
*`fetchTags`* (Boolean)
: Whether to fetch all tags from the remote repository. This is useful when the build process needs to run `git describe` or other commands that require tag information to be available. This parameter implies `leaveDotGit`, as tags are stored in the `.git` directory.
*`sparseCheckout`* (List of String)
: Prevent git from fetching unnecessary blobs from server.
+2
View File
@@ -121,6 +121,8 @@ agda.withPackages {
}
```
To install Agda without GHC, use `ghc = null;`.
## Writing Agda packages {#writing-agda-packages}
To write a nix derivation for an Agda library, first check that the library has a `*.agda-lib` file.
+2 -2
View File
@@ -115,8 +115,8 @@ All new projects should use the CUDA redistributables available in [`cudaPackage
### Updating supported compilers and GPUs {#updating-supported-compilers-and-gpus}
1. Update `nvcc-compatibilities.nix` in `pkgs/development/cuda-modules/` to include the newest release of NVCC, as well as any newly supported host compilers.
2. Update `gpus.nix` in `pkgs/development/cuda-modules/` to include any new GPUs supported by the new release of CUDA.
1. Update `nvccCompatibilities` in `pkgs/development/cuda-modules/_cuda/data/nvcc.nix` to include the newest release of NVCC, as well as any newly supported host compilers.
2. Update `cudaCapabilityToInfo` in `pkgs/development/cuda-modules/_cuda/data/cuda.nix` to include any new GPUs supported by the new release of CUDA.
### Updating the CUDA Toolkit runfile installer {#updating-the-cuda-toolkit}
+1 -1
View File
@@ -59,7 +59,7 @@ For instance, `sqlite-lua` needs `g:sqlite_clib_path` to be set to work. Nixpkgs
- `neovimRcContent`: Extra vimL code sourced by the generated `init.lua`.
- `wrapperArgs`: Extra arguments forwarded to the `makeWrapper` call.
- `wrapRc`: Nix, not being able to write in your `$HOME`, loads the
generated Neovim configuration via its `-u` argument, i.e. : `-u /nix/store/...generatedInit.lua`. This has side effects like preventing Neovim from reading your config in `$XDG_CONFIG_HOME` (see bullet 7 of [`:help startup`](https://neovim.io/doc/user/starting.html#_initialization) in Neovim). Disable it if you want to generate your own wrapper. You can still reuse while reusing the logic of the nixpkgs wrapper and access the generated config via `neovim.passthru.initRc`.
generated Neovim configuration via the `$VIMINIT` environment variable, i.e. : `export VIMINIT='lua dofile("/nix/store/…-init.lua")'`. This has side effects like preventing Neovim from sourcing your `init.lua` in `$XDG_CONFIG_HOME/nvim` (see bullet 7 of [`:help startup`](https://neovim.io/doc/user/starting.html#startup) in Neovim). Disable it if you want to generate your own wrapper. You can still reuse the generated vimscript init code via `neovim.passthru.initRc`.
- `plugins`: A list of plugins to add to the wrapper.
```
+1 -1
View File
@@ -28,7 +28,7 @@
- Applications linked against different Mesa versions than installed on the system should now work correctly going forward (however, applications against older Mesa, e.g. from Nixpkgs releases before 25.05, remain broken)
- Packages that used to depend on Mesa for libgbm or libdri should use `libgbm` or `dri-pkgconfig-stub` as inputs, respectively
- OpenSSH has been updated from 9.9p2 to 10.0p2, dropping support for DSA keys and adding a new `ssh-auth` binary to handle user authentication in a different address space from unauthenticated sessions. Additionally, we now enable a configure option by default that attempts to lock sshd into RAM to prevent it from being swapped out, which may improve performance if the system is under memory pressure. See the [full changelog](https://www.openwall.com/lists/oss-security/2025/04/09/1) for more details.
- OpenSSH has been updated from 9.9p2 to 10.0p2, dropping support for DSA keys and adding a new `ssh-auth` binary to handle user authentication in a different address space from unauthenticated sessions. See the [full changelog](https://www.openwall.com/lists/oss-security/2025/04/09/1) for more details.
- Emacs has been updated to 30.1.
This introduces some backwardsincompatible changes; see the NEWS for details.
+3 -1
View File
@@ -37,4 +37,6 @@
### Additions and Improvements {#sec-nixpkgs-release-25.11-lib-additions-improvements}
- Create the first release note entry in this section!
- `neovim`: Added support for the `vim.o.exrc` option, the `VIMINIT` environment variable, and sourcing of `sysinit.vim`.
See the neovim help page [`:help startup`](https://neovim.io/doc/user/starting.html#startup) for more information, as well as [the nixpkgs neovim wrapper documentation](#neovim-custom-configuration).
+1
View File
@@ -309,6 +309,7 @@ let
stringLength
substring
isString
replaceString
replaceStrings
intersperse
concatStringsSep
+2 -2
View File
@@ -18,7 +18,7 @@ let
concatStrings
escape
head
replaceStrings
replaceString
;
mkPrimitive = t: v: {
@@ -451,7 +451,7 @@ rec {
mkString =
v:
let
sanitize = s: replaceStrings [ "\n" ] [ "\\n" ] (escape [ "'" "\\" ] s);
sanitize = s: replaceString "\n" "\\n" (escape [ "'" "\\" ] s);
in
mkPrimitive type.string v
// {
+36 -1
View File
@@ -332,6 +332,41 @@ rec {
*/
concatLines = concatMapStrings (s: s + "\n");
/**
Given string `s`, replace every occurrence of the string `from` with the string `to`.
# Inputs
`from`
: The string to be replaced
`to`
: The string to replace with
`s`
: The original string where replacements will be made
# Type
```
replaceString :: string -> string -> string -> string
```
# Examples
:::{.example}
## `lib.strings.replaceString` usage example
```nix
replaceString "world" "Nix" "Hello, world!"
=> "Hello, Nix!"
replaceString "." "_" "v1.2.3"
=> "v1_2_3"
```
:::
*/
replaceString = from: to: replaceStrings [ from ] [ to ];
/**
Repeat a string `n` times,
and concatenate the parts into a new string.
@@ -1138,7 +1173,7 @@ rec {
string = toString arg;
in
if match "[[:alnum:],._+:@%/-]+" string == null then
"'${replaceStrings [ "'" ] [ "'\\''" ] string}'"
"'${replaceString "'" "'\\''" string}'"
else
string;
+2 -2
View File
@@ -14,7 +14,7 @@ let
optionalAttrs
optionalString
removeSuffix
replaceStrings
replaceString
toUpper
;
@@ -522,7 +522,7 @@ let
#
# https://github.com/rust-lang/cargo/pull/9169
# https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431
cargoEnvVarTarget = replaceStrings [ "-" ] [ "_" ] (toUpper final.rust.cargoShortTarget);
cargoEnvVarTarget = replaceString "-" "_" (toUpper final.rust.cargoShortTarget);
# True if the target is no_std
# https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421
+11
View File
@@ -583,6 +583,14 @@ rec {
# https://github.com/llvm/llvm-project/pull/132173
cmodel = "medium";
};
linux-kernel = {
name = "loongarch-multiplatform";
target = "vmlinuz.efi";
autoModules = true;
preferBuiltin = true;
baseConfig = "defconfig";
DTB = true;
};
};
# This function takes a minimally-valid "platform" and returns an
@@ -611,6 +619,9 @@ rec {
else if platform.isAarch64 then
if platform.isDarwin then apple-m1 else aarch64-multiplatform
else if platform.isLoongArch64 then
loongarch64-multiplatform
else if platform.isRiscV then
riscv-multiplatform
+11
View File
@@ -91,6 +91,7 @@ let
range
recursiveUpdateUntil
removePrefix
replaceString
replicate
runTests
setFunctionArgs
@@ -497,6 +498,11 @@ runTests {
expected = "/usr/include:/usr/local/include";
};
testReplaceStringString = {
expr = strings.replaceString "." "_" "v1.2.3";
expected = "v1_2_3";
};
testReplicateString = {
expr = strings.replicate 5 "hello";
expected = "hellohellohellohellohello";
@@ -1728,6 +1734,11 @@ runTests {
];
};
testReplaceString = {
expr = replaceString "world" "Nix" "Hello, world!";
expected = "Hello, Nix!";
};
testReplicate = {
expr = replicate 3 "a";
expected = [
+22 -8
View File
@@ -6554,7 +6554,7 @@
name = "DontEatOreo";
github = "DontEatOreo";
githubId = 57304299;
keys = [ { fingerprint = "33CD 5C0A 673C C54D 661E 5E4C 0DB5 361B EEE5 30AB"; } ];
matrix = "@donteatoreo:matrix.org";
};
dopplerian = {
name = "Dopplerian";
@@ -13037,12 +13037,6 @@
githubId = 843652;
name = "Kim Burgess";
};
kindrowboat = {
email = "hello@kindrobot.ca";
github = "kindrowboat";
githubId = 777773;
name = "Stef Dunlap";
};
kini = {
email = "keshav.kini@gmail.com";
github = "kini";
@@ -16604,8 +16598,10 @@
moraxyc = {
name = "Moraxyc Xu";
email = "i@qaq.li";
matrix = "@moraxyc:qaq.li";
github = "Moraxyc";
githubId = 69713071;
keys = [ { fingerprint = "7DD1 A4DF 7DD6 AEEB F07B 1108 8296 4F3A B1D9 DE79"; } ];
};
moredread = {
email = "code@apb.name";
@@ -17609,7 +17605,7 @@
};
nicoo = {
email = "nicoo@debian.org";
github = "nbraud";
github = "nicoonoclaste";
githubId = 1155801;
name = "nicoo";
keys = [ { fingerprint = "E44E 9EA5 4B8E 256A FB73 49D3 EC9D 3708 72BC 7A8C"; } ];
@@ -22924,6 +22920,11 @@
matrix = "@c3n21:matrix.org";
githubId = 37077738;
};
sinjin2300 = {
name = "Sinjin";
github = "Sinjin2300";
githubId = 35543336;
};
sioodmy = {
name = "Antoni Sokołowski";
github = "sioodmy";
@@ -23437,6 +23438,13 @@
githubId = 16364318;
name = "Jeffrey Harmon";
};
squat = {
matrix = "@squat:beeper.com";
name = "squat";
github = "squat";
githubId = 20484159;
keys = [ { fingerprint = "F246 425A 7650 6F37 0552 BA8D DEA9 C405 09D9 65F5"; } ];
};
srghma = {
email = "srghma@gmail.com";
github = "srghma";
@@ -26717,6 +26725,12 @@
githubId = 9132420;
keys = [ { fingerprint = "F943 A0BC 720C 5BEF 73CD E02D B398 93FA 5F65 CAE1"; } ];
};
womeier = {
name = "Wolfgang Meier";
email = "womeier@posteo.de";
github = "womeier";
githubId = 55190123;
};
womfoo = {
email = "kranium@gikos.net";
github = "womfoo";
@@ -29,7 +29,7 @@ Thus you should pick one or more of the following lines:
{
services.xserver.desktopManager.plasma5.enable = true;
services.xserver.desktopManager.xfce.enable = true;
services.xserver.desktopManager.gnome.enable = true;
services.desktopManager.gnome.enable = true;
services.xserver.desktopManager.mate.enable = true;
services.xserver.windowManager.xmonad.enable = true;
services.xserver.windowManager.twm.enable = true;
@@ -46,7 +46,7 @@ alternative one by picking one of the following lines:
```nix
{
services.displayManager.sddm.enable = true;
services.xserver.displayManager.gdm.enable = true;
services.displayManager.gdm.enable = true;
}
```
@@ -20,7 +20,7 @@ Alongside many enhancements to NixOS modules and general system improvements, th
- GNOME has been updated to version 48.
- `decibels` music player is now installed by default. You can disable it using [](#opt-environment.gnome.excludePackages).
- `gnome-shell-extensions` extension collection (which included GNOME Classic extensions, Apps Menu, and User Themes, among others) are no longer installed by default. You can install them again with [](#opt-services.xserver.desktopManager.gnome.sessionPath).
- `gnome-shell-extensions` extension collection (which included GNOME Classic extensions, Apps Menu, and User Themes, among others) are no longer installed by default. You can install them again with {option}`services.xserver.desktopManager.gnome.sessionPath`.
- Option [](#opt-services.gnome.core-developer-tools.enable) now also installs `sysprof` and `d-spy`.
- Option `services.gnome.core-utilities.enable` has been renamed to [](#opt-services.gnome.core-apps.enable).
- `cantarell-fonts`, `source-code-pro` and `source-sans` fonts are no longer installed by default. They have been replaced by `adwaita-fonts`.
@@ -12,6 +12,11 @@
- [gtklock](https://github.com/jovanlanik/gtklock), a GTK-based lockscreen for Wayland. Available as [programs.gtklock](#opt-programs.gtklock.enable).
- [FileBrowser](https://filebrowser.org/), a web application for managing and sharing files. Available as [services.filebrowser](#opt-services.filebrowser.enable).
- [LACT](https://github.com/ilya-zlobintsev/LACT), a GPU monitoring and configuration tool, can now be enabled through [services.lact.enable](#opt-services.lact.enable).
Note that for LACT to work properly on AMD GPU systems, you need to enable [hardware.amdgpu.overdrive.enable](#opt-hardware.amdgpu.overdrive.enable).
- [SuiteNumérique Docs](https://github.com/suitenumerique/docs), a collaborative note taking, wiki and documentation web platform and alternative to Notion or Outline. Available as [services.lasuite-docs](#opt-services.lasuite-docs.enable).
## Backward Incompatibilities {#sec-release-25.11-incompatibilities}
@@ -27,3 +32,6 @@
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
- `services.clamsmtp` is unmaintained and was removed from Nixpkgs.
- `amdgpu` kernel driver overdrive mode can now be enabled by setting [hardware.amdgpu.overdrive.enable](#opt-hardware.amdgpu.overdrive.enable) and customized through [hardware.amdgpu.overdrive.ppfeaturemask](#opt-hardware.amdgpu.overdrive.ppfeaturemask).
This allows for fine-grained control over the GPU's performance and maybe required by overclocking softwares like Corectrl and Lact. These new options replace old options such as {option}`programs.corectrl.gpuOverclock.enable` and {option}`programs.tuxclocker.enableAMD`.
+1
View File
@@ -277,6 +277,7 @@ in
# avoid this race condition.
after = [ "systemd-modules-load.service" ];
wantedBy = [ "${realDevice'}.swap" ];
requiredBy = lib.optionals sw.randomEncryption.enable [ "${realDevice'}.swap" ];
before = [
"${realDevice'}.swap"
"shutdown.target"
+11 -24
View File
@@ -8,13 +8,23 @@ let
inherit (lib)
mkEnableOption
mkIf
mkOption
mkPackageOption
;
cfg = config.programs.corectrl;
in
{
imports = [
(lib.mkRenamedOptionModule
[ "programs" "corectrl" "gpuOverclock" "enable" ]
[ "hardware" "amdgpu" "overdrive" "enable" ]
)
(lib.mkRenamedOptionModule
[ "programs" "corectrl" "gpuOverclock" "ppfeaturemask" ]
[ "hardware" "amdgpu" "overdrive" "ppfeaturemask" ]
)
];
options.programs.corectrl = {
enable = mkEnableOption ''
CoreCtrl, a tool to overclock amd graphics cards and processors.
@@ -24,23 +34,6 @@ in
package = mkPackageOption pkgs "corectrl" {
extraDescription = "Useful for overriding the configuration options used for the package.";
};
gpuOverclock = {
enable = mkEnableOption ''
GPU overclocking
'';
ppfeaturemask = mkOption {
type = lib.types.str;
default = "0xfffd7fff";
example = "0xffffffff";
description = ''
Sets the `amdgpu.ppfeaturemask` kernel option.
In particular, it is used here to set the overdrive bit.
Default is `0xfffd7fff` as it is less likely to cause flicker issues.
Setting it to `0xffffffff` enables all features.
'';
};
};
};
config = mkIf cfg.enable {
@@ -61,12 +54,6 @@ in
}
});
'';
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/amd/include/amd_shared.h#n169
# The overdrive bit
boot.kernelParams = mkIf cfg.gpuOverclock.enable [
"amdgpu.ppfeaturemask=${cfg.gpuOverclock.ppfeaturemask}"
];
};
meta.maintainers = with lib.maintainers; [
@@ -7,7 +7,7 @@
isoImage.edition = lib.mkDefault "gnome";
services.xserver.desktopManager.gnome = {
services.desktopManager.gnome = {
# Add Firefox and other tools useful for installation to the launcher
favoriteAppsOverride = ''
[org.gnome.shell]
@@ -35,7 +35,7 @@
QT_QPA_PLATFORM = "$([[ $XDG_SESSION_TYPE = \"wayland\" ]] && echo \"wayland\")";
};
services.xserver.displayManager.gdm = {
services.displayManager.gdm = {
enable = true;
# autoSuspend makes the machine automatically suspend after inactivity.
# It's possible someone could/try to ssh'd into the machine and obviously
@@ -7,7 +7,7 @@
isoImage.edition = lib.mkDefault "gnome";
services.xserver.desktopManager.gnome = {
services.desktopManager.gnome = {
# Add Firefox and other tools useful for installation to the launcher
favoriteAppsOverride = ''
[org.gnome.shell]
@@ -16,7 +16,7 @@
enable = true;
};
services.xserver.displayManager.gdm = {
services.displayManager.gdm = {
enable = true;
# autoSuspend makes the machine automatically suspend after inactivity.
# It's possible someone could/try to ssh'd into the machine and obviously
+3 -1
View File
@@ -589,6 +589,7 @@
./services/development/zammad.nix
./services/display-managers/cosmic-greeter.nix
./services/display-managers/default.nix
./services/display-managers/gdm.nix
./services/display-managers/greetd.nix
./services/display-managers/ly.nix
./services/display-managers/sddm.nix
@@ -645,6 +646,7 @@
./services/hardware/kanata.nix
./services/hardware/keyd.nix
./services/hardware/kmonad.nix
./services/hardware/lact.nix
./services/hardware/lcd.nix
./services/hardware/libinput.nix
./services/hardware/lirc.nix
@@ -1535,6 +1537,7 @@
./services/web-apps/engelsystem.nix
./services/web-apps/ethercalc.nix
./services/web-apps/fider.nix
./services/web-apps/filebrowser.nix
./services/web-apps/filesender.nix
./services/web-apps/firefly-iii-data-importer.nix
./services/web-apps/firefly-iii.nix
@@ -1706,7 +1709,6 @@
./services/x11/colord.nix
./services/x11/desktop-managers/default.nix
./services/x11/display-managers/default.nix
./services/x11/display-managers/gdm.nix
./services/x11/display-managers/lightdm.nix
./services/x11/display-managers/slim.nix
./services/x11/display-managers/startx.nix
+1 -1
View File
@@ -27,7 +27,7 @@
services.dbus.packages = [ pkgs.gpaste ];
systemd.packages = [ pkgs.gpaste ];
# gnome-control-center crashes in Keyboard Shortcuts pane without the GSettings schemas.
services.xserver.desktopManager.gnome.sessionPath = [ pkgs.gpaste ];
services.desktopManager.gnome.sessionPath = [ pkgs.gpaste ];
# gpaste-reloaded applet doesn't work without the typelib
services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gpaste ];
};
@@ -28,7 +28,7 @@ in
nautilus-open-any-terminal
];
environment.sessionVariables = lib.mkIf (!config.services.xserver.desktopManager.gnome.enable) {
environment.sessionVariables = lib.mkIf (!config.services.desktopManager.gnome.enable) {
NAUTILUS_4_EXTENSION_DIR = "${pkgs.nautilus-python}/lib/nautilus/extensions-4";
};
+1 -1
View File
@@ -14,7 +14,7 @@ in
defaultEditor = lib.mkEnableOption "vim as the default editor";
package = lib.mkPackageOption pkgs "vim" { example = "vim-full"; };
package = lib.mkPackageOption pkgs "vim" { example = [ "vim-full" ]; };
};
# TODO: convert it into assert after 24.11 release
+1 -1
View File
@@ -33,7 +33,7 @@
# Support GNOME desktop environment if it's enabled on the system.
gnomeXdgDesktopPortalSupport =
prev.gnomeXdgDesktopPortalSupport or config.services.xserver.desktopManager.gnome.enable;
prev.gnomeXdgDesktopPortalSupport or config.services.desktopManager.gnome.enable;
# Support Hyprland desktop for Wayland if it's enabled on the system.
hyprlandXdgDesktopPortalSupport =
+19
View File
@@ -249,6 +249,23 @@ let
to provide Google Authenticator token to log in.
'';
};
allowNullOTP = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to allow login for accounts that have no OTP set
(i.e., accounts with no OTP configured or no existing
{file}`~/.google_authenticator`).
'';
};
forwardPass = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
The authentication provides a single field requiring
the user's password followed by the one-time password (OTP).
'';
};
};
otpwAuth = lib.mkOption {
@@ -1048,6 +1065,8 @@ let
modulePath = "${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so";
settings = {
no_increment_hotp = true;
forward_pass = cfg.googleAuthenticator.forwardPass;
nullok = cfg.googleAuthenticator.allowNullOTP;
};
}
{
@@ -336,7 +336,10 @@ in
[
gitMinimal
openssh
util-linux
# TODO (#409339): remove this patch. We had to add it to avoid a mass rebuild
# for the 25.05 release. Once the staging cycle referenced in the above PR completes,
# switch back to plain util-linux.
util-linux.withPatches
iproute2
ethtool
thin-provisioning-tools
@@ -10,8 +10,8 @@ To enable the GNOME desktop use:
```nix
{
services.xserver.desktopManager.gnome.enable = true;
services.xserver.displayManager.gdm.enable = true;
services.desktopManager.gnome.enable = true;
services.displayManager.gdm.enable = true;
}
```
@@ -76,17 +76,17 @@ GNOME Flashback provides a desktop environment based on the classic GNOME 2 arch
```nix
{
services.xserver.desktopManager.gnome.flashback.enableMetacity = true;
services.desktopManager.gnome.flashback.enableMetacity = true;
}
```
It is also possible to create custom sessions that replace Metacity with a different window manager using [](#opt-services.xserver.desktopManager.gnome.flashback.customSessions).
It is also possible to create custom sessions that replace Metacity with a different window manager using [](#opt-services.desktopManager.gnome.flashback.customSessions).
The following example uses `xmonad` window manager:
```nix
{
services.xserver.desktopManager.gnome.flashback.customSessions = [
services.desktopManager.gnome.flashback.customSessions = [
{
wmName = "xmonad";
wmLabel = "XMonad";
@@ -143,10 +143,10 @@ Overrides really only change the default values for GSettings keys so if you or
:::
You can override the default GSettings values using the
[](#opt-services.xserver.desktopManager.gnome.extraGSettingsOverrides) option.
[](#opt-services.desktopManager.gnome.extraGSettingsOverrides) option.
Take note that whatever packages you want to override GSettings for, you need to add them to
[](#opt-services.xserver.desktopManager.gnome.extraGSettingsOverridePackages).
[](#opt-services.desktopManager.gnome.extraGSettingsOverridePackages).
You can use `dconf-editor` tool to explore which GSettings you can set.
@@ -154,7 +154,7 @@ You can use `dconf-editor` tool to explore which GSettings you can set.
```nix
{
services.xserver.desktopManager.gnome = {
services.desktopManager.gnome = {
extraGSettingsOverrides = ''
# Change default background
[org.gnome.desktop.background]
@@ -15,7 +15,7 @@ let
literalExpression
;
cfg = config.services.xserver.desktopManager.gnome;
cfg = config.services.desktopManager.gnome;
serviceCfg = config.services.gnome;
# Prioritize nautilus by default when opening directories
@@ -78,13 +78,45 @@ let
in
{
meta = {
doc = ./gnome.md;
maintainers = lib.teams.gnome.members;
};
imports = [
(lib.mkRenamedOptionModule
[ "services" "xserver" "desktopManager" "gnome" "enable" ]
[ "services" "desktopManager" "gnome" "enable" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "desktopManager" "gnome" "extraGSettingsOverrides" ]
[ "services" "desktopManager" "gnome" "extraGSettingsOverrides" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "desktopManager" "gnome" "extraGSettingsOverridePackages" ]
[ "services" "desktopManager" "gnome" "extraGSettingsOverridePackages" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "desktopManager" "gnome" "debug" ]
[ "services" "desktopManager" "gnome" "debug" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "desktopManager" "gnome" "sessionPath" ]
[ "services" "desktopManager" "gnome" "sessionPath" ]
)
# flashback options
(lib.mkRenamedOptionModule
[ "services" "xserver" "desktopManager" "gnome" "flashback" "customSessions" ]
[ "services" "desktopManager" "gnome" "flashback" "customSessions" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "desktopManager" "gnome" "flashback" "enableMetacity" ]
[ "services" "desktopManager" "gnome" "flashback" "enableMetacity" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "desktopManager" "gnome" "flashback" "panelModulePackages" ]
[ "services" "desktopManager" "gnome" "flashback" "panelModulePackages" ]
)
(lib.mkRenamedOptionModule
[ "services" "gnome" "core-utilities" "enable" ]
[ "services" "gnome" "core-apps" "enable" ]
@@ -101,7 +133,7 @@ in
games.enable = mkEnableOption "GNOME games";
};
services.xserver.desktopManager.gnome = {
services.desktopManager.gnome = {
enable = mkOption {
type = types.bool;
default = false;
@@ -213,8 +245,8 @@ in
system.nixos-generate-config.desktopConfiguration = [
''
# Enable the GNOME Desktop Environment.
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;
services.displayManager.gdm.enable = true;
services.desktopManager.gnome.enable = true;
''
];
@@ -333,7 +365,7 @@ in
})
(lib.mkIf serviceCfg.core-shell.enable {
services.xserver.desktopManager.gnome.sessionPath = [
services.desktopManager.gnome.sessionPath = [
pkgs.gnome-shell
];
@@ -8,7 +8,7 @@
{
meta = {
maintainers = lib.teams.gnome.members;
maintainers = [ ];
};
###### interface
@@ -37,11 +37,6 @@
environment.systemPackages = [ pkgs.telepathy-mission-control ];
services.dbus.packages = [ pkgs.telepathy-mission-control ];
# Enable runtime optional telepathy in gnome-shell
services.xserver.desktopManager.gnome.sessionPath = with pkgs; [
telepathy-glib
];
};
}
@@ -257,7 +257,7 @@ in
dmConf = config.services.xserver.displayManager;
noDmUsed =
!(
dmConf.gdm.enable || cfg.sddm.enable || dmConf.xpra.enable || dmConf.lightdm.enable || cfg.ly.enable
cfg.gdm.enable || cfg.sddm.enable || dmConf.xpra.enable || dmConf.lightdm.enable || cfg.ly.enable
);
in
lib.mkIf noDmUsed (lib.mkDefault false);
@@ -7,19 +7,20 @@
let
cfg = config.services.xserver.displayManager;
cfg = config.services.displayManager.gdm;
gdm = pkgs.gdm;
xdmcfg = config.services.xserver.displayManager;
pamLogin = config.security.pam.services.login;
settingsFormat = pkgs.formats.ini { };
configFile = settingsFormat.generate "custom.conf" cfg.gdm.settings;
configFile = settingsFormat.generate "custom.conf" cfg.settings;
xSessionWrapper =
if (cfg.setupCommands == "") then
if (xdmcfg.setupCommands == "") then
null
else
pkgs.writeScript "gdm-x-session-wrapper" ''
#!${pkgs.bash}/bin/bash
${cfg.setupCommands}
${xdmcfg.setupCommands}
exec "$@"
'';
@@ -41,7 +42,7 @@ let
defaultSessionName = config.services.displayManager.defaultSession;
setSessionScript = pkgs.callPackage ./account-service-util.nix { };
setSessionScript = pkgs.callPackage ../x11/display-managers/account-service-util.nix { };
in
{
@@ -72,6 +73,35 @@ in
"gdm"
"nvidiaWayland"
] "We defer to GDM whether Wayland should be enabled.")
(lib.mkRenamedOptionModule
[ "services" "xserver" "displayManager" "gdm" "enable" ]
[ "services" "displayManager" "gdm" "enable" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "displayManager" "gdm" "debug" ]
[ "services" "displayManager" "gdm" "debug" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "displayManager" "gdm" "banner" ]
[ "services" "displayManager" "gdm" "banner" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "displayManager" "gdm" "settings" ]
[ "services" "displayManager" "gdm" "settings" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "displayManager" "gdm" "wayland" ]
[ "services" "displayManager" "gdm" "wayland" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "displayManager" "gdm" "autoSuspend" ]
[ "services" "displayManager" "gdm" "autoSuspend" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "displayManager" "gdm" "autoLogin" "delay" ]
[ "services" "displayManager" "gdm" "autoLogin" "delay" ]
)
];
meta = {
@@ -82,7 +112,7 @@ in
options = {
services.xserver.displayManager.gdm = {
services.displayManager.gdm = {
enable = lib.mkEnableOption "GDM, the GNOME Display Manager";
@@ -145,7 +175,7 @@ in
###### implementation
config = lib.mkIf cfg.gdm.enable {
config = lib.mkIf cfg.enable {
services.xserver.displayManager.lightdm.enable = false;
@@ -170,7 +200,7 @@ in
environment =
{
GDM_X_SERVER_EXTRA_ARGS = toString (lib.filter (arg: arg != "-terminate") cfg.xserverArgs);
GDM_X_SERVER_EXTRA_ARGS = toString (lib.filter (arg: arg != "-terminate") xdmcfg.xserverArgs);
XDG_DATA_DIRS = lib.makeSearchPath "share" [
gdm # for gnome-login.session
config.services.displayManager.sessionData.desktops
@@ -274,7 +304,7 @@ in
systemd.user.services.dbus.wantedBy = [ "default.target" ];
programs.dconf.profiles.gdm.databases =
lib.optionals (!cfg.gdm.autoSuspend) [
lib.optionals (!cfg.autoSuspend) [
{
settings."org/gnome/settings-daemon/plugins/power" = {
sleep-inactive-ac-type = "nothing";
@@ -284,11 +314,11 @@ in
};
}
]
++ lib.optionals (cfg.gdm.banner != null) [
++ lib.optionals (cfg.banner != null) [
{
settings."org/gnome/login-screen" = {
banner-message-enable = true;
banner-message-text = cfg.gdm.banner;
banner-message-text = cfg.banner;
};
}
]
@@ -297,21 +327,21 @@ in
# Use AutomaticLogin if delay is zero, because it's immediate.
# Otherwise with TimedLogin with zero seconds the prompt is still
# presented and there's a little delay.
services.xserver.displayManager.gdm.settings = {
services.displayManager.gdm.settings = {
daemon = lib.mkMerge [
{ WaylandEnable = cfg.gdm.wayland; }
{ WaylandEnable = cfg.wayland; }
# nested if else didn't work
(lib.mkIf (config.services.displayManager.autoLogin.enable && cfg.gdm.autoLogin.delay != 0) {
(lib.mkIf (config.services.displayManager.autoLogin.enable && cfg.autoLogin.delay != 0) {
TimedLoginEnable = true;
TimedLogin = config.services.displayManager.autoLogin.user;
TimedLoginDelay = cfg.gdm.autoLogin.delay;
TimedLoginDelay = cfg.autoLogin.delay;
})
(lib.mkIf (config.services.displayManager.autoLogin.enable && cfg.gdm.autoLogin.delay == 0) {
(lib.mkIf (config.services.displayManager.autoLogin.enable && cfg.autoLogin.delay == 0) {
AutomaticLoginEnable = true;
AutomaticLogin = config.services.displayManager.autoLogin.user;
})
];
debug = lib.mkIf cfg.gdm.debug {
debug = lib.mkIf cfg.debug {
Enable = true;
};
};
+29 -6
View File
@@ -16,21 +16,44 @@ in
series cards. Note: this removes support for analog video outputs,
which is only available in the `radeon` driver
'';
initrd.enable = lib.mkEnableOption ''
loading `amdgpu` kernelModule in stage 1.
Can fix lower resolution in boot screen during initramfs phase
'';
overdrive = {
enable = lib.mkEnableOption ''`amdgpu` overdrive mode for overclocking'';
ppfeaturemask = lib.mkOption {
type = lib.types.str;
default = "0xfffd7fff";
example = "0xffffffff";
description = ''
Sets the `amdgpu.ppfeaturemask` kernel option. It can be used to enable the overdrive bit.
Default is `0xfffd7fff` as it is less likely to cause flicker issues. Setting it to
`0xffffffff` enables all features, but also can be unstable. See
[the kernel documentation](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/amd/include/amd_shared.h#n169)
for more information.
'';
};
};
opencl.enable = lib.mkEnableOption ''OpenCL support using ROCM runtime library'';
# cfg.amdvlk option is defined in ./amdvlk.nix module
};
config = {
boot.kernelParams = lib.optionals cfg.legacySupport.enable [
"amdgpu.si_support=1"
"amdgpu.cik_support=1"
"radeon.si_support=0"
"radeon.cik_support=0"
];
boot.kernelParams =
lib.optionals cfg.legacySupport.enable [
"amdgpu.si_support=1"
"amdgpu.cik_support=1"
"radeon.si_support=0"
"radeon.cik_support=0"
]
++ lib.optionals cfg.overdrive.enable [
"amdgpu.ppfeaturemask=${cfg.overdrive.ppfeaturemask}"
];
boot.initrd.kernelModules = lib.optionals cfg.initrd.enable [ "amdgpu" ];
+39
View File
@@ -0,0 +1,39 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.lact;
in
{
meta.maintainers = [ lib.maintainers.johnrtitor ];
options.services.lact = {
enable = lib.mkEnableOption null // {
description = ''
Whether to enable LACT, a tool for monitoring, configuring and overclocking GPUs.
::: {.note}
If you are on an AMD GPU, it is recommended to enable overdrive mode by using
`hardware.amdgpu.overdrive.enable = true;` in your configuration.
See [LACT wiki](https://github.com/ilya-zlobintsev/LACT/wiki/Overclocking-(AMD)) for more information.
:::
'';
};
package = lib.mkPackageOption pkgs "lact" { };
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
systemd.packages = [ cfg.package ];
systemd.services.lactd = {
description = "LACT GPU Control Daemon";
wantedBy = [ "multi-user.target" ];
};
};
}
@@ -1,9 +1,11 @@
{
deviceNameStrategy,
csv-files,
device-name-strategy,
discovery-mode,
mounts,
glibc,
jq,
lib,
mounts,
nvidia-container-toolkit,
nvidia-driver,
runtimeShell,
@@ -36,7 +38,14 @@ writeScriptBin "nvidia-cdi-generator" ''
function cdiGenerate {
${lib.getExe' nvidia-container-toolkit "nvidia-ctk"} cdi generate \
--format json \
--device-name-strategy ${deviceNameStrategy} \
${
if (builtins.length csv-files) > 0 then
lib.concatMapStringsSep "\n" (file: "--csv.file ${file} \\") csv-files
else
"\\"
}
--discovery-mode ${discovery-mode} \
--device-name-strategy ${device-name-strategy} \
--ldconfig-path ${lib.getExe' glibc "ldconfig"} \
--library-search-path ${lib.getLib nvidia-driver}/lib \
--nvidia-cdi-hook-path ${lib.getExe' nvidia-container-toolkit.tools "nvidia-cdi-hook"}
@@ -50,12 +50,39 @@
'';
};
suppressNvidiaDriverAssertion = lib.mkOption {
default = false;
type = lib.types.bool;
device-name-strategy = lib.mkOption {
default = "index";
type = lib.types.enum [
"index"
"uuid"
"type-index"
];
description = ''
Suppress the assertion for installing Nvidia driver.
Useful in WSL where drivers are mounted from Windows, not provided by NixOS.
Specify the strategy for generating device names,
passed to `nvidia-ctk cdi generate`. This will affect how
you reference the device using `nvidia.com/gpu=` in
the container runtime.
'';
};
discovery-mode = lib.mkOption {
default = "auto";
type = lib.types.enum [
"auto"
"csv"
"nvml"
"wsl"
];
description = ''
The mode to use when discovering the available entities.
'';
};
csv-files = lib.mkOption {
default = [ ];
type = lib.types.listOf lib.types.path;
description = ''
The path to the list of CSV files to use when generating the CDI specification in CSV mode.
'';
};
@@ -74,21 +101,6 @@
'';
};
device-name-strategy = lib.mkOption {
default = "index";
type = lib.types.enum [
"index"
"uuid"
"type-index"
];
description = ''
Specify the strategy for generating device names,
passed to `nvidia-ctk cdi generate`. This will affect how
you reference the device using `nvidia.com/gpu=` in
the container runtime.
'';
};
mount-nvidia-docker-1-directories = lib.mkOption {
default = true;
type = lib.types.bool;
@@ -98,6 +110,15 @@
'';
};
suppressNvidiaDriverAssertion = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Suppress the assertion for installing Nvidia driver.
Useful in WSL where drivers are mounted from Windows, not provided by NixOS.
'';
};
package = lib.mkPackageOption pkgs "nvidia-container-toolkit" { };
};
@@ -112,6 +133,12 @@
|| config.hardware.nvidia-container-toolkit.suppressNvidiaDriverAssertion;
message = ''`nvidia-container-toolkit` requires nvidia drivers: set `hardware.nvidia.datacenter.enable`, add "nvidia" to `services.xserver.videoDrivers`, or set `hardware.nvidia-container-toolkit.suppressNvidiaDriverAssertion` if the driver is provided by another NixOS module (e.g. from NixOS-WSL)'';
}
{
assertion =
((builtins.length config.hardware.nvidia-container-toolkit.csv-files) > 0)
-> config.hardware.nvidia-container-toolkit.discovery-mode == "csv";
message = ''When CSV files are provided, `config.hardware.nvidia-container-toolkit.discovery-mode` has to be set to `csv`.'';
}
];
virtualisation.docker = {
@@ -209,10 +236,14 @@
ExecStart =
let
script = pkgs.callPackage ./cdi-generate.nix {
inherit (config.hardware.nvidia-container-toolkit) mounts;
inherit (config.hardware.nvidia-container-toolkit)
csv-files
device-name-strategy
discovery-mode
mounts
;
nvidia-container-toolkit = config.hardware.nvidia-container-toolkit.package;
nvidia-driver = config.hardware.nvidia.package;
deviceNameStrategy = config.hardware.nvidia-container-toolkit.device-name-strategy;
};
in
lib.getExe script;
+48 -4
View File
@@ -13,7 +13,7 @@ let
haveAliases = cfg.postmasterAlias != "" || cfg.rootAlias != "" || cfg.extraAliases != "";
haveCanonical = cfg.canonical != "";
haveTransport = cfg.transport != "";
haveTransport = cfg.transport != "" || (cfg.enableSlowDomains && cfg.slowDomains != [ ]);
haveVirtual = cfg.virtual != "";
haveLocalRecipients = cfg.localRecipients != null;
@@ -319,13 +319,20 @@ let
aliasesFile = pkgs.writeText "postfix-aliases" aliases;
canonicalFile = pkgs.writeText "postfix-canonical" cfg.canonical;
virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual;
transportFile = pkgs.writeText "postfix-transport" (
lib.optionalString (cfg.enableSlowDomains && cfg.slowDomains != [ ]) (
lib.concatMapStrings (domain: ''
${domain} slow:
'') cfg.slowDomains
)
+ cfg.transport
);
localRecipientMapFile = pkgs.writeText "postfix-local-recipient-map" (
lib.concatMapStrings (x: x + " ACCEPT\n") cfg.localRecipients
);
checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides;
mainCfFile = pkgs.writeText "postfix-main.cf" mainCf;
masterCfFile = pkgs.writeText "postfix-master.cf" masterCfContent;
transportFile = pkgs.writeText "postfix-transport" cfg.transport;
headerChecksFile = pkgs.writeText "postfix-header-checks" headerChecks;
in
@@ -550,6 +557,32 @@ in
'';
};
enableSlowDomains = lib.mkEnableOption "slow domains feature for rate limiting specific domains";
slowDomains = lib.mkOption {
type = with lib.types; listOf str;
default = [ ];
example = [
"orange.fr"
"gmail.com"
];
description = "List of domains to be rate-limited using the slow transport.";
};
slowDomainsConfig = {
defaultDestinationRateDelay = lib.mkOption {
type = lib.types.str;
default = "5s";
description = "Default rate delay for destinations.";
};
defaultDestinationConcurrencyLimit = lib.mkOption {
type = lib.types.int;
default = 3;
description = "Concurrency limit for slow destinations.";
};
};
aliasMapType = lib.mkOption {
type =
with lib.types;
@@ -985,7 +1018,10 @@ in
smtpd_tls_key_file = cfg.sslKey;
smtpd_tls_security_level = lib.mkDefault "may";
}
// lib.optionalAttrs cfg.enableSlowDomains {
default_destination_rate_delay = cfg.slowDomainsConfig.defaultDestinationRateDelay;
default_destination_concurrency_limit = cfg.slowDomainsConfig.defaultDestinationConcurrencyLimit;
};
services.postfix.masterConfig =
@@ -1077,6 +1113,14 @@ in
lib.concatLists (lib.mapAttrsToList mkKeyVal cfg.submissionOptions);
};
}
// lib.optionalAttrs cfg.enableSlowDomains {
slow = {
command = "smtp";
type = "unix";
private = true;
maxproc = 2;
};
}
// lib.optionalAttrs cfg.enableSmtp {
smtp_inet = {
name = "smtp";
@@ -1128,7 +1172,7 @@ in
(lib.mkIf haveCanonical {
services.postfix.mapFiles.canonical = canonicalFile;
})
(lib.mkIf haveTransport {
(lib.mkIf (haveTransport || (cfg.enableSlowDomains && cfg.slowDomains != [ ])) {
services.postfix.mapFiles.transport = transportFile;
})
(lib.mkIf haveVirtual {
@@ -45,6 +45,11 @@ in
nixos-icons # needed for gnome and pantheon about dialog, nixos-manual and maybe more
xdg-utils
];
# needed for some display managers to locate desktop manager sessions
pathsToLink = [
"/share/xsessions"
"/share/wayland-sessions"
];
};
fonts.enableDefaultPackages = lib.mkDefault true;
@@ -132,6 +132,21 @@ in
"@system-service"
"~@privileged"
];
SupplementaryGroups = [ "render" ]; # for rocm to access /dev/dri/renderD* devices
DeviceAllow = [
# CUDA
# https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
"char-nvidiactl"
"char-nvidia-caps"
"char-nvidia-frontend"
"char-nvidia-uvm"
# ROCm
"char-drm"
"char-fb"
"char-kfd"
# WSL (Windows Subsystem for Linux)
"/dev/dxg"
];
};
};
+7 -9
View File
@@ -8,16 +8,18 @@ let
cfg = config.programs.tuxclocker;
in
{
imports = [
(lib.mkRenamedOptionModule
[ "programs" "tuxclocker" "enableAMD" ]
[ "hardware" "amdgpu" "overdrive" "enable" ]
)
];
options.programs.tuxclocker = {
enable = lib.mkEnableOption ''
TuxClocker, a hardware control and monitoring program
'';
enableAMD = lib.mkEnableOption ''
AMD GPU controls.
Sets the `amdgpu.ppfeaturemask` kernel parameter to 0xfffd7fff to enable all TuxClocker controls
'';
enabledNVIDIADevices = lib.mkOption {
type = lib.types.listOf lib.types.int;
default = [ ];
@@ -72,9 +74,5 @@ in
);
in
lib.concatStrings (map configSection cfg.enabledNVIDIADevices);
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/amd/include/amd_shared.h#n207
# Enable everything modifiable in TuxClocker
boot.kernelParams = lib.mkIf cfg.enableAMD [ "amdgpu.ppfeaturemask=0xfffd7fff" ];
};
}
@@ -476,4 +476,10 @@ in
map (instance: lib.mkIf instance.enable (mkInstanceUsersConfig instance)) instances
);
};
meta.maintainers = with lib.maintainers; [
jk
dit7ya
nicomem
];
}
+4 -4
View File
@@ -185,7 +185,9 @@ let
finalJson =
if cfg.provision.extraJsonFile != null then
"<(${lib.getExe pkgs.jq} -s '.[0] * .[1]' ${provisionStateJson} ${cfg.provision.extraJsonFile})"
''
<(${lib.getExe pkgs.yq-go} '. *+ load("${cfg.provision.extraJsonFile}") | (.. | select(type == "!!seq")) |= unique' ${provisionStateJson})
''
else
provisionStateJson;
@@ -442,10 +444,8 @@ in
description = ''
A JSON file for provisioning persons, groups & systems.
Options set in this file take precedence over values set using the other options.
In the case of duplicates, `jq` will remove all but the last one
when merging this file with the options.
The files get deeply merged, and deduplicated.
The accepted JSON schema can be found at <https://github.com/oddlama/kanidm-provision#json-schema>.
Note: theoretically `jq` cannot merge nested types, but this does not pose an issue as kanidm-provision's JSON scheme does not use nested types.
'';
type = types.nullOr types.path;
default = null;
@@ -0,0 +1,137 @@
{
config,
pkgs,
lib,
utils,
...
}:
let
cfg = config.services.filebrowser;
inherit (lib) types;
format = pkgs.formats.json { };
in
{
options = {
services.filebrowser = {
enable = lib.mkEnableOption "FileBrowser";
package = lib.mkPackageOption pkgs "filebrowser" { };
openFirewall = lib.mkEnableOption "opening firewall ports for FileBrowser";
settings = lib.mkOption {
default = { };
description = ''
Settings for FileBrowser.
Refer to <https://filebrowser.org/cli/filebrowser#options> for all supported values.
'';
type = types.submodule {
freeformType = format.type;
options = {
address = lib.mkOption {
default = "localhost";
description = ''
The address to listen on.
'';
type = types.str;
};
port = lib.mkOption {
default = 8080;
description = ''
The port to listen on.
'';
type = types.port;
};
root = lib.mkOption {
default = "/var/lib/filebrowser/data";
description = ''
The directory where FileBrowser stores files.
'';
type = types.path;
};
database = lib.mkOption {
default = "/var/lib/filebrowser/database.db";
description = ''
The path to FileBrowser's Bolt database.
'';
type = types.path;
};
cache-dir = lib.mkOption {
default = "/var/cache/filebrowser";
description = ''
The directory where FileBrowser stores its cache.
'';
type = types.path;
readOnly = true;
};
};
};
};
};
};
config = lib.mkIf cfg.enable {
systemd = {
services.filebrowser = {
after = [ "network.target" ];
description = "FileBrowser";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart =
let
args = [
(lib.getExe cfg.package)
"--config"
(format.generate "config.json" cfg.settings)
];
in
utils.escapeSystemdExecArgs args;
StateDirectory = "filebrowser";
CacheDirectory = "filebrowser";
WorkingDirectory = cfg.settings.root;
DynamicUser = true;
NoNewPrivileges = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
DevicePolicy = "closed";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
};
};
tmpfiles.settings.filebrowser =
lib.genAttrs
[
cfg.settings.root
(builtins.dirOf cfg.settings.database)
]
(_: {
d.mode = "0700";
});
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.settings.port ];
};
meta.maintainers = [
lib.maintainers.lukaswrz
];
}
+5 -2
View File
@@ -631,6 +631,7 @@ in
psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role"
psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"'
'';
enableStrictShellChecks = true;
};
systemd.services.keycloakMySQLInit = mkIf createLocalMySQL {
@@ -662,6 +663,7 @@ in
echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';"
) | mysql -N
'';
enableStrictShellChecks = true;
};
systemd.tmpfiles.settings."10-keycloak" =
@@ -699,7 +701,7 @@ in
[ ];
secretPaths = catAttrs "_secret" (collect isSecret cfg.settings);
mkSecretReplacement = file: ''
replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf
replace-secret ${hashString "sha256" file} "$CREDENTIALS_DIRECTORY/${baseNameOf file}" /run/keycloak/conf/keycloak.conf
'';
secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
in
@@ -760,11 +762,12 @@ in
''
+ optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) ''
mkdir -p /run/keycloak/ssl
cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/
cp "$CREDENTIALS_DIRECTORY"/ssl_{cert,key} /run/keycloak/ssl/
''
+ ''
kc.sh --verbose start --optimized ${lib.optionalString (cfg.realmFiles != [ ]) "--import-realm"}
'';
enableStrictShellChecks = true;
};
services.postgresql.enable = mkDefault createLocalPostgreSQL;
+23 -19
View File
@@ -25,22 +25,32 @@ let
iniFormat = pkgs.formats.ini { };
# we need to build our own GI_TYPELIB_PATH because celery and paster need this information, too and cannot easily be re-wrapped
GI_TYPELIB_PATH =
# we need to build our own GI_TYPELIB_PATH and GST_PLUGIN_PATH because celery, paster and gmg need this information and it cannot easily be re-wrapped
gst =
let
needsGst =
(cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.audio")
|| (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.video");
in
lib.makeSearchPathOutput "out" "lib/girepository-1.0" (
with pkgs.gst_all_1;
with pkgs.gst_all_1;
[
pkgs.glib
gst-plugins-base
gstreamer
]
# audio and video share most dependencies, so we can just take audio
++ lib.optionals needsGst cfg.package.optional-dependencies.audio;
GI_TYPELIB_PATH = lib.makeSearchPathOutput "out" "lib/girepository-1.0" gst;
GST_PLUGIN_PATH = lib.makeSearchPathOutput "lib" "lib/gstreamer-1.0" gst;
path =
lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.stl") [ pkgs.blender ]
++ lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.pdf") (
with pkgs;
[
pkgs.glib
gst-plugins-base
gstreamer
poppler-utils
unoconv
]
# audio and video share most dependencies, so we can just take audio
++ lib.optionals needsGst cfg.package.optional-dependencies.audio
);
finalPackage = cfg.package.python.buildEnv.override {
@@ -189,7 +199,7 @@ in
if [[ "$USER" != mediagoblin ]]; then
sudo='exec /run/wrappers/bin/sudo -u mediagoblin'
fi
$sudo sh -c "cd /var/lib/mediagoblin; env GI_TYPELIB_PATH=${GI_TYPELIB_PATH} ${lib.getExe' finalPackage "gmg"} $@"
$sudo sh -c "cd /var/lib/mediagoblin; env GI_TYPELIB_PATH=${GI_TYPELIB_PATH} GST_PLUGIN_PATH=${GST_PLUGIN_PATH} PATH=$PATH:${lib.makeBinPath path} ${lib.getExe' finalPackage "gmg"} $*"
'')
];
@@ -248,15 +258,7 @@ in
let
serviceDefaults = {
wantedBy = [ "multi-user.target" ];
path =
lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.stl") [ pkgs.blender ]
++ lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.pdf") (
with pkgs;
[
poppler-utils
unoconv
]
);
inherit path;
serviceConfig = {
AmbientCapabilities = "";
CapabilityBoundingSet = [ "" ];
@@ -325,6 +327,7 @@ in
Environment = [
"CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_celery"
"GI_TYPELIB_PATH=${GI_TYPELIB_PATH}"
"GST_PLUGIN_PATH=${GST_PLUGIN_PATH}"
"MEDIAGOBLIN_CONFIG=/var/lib/mediagoblin/mediagoblin.ini"
"PASTE_CONFIG=${pasteConfig}"
];
@@ -350,6 +353,7 @@ in
Environment = [
"CELERY_ALWAYS_EAGER=false"
"GI_TYPELIB_PATH=${GI_TYPELIB_PATH}"
"GST_PLUGIN_PATH=${GST_PLUGIN_PATH}"
];
ExecStart = "${lib.getExe' finalPackage "paster"} serve /var/lib/mediagoblin/paste.ini";
};
@@ -31,7 +31,6 @@ in
./lumina.nix
./lxqt.nix
./enlightenment.nix
./gnome.nix
./retroarch.nix
./kodi.nix
./mate.nix
@@ -43,6 +42,7 @@ in
./deepin.nix
../../desktop-managers/lomiri.nix
../../desktop-managers/cosmic.nix
../../desktop-managers/gnome.nix
];
options = {
@@ -17,7 +17,7 @@ with lib;
services.xserver.videoDrivers = [ ];
# Enable GDM. Any display manager will do as long as it supports XDMCP.
services.xserver.displayManager.gdm.enable = true;
services.displayManager.gdm.enable = true;
systemd.sockets.terminal-server = {
description = "Terminal Server Socket";
+1 -1
View File
@@ -761,7 +761,7 @@ in
dmConf = cfg.displayManager;
default =
!(
dmConf.gdm.enable
config.services.displayManager.gdm.enable
|| config.services.displayManager.sddm.enable
|| dmConf.xpra.enable
|| dmConf.sx.enable
+6
View File
@@ -100,6 +100,9 @@ let
"ReceiveQueues"
"TransmitQueues"
"TransmitQueueLength"
"RxFlowControl"
"TxFlowControl"
"AutoNegotiationFlowControl"
])
(assertValueOneOf "MACAddressPolicy" [
"persistent"
@@ -137,6 +140,9 @@ let
(assertValueOneOf "GenericSegmentationOffload" boolValues)
(assertValueOneOf "GenericReceiveOffload" boolValues)
(assertValueOneOf "LargeReceiveOffload" boolValues)
(assertValueOneOf "RxFlowControl" boolValues)
(assertValueOneOf "TxFlowControl" boolValues)
(assertValueOneOf "AutoNegotiationFlowControl" boolValues)
(assertInt "RxChannels")
(assertRange "RxChannels" 1 4294967295)
(assertInt "TxChannels")
+4 -2
View File
@@ -125,7 +125,9 @@ let
name = "initrd-${kernel-name}";
inherit (config.boot.initrd) compressor compressorArgs prepend;
contents = lib.filter ({ source, ... }: !lib.elem source cfg.suppressedStorePaths) cfg.storePaths;
contents = lib.filter (
{ source, enable, ... }: (!lib.elem source cfg.suppressedStorePaths) && enable
) cfg.storePaths;
};
in
@@ -634,7 +636,7 @@ in
{
where = "/sysroot/run";
what = "/run";
options = "bind";
options = "rbind";
unitConfig = {
# See the comment on the mount unit for /run/etc-metadata
DefaultDependencies = false;
@@ -46,6 +46,10 @@
"overlay"
];
system.requiredKernelConfig = with config.lib.kernelConfig; [
(isEnabled "EROFS_FS")
];
boot.initrd.systemd = {
mounts = [
{
+2 -2
View File
@@ -610,8 +610,8 @@ rec {
{ ... }:
{
services.xserver.enable = true;
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;
services.displayManager.gdm.enable = true;
services.desktopManager.gnome.enable = true;
}
);
+2 -1
View File
@@ -22,7 +22,8 @@
&& lib.isDerivation o.value
&& o.value ? outputs
&& builtins.elem "terminfo" o.value.outputs
&& !o.value.meta.broken;
&& !o.value.meta.broken
&& lib.meta.availableOn pkgs.stdenv.hostPlatform o.value;
terminfos = lib.filterAttrs infoFilter pkgs;
excludedTerminfos = lib.filterAttrs (
_: drv: !(builtins.elem drv.terminfo config.environment.systemPackages)
+562 -546
View File
File diff suppressed because it is too large Load Diff
+111 -114
View File
@@ -1,131 +1,128 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
let
{ pkgs, lib, ... }:
baud = 57600;
tty = "/dev/ttyACM0";
port = "tnc0";
socatPort = 1234;
let
baud = 57600;
tty = "/dev/ttyACM0";
port = "tnc0";
socatPort = 1234;
createAX25Node = nodeId: {
createAX25Node = nodeId: {
boot.kernelPackages = pkgs.linuxPackages_ham;
boot.kernelModules = [ "ax25" ];
boot.kernelPackages = pkgs.linuxPackages_ham;
boot.kernelModules = [ "ax25" ];
networking.firewall.allowedTCPPorts = [ socatPort ];
networking.firewall.allowedTCPPorts = [ socatPort ];
environment.systemPackages = with pkgs; [
libax25
ax25-tools
ax25-apps
socat
];
environment.systemPackages = with pkgs; [
libax25
ax25-tools
ax25-apps
socat
services.ax25.axports."${port}" = {
inherit baud tty;
enable = true;
callsign = "NOCALL-${toString nodeId}";
description = "mocked tnc";
};
services.ax25.axlisten = {
enable = true;
};
# All mocks radios will connect back to socat-broker on node 1 in order to get
# all messages that are "broadcasted over the ether"
systemd.services.ax25-mock-hardware = {
description = "mock AX.25 TNC and Radio";
wantedBy = [ "default.target" ];
before = [
"ax25-kissattach-${port}.service"
"axlisten.service"
];
services.ax25.axports."${port}" = {
inherit baud tty;
enable = true;
callsign = "NOCALL-${toString nodeId}";
description = "mocked tnc";
};
services.ax25.axlisten = {
enable = true;
};
# All mocks radios will connect back to socat-broker on node 1 in order to get
# all messages that are "broadcasted over the ether"
systemd.services.ax25-mock-hardware = {
description = "mock AX.25 TNC and Radio";
wantedBy = [ "default.target" ];
before = [
"ax25-kissattach-${port}.service"
"axlisten.service"
];
after = [ "network.target" ];
serviceConfig = {
Type = "exec";
ExecStart = "${pkgs.socat}/bin/socat -d -d tcp:192.168.1.1:${toString socatPort} pty,link=${tty},b${toString baud},raw";
};
after = [ "network.target" ];
serviceConfig = {
Type = "exec";
ExecStart = "${pkgs.socat}/bin/socat -d -d tcp:192.168.1.1:${toString socatPort} pty,link=${tty},b${toString baud},raw";
};
};
in
{
name = "ax25Simple";
nodes = {
node1 = lib.mkMerge [
(createAX25Node 1)
# mimicking radios on the same frequency
{
systemd.services.ax25-mock-ether = {
description = "mock radio ether";
wantedBy = [ "default.target" ];
requires = [ "network.target" ];
before = [ "ax25-mock-hardware.service" ];
# broken needs access to "ss" or "netstat"
path = [ pkgs.iproute2 ];
serviceConfig = {
Type = "exec";
ExecStart = "${pkgs.socat}/bin/socat-broker.sh tcp4-listen:${toString socatPort}";
};
postStart = "${pkgs.coreutils}/bin/sleep 2";
};
in
{
name = "ax25Simple";
nodes = {
node1 = lib.mkMerge [
(createAX25Node 1)
# mimicking radios on the same frequency
{
systemd.services.ax25-mock-ether = {
description = "mock radio ether";
wantedBy = [ "default.target" ];
requires = [ "network.target" ];
before = [ "ax25-mock-hardware.service" ];
# broken needs access to "ss" or "netstat"
path = [ pkgs.iproute2 ];
serviceConfig = {
Type = "exec";
ExecStart = "${pkgs.socat}/bin/socat-broker.sh tcp4-listen:${toString socatPort}";
};
}
];
node2 = createAX25Node 2;
node3 = createAX25Node 3;
};
testScript =
{ ... }:
''
def wait_for_machine(m):
m.succeed("lsmod | grep ax25")
m.wait_for_unit("ax25-axports.target")
m.wait_for_unit("axlisten.service")
m.fail("journalctl -o cat -u axlisten.service | grep -i \"no AX.25 port data configured\"")
postStart = "${pkgs.coreutils}/bin/sleep 2";
};
}
];
node2 = createAX25Node 2;
node3 = createAX25Node 3;
};
testScript =
{ ... }:
''
def wait_for_machine(m):
m.succeed("lsmod | grep ax25")
m.wait_for_unit("ax25-axports.target")
m.wait_for_unit("axlisten.service")
m.fail("journalctl -o cat -u axlisten.service | grep -i \"no AX.25 port data configured\"")
# start the first node since the socat-broker needs to be running
node1.start()
node1.wait_for_unit("ax25-mock-ether.service")
wait_for_machine(node1)
# start the first node since the socat-broker needs to be running
node1.start()
node1.wait_for_unit("ax25-mock-ether.service")
wait_for_machine(node1)
node2.start()
node3.start()
wait_for_machine(node2)
wait_for_machine(node3)
node2.start()
node3.start()
wait_for_machine(node2)
wait_for_machine(node3)
# Node 1 -> Node 2
node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-2")
node2.sleep(1)
node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-2 ctl I00\" | grep hello")
# Node 1 -> Node 2
node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-2")
node2.sleep(1)
node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-2 ctl I00\" | grep hello")
# Node 1 -> Node 3
node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-3")
node3.sleep(1)
node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-3 ctl I00\" | grep hello")
# Node 1 -> Node 3
node1.succeed("echo hello | ax25_call ${port} NOCALL-1 NOCALL-3")
node3.sleep(1)
node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-1 to NOCALL-3 ctl I00\" | grep hello")
# Node 2 -> Node 1
# must sleep due to previous ax25_call lingering
node2.sleep(5)
node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-1")
node1.sleep(1)
node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-1 ctl I00\" | grep hello")
# Node 2 -> Node 1
# must sleep due to previous ax25_call lingering
node2.sleep(5)
node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-1")
node1.sleep(1)
node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-1 ctl I00\" | grep hello")
# Node 2 -> Node 3
node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-3")
node3.sleep(1)
node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-3 ctl I00\" | grep hello")
# Node 2 -> Node 3
node2.succeed("echo hello | ax25_call ${port} NOCALL-2 NOCALL-3")
node3.sleep(1)
node3.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-2 to NOCALL-3 ctl I00\" | grep hello")
# Node 3 -> Node 1
# must sleep due to previous ax25_call lingering
node3.sleep(5)
node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-1")
node1.sleep(1)
node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-1 ctl I00\" | grep hello")
# Node 3 -> Node 1
# must sleep due to previous ax25_call lingering
node3.sleep(5)
node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-1")
node1.sleep(1)
node1.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-1 ctl I00\" | grep hello")
# Node 3 -> Node 2
node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-2")
node2.sleep(1)
node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-2 ctl I00\" | grep hello")
'';
}
)
# Node 3 -> Node 2
node3.succeed("echo hello | ax25_call ${port} NOCALL-3 NOCALL-2")
node2.sleep(1)
node2.succeed("journalctl -o cat -u axlisten.service | grep -A1 \"NOCALL-3 to NOCALL-2 ctl I00\" | grep hello")
'';
}
+58 -60
View File
@@ -1,64 +1,62 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
let
user = "alice";
in
{
name = "benchexec";
{ pkgs, lib, ... }:
let
user = "alice";
in
{
name = "benchexec";
nodes.benchexec = {
imports = [ ./common/user-account.nix ];
nodes.benchexec = {
imports = [ ./common/user-account.nix ];
programs.benchexec = {
enable = true;
users = [ user ];
};
};
testScript =
{ ... }:
let
runexec = lib.getExe' pkgs.benchexec "runexec";
echo = builtins.toString pkgs.benchexec;
test = lib.getExe (
pkgs.writeShellApplication rec {
name = "test";
meta.mainProgram = name;
text = "echo '${echo}'";
}
);
wd = "/tmp";
stdout = "${wd}/runexec.out";
stderr = "${wd}/runexec.err";
in
''
start_all()
machine.wait_for_unit("multi-user.target")
benchexec.succeed(''''\
systemd-run \
--property='StandardOutput=file:${stdout}' \
--property='StandardError=file:${stderr}' \
--unit=runexec --wait --user --machine='${user}@' \
--working-directory ${wd} \
'${runexec}' \
--debug \
--read-only-dir / \
--hidden-dir /home \
'${test}' \
'''')
benchexec.succeed("grep -s '${echo}' ${wd}/output.log")
benchexec.succeed("test \"$(grep -Ec '((start|wall|cpu)time|memory)=' ${stdout})\" = 4")
benchexec.succeed("! grep -E '(WARNING|ERROR)' ${stderr}")
'';
interactive.nodes.benchexec.services.kmscon = {
programs.benchexec = {
enable = true;
fonts = [
{
name = "Fira Code";
package = pkgs.fira-code;
}
];
users = [ user ];
};
}
)
};
testScript =
{ ... }:
let
runexec = lib.getExe' pkgs.benchexec "runexec";
echo = builtins.toString pkgs.benchexec;
test = lib.getExe (
pkgs.writeShellApplication rec {
name = "test";
meta.mainProgram = name;
text = "echo '${echo}'";
}
);
wd = "/tmp";
stdout = "${wd}/runexec.out";
stderr = "${wd}/runexec.err";
in
''
start_all()
machine.wait_for_unit("multi-user.target")
benchexec.succeed(''''\
systemd-run \
--property='StandardOutput=file:${stdout}' \
--property='StandardError=file:${stderr}' \
--unit=runexec --wait --user --machine='${user}@' \
--working-directory ${wd} \
'${runexec}' \
--debug \
--read-only-dir / \
--hidden-dir /home \
'${test}' \
'''')
benchexec.succeed("grep -s '${echo}' ${wd}/output.log")
benchexec.succeed("test \"$(grep -Ec '((start|wall|cpu)time|memory)=' ${stdout})\" = 4")
benchexec.succeed("! grep -E '(WARNING|ERROR)' ${stderr}")
'';
interactive.nodes.benchexec.services.kmscon = {
enable = true;
fonts = [
{
name = "Fira Code";
package = pkgs.fira-code;
}
];
};
}
+49 -51
View File
@@ -1,57 +1,55 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "bitcoind";
meta = with pkgs.lib; {
maintainers = with maintainers; [ _1000101 ];
};
{ pkgs, ... }:
{
name = "bitcoind";
meta = with pkgs.lib; {
maintainers = with maintainers; [ _1000101 ];
};
nodes.machine =
{ ... }:
{
services.bitcoind."mainnet" = {
enable = true;
rpc = {
port = 8332;
users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
users.rpc2.passwordHMAC = "1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225";
};
};
environment.etc."test.blank".text = "";
services.bitcoind."testnet" = {
enable = true;
configFile = "/etc/test.blank";
testnet = true;
rpc = {
port = 18332;
};
extraCmdlineOptions = [
"-rpcuser=rpc"
"-rpcpassword=rpc"
"-rpcauth=rpc2:1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225"
];
nodes.machine =
{ ... }:
{
services.bitcoind."mainnet" = {
enable = true;
rpc = {
port = 8332;
users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
users.rpc2.passwordHMAC = "1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225";
};
};
testScript = ''
start_all()
environment.etc."test.blank".text = "";
services.bitcoind."testnet" = {
enable = true;
configFile = "/etc/test.blank";
testnet = true;
rpc = {
port = 18332;
};
extraCmdlineOptions = [
"-rpcuser=rpc"
"-rpcpassword=rpc"
"-rpcauth=rpc2:1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225"
];
};
};
machine.wait_for_unit("bitcoind-mainnet.service")
machine.wait_for_unit("bitcoind-testnet.service")
testScript = ''
start_all()
machine.wait_until_succeeds(
'curl --fail --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 | grep \'"chain":"main"\' '
)
machine.wait_until_succeeds(
'curl --fail --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 | grep \'"chain":"main"\' '
)
machine.wait_until_succeeds(
'curl --fail --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 | grep \'"chain":"test"\' '
)
machine.wait_until_succeeds(
'curl --fail --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 | grep \'"chain":"test"\' '
)
'';
}
)
machine.wait_for_unit("bitcoind-mainnet.service")
machine.wait_for_unit("bitcoind-testnet.service")
machine.wait_until_succeeds(
'curl --fail --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 | grep \'"chain":"main"\' '
)
machine.wait_until_succeeds(
'curl --fail --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 | grep \'"chain":"main"\' '
)
machine.wait_until_succeeds(
'curl --fail --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 | grep \'"chain":"test"\' '
)
machine.wait_until_succeeds(
'curl --fail --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 | grep \'"chain":"test"\' '
)
'';
}
+167 -169
View File
@@ -6,199 +6,197 @@
# which only works if the first client successfully uses the UPnP-IGD
# protocol to poke a hole in the NAT.
import ./make-test-python.nix (
{ pkgs, ... }:
{ pkgs, ... }:
let
let
# Some random file to serve.
file = pkgs.hello.src;
# Some random file to serve.
file = pkgs.hello.src;
internalRouterAddress = "192.168.3.1";
internalClient1Address = "192.168.3.2";
externalRouterAddress = "80.100.100.1";
externalClient2Address = "80.100.100.2";
externalTrackerAddress = "80.100.100.3";
internalRouterAddress = "192.168.3.1";
internalClient1Address = "192.168.3.2";
externalRouterAddress = "80.100.100.1";
externalClient2Address = "80.100.100.2";
externalTrackerAddress = "80.100.100.3";
download-dir = "/var/lib/transmission/Downloads";
transmissionConfig =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.transmission_3 ];
services.transmission = {
enable = true;
settings = {
dht-enabled = false;
message-level = 2;
inherit download-dir;
};
download-dir = "/var/lib/transmission/Downloads";
transmissionConfig =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.transmission_3 ];
services.transmission = {
enable = true;
settings = {
dht-enabled = false;
message-level = 2;
inherit download-dir;
};
};
in
{
name = "bittorrent";
meta = with pkgs.lib.maintainers; {
maintainers = [
domenkozar
rob
bobvanderlinden
];
};
in
nodes = {
tracker =
{ pkgs, ... }:
{
imports = [ transmissionConfig ];
{
name = "bittorrent";
meta = with pkgs.lib.maintainers; {
maintainers = [
domenkozar
rob
bobvanderlinden
];
};
virtualisation.vlans = [ 1 ];
networking.firewall.enable = false;
networking.interfaces.eth1.ipv4.addresses = [
{
address = externalTrackerAddress;
prefixLength = 24;
}
];
nodes = {
tracker =
{ pkgs, ... }:
{
imports = [ transmissionConfig ];
# We need Apache on the tracker to serve the torrents.
services.httpd = {
enable = true;
virtualHosts = {
"torrentserver.org" = {
adminAddr = "foo@example.org";
documentRoot = "/tmp";
};
virtualisation.vlans = [ 1 ];
networking.firewall.enable = false;
networking.interfaces.eth1.ipv4.addresses = [
{
address = externalTrackerAddress;
prefixLength = 24;
}
];
# We need Apache on the tracker to serve the torrents.
services.httpd = {
enable = true;
virtualHosts = {
"torrentserver.org" = {
adminAddr = "foo@example.org";
documentRoot = "/tmp";
};
};
services.opentracker.enable = true;
};
services.opentracker.enable = true;
};
router =
{ pkgs, nodes, ... }:
{
virtualisation.vlans = [
1
2
];
networking.nat.enable = true;
networking.nat.internalInterfaces = [ "eth2" ];
networking.nat.externalInterface = "eth1";
networking.firewall.enable = true;
networking.firewall.trustedInterfaces = [ "eth2" ];
networking.interfaces.eth0.ipv4.addresses = [ ];
networking.interfaces.eth1.ipv4.addresses = [
{
address = externalRouterAddress;
prefixLength = 24;
}
];
networking.interfaces.eth2.ipv4.addresses = [
{
address = internalRouterAddress;
prefixLength = 24;
}
];
services.miniupnpd = {
enable = true;
externalInterface = "eth1";
internalIPs = [ "eth2" ];
appendConfig = ''
ext_ip=${externalRouterAddress}
'';
};
router =
{ pkgs, nodes, ... }:
{
virtualisation.vlans = [
1
2
];
networking.nat.enable = true;
networking.nat.internalInterfaces = [ "eth2" ];
networking.nat.externalInterface = "eth1";
networking.firewall.enable = true;
networking.firewall.trustedInterfaces = [ "eth2" ];
networking.interfaces.eth0.ipv4.addresses = [ ];
networking.interfaces.eth1.ipv4.addresses = [
{
address = externalRouterAddress;
prefixLength = 24;
}
];
networking.interfaces.eth2.ipv4.addresses = [
{
address = internalRouterAddress;
prefixLength = 24;
}
];
services.miniupnpd = {
enable = true;
externalInterface = "eth1";
internalIPs = [ "eth2" ];
appendConfig = ''
ext_ip=${externalRouterAddress}
'';
};
};
client1 =
{ pkgs, nodes, ... }:
{
imports = [ transmissionConfig ];
environment.systemPackages = [ pkgs.miniupnpc ];
client1 =
{ pkgs, nodes, ... }:
{
imports = [ transmissionConfig ];
environment.systemPackages = [ pkgs.miniupnpc ];
virtualisation.vlans = [ 2 ];
networking.interfaces.eth0.ipv4.addresses = [ ];
networking.interfaces.eth1.ipv4.addresses = [
{
address = internalClient1Address;
prefixLength = 24;
}
];
networking.defaultGateway = internalRouterAddress;
networking.firewall.enable = false;
};
virtualisation.vlans = [ 2 ];
networking.interfaces.eth0.ipv4.addresses = [ ];
networking.interfaces.eth1.ipv4.addresses = [
{
address = internalClient1Address;
prefixLength = 24;
}
];
networking.defaultGateway = internalRouterAddress;
networking.firewall.enable = false;
};
client2 =
{ pkgs, ... }:
{
imports = [ transmissionConfig ];
client2 =
{ pkgs, ... }:
{
imports = [ transmissionConfig ];
virtualisation.vlans = [ 1 ];
networking.interfaces.eth0.ipv4.addresses = [ ];
networking.interfaces.eth1.ipv4.addresses = [
{
address = externalClient2Address;
prefixLength = 24;
}
];
networking.firewall.enable = false;
};
};
virtualisation.vlans = [ 1 ];
networking.interfaces.eth0.ipv4.addresses = [ ];
networking.interfaces.eth1.ipv4.addresses = [
{
address = externalClient2Address;
prefixLength = 24;
}
];
networking.firewall.enable = false;
};
};
testScript =
{ nodes, ... }:
''
start_all()
testScript =
{ nodes, ... }:
''
start_all()
# Wait for network and miniupnpd.
router.systemctl("start network-online.target")
router.wait_for_unit("network-online.target")
router.wait_for_unit("miniupnpd")
# Wait for network and miniupnpd.
router.systemctl("start network-online.target")
router.wait_for_unit("network-online.target")
router.wait_for_unit("miniupnpd")
# Create the torrent.
tracker.succeed("mkdir ${download-dir}/data")
tracker.succeed(
"cp ${file} ${download-dir}/data/test.tar.bz2"
)
tracker.succeed(
"transmission-create ${download-dir}/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
)
tracker.succeed("chmod 644 /tmp/test.torrent")
# Create the torrent.
tracker.succeed("mkdir ${download-dir}/data")
tracker.succeed(
"cp ${file} ${download-dir}/data/test.tar.bz2"
)
tracker.succeed(
"transmission-create ${download-dir}/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
)
tracker.succeed("chmod 644 /tmp/test.torrent")
# Start the tracker. !!! use a less crappy tracker
tracker.systemctl("start network-online.target")
tracker.wait_for_unit("network-online.target")
tracker.wait_for_unit("opentracker.service")
tracker.wait_for_open_port(6969)
# Start the tracker. !!! use a less crappy tracker
tracker.systemctl("start network-online.target")
tracker.wait_for_unit("network-online.target")
tracker.wait_for_unit("opentracker.service")
tracker.wait_for_open_port(6969)
# Start the initial seeder.
tracker.succeed(
"transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir ${download-dir}/data"
)
# Start the initial seeder.
tracker.succeed(
"transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir ${download-dir}/data"
)
# Now we should be able to download from the client behind the NAT.
tracker.wait_for_unit("httpd")
client1.systemctl("start network-online.target")
client1.wait_for_unit("network-online.target")
client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
client1.wait_for_file("${download-dir}/test.tar.bz2")
client1.succeed(
"cmp ${download-dir}/test.tar.bz2 ${file}"
)
# Now we should be able to download from the client behind the NAT.
tracker.wait_for_unit("httpd")
client1.systemctl("start network-online.target")
client1.wait_for_unit("network-online.target")
client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
client1.wait_for_file("${download-dir}/test.tar.bz2")
client1.succeed(
"cmp ${download-dir}/test.tar.bz2 ${file}"
)
# Bring down the initial seeder.
tracker.stop_job("transmission")
# Bring down the initial seeder.
tracker.stop_job("transmission")
# Now download from the second client. This can only succeed if
# the first client created a NAT hole in the router.
client2.systemctl("start network-online.target")
client2.wait_for_unit("network-online.target")
client2.succeed(
"transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
)
client2.wait_for_file("${download-dir}/test.tar.bz2")
client2.succeed(
"cmp ${download-dir}/test.tar.bz2 ${file}"
)
'';
}
)
# Now download from the second client. This can only succeed if
# the first client created a NAT hole in the router.
client2.systemctl("start network-online.target")
client2.wait_for_unit("network-online.target")
client2.succeed(
"transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
)
client2.wait_for_file("${download-dir}/test.tar.bz2")
client2.succeed(
"cmp ${download-dir}/test.tar.bz2 ${file}"
)
'';
}
+25 -27
View File
@@ -1,33 +1,31 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "blockbook-frontend";
meta = with pkgs.lib; {
maintainers = with maintainers; [ _1000101 ];
};
{ pkgs, ... }:
{
name = "blockbook-frontend";
meta = with pkgs.lib; {
maintainers = with maintainers; [ _1000101 ];
};
nodes.machine =
{ ... }:
{
services.blockbook-frontend."test" = {
enable = true;
};
services.bitcoind.mainnet = {
enable = true;
rpc = {
port = 8030;
users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
};
nodes.machine =
{ ... }:
{
services.blockbook-frontend."test" = {
enable = true;
};
services.bitcoind.mainnet = {
enable = true;
rpc = {
port = 8030;
users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
};
};
};
testScript = ''
start_all()
machine.wait_for_unit("blockbook-frontend-test.service")
testScript = ''
start_all()
machine.wait_for_unit("blockbook-frontend-test.service")
machine.wait_for_open_port(9030)
machine.wait_for_open_port(9030)
machine.succeed("curl -sSfL http://localhost:9030 | grep 'Blockbook'")
'';
}
)
machine.succeed("curl -sSfL http://localhost:9030 | grep 'Blockbook'")
'';
}
+173 -175
View File
@@ -1,193 +1,191 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "boot-stage1";
{ pkgs, ... }:
{
name = "boot-stage1";
nodes.machine =
{
config,
pkgs,
lib,
...
}:
{
boot.extraModulePackages =
let
compileKernelModule =
name: source:
pkgs.runCommandCC name
rec {
inherit source;
kdev = config.boot.kernelPackages.kernel.dev;
kver = config.boot.kernelPackages.kernel.modDirVersion;
ksrc = "${kdev}/lib/modules/${kver}/build";
hardeningDisable = [ "pic" ];
nativeBuildInputs = kdev.moduleBuildDependencies;
}
''
echo "obj-m += $name.o" > Makefile
echo "$source" > "$name.c"
make -C "$ksrc" M=$(pwd) modules
install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko"
'';
# This spawns a kthread which just waits until it gets a signal and
# terminates if that is the case. We want to make sure that nothing during
# the boot process kills any kthread by accident, like what happened in
# issue #15226.
kcanary = compileKernelModule "kcanary" ''
#include <linux/version.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/signal.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#include <linux/sched/signal.h>
#endif
MODULE_LICENSE("GPL");
struct task_struct *canaryTask;
static int kcanary(void *nothing)
{
allow_signal(SIGINT);
allow_signal(SIGTERM);
allow_signal(SIGKILL);
while (!kthread_should_stop()) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout_interruptible(msecs_to_jiffies(100));
if (signal_pending(current)) break;
}
return 0;
nodes.machine =
{
config,
pkgs,
lib,
...
}:
{
boot.extraModulePackages =
let
compileKernelModule =
name: source:
pkgs.runCommandCC name
rec {
inherit source;
kdev = config.boot.kernelPackages.kernel.dev;
kver = config.boot.kernelPackages.kernel.modDirVersion;
ksrc = "${kdev}/lib/modules/${kver}/build";
hardeningDisable = [ "pic" ];
nativeBuildInputs = kdev.moduleBuildDependencies;
}
static int kcanaryInit(void)
{
kthread_run(&kcanary, NULL, "kcanary");
return 0;
}
static void kcanaryExit(void)
{
kthread_stop(canaryTask);
}
module_init(kcanaryInit);
module_exit(kcanaryExit);
'';
in
lib.singleton kcanary;
boot.initrd.kernelModules = [ "kcanary" ];
boot.initrd.extraUtilsCommands =
let
compile =
name: source:
pkgs.runCommandCC name { inherit source; } ''
mkdir -p "$out/bin"
echo "$source" | gcc -Wall -o "$out/bin/$name" -xc -
''
echo "obj-m += $name.o" > Makefile
echo "$source" > "$name.c"
make -C "$ksrc" M=$(pwd) modules
install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko"
'';
daemonize =
name: source:
compile name ''
# This spawns a kthread which just waits until it gets a signal and
# terminates if that is the case. We want to make sure that nothing during
# the boot process kills any kthread by accident, like what happened in
# issue #15226.
kcanary = compileKernelModule "kcanary" ''
#include <linux/version.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/signal.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#include <linux/sched/signal.h>
#endif
MODULE_LICENSE("GPL");
struct task_struct *canaryTask;
static int kcanary(void *nothing)
{
allow_signal(SIGINT);
allow_signal(SIGTERM);
allow_signal(SIGKILL);
while (!kthread_should_stop()) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout_interruptible(msecs_to_jiffies(100));
if (signal_pending(current)) break;
}
return 0;
}
static int kcanaryInit(void)
{
kthread_run(&kcanary, NULL, "kcanary");
return 0;
}
static void kcanaryExit(void)
{
kthread_stop(canaryTask);
}
module_init(kcanaryInit);
module_exit(kcanaryExit);
'';
in
lib.singleton kcanary;
boot.initrd.kernelModules = [ "kcanary" ];
boot.initrd.extraUtilsCommands =
let
compile =
name: source:
pkgs.runCommandCC name { inherit source; } ''
mkdir -p "$out/bin"
echo "$source" | gcc -Wall -o "$out/bin/$name" -xc -
'';
daemonize =
name: source:
compile name ''
#include <stdio.h>
#include <unistd.h>
void runSource(void) {
${source}
}
int main(void) {
if (fork() > 0) return 0;
setsid();
runSource();
return 1;
}
'';
mkCmdlineCanary =
{
name,
cmdline ? "",
source ? "",
}:
(daemonize name ''
char *argv[] = {"${cmdline}", NULL};
execvp("${name}-child", argv);
'')
// {
child = compile "${name}-child" ''
#include <stdio.h>
#include <unistd.h>
void runSource(void) {
${source}
}
int main(void) {
if (fork() > 0) return 0;
setsid();
runSource();
${source}
while (1) sleep(1);
return 1;
}
'';
};
mkCmdlineCanary =
{
name,
cmdline ? "",
source ? "",
}:
(daemonize name ''
char *argv[] = {"${cmdline}", NULL};
execvp("${name}-child", argv);
'')
// {
child = compile "${name}-child" ''
#include <stdio.h>
#include <unistd.h>
copyCanaries = lib.concatMapStrings (canary: ''
${lib.optionalString (canary ? child) ''
copy_bin_and_libs "${canary.child}/bin/${canary.child.name}"
''}
copy_bin_and_libs "${canary}/bin/${canary.name}"
'');
int main(void) {
${source}
while (1) sleep(1);
return 1;
}
'';
};
in
copyCanaries [
# Simple canary process which just sleeps forever and should be killed by
# stage 2.
(daemonize "canary1" "while (1) sleep(1);")
copyCanaries = lib.concatMapStrings (canary: ''
${lib.optionalString (canary ? child) ''
copy_bin_and_libs "${canary.child}/bin/${canary.child.name}"
''}
copy_bin_and_libs "${canary}/bin/${canary.name}"
'');
# We want this canary process to try mimicking a kthread using a cmdline
# with a zero length so we can make sure that the process is properly
# killed in stage 1.
(mkCmdlineCanary {
name = "canary2";
source = ''
FILE *f;
f = fopen("/run/canary2.pid", "w");
fprintf(f, "%d\n", getpid());
fclose(f);
'';
})
in
copyCanaries [
# Simple canary process which just sleeps forever and should be killed by
# stage 2.
(daemonize "canary1" "while (1) sleep(1);")
# This canary process mimics a storage daemon, which we do NOT want to be
# killed before going into stage 2. For more on root storage daemons, see:
# https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
(mkCmdlineCanary {
name = "canary3";
cmdline = "@canary3";
})
];
# We want this canary process to try mimicking a kthread using a cmdline
# with a zero length so we can make sure that the process is properly
# killed in stage 1.
(mkCmdlineCanary {
name = "canary2";
source = ''
FILE *f;
f = fopen("/run/canary2.pid", "w");
fprintf(f, "%d\n", getpid());
fclose(f);
'';
})
boot.initrd.postMountCommands = ''
canary1
canary2
canary3
# Make sure the pidfile of canary 2 is created so that we still can get
# its former pid after the killing spree starts next within stage 1.
while [ ! -s /run/canary2.pid ]; do sleep 0.1; done
'';
};
# This canary process mimics a storage daemon, which we do NOT want to be
# killed before going into stage 2. For more on root storage daemons, see:
# https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
(mkCmdlineCanary {
name = "canary3";
cmdline = "@canary3";
})
];
testScript = ''
machine.wait_for_unit("multi-user.target")
machine.succeed("test -s /run/canary2.pid")
machine.fail("pgrep -a canary1")
machine.fail("kill -0 $(< /run/canary2.pid)")
machine.succeed('pgrep -a -f "^@canary3$"')
machine.succeed('pgrep -a -f "^\\[kcanary\\]$"')
'';
boot.initrd.postMountCommands = ''
canary1
canary2
canary3
# Make sure the pidfile of canary 2 is created so that we still can get
# its former pid after the killing spree starts next within stage 1.
while [ ! -s /run/canary2.pid ]; do sleep 0.1; done
'';
};
testScript = ''
machine.wait_for_unit("multi-user.target")
machine.succeed("test -s /run/canary2.pid")
machine.fail("pgrep -a canary1")
machine.fail("kill -0 $(< /run/canary2.pid)")
machine.succeed('pgrep -a -f "^@canary3$"')
machine.succeed('pgrep -a -f "^\\[kcanary\\]$"')
'';
meta.maintainers = with pkgs.lib.maintainers; [ aszlig ];
}
)
meta.maintainers = with pkgs.lib.maintainers; [ aszlig ];
}
+63 -65
View File
@@ -1,73 +1,71 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "boot-stage2";
{ pkgs, ... }:
{
name = "boot-stage2";
nodes.machine =
{
config,
pkgs,
lib,
...
}:
{
virtualisation = {
emptyDiskImages = [ 256 ];
nodes.machine =
{
config,
pkgs,
lib,
...
}:
{
virtualisation = {
emptyDiskImages = [ 256 ];
# Mount an ext4 as the upper layer of the Nix store.
fileSystems = {
"/nix/store" = lib.mkForce {
device = "/dev/vdb"; # the above disk image
fsType = "ext4";
# Mount an ext4 as the upper layer of the Nix store.
fileSystems = {
"/nix/store" = lib.mkForce {
device = "/dev/vdb"; # the above disk image
fsType = "ext4";
# data=journal always displays after errors=remount-ro; this is only needed because of the overlay
# and #375257 will trigger with `errors=remount-ro` on a non-overlaid store:
# see ordering in https://github.com/torvalds/linux/blob/v6.12/fs/ext4/super.c#L2974
options = [
"defaults"
"errors=remount-ro"
"data=journal"
];
};
# data=journal always displays after errors=remount-ro; this is only needed because of the overlay
# and #375257 will trigger with `errors=remount-ro` on a non-overlaid store:
# see ordering in https://github.com/torvalds/linux/blob/v6.12/fs/ext4/super.c#L2974
options = [
"defaults"
"errors=remount-ro"
"data=journal"
];
};
};
boot = {
initrd = {
# Format the upper Nix store.
postDeviceCommands = ''
${pkgs.e2fsprogs}/bin/mkfs.ext4 /dev/vdb
'';
# Overlay the RO store onto it.
# Note that bug #375257 can be triggered without an overlay,
# using the errors=remount-ro option (or similar) or with an overlay where any of the
# paths ends in 'ro'. The offending mountpoint also has to be the last (top) one
# if an option ending in 'ro' is the last in the list, so test both cases here.
postMountCommands = ''
mkdir -p /mnt-root/nix/store/ro /mnt-root/nix/store/rw /mnt-root/nix/store/work
mount --bind /mnt-root/nix/.ro-store /mnt-root/nix/store/ro
mount -t overlay overlay \
-o lowerdir=/mnt-root/nix/store/ro,upperdir=/mnt-root/nix/store/rw,workdir=/mnt-root/nix/store/work \
/mnt-root/nix/store
'';
kernelModules = [ "overlay" ];
};
postBootCommands = ''
touch /etc/post-boot-ran
mount
'';
};
};
testScript = ''
machine.wait_for_unit("multi-user.target")
machine.succeed("test /etc/post-boot-ran")
machine.fail("touch /nix/store/should-not-work");
'';
boot = {
initrd = {
# Format the upper Nix store.
postDeviceCommands = ''
${pkgs.e2fsprogs}/bin/mkfs.ext4 /dev/vdb
'';
meta.maintainers = with pkgs.lib.maintainers; [ numinit ];
}
)
# Overlay the RO store onto it.
# Note that bug #375257 can be triggered without an overlay,
# using the errors=remount-ro option (or similar) or with an overlay where any of the
# paths ends in 'ro'. The offending mountpoint also has to be the last (top) one
# if an option ending in 'ro' is the last in the list, so test both cases here.
postMountCommands = ''
mkdir -p /mnt-root/nix/store/ro /mnt-root/nix/store/rw /mnt-root/nix/store/work
mount --bind /mnt-root/nix/.ro-store /mnt-root/nix/store/ro
mount -t overlay overlay \
-o lowerdir=/mnt-root/nix/store/ro,upperdir=/mnt-root/nix/store/rw,workdir=/mnt-root/nix/store/work \
/mnt-root/nix/store
'';
kernelModules = [ "overlay" ];
};
postBootCommands = ''
touch /etc/post-boot-ran
mount
'';
};
};
testScript = ''
machine.wait_for_unit("multi-user.target")
machine.succeed("test /etc/post-boot-ran")
machine.fail("touch /nix/store/should-not-work");
'';
meta.maintainers = with pkgs.lib.maintainers; [ numinit ];
}
+243 -245
View File
@@ -1,276 +1,274 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{ pkgs, ... }:
let
passphrase = "supersecret";
dataDir = "/ran:dom/data";
subDir = "not_anything_here";
excludedSubDirFile = "not_this_file_either";
excludeFile = "not_this_file";
keepFile = "important_file";
keepFileData = "important_data";
localRepo = "/root/back:up";
# a repository on a file system which is not mounted automatically
localRepoMount = "/noAutoMount";
archiveName = "my_archive";
remoteRepo = "borg@server:."; # No need to specify path
privateKey = pkgs.writeText "id_ed25519" ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
-----END OPENSSH PRIVATE KEY-----
'';
publicKey = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client
'';
privateKeyAppendOnly = pkgs.writeText "id_ed25519" ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLwAAAJC9YTxxvWE8
cQAAAAtzc2gtZWQyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLw
AAAEAAhV7wTl5dL/lz+PF/d4PnZXuG1Id6L/mFEiGT1tZsuFpxm7PUQsZB2Ejs8Xp0YVp8
IOW+HylIRzhweORbRCMvAAAADXJzY2h1ZXR6QGt1cnQ=
-----END OPENSSH PRIVATE KEY-----
'';
publicKeyAppendOnly = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client
'';
let
passphrase = "supersecret";
dataDir = "/ran:dom/data";
subDir = "not_anything_here";
excludedSubDirFile = "not_this_file_either";
excludeFile = "not_this_file";
keepFile = "important_file";
keepFileData = "important_data";
localRepo = "/root/back:up";
# a repository on a file system which is not mounted automatically
localRepoMount = "/noAutoMount";
archiveName = "my_archive";
remoteRepo = "borg@server:."; # No need to specify path
privateKey = pkgs.writeText "id_ed25519" ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
-----END OPENSSH PRIVATE KEY-----
'';
publicKey = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client
'';
privateKeyAppendOnly = pkgs.writeText "id_ed25519" ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLwAAAJC9YTxxvWE8
cQAAAAtzc2gtZWQyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLw
AAAEAAhV7wTl5dL/lz+PF/d4PnZXuG1Id6L/mFEiGT1tZsuFpxm7PUQsZB2Ejs8Xp0YVp8
IOW+HylIRzhweORbRCMvAAAADXJzY2h1ZXR6QGt1cnQ=
-----END OPENSSH PRIVATE KEY-----
'';
publicKeyAppendOnly = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client
'';
in
{
name = "borgbackup";
meta = with pkgs.lib; {
maintainers = with maintainers; [ dotlambda ];
};
in
{
name = "borgbackup";
meta = with pkgs.lib; {
maintainers = with maintainers; [ dotlambda ];
};
nodes = {
client =
{ ... }:
{
virtualisation.fileSystems.${localRepoMount} = {
device = "tmpfs";
fsType = "tmpfs";
options = [ "noauto" ];
nodes = {
client =
{ ... }:
{
virtualisation.fileSystems.${localRepoMount} = {
device = "tmpfs";
fsType = "tmpfs";
options = [ "noauto" ];
};
services.borgbackup.jobs = {
local = {
paths = dataDir;
repo = localRepo;
preHook = ''
# Don't append a timestamp
archiveName="${archiveName}"
'';
encryption = {
mode = "repokey";
inherit passphrase;
};
compression = "auto,zlib,9";
prune.keep = {
within = "1y";
yearly = 5;
};
exclude = [ "*/${excludeFile}" ];
extraCreateArgs = [
"--exclude-caches"
"--exclude-if-present"
".dont backup"
];
postHook = "echo post";
startAt = [ ]; # Do not run automatically
};
services.borgbackup.jobs = {
localMount = {
paths = dataDir;
repo = localRepoMount;
encryption.mode = "none";
startAt = [ ];
};
local = {
paths = dataDir;
repo = localRepo;
preHook = ''
# Don't append a timestamp
archiveName="${archiveName}"
'';
encryption = {
mode = "repokey";
inherit passphrase;
};
compression = "auto,zlib,9";
prune.keep = {
within = "1y";
yearly = 5;
};
exclude = [ "*/${excludeFile}" ];
extraCreateArgs = [
"--exclude-caches"
"--exclude-if-present"
".dont backup"
];
postHook = "echo post";
startAt = [ ]; # Do not run automatically
};
remote = {
paths = dataDir;
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
localMount = {
paths = dataDir;
repo = localRepoMount;
encryption.mode = "none";
startAt = [ ];
};
remoteAppendOnly = {
paths = dataDir;
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
};
remote = {
paths = dataDir;
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
commandSuccess = {
dumpCommand = pkgs.writeScript "commandSuccess" ''
echo -n test
'';
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
remoteAppendOnly = {
paths = dataDir;
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
};
commandFail = {
dumpCommand = "${pkgs.coreutils}/bin/false";
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
commandSuccess = {
dumpCommand = pkgs.writeScript "commandSuccess" ''
echo -n test
'';
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
sleepInhibited = {
inhibitsSleep = true;
# Blocks indefinitely while "backing up" so that we can try to suspend the local system while it's hung
dumpCommand = pkgs.writeScript "sleepInhibited" ''
cat /dev/zero
'';
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
commandFail = {
dumpCommand = "${pkgs.coreutils}/bin/false";
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
sleepInhibited = {
inhibitsSleep = true;
# Blocks indefinitely while "backing up" so that we can try to suspend the local system while it's hung
dumpCommand = pkgs.writeScript "sleepInhibited" ''
cat /dev/zero
'';
repo = remoteRepo;
encryption.mode = "none";
startAt = [ ];
environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
};
};
};
server =
{ ... }:
{
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
};
};
server =
{ ... }:
{
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
};
};
services.borgbackup.repos.repo1 = {
authorizedKeys = [ publicKey ];
path = "/data/borgbackup";
};
# Second repo to make sure the authorizedKeys options are merged correctly
services.borgbackup.repos.repo2 = {
authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
path = "/data/borgbackup";
quota = ".5G";
};
services.borgbackup.repos.repo1 = {
authorizedKeys = [ publicKey ];
path = "/data/borgbackup";
};
};
testScript = ''
start_all()
# Second repo to make sure the authorizedKeys options are merged correctly
services.borgbackup.repos.repo2 = {
authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
path = "/data/borgbackup";
quota = ".5G";
};
};
};
client.fail('test -d "${remoteRepo}"')
testScript = ''
start_all()
client.succeed(
"cp ${privateKey} /root/id_ed25519"
)
client.succeed("chmod 0600 /root/id_ed25519")
client.succeed(
"cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly"
)
client.succeed("chmod 0600 /root/id_ed25519.appendOnly")
client.fail('test -d "${remoteRepo}"')
client.succeed("mkdir -p ${dataDir}/${subDir}")
client.succeed("touch ${dataDir}/${excludeFile}")
client.succeed("touch '${dataDir}/${subDir}/.dont backup'")
client.succeed("touch ${dataDir}/${subDir}/${excludedSubDirFile}")
client.succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}")
client.succeed(
"cp ${privateKey} /root/id_ed25519"
)
client.succeed("chmod 0600 /root/id_ed25519")
client.succeed(
"cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly"
)
client.succeed("chmod 0600 /root/id_ed25519.appendOnly")
with subtest("local"):
borg = "BORG_PASSPHRASE='${passphrase}' borg"
client.systemctl("start --wait borgbackup-job-local")
client.fail("systemctl is-failed borgbackup-job-local")
# Make sure exactly one archive has been created
assert int(client.succeed("{} list '${localRepo}' | wc -l".format(borg))) > 0
# Make sure excludeFile has been excluded
client.fail(
"{} list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'".format(borg)
)
# Make sure excludedSubDirFile has been excluded
client.fail(
"{} list '${localRepo}::${archiveName}' | grep -qF '${subDir}/${excludedSubDirFile}".format(borg)
)
# Make sure keepFile has the correct content
client.succeed("{} extract '${localRepo}::${archiveName}'".format(borg))
assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}")
# Make sure the same is true when using `borg mount`
client.succeed(
"mkdir -p /mnt/borg && {} mount '${localRepo}::${archiveName}' /mnt/borg".format(
borg
)
)
assert "${keepFileData}" in client.succeed(
"cat /mnt/borg/${dataDir}/${keepFile}"
)
client.succeed("mkdir -p ${dataDir}/${subDir}")
client.succeed("touch ${dataDir}/${excludeFile}")
client.succeed("touch '${dataDir}/${subDir}/.dont backup'")
client.succeed("touch ${dataDir}/${subDir}/${excludedSubDirFile}")
client.succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}")
with subtest("localMount"):
# the file system for the repo should not be already mounted
client.fail("mount | grep ${localRepoMount}")
# ensure trying to write to the mountpoint before the fs is mounted fails
client.succeed("chattr +i ${localRepoMount}")
borg = "borg"
client.systemctl("start --wait borgbackup-job-localMount")
client.fail("systemctl is-failed borgbackup-job-localMount")
# Make sure exactly one archive has been created
assert int(client.succeed("{} list '${localRepoMount}' | wc -l".format(borg))) > 0
with subtest("local"):
borg = "BORG_PASSPHRASE='${passphrase}' borg"
client.systemctl("start --wait borgbackup-job-local")
client.fail("systemctl is-failed borgbackup-job-local")
# Make sure exactly one archive has been created
assert int(client.succeed("{} list '${localRepo}' | wc -l".format(borg))) > 0
# Make sure excludeFile has been excluded
client.fail(
"{} list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'".format(borg)
)
# Make sure excludedSubDirFile has been excluded
client.fail(
"{} list '${localRepo}::${archiveName}' | grep -qF '${subDir}/${excludedSubDirFile}".format(borg)
)
# Make sure keepFile has the correct content
client.succeed("{} extract '${localRepo}::${archiveName}'".format(borg))
assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}")
# Make sure the same is true when using `borg mount`
client.succeed(
"mkdir -p /mnt/borg && {} mount '${localRepo}::${archiveName}' /mnt/borg".format(
borg
)
)
assert "${keepFileData}" in client.succeed(
"cat /mnt/borg/${dataDir}/${keepFile}"
)
with subtest("remote"):
borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg"
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-remote")
client.fail("systemctl is-failed borgbackup-job-remote")
with subtest("localMount"):
# the file system for the repo should not be already mounted
client.fail("mount | grep ${localRepoMount}")
# ensure trying to write to the mountpoint before the fs is mounted fails
client.succeed("chattr +i ${localRepoMount}")
borg = "borg"
client.systemctl("start --wait borgbackup-job-localMount")
client.fail("systemctl is-failed borgbackup-job-localMount")
# Make sure exactly one archive has been created
assert int(client.succeed("{} list '${localRepoMount}' | wc -l".format(borg))) > 0
# Make sure we can't access repos other than the specified one
client.fail("{} list borg\@server:wrong".format(borg))
with subtest("remote"):
borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg"
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-remote")
client.fail("systemctl is-failed borgbackup-job-remote")
# TODO: Make sure that data is actually deleted
# Make sure we can't access repos other than the specified one
client.fail("{} list borg\@server:wrong".format(borg))
with subtest("remoteAppendOnly"):
borg = (
"BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg"
)
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-remoteAppendOnly")
client.fail("systemctl is-failed borgbackup-job-remoteAppendOnly")
# TODO: Make sure that data is actually deleted
# Make sure we can't access repos other than the specified one
client.fail("{} list borg\@server:wrong".format(borg))
with subtest("remoteAppendOnly"):
borg = (
"BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg"
)
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-remoteAppendOnly")
client.fail("systemctl is-failed borgbackup-job-remoteAppendOnly")
# TODO: Make sure that data is not actually deleted
# Make sure we can't access repos other than the specified one
client.fail("{} list borg\@server:wrong".format(borg))
with subtest("commandSuccess"):
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-commandSuccess")
client.fail("systemctl is-failed borgbackup-job-commandSuccess")
id = client.succeed("borg-job-commandSuccess list | tail -n1 | cut -d' ' -f1").strip()
client.succeed(f"borg-job-commandSuccess extract ::{id} stdin")
assert "test" == client.succeed("cat stdin")
# TODO: Make sure that data is not actually deleted
with subtest("commandFail"):
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-commandFail")
client.succeed("systemctl is-failed borgbackup-job-commandFail")
with subtest("commandSuccess"):
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-commandSuccess")
client.fail("systemctl is-failed borgbackup-job-commandSuccess")
id = client.succeed("borg-job-commandSuccess list | tail -n1 | cut -d' ' -f1").strip()
client.succeed(f"borg-job-commandSuccess extract ::{id} stdin")
assert "test" == client.succeed("cat stdin")
with subtest("sleepInhibited"):
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.fail("systemd-inhibit --list | grep -q borgbackup")
client.systemctl("start borgbackup-job-sleepInhibited")
client.wait_until_succeeds("systemd-inhibit --list | grep -q borgbackup")
client.systemctl("stop borgbackup-job-sleepInhibited")
'';
}
)
with subtest("commandFail"):
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.systemctl("start --wait borgbackup-job-commandFail")
client.succeed("systemctl is-failed borgbackup-job-commandFail")
with subtest("sleepInhibited"):
server.wait_for_unit("sshd.service")
client.wait_for_unit("network.target")
client.fail("systemd-inhibit --list | grep -q borgbackup")
client.systemctl("start borgbackup-job-sleepInhibited")
client.wait_until_succeeds("systemd-inhibit --list | grep -q borgbackup")
client.systemctl("stop borgbackup-job-sleepInhibited")
'';
}
+23 -25
View File
@@ -1,28 +1,26 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "borgmatic";
nodes.machine =
{ ... }:
{
services.borgmatic = {
enable = true;
settings = {
source_directories = [ "/home" ];
repositories = [
{
label = "local";
path = "/var/backup";
}
];
keep_daily = 7;
};
{ pkgs, ... }:
{
name = "borgmatic";
nodes.machine =
{ ... }:
{
services.borgmatic = {
enable = true;
settings = {
source_directories = [ "/home" ];
repositories = [
{
label = "local";
path = "/var/backup";
}
];
keep_daily = 7;
};
};
};
testScript = ''
machine.succeed("borgmatic rcreate -e none")
machine.succeed("borgmatic")
'';
}
)
testScript = ''
machine.succeed("borgmatic rcreate -e none")
machine.succeed("borgmatic")
'';
}
+18 -20
View File
@@ -1,25 +1,23 @@
import ./make-test-python.nix (
{ lib, pkgs, ... }:
{
{ lib, pkgs, ... }:
{
name = "bpftune";
name = "bpftune";
meta = {
maintainers = with lib.maintainers; [ nickcao ];
};
meta = {
maintainers = with lib.maintainers; [ nickcao ];
};
nodes = {
machine =
{ pkgs, ... }:
{
services.bpftune.enable = true;
};
};
nodes = {
machine =
{ pkgs, ... }:
{
services.bpftune.enable = true;
};
};
testScript = ''
machine.wait_for_unit("bpftune.service")
machine.wait_for_console_text("bpftune works")
'';
testScript = ''
machine.wait_for_unit("bpftune.service")
machine.wait_for_console_text("bpftune works")
'';
}
)
}
+35 -35
View File
@@ -1,41 +1,41 @@
import ./make-test-python.nix (
{ lib, ... }:
{
name = "breitbandmessung";
meta.maintainers = with lib.maintainers; [ b4dm4n ];
{ lib, ... }:
{
name = "breitbandmessung";
meta.maintainers = with lib.maintainers; [ b4dm4n ];
nodes.machine =
{ pkgs, ... }:
{
imports = [
./common/user-account.nix
./common/x11.nix
];
node.pkgsReadOnly = false;
# increase screen size to make the whole program visible
virtualisation.resolution = {
x = 1280;
y = 1024;
};
nodes.machine =
{ pkgs, ... }:
{
imports = [
./common/user-account.nix
./common/x11.nix
];
test-support.displayManager.auto.user = "alice";
environment.systemPackages = with pkgs; [ breitbandmessung ];
environment.variables.XAUTHORITY = "/home/alice/.Xauthority";
# breitbandmessung is unfree
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "breitbandmessung" ];
# increase screen size to make the whole program visible
virtualisation.resolution = {
x = 1280;
y = 1024;
};
enableOCR = true;
test-support.displayManager.auto.user = "alice";
testScript = ''
machine.wait_for_x()
machine.execute("su - alice -c breitbandmessung >&2 &")
machine.wait_for_window("Breitbandmessung")
machine.wait_for_text("Breitbandmessung")
machine.wait_for_text("Datenschutz")
machine.screenshot("breitbandmessung")
'';
}
)
environment.systemPackages = with pkgs; [ breitbandmessung ];
environment.variables.XAUTHORITY = "/home/alice/.Xauthority";
# breitbandmessung is unfree
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "breitbandmessung" ];
};
enableOCR = true;
testScript = ''
machine.wait_for_x()
machine.execute("su - alice -c breitbandmessung >&2 &")
machine.wait_for_window("Breitbandmessung")
machine.wait_for_text("Breitbandmessung")
machine.wait_for_text("Datenschutz")
machine.screenshot("breitbandmessung")
'';
}
+38 -44
View File
@@ -1,53 +1,47 @@
# integration tests for brscan5 sane driver
#
{ lib, ... }:
{
name = "brscan5";
meta.maintainers = with lib.maintainers; [ mattchrist ];
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "brscan5";
meta = with pkgs.lib.maintainers; {
maintainers = [ mattchrist ];
};
node.pkgsReadOnly = false;
nodes.machine =
{ pkgs, ... }:
{
nixpkgs.config.allowUnfree = true;
hardware.sane = {
enable = true;
brscan5 = {
enable = true;
netDevices = {
"a" = {
model = "ADS-1200";
nodename = "BRW0080927AFBCE";
};
"b" = {
model = "ADS-1200";
ip = "192.168.1.2";
};
};
nodes.machine = {
nixpkgs.config.allowUnfree = true;
hardware.sane = {
enable = true;
brscan5 = {
enable = true;
netDevices = {
"a" = {
model = "ADS-1200";
nodename = "BRW0080927AFBCE";
};
"b" = {
model = "ADS-1200";
ip = "192.168.1.2";
};
};
};
};
};
testScript = ''
import re
# sane loads libsane-brother5.so.1 successfully, and scanimage doesn't die
strace = machine.succeed('strace scanimage -L 2>&1').split("\n")
regexp = 'openat\(.*libsane-brother5.so.1", O_RDONLY|O_CLOEXEC\) = \d\d*$'
assert len([x for x in strace if re.match(regexp,x)]) > 0
testScript = ''
import re
# sane loads libsane-brother5.so.1 successfully, and scanimage doesn't die
strace = machine.succeed('strace scanimage -L 2>&1').split("\n")
regexp = 'openat\(.*libsane-brother5.so.1", O_RDONLY|O_CLOEXEC\) = \d\d*$'
assert len([x for x in strace if re.match(regexp,x)]) > 0
# module creates a config
cfg = machine.succeed('cat /etc/opt/brother/scanner/brscan5/brsanenetdevice.cfg')
assert 'DEVICE=a , "ADS-1200" , 0x4f9:0x459 , NODENAME=BRW0080927AFBCE' in cfg
assert 'DEVICE=b , "ADS-1200" , 0x4f9:0x459 , IP-ADDRESS=192.168.1.2' in cfg
# module creates a config
cfg = machine.succeed('cat /etc/opt/brother/scanner/brscan5/brsanenetdevice.cfg')
assert 'DEVICE=a , "ADS-1200" , 0x4f9:0x459 , NODENAME=BRW0080927AFBCE' in cfg
assert 'DEVICE=b , "ADS-1200" , 0x4f9:0x459 , IP-ADDRESS=192.168.1.2' in cfg
# scanimage lists the two network scanners
scanimage = machine.succeed("scanimage -L")
print(scanimage)
assert """device `brother5:net1;dev0' is a Brother b ADS-1200""" in scanimage
assert """device `brother5:net1;dev1' is a Brother a ADS-1200""" in scanimage
'';
}
)
# scanimage lists the two network scanners
scanimage = machine.succeed("scanimage -L")
print(scanimage)
assert """device `brother5:net1;dev0' is a Brother b ADS-1200""" in scanimage
assert """device `brother5:net1;dev1' is a Brother a ADS-1200""" in scanimage
'';
}
+106 -108
View File
@@ -1,128 +1,126 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{ pkgs, ... }:
let
privateKey = ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
-----END OPENSSH PRIVATE KEY-----
'';
publicKey = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
'';
in
{
name = "btrbk-doas";
meta = with pkgs.lib; {
maintainers = with maintainers; [
symphorien
tu-maurice
];
};
let
privateKey = ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
-----END OPENSSH PRIVATE KEY-----
'';
publicKey = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
'';
in
{
name = "btrbk-doas";
meta = with pkgs.lib; {
maintainers = with maintainers; [
symphorien
tu-maurice
];
};
nodes = {
archive =
{ ... }:
{
security.sudo.enable = false;
security.doas.enable = true;
environment.systemPackages = with pkgs; [ btrfs-progs ];
# note: this makes the privateKey world readable.
# don't do it with real ssh keys.
environment.etc."btrbk_key".text = privateKey;
services.btrbk = {
extraPackages = [ pkgs.lz4 ];
instances = {
remote = {
onCalendar = "minutely";
settings = {
ssh_identity = "/etc/btrbk_key";
ssh_user = "btrbk";
stream_compress = "lz4";
volume = {
"ssh://main/mnt" = {
target = "/mnt";
snapshot_dir = "btrbk/remote";
subvolume = "to_backup";
};
nodes = {
archive =
{ ... }:
{
security.sudo.enable = false;
security.doas.enable = true;
environment.systemPackages = with pkgs; [ btrfs-progs ];
# note: this makes the privateKey world readable.
# don't do it with real ssh keys.
environment.etc."btrbk_key".text = privateKey;
services.btrbk = {
extraPackages = [ pkgs.lz4 ];
instances = {
remote = {
onCalendar = "minutely";
settings = {
ssh_identity = "/etc/btrbk_key";
ssh_user = "btrbk";
stream_compress = "lz4";
volume = {
"ssh://main/mnt" = {
target = "/mnt";
snapshot_dir = "btrbk/remote";
subvolume = "to_backup";
};
};
};
};
};
};
};
main =
{ ... }:
{
security.sudo.enable = false;
security.doas.enable = true;
environment.systemPackages = with pkgs; [ btrfs-progs ];
services.openssh = {
enable = true;
passwordAuthentication = false;
kbdInteractiveAuthentication = false;
};
services.btrbk = {
extraPackages = [ pkgs.lz4 ];
sshAccess = [
{
key = publicKey;
roles = [
"source"
"send"
"info"
"delete"
];
}
];
instances = {
local = {
onCalendar = "minutely";
settings = {
volume = {
"/mnt" = {
snapshot_dir = "btrbk/local";
subvolume = "to_backup";
};
main =
{ ... }:
{
security.sudo.enable = false;
security.doas.enable = true;
environment.systemPackages = with pkgs; [ btrfs-progs ];
services.openssh = {
enable = true;
passwordAuthentication = false;
kbdInteractiveAuthentication = false;
};
services.btrbk = {
extraPackages = [ pkgs.lz4 ];
sshAccess = [
{
key = publicKey;
roles = [
"source"
"send"
"info"
"delete"
];
}
];
instances = {
local = {
onCalendar = "minutely";
settings = {
volume = {
"/mnt" = {
snapshot_dir = "btrbk/local";
subvolume = "to_backup";
};
};
};
};
};
};
};
};
};
testScript = ''
start_all()
testScript = ''
start_all()
# create btrfs partition at /mnt
for machine in (archive, main):
machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
machine.succeed("mkfs.btrfs /data_fs")
machine.succeed("mkdir /mnt")
machine.succeed("mount /data_fs /mnt")
# create btrfs partition at /mnt
for machine in (archive, main):
machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
machine.succeed("mkfs.btrfs /data_fs")
machine.succeed("mkdir /mnt")
machine.succeed("mount /data_fs /mnt")
# what to backup and where
main.succeed("btrfs subvolume create /mnt/to_backup")
main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
# what to backup and where
main.succeed("btrfs subvolume create /mnt/to_backup")
main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
# check that local snapshots work
with subtest("local"):
main.succeed("echo foo > /mnt/to_backup/bar")
main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
main.succeed("echo bar > /mnt/to_backup/bar")
main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
# check that local snapshots work
with subtest("local"):
main.succeed("echo foo > /mnt/to_backup/bar")
main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
main.succeed("echo bar > /mnt/to_backup/bar")
main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
# check that btrfs send/receive works and ssh access works
with subtest("remote"):
archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
main.succeed("echo baz > /mnt/to_backup/bar")
archive.succeed("cat /mnt/*/bar | grep bar")
'';
}
)
# check that btrfs send/receive works and ssh access works
with subtest("remote"):
archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
main.succeed("echo baz > /mnt/to_backup/bar")
archive.succeed("cat /mnt/*/bar | grep bar")
'';
}
+32 -34
View File
@@ -1,41 +1,39 @@
import ./make-test-python.nix (
{ lib, pkgs, ... }:
{
name = "btrbk-no-timer";
meta.maintainers = with lib.maintainers; [ oxalica ];
{ lib, pkgs, ... }:
{
name = "btrbk-no-timer";
meta.maintainers = with lib.maintainers; [ oxalica ];
nodes.machine =
{ ... }:
{
environment.systemPackages = with pkgs; [ btrfs-progs ];
services.btrbk.instances.local = {
onCalendar = null;
settings.volume."/mnt" = {
snapshot_dir = "btrbk/local";
subvolume = "to_backup";
};
nodes.machine =
{ ... }:
{
environment.systemPackages = with pkgs; [ btrfs-progs ];
services.btrbk.instances.local = {
onCalendar = null;
settings.volume."/mnt" = {
snapshot_dir = "btrbk/local";
subvolume = "to_backup";
};
};
};
testScript = ''
start_all()
testScript = ''
start_all()
# Create btrfs partition at /mnt
machine.succeed("truncate --size=128M /data_fs")
machine.succeed("mkfs.btrfs /data_fs")
machine.succeed("mkdir /mnt")
machine.succeed("mount /data_fs /mnt")
machine.succeed("btrfs subvolume create /mnt/to_backup")
machine.succeed("mkdir -p /mnt/btrbk/local")
# Create btrfs partition at /mnt
machine.succeed("truncate --size=128M /data_fs")
machine.succeed("mkfs.btrfs /data_fs")
machine.succeed("mkdir /mnt")
machine.succeed("mount /data_fs /mnt")
machine.succeed("btrfs subvolume create /mnt/to_backup")
machine.succeed("mkdir -p /mnt/btrbk/local")
# The service should not have any triggering timer.
unit = machine.get_unit_info('btrbk-local.service')
assert "TriggeredBy" not in unit
# The service should not have any triggering timer.
unit = machine.get_unit_info('btrbk-local.service')
assert "TriggeredBy" not in unit
# Manually starting the service should still work.
machine.succeed("echo foo > /mnt/to_backup/bar")
machine.start_job("btrbk-local.service")
machine.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
'';
}
)
# Manually starting the service should still work.
machine.succeed("echo foo > /mnt/to_backup/bar")
machine.start_job("btrbk-local.service")
machine.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
'';
}
+44 -46
View File
@@ -6,56 +6,54 @@
# order-sensitive config format.
#
# Issue: https://github.com/NixOS/nixpkgs/issues/195660
import ./make-test-python.nix (
{ lib, pkgs, ... }:
{
name = "btrbk-section-order";
meta.maintainers = with lib.maintainers; [ oxalica ];
{ lib, pkgs, ... }:
{
name = "btrbk-section-order";
meta.maintainers = with lib.maintainers; [ oxalica ];
nodes.machine =
{ ... }:
{
services.btrbk.instances.local = {
onCalendar = null;
settings = {
timestamp_format = "long";
target."ssh://global-target/".ssh_user = "root";
volume."/btrfs" = {
snapshot_dir = "/volume-snapshots";
target."ssh://volume-target/".ssh_user = "root";
subvolume."@subvolume" = {
snapshot_dir = "/subvolume-snapshots";
target."ssh://subvolume-target/".ssh_user = "root";
};
nodes.machine =
{ ... }:
{
services.btrbk.instances.local = {
onCalendar = null;
settings = {
timestamp_format = "long";
target."ssh://global-target/".ssh_user = "root";
volume."/btrfs" = {
snapshot_dir = "/volume-snapshots";
target."ssh://volume-target/".ssh_user = "root";
subvolume."@subvolume" = {
snapshot_dir = "/subvolume-snapshots";
target."ssh://subvolume-target/".ssh_user = "root";
};
};
};
};
};
testScript = ''
import difflib
machine.wait_for_unit("basic.target")
got = machine.succeed("cat /etc/btrbk/local.conf").strip()
expect = """
backend btrfs-progs-sudo
stream_compress no
timestamp_format long
target ssh://global-target/
testScript = ''
import difflib
machine.wait_for_unit("basic.target")
got = machine.succeed("cat /etc/btrbk/local.conf").strip()
expect = """
backend btrfs-progs-sudo
stream_compress no
timestamp_format long
target ssh://global-target/
ssh_user root
volume /btrfs
snapshot_dir /volume-snapshots
target ssh://volume-target/
ssh_user root
subvolume @subvolume
snapshot_dir /subvolume-snapshots
target ssh://subvolume-target/
ssh_user root
volume /btrfs
snapshot_dir /volume-snapshots
target ssh://volume-target/
ssh_user root
subvolume @subvolume
snapshot_dir /subvolume-snapshots
target ssh://subvolume-target/
ssh_user root
""".strip()
print(got)
if got != expect:
diff = difflib.unified_diff(expect.splitlines(keepends=True), got.splitlines(keepends=True), fromfile="expected", tofile="got")
print("".join(diff))
assert got == expect
'';
}
)
""".strip()
print(got)
if got != expect:
diff = difflib.unified_diff(expect.splitlines(keepends=True), got.splitlines(keepends=True), fromfile="expected", tofile="got")
print("".join(diff))
assert got == expect
'';
}
+99 -101
View File
@@ -1,122 +1,120 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{ pkgs, ... }:
let
privateKey = ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
-----END OPENSSH PRIVATE KEY-----
'';
publicKey = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
'';
in
{
name = "btrbk";
meta = with pkgs.lib; {
maintainers = with maintainers; [ symphorien ];
};
let
privateKey = ''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
-----END OPENSSH PRIVATE KEY-----
'';
publicKey = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
'';
in
{
name = "btrbk";
meta = with pkgs.lib; {
maintainers = with maintainers; [ symphorien ];
};
nodes = {
archive =
{ ... }:
{
environment.systemPackages = with pkgs; [ btrfs-progs ];
# note: this makes the privateKey world readable.
# don't do it with real ssh keys.
environment.etc."btrbk_key".text = privateKey;
services.btrbk = {
instances = {
remote = {
onCalendar = "minutely";
settings = {
ssh_identity = "/etc/btrbk_key";
ssh_user = "btrbk";
stream_compress = "lz4";
volume = {
"ssh://main/mnt" = {
target = "/mnt";
snapshot_dir = "btrbk/remote";
subvolume = "to_backup";
};
nodes = {
archive =
{ ... }:
{
environment.systemPackages = with pkgs; [ btrfs-progs ];
# note: this makes the privateKey world readable.
# don't do it with real ssh keys.
environment.etc."btrbk_key".text = privateKey;
services.btrbk = {
instances = {
remote = {
onCalendar = "minutely";
settings = {
ssh_identity = "/etc/btrbk_key";
ssh_user = "btrbk";
stream_compress = "lz4";
volume = {
"ssh://main/mnt" = {
target = "/mnt";
snapshot_dir = "btrbk/remote";
subvolume = "to_backup";
};
};
};
};
};
};
};
main =
{ ... }:
{
environment.systemPackages = with pkgs; [ btrfs-progs ];
services.openssh = {
enable = true;
settings = {
KbdInteractiveAuthentication = false;
PasswordAuthentication = false;
};
main =
{ ... }:
{
environment.systemPackages = with pkgs; [ btrfs-progs ];
services.openssh = {
enable = true;
settings = {
KbdInteractiveAuthentication = false;
PasswordAuthentication = false;
};
services.btrbk = {
extraPackages = [ pkgs.lz4 ];
sshAccess = [
{
key = publicKey;
roles = [
"source"
"send"
"info"
"delete"
];
}
];
instances = {
local = {
onCalendar = "minutely";
settings = {
volume = {
"/mnt" = {
snapshot_dir = "btrbk/local";
subvolume = "to_backup";
};
};
services.btrbk = {
extraPackages = [ pkgs.lz4 ];
sshAccess = [
{
key = publicKey;
roles = [
"source"
"send"
"info"
"delete"
];
}
];
instances = {
local = {
onCalendar = "minutely";
settings = {
volume = {
"/mnt" = {
snapshot_dir = "btrbk/local";
subvolume = "to_backup";
};
};
};
};
};
};
};
};
};
testScript = ''
start_all()
testScript = ''
start_all()
# create btrfs partition at /mnt
for machine in (archive, main):
machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
machine.succeed("mkfs.btrfs /data_fs")
machine.succeed("mkdir /mnt")
machine.succeed("mount /data_fs /mnt")
# create btrfs partition at /mnt
for machine in (archive, main):
machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
machine.succeed("mkfs.btrfs /data_fs")
machine.succeed("mkdir /mnt")
machine.succeed("mount /data_fs /mnt")
# what to backup and where
main.succeed("btrfs subvolume create /mnt/to_backup")
main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
# what to backup and where
main.succeed("btrfs subvolume create /mnt/to_backup")
main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
# check that local snapshots work
with subtest("local"):
main.succeed("echo foo > /mnt/to_backup/bar")
main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
main.succeed("echo bar > /mnt/to_backup/bar")
main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
# check that local snapshots work
with subtest("local"):
main.succeed("echo foo > /mnt/to_backup/bar")
main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
main.succeed("echo bar > /mnt/to_backup/bar")
main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
# check that btrfs send/receive works and ssh access works
with subtest("remote"):
archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
main.succeed("echo baz > /mnt/to_backup/bar")
archive.succeed("cat /mnt/*/bar | grep bar")
'';
}
)
# check that btrfs send/receive works and ssh access works
with subtest("remote"):
archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
main.succeed("echo baz > /mnt/to_backup/bar")
archive.succeed("cat /mnt/*/bar | grep bar")
'';
}
+86 -88
View File
@@ -1,104 +1,102 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "budgie";
{ pkgs, lib, ... }:
{
name = "budgie";
meta.maintainers = lib.teams.budgie.members;
meta.maintainers = lib.teams.budgie.members;
nodes.machine =
{ ... }:
{
imports = [
./common/user-account.nix
];
nodes.machine =
{ ... }:
{
imports = [
./common/user-account.nix
];
services.xserver.enable = true;
services.xserver.enable = true;
services.xserver.displayManager = {
lightdm.enable = true;
autoLogin = {
enable = true;
user = "alice";
};
};
# We don't ship gnome-text-editor in Budgie module, we add this line mainly
# to catch eval issues related to this option.
environment.budgie.excludePackages = [ pkgs.gnome-text-editor ];
services.xserver.desktopManager.budgie = {
services.xserver.displayManager = {
lightdm.enable = true;
autoLogin = {
enable = true;
extraPlugins = [
pkgs.budgie-analogue-clock-applet
];
user = "alice";
};
};
testScript =
{ nodes, ... }:
let
user = nodes.machine.users.users.alice;
env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus DISPLAY=:0";
su = command: "su - ${user.name} -c '${env} ${command}'";
in
''
with subtest("Wait for login"):
# wait_for_x() checks graphical-session.target, which is expected to be
# inactive on Budgie before Budgie manages user session with systemd.
# https://github.com/BuddiesOfBudgie/budgie-desktop/blob/39e9f0895c978f76/src/session/budgie-desktop.in#L16
#
# Previously this was unconditionally touched by xsessionWrapper but was
# changed in #233981 (we have Budgie:GNOME in XDG_CURRENT_DESKTOP).
# machine.wait_for_x()
machine.wait_until_succeeds('journalctl -t budgie-session-binary --grep "Entering running state"')
machine.wait_for_file("${user.home}/.Xauthority")
machine.succeed("xauth merge ${user.home}/.Xauthority")
# We don't ship gnome-text-editor in Budgie module, we add this line mainly
# to catch eval issues related to this option.
environment.budgie.excludePackages = [ pkgs.gnome-text-editor ];
with subtest("Check that logging in has given the user ownership of devices"):
machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
services.xserver.desktopManager.budgie = {
enable = true;
extraPlugins = [
pkgs.budgie-analogue-clock-applet
];
};
};
with subtest("Check if Budgie session components actually start"):
for i in ["budgie-daemon", "budgie-panel", "budgie-wm", "budgie-desktop-view", "gsd-media-keys"]:
machine.wait_until_succeeds(f"pgrep -f {i}")
# We don't check xwininfo for budgie-wm.
# See https://github.com/NixOS/nixpkgs/pull/216737#discussion_r1155312754
machine.wait_for_window("budgie-daemon")
machine.wait_for_window("budgie-panel")
testScript =
{ nodes, ... }:
let
user = nodes.machine.users.users.alice;
env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus DISPLAY=:0";
su = command: "su - ${user.name} -c '${env} ${command}'";
in
''
with subtest("Wait for login"):
# wait_for_x() checks graphical-session.target, which is expected to be
# inactive on Budgie before Budgie manages user session with systemd.
# https://github.com/BuddiesOfBudgie/budgie-desktop/blob/39e9f0895c978f76/src/session/budgie-desktop.in#L16
#
# Previously this was unconditionally touched by xsessionWrapper but was
# changed in #233981 (we have Budgie:GNOME in XDG_CURRENT_DESKTOP).
# machine.wait_for_x()
machine.wait_until_succeeds('journalctl -t budgie-session-binary --grep "Entering running state"')
machine.wait_for_file("${user.home}/.Xauthority")
machine.succeed("xauth merge ${user.home}/.Xauthority")
with subtest("Check if various environment variables are set"):
cmd = "xargs --null --max-args=1 echo < /proc/$(pgrep -xf /run/current-system/sw/bin/budgie-wm)/environ"
machine.succeed(f"{cmd} | grep 'XDG_CURRENT_DESKTOP' | grep 'Budgie:GNOME'")
machine.succeed(f"{cmd} | grep 'BUDGIE_PLUGIN_DATADIR' | grep '${pkgs.budgie-desktop-with-plugins.pname}'")
with subtest("Check that logging in has given the user ownership of devices"):
machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
with subtest("Open run dialog"):
machine.send_key("alt-f2")
machine.wait_for_window("budgie-run-dialog")
machine.sleep(2)
machine.screenshot("run_dialog")
machine.send_key("esc")
with subtest("Check if Budgie session components actually start"):
for i in ["budgie-daemon", "budgie-panel", "budgie-wm", "budgie-desktop-view", "gsd-media-keys"]:
machine.wait_until_succeeds(f"pgrep -f {i}")
# We don't check xwininfo for budgie-wm.
# See https://github.com/NixOS/nixpkgs/pull/216737#discussion_r1155312754
machine.wait_for_window("budgie-daemon")
machine.wait_for_window("budgie-panel")
with subtest("Open Budgie Control Center"):
machine.succeed("${su "budgie-control-center >&2 &"}")
machine.wait_for_window("Budgie Control Center")
with subtest("Check if various environment variables are set"):
cmd = "xargs --null --max-args=1 echo < /proc/$(pgrep -xf /run/current-system/sw/bin/budgie-wm)/environ"
machine.succeed(f"{cmd} | grep 'XDG_CURRENT_DESKTOP' | grep 'Budgie:GNOME'")
machine.succeed(f"{cmd} | grep 'BUDGIE_PLUGIN_DATADIR' | grep '${pkgs.budgie-desktop-with-plugins.pname}'")
with subtest("Lock the screen"):
machine.succeed("${su "budgie-screensaver-command -l >&2 &"}")
machine.wait_until_succeeds("${su "budgie-screensaver-command -q"} | grep 'The screensaver is active'")
machine.sleep(2)
machine.send_chars("${user.password}", delay=0.5)
machine.screenshot("budgie_screensaver")
machine.send_chars("\n")
machine.wait_until_succeeds("${su "budgie-screensaver-command -q"} | grep 'The screensaver is inactive'")
machine.sleep(2)
with subtest("Open run dialog"):
machine.send_key("alt-f2")
machine.wait_for_window("budgie-run-dialog")
machine.sleep(2)
machine.screenshot("run_dialog")
machine.send_key("esc")
with subtest("Open GNOME terminal"):
machine.succeed("${su "gnome-terminal"}")
machine.wait_for_window("${user.name}@machine: ~")
with subtest("Open Budgie Control Center"):
machine.succeed("${su "budgie-control-center >&2 &"}")
machine.wait_for_window("Budgie Control Center")
with subtest("Check if Budgie has ever coredumped"):
machine.fail("coredumpctl --json=short | grep budgie")
machine.sleep(10)
machine.screenshot("screen")
'';
}
)
with subtest("Lock the screen"):
machine.succeed("${su "budgie-screensaver-command -l >&2 &"}")
machine.wait_until_succeeds("${su "budgie-screensaver-command -q"} | grep 'The screensaver is active'")
machine.sleep(2)
machine.send_chars("${user.password}", delay=0.5)
machine.screenshot("budgie_screensaver")
machine.send_chars("\n")
machine.wait_until_succeeds("${su "budgie-screensaver-command -q"} | grep 'The screensaver is inactive'")
machine.sleep(2)
with subtest("Open GNOME terminal"):
machine.succeed("${su "gnome-terminal"}")
machine.wait_for_window("${user.name}@machine: ~")
with subtest("Check if Budgie has ever coredumped"):
machine.fail("coredumpctl --json=short | grep budgie")
machine.sleep(10)
machine.screenshot("screen")
'';
}
+24 -26
View File
@@ -1,33 +1,31 @@
import ./make-test-python.nix (
{ lib, pkgs, ... }:
{ lib, pkgs, ... }:
{
name = "buildkite-agent";
meta.maintainers = with lib.maintainers; [ flokli ];
{
name = "buildkite-agent";
meta.maintainers = with lib.maintainers; [ flokli ];
nodes.machine =
{ pkgs, ... }:
{
services.buildkite-agents = {
one = {
privateSshKeyPath = (import ./ssh-keys.nix pkgs).snakeOilPrivateKey;
tokenPath = (pkgs.writeText "my-token" "5678");
};
two = {
tokenPath = (pkgs.writeText "my-token" "1234");
};
nodes.machine =
{ pkgs, ... }:
{
services.buildkite-agents = {
one = {
privateSshKeyPath = (import ./ssh-keys.nix pkgs).snakeOilPrivateKey;
tokenPath = (pkgs.writeText "my-token" "5678");
};
two = {
tokenPath = (pkgs.writeText "my-token" "1234");
};
};
};
testScript = ''
start_all()
# we can't wait on the unit to start up, as we obviously can't connect to buildkite,
# but we can look whether files are set up correctly
testScript = ''
start_all()
# we can't wait on the unit to start up, as we obviously can't connect to buildkite,
# but we can look whether files are set up correctly
machine.wait_for_file("/var/lib/buildkite-agent-one/buildkite-agent.cfg")
machine.wait_for_file("/var/lib/buildkite-agent-one/.ssh/id_rsa")
machine.wait_for_file("/var/lib/buildkite-agent-one/buildkite-agent.cfg")
machine.wait_for_file("/var/lib/buildkite-agent-one/.ssh/id_rsa")
machine.wait_for_file("/var/lib/buildkite-agent-two/buildkite-agent.cfg")
'';
}
)
machine.wait_for_file("/var/lib/buildkite-agent-two/buildkite-agent.cfg")
'';
}
+73 -75
View File
@@ -1,87 +1,85 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "c2FmZQ";
meta.maintainers = with lib.maintainers; [ hmenke ];
{ pkgs, lib, ... }:
{
name = "c2FmZQ";
meta.maintainers = with lib.maintainers; [ hmenke ];
nodes.machine = {
services.c2fmzq-server = {
enable = true;
port = 8080;
passphraseFile = builtins.toFile "pwfile" "hunter2"; # don't do this on real deployments
settings = {
verbose = 3; # debug
# make sure multiple freeform options evaluate
allow-new-accounts = true;
auto-approve-new-accounts = true;
licenses = false;
};
};
environment = {
sessionVariables = {
C2FMZQ_PASSPHRASE = "lol";
C2FMZQ_API_SERVER = "http://localhost:8080";
};
systemPackages = [
pkgs.c2fmzq
(pkgs.writeScriptBin "c2FmZQ-client-wrapper" ''
#!${pkgs.expect}/bin/expect -f
spawn c2FmZQ-client {*}$argv
expect {
"Enter password:" { send "$env(PASSWORD)\r" }
"Type YES to confirm:" { send "YES\r" }
timeout { exit 1 }
eof { exit 0 }
}
interact
'')
];
nodes.machine = {
services.c2fmzq-server = {
enable = true;
port = 8080;
passphraseFile = builtins.toFile "pwfile" "hunter2"; # don't do this on real deployments
settings = {
verbose = 3; # debug
# make sure multiple freeform options evaluate
allow-new-accounts = true;
auto-approve-new-accounts = true;
licenses = false;
};
};
environment = {
sessionVariables = {
C2FMZQ_PASSPHRASE = "lol";
C2FMZQ_API_SERVER = "http://localhost:8080";
};
systemPackages = [
pkgs.c2fmzq
(pkgs.writeScriptBin "c2FmZQ-client-wrapper" ''
#!${pkgs.expect}/bin/expect -f
spawn c2FmZQ-client {*}$argv
expect {
"Enter password:" { send "$env(PASSWORD)\r" }
"Type YES to confirm:" { send "YES\r" }
timeout { exit 1 }
eof { exit 0 }
}
interact
'')
];
};
};
testScript =
{ nodes, ... }:
''
machine.start()
machine.wait_for_unit("c2fmzq-server.service")
machine.wait_for_open_port(8080)
testScript =
{ nodes, ... }:
''
machine.start()
machine.wait_for_unit("c2fmzq-server.service")
machine.wait_for_open_port(8080)
with subtest("Create accounts for alice and bob"):
machine.succeed("PASSWORD=foobar c2FmZQ-client-wrapper -- -v 3 create-account alice@example.com")
machine.succeed("PASSWORD=fizzbuzz c2FmZQ-client-wrapper -- -v 3 create-account bob@example.com")
with subtest("Create accounts for alice and bob"):
machine.succeed("PASSWORD=foobar c2FmZQ-client-wrapper -- -v 3 create-account alice@example.com")
machine.succeed("PASSWORD=fizzbuzz c2FmZQ-client-wrapper -- -v 3 create-account bob@example.com")
with subtest("Log in as alice"):
machine.succeed("PASSWORD=foobar c2FmZQ-client-wrapper -- -v 3 login alice@example.com")
msg = machine.succeed("c2FmZQ-client -v 3 status")
assert "Logged in as alice@example.com" in msg, f"ERROR: Not logged in as alice:\n{msg}"
with subtest("Log in as alice"):
machine.succeed("PASSWORD=foobar c2FmZQ-client-wrapper -- -v 3 login alice@example.com")
msg = machine.succeed("c2FmZQ-client -v 3 status")
assert "Logged in as alice@example.com" in msg, f"ERROR: Not logged in as alice:\n{msg}"
with subtest("Create a new album, upload a file, and delete the uploaded file"):
machine.succeed("c2FmZQ-client -v 3 create-album 'Rarest Memes'")
machine.succeed("echo 'pls do not steal' > meme.txt")
machine.succeed("c2FmZQ-client -v 3 import meme.txt 'Rarest Memes'")
machine.succeed("c2FmZQ-client -v 3 sync")
machine.succeed("rm meme.txt")
with subtest("Create a new album, upload a file, and delete the uploaded file"):
machine.succeed("c2FmZQ-client -v 3 create-album 'Rarest Memes'")
machine.succeed("echo 'pls do not steal' > meme.txt")
machine.succeed("c2FmZQ-client -v 3 import meme.txt 'Rarest Memes'")
machine.succeed("c2FmZQ-client -v 3 sync")
machine.succeed("rm meme.txt")
with subtest("Share the album with bob"):
machine.succeed("c2FmZQ-client-wrapper -- -v 3 share 'Rarest Memes' bob@example.com")
with subtest("Share the album with bob"):
machine.succeed("c2FmZQ-client-wrapper -- -v 3 share 'Rarest Memes' bob@example.com")
with subtest("Log in as bob"):
machine.succeed("PASSWORD=fizzbuzz c2FmZQ-client-wrapper -- -v 3 login bob@example.com")
msg = machine.succeed("c2FmZQ-client -v 3 status")
assert "Logged in as bob@example.com" in msg, f"ERROR: Not logged in as bob:\n{msg}"
with subtest("Log in as bob"):
machine.succeed("PASSWORD=fizzbuzz c2FmZQ-client-wrapper -- -v 3 login bob@example.com")
msg = machine.succeed("c2FmZQ-client -v 3 status")
assert "Logged in as bob@example.com" in msg, f"ERROR: Not logged in as bob:\n{msg}"
with subtest("Download the shared file"):
machine.succeed("c2FmZQ-client -v 3 download 'shared/Rarest Memes/meme.txt'")
machine.succeed("c2FmZQ-client -v 3 export 'shared/Rarest Memes/meme.txt' .")
msg = machine.succeed("cat meme.txt")
assert "pls do not steal\n" == msg, f"File content is not the same:\n{msg}"
with subtest("Download the shared file"):
machine.succeed("c2FmZQ-client -v 3 download 'shared/Rarest Memes/meme.txt'")
machine.succeed("c2FmZQ-client -v 3 export 'shared/Rarest Memes/meme.txt' .")
msg = machine.succeed("cat meme.txt")
assert "pls do not steal\n" == msg, f"File content is not the same:\n{msg}"
with subtest("Test that PWA is served"):
msg = machine.succeed("curl -sSfL http://localhost:8080")
assert "c2FmZQ" in msg, f"Could not find 'c2FmZQ' in the output:\n{msg}"
with subtest("Test that PWA is served"):
msg = machine.succeed("curl -sSfL http://localhost:8080")
assert "c2FmZQ" in msg, f"Could not find 'c2FmZQ' in the output:\n{msg}"
with subtest("A setting with false value is properly passed"):
machine.succeed("systemctl show -p ExecStart --value c2fmzq-server.service | grep -F -- '--licenses=false'");
'';
}
)
with subtest("A setting with false value is properly passed"):
machine.succeed("systemctl show -p ExecStart --value c2fmzq-server.service | grep -F -- '--licenses=false'");
'';
}
+34 -36
View File
@@ -1,44 +1,42 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{ pkgs, ... }:
{
name = "cage";
meta = with pkgs.lib.maintainers; {
maintainers = [ matthewbauer ];
};
{
name = "cage";
meta = with pkgs.lib.maintainers; {
maintainers = [ matthewbauer ];
};
nodes.machine =
{ ... }:
nodes.machine =
{ ... }:
{
imports = [ ./common/user-account.nix ];
{
imports = [ ./common/user-account.nix ];
fonts.packages = with pkgs; [ dejavu_fonts ];
fonts.packages = with pkgs; [ dejavu_fonts ];
services.cage = {
enable = true;
user = "alice";
program = "${pkgs.xterm}/bin/xterm";
};
# Need to switch to a different GPU driver than the default one (-vga std) so that Cage can launch:
virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
services.cage = {
enable = true;
user = "alice";
program = "${pkgs.xterm}/bin/xterm";
};
enableOCR = true;
# Need to switch to a different GPU driver than the default one (-vga std) so that Cage can launch:
virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
};
testScript =
{ nodes, ... }:
let
user = nodes.machine.config.users.users.alice;
in
''
with subtest("Wait for cage to boot up"):
start_all()
machine.wait_for_file("/run/user/${toString user.uid}/wayland-0.lock")
machine.wait_until_succeeds("pgrep xterm")
machine.wait_for_text("alice@machine")
machine.screenshot("screen")
'';
}
)
enableOCR = true;
testScript =
{ nodes, ... }:
let
user = nodes.machine.config.users.users.alice;
in
''
with subtest("Wait for cage to boot up"):
start_all()
machine.wait_for_file("/run/user/${toString user.uid}/wayland-0.lock")
machine.wait_until_succeeds("pgrep xterm")
machine.wait_for_text("alice@machine")
machine.screenshot("screen")
'';
}
+64 -66
View File
@@ -1,72 +1,70 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{ pkgs, lib, ... }:
let
cagebreakConfigfile = pkgs.writeText "config" ''
workspaces 1
escape C-t
bind t exec env DISPLAY=:0 ${pkgs.xterm}/bin/xterm -cm -pc
'';
in
{
name = "cagebreak";
meta = with pkgs.lib.maintainers; {
maintainers = [ berbiche ];
let
cagebreakConfigfile = pkgs.writeText "config" ''
workspaces 1
escape C-t
bind t exec env DISPLAY=:0 ${pkgs.xterm}/bin/xterm -cm -pc
'';
in
{
name = "cagebreak";
meta = with pkgs.lib.maintainers; {
maintainers = [ berbiche ];
};
nodes.machine =
{ config, ... }:
{
# Automatically login on tty1 as a normal user:
imports = [ ./common/user-account.nix ];
services.getty.autologinUser = "alice";
programs.bash.loginShellInit = ''
if [ "$(tty)" = "/dev/tty1" ]; then
set -e
mkdir -p ~/.config/cagebreak
cp -f ${cagebreakConfigfile} ~/.config/cagebreak/config
cagebreak
fi
'';
hardware.graphics.enable = true;
programs.xwayland.enable = true;
security.polkit.enable = true;
environment.systemPackages = [
pkgs.cagebreak
pkgs.wayland-utils
];
# Need to switch to a different GPU driver than the default one (-vga std) so that Cagebreak can launch:
virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
};
nodes.machine =
{ config, ... }:
{
# Automatically login on tty1 as a normal user:
imports = [ ./common/user-account.nix ];
services.getty.autologinUser = "alice";
programs.bash.loginShellInit = ''
if [ "$(tty)" = "/dev/tty1" ]; then
set -e
enableOCR = true;
mkdir -p ~/.config/cagebreak
cp -f ${cagebreakConfigfile} ~/.config/cagebreak/config
testScript =
{ nodes, ... }:
let
user = nodes.machine.config.users.users.alice;
XDG_RUNTIME_DIR = "/run/user/${toString user.uid}";
in
''
start_all()
machine.wait_for_unit("multi-user.target")
machine.wait_for_file("${XDG_RUNTIME_DIR}/wayland-0")
cagebreak
fi
'';
with subtest("ensure wayland works with wayinfo from wallutils"):
print(machine.succeed("env XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR} wayland-info"))
hardware.graphics.enable = true;
programs.xwayland.enable = true;
security.polkit.enable = true;
environment.systemPackages = [
pkgs.cagebreak
pkgs.wayland-utils
];
# Need to switch to a different GPU driver than the default one (-vga std) so that Cagebreak can launch:
virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
};
enableOCR = true;
testScript =
{ nodes, ... }:
let
user = nodes.machine.config.users.users.alice;
XDG_RUNTIME_DIR = "/run/user/${toString user.uid}";
in
''
start_all()
machine.wait_for_unit("multi-user.target")
machine.wait_for_file("${XDG_RUNTIME_DIR}/wayland-0")
with subtest("ensure wayland works with wayinfo from wallutils"):
print(machine.succeed("env XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR} wayland-info"))
# TODO: Fix the XWayland test (log the cagebreak output to debug):
# with subtest("ensure xwayland works with xterm"):
# machine.send_key("ctrl-t")
# machine.send_key("t")
# machine.wait_until_succeeds("pgrep xterm")
# machine.wait_for_text("${user.name}@machine")
# machine.screenshot("screen")
# machine.send_key("ctrl-d")
'';
}
)
# TODO: Fix the XWayland test (log the cagebreak output to debug):
# with subtest("ensure xwayland works with xterm"):
# machine.send_key("ctrl-t")
# machine.send_key("t")
# machine.wait_until_succeeds("pgrep xterm")
# machine.wait_for_text("${user.name}@machine")
# machine.screenshot("screen")
# machine.send_key("ctrl-d")
'';
}
+53 -55
View File
@@ -1,62 +1,60 @@
import ./make-test-python.nix (
{ pkgs, ... }:
let
certs = import ./common/acme/server/snakeoil-certs.nix;
inherit (certs) domain;
in
{
name = "canaille";
meta.maintainers = with pkgs.lib.maintainers; [ erictapen ];
{ pkgs, ... }:
let
certs = import ./common/acme/server/snakeoil-certs.nix;
inherit (certs) domain;
in
{
name = "canaille";
meta.maintainers = with pkgs.lib.maintainers; [ erictapen ];
nodes.server =
{ pkgs, lib, ... }:
{
services.canaille = {
enable = true;
secretKeyFile = pkgs.writeText "canaille-secret-key" ''
this is not a secret key
'';
settings = {
SERVER_NAME = domain;
};
nodes.server =
{ pkgs, lib, ... }:
{
services.canaille = {
enable = true;
secretKeyFile = pkgs.writeText "canaille-secret-key" ''
this is not a secret key
'';
settings = {
SERVER_NAME = domain;
};
services.nginx.virtualHosts."${domain}" = {
enableACME = lib.mkForce false;
sslCertificate = certs."${domain}".cert;
sslCertificateKey = certs."${domain}".key;
};
networking.hosts."::1" = [ "${domain}" ];
networking.firewall.allowedTCPPorts = [
80
443
];
users.users.canaille.shell = pkgs.bashInteractive;
security.pki.certificateFiles = [ certs.ca.cert ];
};
nodes.client =
{ nodes, ... }:
{
networking.hosts."${nodes.server.networking.primaryIPAddress}" = [ "${domain}" ];
security.pki.certificateFiles = [ certs.ca.cert ];
services.nginx.virtualHosts."${domain}" = {
enableACME = lib.mkForce false;
sslCertificate = certs."${domain}".cert;
sslCertificateKey = certs."${domain}".key;
};
testScript =
{ ... }:
''
import json
networking.hosts."::1" = [ "${domain}" ];
networking.firewall.allowedTCPPorts = [
80
443
];
start_all()
server.wait_for_unit("canaille.socket")
server.wait_until_succeeds("curl -f https://${domain}")
server.succeed("sudo -iu canaille -- canaille create user --user-name admin --password adminpass --emails admin@${domain}")
json_str = server.succeed("sudo -iu canaille -- canaille get user")
assert json.loads(json_str)[0]["user_name"] == "admin"
server.succeed("sudo -iu canaille -- canaille config check")
'';
}
)
users.users.canaille.shell = pkgs.bashInteractive;
security.pki.certificateFiles = [ certs.ca.cert ];
};
nodes.client =
{ nodes, ... }:
{
networking.hosts."${nodes.server.networking.primaryIPAddress}" = [ "${domain}" ];
security.pki.certificateFiles = [ certs.ca.cert ];
};
testScript =
{ ... }:
''
import json
start_all()
server.wait_for_unit("canaille.socket")
server.wait_until_succeeds("curl -f https://${domain}")
server.succeed("sudo -iu canaille -- canaille create user --user-name admin --password adminpass --emails admin@${domain}")
json_str = server.succeed("sudo -iu canaille -- canaille get user")
assert json.loads(json_str)[0]["user_name"] == "admin"
server.succeed("sudo -iu canaille -- canaille config check")
'';
}
+217 -219
View File
@@ -1,250 +1,248 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "castopod";
meta = with lib.maintainers; {
maintainers = [ alexoundos ];
{ pkgs, lib, ... }:
{
name = "castopod";
meta = with lib.maintainers; {
maintainers = [ alexoundos ];
};
nodes.castopod =
{ nodes, ... }:
{
# otherwise 500 MiB file upload fails!
virtualisation.diskSize = 512 + 3 * 512;
networking.firewall.allowedTCPPorts = [ 80 ];
networking.extraHosts = lib.strings.concatStringsSep "\n" (
lib.attrsets.mapAttrsToList (
name: _: "127.0.0.1 ${name}"
) nodes.castopod.services.nginx.virtualHosts
);
services.castopod = {
enable = true;
database.createLocally = true;
localDomain = "castopod.example.com";
maxUploadSize = "512M";
};
};
nodes.castopod =
{ nodes, ... }:
{
# otherwise 500 MiB file upload fails!
virtualisation.diskSize = 512 + 3 * 512;
nodes.client =
{
nodes,
pkgs,
lib,
...
}:
let
domain = nodes.castopod.services.castopod.localDomain;
networking.firewall.allowedTCPPorts = [ 80 ];
networking.extraHosts = lib.strings.concatStringsSep "\n" (
lib.attrsets.mapAttrsToList (
name: _: "127.0.0.1 ${name}"
) nodes.castopod.services.nginx.virtualHosts
);
getIP = node: (builtins.head node.networking.interfaces.eth1.ipv4.addresses).address;
services.castopod = {
enable = true;
database.createLocally = true;
localDomain = "castopod.example.com";
maxUploadSize = "512M";
};
};
targetPodcastSize = 500 * 1024 * 1024;
lameMp3Bitrate = 348300;
lameMp3FileAdjust = -800;
targetPodcastDuration = toString ((targetPodcastSize + lameMp3FileAdjust) / (lameMp3Bitrate / 8));
bannerWidth = 3000;
banner = pkgs.runCommand "gen-castopod-cover.jpg" { } ''
${pkgs.imagemagick}/bin/magick `
`-background green -bordercolor white -gravity northwest xc:black `
`-duplicate 99 `
`-seed 1 -resize "%[fx:rand()*72+24]" `
`-seed 0 -rotate "%[fx:rand()*360]" -border 6x6 -splice 16x36 `
`-seed 0 -rotate "%[fx:floor(rand()*4)*90]" -resize "150x50!" `
`+append -crop 10x1@ +repage -roll "+%[fx:(t%2)*72]+0" -append `
`-resize ${toString bannerWidth} -quality 1 $out
'';
nodes.client =
{
nodes,
pkgs,
lib,
...
}:
let
domain = nodes.castopod.services.castopod.localDomain;
coverWidth = toString 3000;
cover = pkgs.runCommand "gen-castopod-banner.jpg" { } ''
${pkgs.imagemagick}/bin/magick `
`-background white -bordercolor white -gravity northwest xc:black `
`-duplicate 99 `
`-seed 1 -resize "%[fx:rand()*72+24]" `
`-seed 0 -rotate "%[fx:rand()*360]" -border 6x6 -splice 36x36 `
`-seed 0 -rotate "%[fx:floor(rand()*4)*90]" -resize "144x144!" `
`+append -crop 10x1@ +repage -roll "+%[fx:(t%2)*72]+0" -append `
`-resize ${coverWidth} -quality 1 $out
'';
in
{
networking.extraHosts = lib.strings.concatStringsSep "\n" (
lib.attrsets.mapAttrsToList (
name: _: "${getIP nodes.castopod} ${name}"
) nodes.castopod.services.nginx.virtualHosts
);
getIP = node: (builtins.head node.networking.interfaces.eth1.ipv4.addresses).address;
environment.systemPackages =
let
username = "admin";
email = "admin@${domain}";
password = "Abcd1234";
podcastTitle = "Some Title";
episodeTitle = "Episode Title";
browser-test =
pkgs.writers.writePython3Bin "browser-test"
{
libraries = [ pkgs.python3Packages.selenium ];
flakeIgnore = [
"E124"
"E501"
];
}
''
from selenium.webdriver.common.by import By
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from subprocess import STDOUT
import logging
targetPodcastSize = 500 * 1024 * 1024;
lameMp3Bitrate = 348300;
lameMp3FileAdjust = -800;
targetPodcastDuration = toString ((targetPodcastSize + lameMp3FileAdjust) / (lameMp3Bitrate / 8));
bannerWidth = 3000;
banner = pkgs.runCommand "gen-castopod-cover.jpg" { } ''
${pkgs.imagemagick}/bin/magick `
`-background green -bordercolor white -gravity northwest xc:black `
`-duplicate 99 `
`-seed 1 -resize "%[fx:rand()*72+24]" `
`-seed 0 -rotate "%[fx:rand()*360]" -border 6x6 -splice 16x36 `
`-seed 0 -rotate "%[fx:floor(rand()*4)*90]" -resize "150x50!" `
`+append -crop 10x1@ +repage -roll "+%[fx:(t%2)*72]+0" -append `
`-resize ${toString bannerWidth} -quality 1 $out
'';
selenium_logger = logging.getLogger("selenium")
selenium_logger.setLevel(logging.DEBUG)
selenium_logger.addHandler(logging.StreamHandler())
coverWidth = toString 3000;
cover = pkgs.runCommand "gen-castopod-banner.jpg" { } ''
${pkgs.imagemagick}/bin/magick `
`-background white -bordercolor white -gravity northwest xc:black `
`-duplicate 99 `
`-seed 1 -resize "%[fx:rand()*72+24]" `
`-seed 0 -rotate "%[fx:rand()*360]" -border 6x6 -splice 36x36 `
`-seed 0 -rotate "%[fx:floor(rand()*4)*90]" -resize "144x144!" `
`+append -crop 10x1@ +repage -roll "+%[fx:(t%2)*72]+0" -append `
`-resize ${coverWidth} -quality 1 $out
'';
in
{
networking.extraHosts = lib.strings.concatStringsSep "\n" (
lib.attrsets.mapAttrsToList (
name: _: "${getIP nodes.castopod} ${name}"
) nodes.castopod.services.nginx.virtualHosts
);
options = Options()
options.add_argument('--headless')
service = Service(log_output=STDOUT)
driver = Firefox(options=options, service=service)
driver = Firefox(options=options)
driver.implicitly_wait(30)
driver.set_page_load_timeout(60)
environment.systemPackages =
let
username = "admin";
email = "admin@${domain}";
password = "Abcd1234";
podcastTitle = "Some Title";
episodeTitle = "Episode Title";
browser-test =
pkgs.writers.writePython3Bin "browser-test"
{
libraries = [ pkgs.python3Packages.selenium ];
flakeIgnore = [
"E124"
"E501"
];
}
''
from selenium.webdriver.common.by import By
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from subprocess import STDOUT
import logging
# install ##########################################################
selenium_logger = logging.getLogger("selenium")
selenium_logger.setLevel(logging.DEBUG)
selenium_logger.addHandler(logging.StreamHandler())
driver.get('http://${domain}/cp-install')
options = Options()
options.add_argument('--headless')
service = Service(log_output=STDOUT)
driver = Firefox(options=options, service=service)
driver = Firefox(options=options)
driver.implicitly_wait(30)
driver.set_page_load_timeout(60)
wait = WebDriverWait(driver, 20)
# install ##########################################################
wait.until(EC.title_contains("installer"))
driver.get('http://${domain}/cp-install')
driver.find_element(By.CSS_SELECTOR, '#username').send_keys(
'${username}'
)
driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
'${email}'
)
driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
'${password}'
)
driver.find_element(By.XPATH,
"//button[contains(., 'Finish install')]"
).click()
wait = WebDriverWait(driver, 20)
wait.until(EC.title_contains("Auth"))
wait.until(EC.title_contains("installer"))
driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
'${email}'
)
driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
'${password}'
)
driver.find_element(By.XPATH,
"//button[contains(., 'Login')]"
).click()
driver.find_element(By.CSS_SELECTOR, '#username').send_keys(
'${username}'
)
driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
'${email}'
)
driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
'${password}'
)
driver.find_element(By.XPATH,
"//button[contains(., 'Finish install')]"
).click()
wait.until(EC.title_contains("Admin dashboard"))
wait.until(EC.title_contains("Auth"))
# create podcast ###################################################
driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
'${email}'
)
driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
'${password}'
)
driver.find_element(By.XPATH,
"//button[contains(., 'Login')]"
).click()
driver.get('http://${domain}/admin/podcasts/new')
wait.until(EC.title_contains("Admin dashboard"))
wait.until(EC.title_contains("Create podcast"))
# create podcast ###################################################
driver.find_element(By.CSS_SELECTOR, '#cover').send_keys(
'${cover}'
)
driver.find_element(By.CSS_SELECTOR, '#banner').send_keys(
'${banner}'
)
driver.find_element(By.CSS_SELECTOR, '#title').send_keys(
'${podcastTitle}'
)
driver.find_element(By.CSS_SELECTOR, '#handle').send_keys(
'some_handle'
)
driver.find_element(By.CSS_SELECTOR, '#description').send_keys(
'Some description'
)
driver.find_element(By.CSS_SELECTOR, '#owner_name').send_keys(
'Owner Name'
)
driver.find_element(By.CSS_SELECTOR, '#owner_email').send_keys(
'owner@email.xyz'
)
driver.find_element(By.XPATH,
"//button[contains(., 'Create podcast')]"
).click()
driver.get('http://${domain}/admin/podcasts/new')
wait.until(EC.title_contains("${podcastTitle}"))
wait.until(EC.title_contains("Create podcast"))
driver.find_element(By.XPATH,
"//span[contains(., 'Add an episode')]"
).click()
driver.find_element(By.CSS_SELECTOR, '#cover').send_keys(
'${cover}'
)
driver.find_element(By.CSS_SELECTOR, '#banner').send_keys(
'${banner}'
)
driver.find_element(By.CSS_SELECTOR, '#title').send_keys(
'${podcastTitle}'
)
driver.find_element(By.CSS_SELECTOR, '#handle').send_keys(
'some_handle'
)
driver.find_element(By.CSS_SELECTOR, '#description').send_keys(
'Some description'
)
driver.find_element(By.CSS_SELECTOR, '#owner_name').send_keys(
'Owner Name'
)
driver.find_element(By.CSS_SELECTOR, '#owner_email').send_keys(
'owner@email.xyz'
)
driver.find_element(By.XPATH,
"//button[contains(., 'Create podcast')]"
).click()
wait.until(EC.title_contains("Add an episode"))
wait.until(EC.title_contains("${podcastTitle}"))
# upload podcast ###################################################
driver.find_element(By.XPATH,
"//span[contains(., 'Add an episode')]"
).click()
driver.find_element(By.CSS_SELECTOR, '#audio_file').send_keys(
'/tmp/podcast.mp3'
)
driver.find_element(By.CSS_SELECTOR, '#cover').send_keys(
'${cover}'
)
driver.find_element(By.CSS_SELECTOR, '#description').send_keys(
'Episode description'
)
driver.find_element(By.CSS_SELECTOR, '#title').send_keys(
'${episodeTitle}'
)
driver.find_element(By.XPATH,
"//button[contains(., 'Create episode')]"
).click()
wait.until(EC.title_contains("Add an episode"))
wait.until(EC.title_contains("${episodeTitle}"))
# upload podcast ###################################################
driver.find_element(By.CSS_SELECTOR, '#audio_file').send_keys(
'/tmp/podcast.mp3'
)
driver.find_element(By.CSS_SELECTOR, '#cover').send_keys(
'${cover}'
)
driver.find_element(By.CSS_SELECTOR, '#description').send_keys(
'Episode description'
)
driver.find_element(By.CSS_SELECTOR, '#title').send_keys(
'${episodeTitle}'
)
driver.find_element(By.XPATH,
"//button[contains(., 'Create episode')]"
).click()
wait.until(EC.title_contains("${episodeTitle}"))
driver.close()
driver.quit()
'';
in
[
pkgs.firefox-unwrapped
pkgs.geckodriver
browser-test
(pkgs.writeShellApplication {
name = "build-mp3";
runtimeInputs = with pkgs; [
sox
lame
];
text = ''
out=/tmp/podcast.mp3
sox -n -r 48000 -t wav - synth ${targetPodcastDuration} sine 440 `
`| lame --noreplaygain --cbr -q 9 -b 320 - $out
FILESIZE="$(stat -c%s $out)"
[ "$FILESIZE" -gt 0 ]
[ "$FILESIZE" -le "${toString targetPodcastSize}" ]
driver.close()
driver.quit()
'';
})
];
};
in
[
pkgs.firefox-unwrapped
pkgs.geckodriver
browser-test
(pkgs.writeShellApplication {
name = "build-mp3";
runtimeInputs = with pkgs; [
sox
lame
];
text = ''
out=/tmp/podcast.mp3
sox -n -r 48000 -t wav - synth ${targetPodcastDuration} sine 440 `
`| lame --noreplaygain --cbr -q 9 -b 320 - $out
FILESIZE="$(stat -c%s $out)"
[ "$FILESIZE" -gt 0 ]
[ "$FILESIZE" -le "${toString targetPodcastSize}" ]
'';
})
];
};
testScript = ''
start_all()
castopod.wait_for_unit("castopod-setup.service")
castopod.wait_for_file("/run/phpfpm/castopod.sock")
castopod.wait_for_unit("nginx.service")
castopod.wait_for_open_port(80)
castopod.wait_until_succeeds("curl -sS -f http://castopod.example.com")
testScript = ''
start_all()
castopod.wait_for_unit("castopod-setup.service")
castopod.wait_for_file("/run/phpfpm/castopod.sock")
castopod.wait_for_unit("nginx.service")
castopod.wait_for_open_port(80)
castopod.wait_until_succeeds("curl -sS -f http://castopod.example.com")
client.succeed("build-mp3")
client.succeed("build-mp3")
with subtest("Create superadmin, log in, create and upload a podcast"):
client.succeed(\
"PYTHONUNBUFFERED=1 systemd-cat -t browser-test browser-test")
'';
}
)
with subtest("Create superadmin, log in, create and upload a podcast"):
client.succeed(\
"PYTHONUNBUFFERED=1 systemd-cat -t browser-test browser-test")
'';
}
+40 -42
View File
@@ -1,49 +1,47 @@
# This test checks charliecloud image construction and run
import ./make-test-python.nix (
{ pkgs, ... }:
let
{ pkgs, ... }:
let
dockerfile = pkgs.writeText "Dockerfile" ''
FROM nix
RUN mkdir /home /tmp
RUN touch /etc/passwd /etc/group
CMD ["true"]
'';
dockerfile = pkgs.writeText "Dockerfile" ''
FROM nix
RUN mkdir /home /tmp
RUN touch /etc/passwd /etc/group
CMD ["true"]
'';
in
{
name = "charliecloud";
meta = with pkgs.lib.maintainers; {
maintainers = [ bzizou ];
};
in
{
name = "charliecloud";
meta = with pkgs.lib.maintainers; {
maintainers = [ bzizou ];
};
nodes = {
host =
{ ... }:
{
environment.systemPackages = [ pkgs.charliecloud ];
virtualisation.docker.enable = true;
users.users.alice = {
isNormalUser = true;
extraGroups = [ "docker" ];
};
nodes = {
host =
{ ... }:
{
environment.systemPackages = [ pkgs.charliecloud ];
virtualisation.docker.enable = true;
users.users.alice = {
isNormalUser = true;
extraGroups = [ "docker" ];
};
};
};
};
testScript = ''
host.start()
host.wait_for_unit("docker.service")
host.succeed(
'su - alice -c "docker load --input=${pkgs.dockerTools.examples.nix}"'
)
host.succeed(
"cp ${dockerfile} /home/alice/Dockerfile"
)
host.succeed('su - alice -c "ch-build -t hello ."')
host.succeed('su - alice -c "ch-builder2tar hello /var/tmp"')
host.succeed('su - alice -c "ch-tar2dir /var/tmp/hello.tar.gz /var/tmp"')
host.succeed('su - alice -c "ch-run /var/tmp/hello -- echo Running_From_Container_OK"')
'';
}
)
testScript = ''
host.start()
host.wait_for_unit("docker.service")
host.succeed(
'su - alice -c "docker load --input=${pkgs.dockerTools.examples.nix}"'
)
host.succeed(
"cp ${dockerfile} /home/alice/Dockerfile"
)
host.succeed('su - alice -c "ch-build -t hello ."')
host.succeed('su - alice -c "ch-builder2tar hello /var/tmp"')
host.succeed('su - alice -c "ch-tar2dir /var/tmp/hello.tar.gz /var/tmp"')
host.succeed('su - alice -c "ch-run /var/tmp/hello -- echo Running_From_Container_OK"')
'';
}
+67 -69
View File
@@ -1,84 +1,82 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "cinnamon-wayland";
{ pkgs, lib, ... }:
{
name = "cinnamon-wayland";
meta.maintainers = lib.teams.cinnamon.members;
meta.maintainers = lib.teams.cinnamon.members;
nodes.machine =
{ nodes, ... }:
{
imports = [ ./common/user-account.nix ];
services.xserver.enable = true;
services.xserver.desktopManager.cinnamon.enable = true;
services.displayManager = {
autoLogin.enable = true;
autoLogin.user = nodes.machine.users.users.alice.name;
defaultSession = "cinnamon-wayland";
};
# For the sessionPath subtest.
services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gpaste ];
nodes.machine =
{ nodes, ... }:
{
imports = [ ./common/user-account.nix ];
services.xserver.enable = true;
services.xserver.desktopManager.cinnamon.enable = true;
services.displayManager = {
autoLogin.enable = true;
autoLogin.user = nodes.machine.users.users.alice.name;
defaultSession = "cinnamon-wayland";
};
enableOCR = true;
# For the sessionPath subtest.
services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gpaste ];
};
testScript =
{ nodes, ... }:
let
user = nodes.machine.users.users.alice;
env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
su = command: "su - ${user.name} -c '${env} ${command}'";
enableOCR = true;
# Call javascript in cinnamon (the shell), returns a tuple (success, output),
# where `success` is true if the dbus call was successful and `output` is what
# the javascript evaluates to.
eval =
name: su "gdbus call --session -d org.Cinnamon -o /org/Cinnamon -m org.Cinnamon.Eval ${name}";
in
''
machine.wait_for_unit("display-manager.service")
testScript =
{ nodes, ... }:
let
user = nodes.machine.users.users.alice;
env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
su = command: "su - ${user.name} -c '${env} ${command}'";
with subtest("Wait for wayland server"):
machine.wait_for_file("/run/user/${toString user.uid}/wayland-0")
# Call javascript in cinnamon (the shell), returns a tuple (success, output),
# where `success` is true if the dbus call was successful and `output` is what
# the javascript evaluates to.
eval =
name: su "gdbus call --session -d org.Cinnamon -o /org/Cinnamon -m org.Cinnamon.Eval ${name}";
in
''
machine.wait_for_unit("display-manager.service")
with subtest("Check that logging in has given the user ownership of devices"):
machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
with subtest("Wait for wayland server"):
machine.wait_for_file("/run/user/${toString user.uid}/wayland-0")
with subtest("Wait for the Cinnamon shell"):
# Correct output should be (true, '2')
# https://github.com/linuxmint/cinnamon/blob/5.4.0/js/ui/main.js#L183-L187
machine.wait_until_succeeds("${eval "Main.runState"} | grep -q 'true,..2'")
with subtest("Check that logging in has given the user ownership of devices"):
machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
with subtest("Check if Cinnamon components actually start"):
for i in ["csd-media-keys", "xapp-sn-watcher", "nemo-desktop"]:
machine.wait_until_succeeds(f"pgrep -f {i}")
machine.wait_until_succeeds("journalctl -b --grep 'Loaded applet menu@cinnamon.org'")
machine.wait_until_succeeds("journalctl -b --grep 'calendar@cinnamon.org: Calendar events supported'")
with subtest("Wait for the Cinnamon shell"):
# Correct output should be (true, '2')
# https://github.com/linuxmint/cinnamon/blob/5.4.0/js/ui/main.js#L183-L187
machine.wait_until_succeeds("${eval "Main.runState"} | grep -q 'true,..2'")
with subtest("Check if sessionPath option actually works"):
machine.succeed("${eval "imports.gi.GIRepository.Repository.get_search_path\\(\\)"} | grep gpaste")
with subtest("Check if Cinnamon components actually start"):
for i in ["csd-media-keys", "xapp-sn-watcher", "nemo-desktop"]:
machine.wait_until_succeeds(f"pgrep -f {i}")
machine.wait_until_succeeds("journalctl -b --grep 'Loaded applet menu@cinnamon.org'")
machine.wait_until_succeeds("journalctl -b --grep 'calendar@cinnamon.org: Calendar events supported'")
with subtest("Open Cinnamon Settings"):
machine.succeed("${su "cinnamon-settings themes >&2 &"}")
machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'cinnamon-settings'")
machine.wait_for_text('(Style|Appearance|Color)')
machine.sleep(2)
machine.screenshot("cinnamon_settings")
with subtest("Check if sessionPath option actually works"):
machine.succeed("${eval "imports.gi.GIRepository.Repository.get_search_path\\(\\)"} | grep gpaste")
with subtest("Check if screensaver works"):
# This is not supported at the moment.
# https://trello.com/b/HHs01Pab/cinnamon-wayland
machine.execute("${su "cinnamon-screensaver-command -l >&2 &"}")
machine.wait_until_succeeds("journalctl -b --grep 'cinnamon-screensaver is disabled in wayland sessions'")
with subtest("Open Cinnamon Settings"):
machine.succeed("${su "cinnamon-settings themes >&2 &"}")
machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'cinnamon-settings'")
machine.wait_for_text('(Style|Appearance|Color)')
machine.sleep(2)
machine.screenshot("cinnamon_settings")
with subtest("Open GNOME Terminal"):
machine.succeed("${su "dbus-launch gnome-terminal"}")
machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'gnome-terminal'")
machine.sleep(2)
with subtest("Check if screensaver works"):
# This is not supported at the moment.
# https://trello.com/b/HHs01Pab/cinnamon-wayland
machine.execute("${su "cinnamon-screensaver-command -l >&2 &"}")
machine.wait_until_succeeds("journalctl -b --grep 'cinnamon-screensaver is disabled in wayland sessions'")
with subtest("Check if Cinnamon has ever coredumped"):
machine.fail("coredumpctl --json=short | grep -E 'cinnamon|nemo'")
'';
}
)
with subtest("Open GNOME Terminal"):
machine.succeed("${su "dbus-launch gnome-terminal"}")
machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'gnome-terminal'")
machine.sleep(2)
with subtest("Check if Cinnamon has ever coredumped"):
machine.fail("coredumpctl --json=short | grep -E 'cinnamon|nemo'")
'';
}
+96 -98
View File
@@ -1,104 +1,102 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "cinnamon";
{ pkgs, lib, ... }:
{
name = "cinnamon";
meta.maintainers = lib.teams.cinnamon.members;
meta.maintainers = lib.teams.cinnamon.members;
nodes.machine =
{ ... }:
{
imports = [ ./common/user-account.nix ];
services.xserver.enable = true;
services.xserver.desktopManager.cinnamon.enable = true;
nodes.machine =
{ ... }:
{
imports = [ ./common/user-account.nix ];
services.xserver.enable = true;
services.xserver.desktopManager.cinnamon.enable = true;
# We don't ship gnome-text-editor in Cinnamon module, we add this line mainly
# to catch eval issues related to this option.
environment.cinnamon.excludePackages = [ pkgs.gnome-text-editor ];
# We don't ship gnome-text-editor in Cinnamon module, we add this line mainly
# to catch eval issues related to this option.
environment.cinnamon.excludePackages = [ pkgs.gnome-text-editor ];
# For the sessionPath subtest.
services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gpaste ];
# For the sessionPath subtest.
services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gpaste ];
# For OCR test.
services.xserver.displayManager.lightdm.greeters.slick.extraConfig = ''
enable-hidpi = on
'';
};
enableOCR = true;
testScript =
{ nodes, ... }:
let
user = nodes.machine.users.users.alice;
env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus DISPLAY=:0";
su = command: "su - ${user.name} -c '${env} ${command}'";
# Call javascript in cinnamon (the shell), returns a tuple (success, output),
# where `success` is true if the dbus call was successful and `output` is what
# the javascript evaluates to.
eval =
name: su "gdbus call --session -d org.Cinnamon -o /org/Cinnamon -m org.Cinnamon.Eval ${name}";
in
''
machine.wait_for_unit("display-manager.service")
with subtest("Test if we can see username in slick-greeter"):
machine.wait_for_text("${user.description}")
machine.screenshot("slick_greeter_lightdm")
with subtest("Login with slick-greeter"):
machine.send_chars("${user.password}\n")
machine.wait_for_x()
machine.wait_for_file("${user.home}/.Xauthority")
machine.succeed("xauth merge ${user.home}/.Xauthority")
with subtest("Check that logging in has given the user ownership of devices"):
machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
with subtest("Wait for the Cinnamon shell"):
# Correct output should be (true, '2')
# https://github.com/linuxmint/cinnamon/blob/5.4.0/js/ui/main.js#L183-L187
machine.wait_until_succeeds("${eval "Main.runState"} | grep -q 'true,..2'")
with subtest("Check if Cinnamon components actually start"):
for i in ["csd-media-keys", "cinnamon-killer-daemon", "xapp-sn-watcher", "nemo-desktop"]:
machine.wait_until_succeeds(f"pgrep -f {i}")
machine.wait_until_succeeds("journalctl -b --grep 'Loaded applet menu@cinnamon.org'")
machine.wait_until_succeeds("journalctl -b --grep 'calendar@cinnamon.org: Calendar events supported'")
with subtest("Check if sessionPath option actually works"):
machine.succeed("${eval "imports.gi.GIRepository.Repository.get_search_path\\(\\)"} | grep gpaste")
with subtest("Open Cinnamon Settings"):
machine.succeed("${su "cinnamon-settings themes >&2 &"}")
machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'cinnamon-settings'")
machine.wait_for_text('(Style|Appearance|Color)')
machine.sleep(2)
machine.screenshot("cinnamon_settings")
with subtest("Lock the screen"):
machine.succeed("${su "cinnamon-screensaver-command -l >&2 &"}")
machine.wait_until_succeeds("${su "cinnamon-screensaver-command -q"} | grep 'The screensaver is active'")
machine.sleep(2)
machine.screenshot("cinnamon_screensaver")
machine.send_chars("${user.password}\n", delay=0.2)
machine.wait_until_succeeds("${su "cinnamon-screensaver-command -q"} | grep 'The screensaver is inactive'")
machine.sleep(2)
with subtest("Open GNOME Terminal"):
machine.succeed("${su "gnome-terminal"}")
machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'gnome-terminal'")
machine.sleep(2)
with subtest("Open virtual keyboard"):
machine.succeed("${su "dbus-send --print-reply --dest=org.Cinnamon /org/Cinnamon org.Cinnamon.ToggleKeyboard"}")
machine.wait_for_text('(Ctrl|Alt)')
machine.sleep(2)
machine.screenshot("cinnamon_virtual_keyboard")
with subtest("Check if Cinnamon has ever coredumped"):
machine.fail("coredumpctl --json=short | grep -E 'cinnamon|nemo'")
# For OCR test.
services.xserver.displayManager.lightdm.greeters.slick.extraConfig = ''
enable-hidpi = on
'';
}
)
};
enableOCR = true;
testScript =
{ nodes, ... }:
let
user = nodes.machine.users.users.alice;
env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus DISPLAY=:0";
su = command: "su - ${user.name} -c '${env} ${command}'";
# Call javascript in cinnamon (the shell), returns a tuple (success, output),
# where `success` is true if the dbus call was successful and `output` is what
# the javascript evaluates to.
eval =
name: su "gdbus call --session -d org.Cinnamon -o /org/Cinnamon -m org.Cinnamon.Eval ${name}";
in
''
machine.wait_for_unit("display-manager.service")
with subtest("Test if we can see username in slick-greeter"):
machine.wait_for_text("${user.description}")
machine.screenshot("slick_greeter_lightdm")
with subtest("Login with slick-greeter"):
machine.send_chars("${user.password}\n")
machine.wait_for_x()
machine.wait_for_file("${user.home}/.Xauthority")
machine.succeed("xauth merge ${user.home}/.Xauthority")
with subtest("Check that logging in has given the user ownership of devices"):
machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
with subtest("Wait for the Cinnamon shell"):
# Correct output should be (true, '2')
# https://github.com/linuxmint/cinnamon/blob/5.4.0/js/ui/main.js#L183-L187
machine.wait_until_succeeds("${eval "Main.runState"} | grep -q 'true,..2'")
with subtest("Check if Cinnamon components actually start"):
for i in ["csd-media-keys", "cinnamon-killer-daemon", "xapp-sn-watcher", "nemo-desktop"]:
machine.wait_until_succeeds(f"pgrep -f {i}")
machine.wait_until_succeeds("journalctl -b --grep 'Loaded applet menu@cinnamon.org'")
machine.wait_until_succeeds("journalctl -b --grep 'calendar@cinnamon.org: Calendar events supported'")
with subtest("Check if sessionPath option actually works"):
machine.succeed("${eval "imports.gi.GIRepository.Repository.get_search_path\\(\\)"} | grep gpaste")
with subtest("Open Cinnamon Settings"):
machine.succeed("${su "cinnamon-settings themes >&2 &"}")
machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'cinnamon-settings'")
machine.wait_for_text('(Style|Appearance|Color)')
machine.sleep(2)
machine.screenshot("cinnamon_settings")
with subtest("Lock the screen"):
machine.succeed("${su "cinnamon-screensaver-command -l >&2 &"}")
machine.wait_until_succeeds("${su "cinnamon-screensaver-command -q"} | grep 'The screensaver is active'")
machine.sleep(2)
machine.screenshot("cinnamon_screensaver")
machine.send_chars("${user.password}\n", delay=0.2)
machine.wait_until_succeeds("${su "cinnamon-screensaver-command -q"} | grep 'The screensaver is inactive'")
machine.sleep(2)
with subtest("Open GNOME Terminal"):
machine.succeed("${su "gnome-terminal"}")
machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'gnome-terminal'")
machine.sleep(2)
with subtest("Open virtual keyboard"):
machine.succeed("${su "dbus-send --print-reply --dest=org.Cinnamon /org/Cinnamon org.Cinnamon.ToggleKeyboard"}")
machine.wait_for_text('(Ctrl|Alt)')
machine.sleep(2)
machine.screenshot("cinnamon_virtual_keyboard")
with subtest("Check if Cinnamon has ever coredumped"):
machine.fail("coredumpctl --json=short | grep -E 'cinnamon|nemo'")
'';
}
+83 -85
View File
@@ -18,117 +18,115 @@ let
in
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "cjdns";
meta = with pkgs.lib.maintainers; {
maintainers = [ ehmry ];
};
{ pkgs, ... }:
{
name = "cjdns";
meta = with pkgs.lib.maintainers; {
maintainers = [ ehmry ];
};
nodes = {
# Alice finds peers over over ETHInterface.
alice =
{ ... }:
{
imports = [ basicConfig ];
nodes = {
# Alice finds peers over over ETHInterface.
alice =
{ ... }:
{
imports = [ basicConfig ];
services.cjdns.ETHInterface.bind = "eth1";
services.cjdns.ETHInterface.bind = "eth1";
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
networking.firewall.allowedTCPPorts = [ 80 ];
};
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
networking.firewall.allowedTCPPorts = [ 80 ];
};
# Bob explicitly connects to Carol over UDPInterface.
bob =
{ ... }:
# Bob explicitly connects to Carol over UDPInterface.
bob =
{ ... }:
{
imports = [ basicConfig ];
{
imports = [ basicConfig ];
networking.interfaces.eth1.ipv4.addresses = [
{
address = "192.168.0.2";
prefixLength = 24;
}
];
networking.interfaces.eth1.ipv4.addresses = [
{
address = "192.168.0.2";
prefixLength = 24;
}
];
services.cjdns = {
UDPInterface = {
bind = "0.0.0.0:1024";
connectTo."192.168.0.1:1024" = {
password = carolPassword;
publicKey = carolPubKey;
};
services.cjdns = {
UDPInterface = {
bind = "0.0.0.0:1024";
connectTo."192.168.0.1:1024" = {
password = carolPassword;
publicKey = carolPubKey;
};
};
};
};
# Carol listens on ETHInterface and UDPInterface,
# but knows neither Alice or Bob.
carol =
{ ... }:
{
imports = [ basicConfig ];
# Carol listens on ETHInterface and UDPInterface,
# but knows neither Alice or Bob.
carol =
{ ... }:
{
imports = [ basicConfig ];
environment.etc."cjdns.keys".text = ''
CJDNS_PRIVATE_KEY=${carolKey}
CJDNS_ADMIN_PASSWORD=FOOBAR
'';
environment.etc."cjdns.keys".text = ''
CJDNS_PRIVATE_KEY=${carolKey}
CJDNS_ADMIN_PASSWORD=FOOBAR
'';
networking.interfaces.eth1.ipv4.addresses = [
{
address = "192.168.0.1";
prefixLength = 24;
}
];
networking.interfaces.eth1.ipv4.addresses = [
{
address = "192.168.0.1";
prefixLength = 24;
}
];
services.cjdns = {
authorizedPasswords = [ carolPassword ];
ETHInterface.bind = "eth1";
UDPInterface.bind = "192.168.0.1:1024";
};
networking.firewall.allowedUDPPorts = [ 1024 ];
services.cjdns = {
authorizedPasswords = [ carolPassword ];
ETHInterface.bind = "eth1";
UDPInterface.bind = "192.168.0.1:1024";
};
networking.firewall.allowedUDPPorts = [ 1024 ];
};
};
};
testScript = ''
import re
testScript = ''
import re
start_all()
start_all()
alice.wait_for_unit("cjdns.service")
bob.wait_for_unit("cjdns.service")
carol.wait_for_unit("cjdns.service")
alice.wait_for_unit("cjdns.service")
bob.wait_for_unit("cjdns.service")
carol.wait_for_unit("cjdns.service")
def cjdns_ip(machine):
res = machine.succeed("ip -o -6 addr show dev tun0")
ip = re.split("\s+|/", res)[3]
machine.log("has ip {}".format(ip))
return ip
def cjdns_ip(machine):
res = machine.succeed("ip -o -6 addr show dev tun0")
ip = re.split("\s+|/", res)[3]
machine.log("has ip {}".format(ip))
return ip
alice_ip6 = cjdns_ip(alice)
bob_ip6 = cjdns_ip(bob)
carol_ip6 = cjdns_ip(carol)
alice_ip6 = cjdns_ip(alice)
bob_ip6 = cjdns_ip(bob)
carol_ip6 = cjdns_ip(carol)
# ping a few times each to let the routing table establish itself
# ping a few times each to let the routing table establish itself
alice.succeed("ping -c 4 {}".format(carol_ip6))
bob.succeed("ping -c 4 {}".format(carol_ip6))
alice.succeed("ping -c 4 {}".format(carol_ip6))
bob.succeed("ping -c 4 {}".format(carol_ip6))
carol.succeed("ping -c 4 {}".format(alice_ip6))
carol.succeed("ping -c 4 {}".format(bob_ip6))
carol.succeed("ping -c 4 {}".format(alice_ip6))
carol.succeed("ping -c 4 {}".format(bob_ip6))
alice.succeed("ping -c 4 {}".format(bob_ip6))
bob.succeed("ping -c 4 {}".format(alice_ip6))
alice.succeed("ping -c 4 {}".format(bob_ip6))
bob.succeed("ping -c 4 {}".format(alice_ip6))
alice.wait_for_unit("httpd.service")
alice.wait_for_unit("httpd.service")
bob.succeed("curl --fail -g http://[{}]".format(alice_ip6))
'';
}
)
bob.succeed("curl --fail -g http://[{}]".format(alice_ip6))
'';
}
+30 -32
View File
@@ -1,35 +1,33 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "clickhouse";
meta.maintainers = with pkgs.lib.maintainers; [ ];
{ pkgs, ... }:
{
name = "clickhouse";
meta.maintainers = with pkgs.lib.maintainers; [ ];
nodes.machine = {
services.clickhouse.enable = true;
virtualisation.memorySize = 4096;
};
nodes.machine = {
services.clickhouse.enable = true;
virtualisation.memorySize = 4096;
};
testScript =
let
# work around quote/substitution complexity by Nix, Perl, bash and SQL.
tableDDL = pkgs.writeText "ddl.sql" "CREATE TABLE `demo` (`value` FixedString(10)) engine = MergeTree PARTITION BY value ORDER BY tuple();";
insertQuery = pkgs.writeText "insert.sql" "INSERT INTO `demo` (`value`) VALUES ('foo');";
selectQuery = pkgs.writeText "select.sql" "SELECT * from `demo`";
in
''
machine.start()
machine.wait_for_unit("clickhouse.service")
machine.wait_for_open_port(9000)
testScript =
let
# work around quote/substitution complexity by Nix, Perl, bash and SQL.
tableDDL = pkgs.writeText "ddl.sql" "CREATE TABLE `demo` (`value` FixedString(10)) engine = MergeTree PARTITION BY value ORDER BY tuple();";
insertQuery = pkgs.writeText "insert.sql" "INSERT INTO `demo` (`value`) VALUES ('foo');";
selectQuery = pkgs.writeText "select.sql" "SELECT * from `demo`";
in
''
machine.start()
machine.wait_for_unit("clickhouse.service")
machine.wait_for_open_port(9000)
machine.succeed(
"cat ${tableDDL} | clickhouse-client"
)
machine.succeed(
"cat ${insertQuery} | clickhouse-client"
)
machine.succeed(
"cat ${selectQuery} | clickhouse-client | grep foo"
)
'';
}
)
machine.succeed(
"cat ${tableDDL} | clickhouse-client"
)
machine.succeed(
"cat ${insertQuery} | clickhouse-client"
)
machine.succeed(
"cat ${selectQuery} | clickhouse-client | grep foo"
)
'';
}
+18 -20
View File
@@ -1,21 +1,19 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "cloudlog";
meta = {
maintainers = with pkgs.lib.maintainers; [ melling ];
{ pkgs, ... }:
{
name = "cloudlog";
meta = {
maintainers = with pkgs.lib.maintainers; [ melling ];
};
nodes = {
machine = {
services.mysql.package = pkgs.mariadb;
services.cloudlog.enable = true;
};
nodes = {
machine = {
services.mysql.package = pkgs.mariadb;
services.cloudlog.enable = true;
};
};
testScript = ''
start_all()
machine.wait_for_unit("phpfpm-cloudlog")
machine.wait_for_open_port(80);
machine.wait_until_succeeds("curl -s -L --fail http://localhost | grep 'Login - Cloudlog'")
'';
}
)
};
testScript = ''
start_all()
machine.wait_for_unit("phpfpm-cloudlog")
machine.wait_for_open_port(80);
machine.wait_until_succeeds("curl -s -L --fail http://localhost | grep 'Login - Cloudlog'")
'';
}
+148 -150
View File
@@ -1,156 +1,154 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{ pkgs, lib, ... }:
let
user = "alice"; # from ./common/user-account.nix
password = "foobar"; # from ./common/user-account.nix
in
{
name = "cockpit";
meta = {
maintainers = with lib.maintainers; [ lucasew ];
};
nodes = {
server =
{ config, ... }:
{
imports = [ ./common/user-account.nix ];
security.polkit.enable = true;
users.users.${user} = {
extraGroups = [ "wheel" ];
};
services.cockpit = {
enable = true;
port = 7890;
openFirewall = true;
allowed-origins = [
"https://server:${toString config.services.cockpit.port}"
];
};
let
user = "alice"; # from ./common/user-account.nix
password = "foobar"; # from ./common/user-account.nix
in
{
name = "cockpit";
meta = {
maintainers = with lib.maintainers; [ lucasew ];
};
nodes = {
server =
{ config, ... }:
{
imports = [ ./common/user-account.nix ];
security.polkit.enable = true;
users.users.${user} = {
extraGroups = [ "wheel" ];
};
client =
{ config, ... }:
{
imports = [ ./common/user-account.nix ];
environment.systemPackages =
let
seleniumScript =
pkgs.writers.writePython3Bin "selenium-script"
{
libraries = with pkgs.python3Packages; [ selenium ];
}
''
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from time import sleep
def log(msg):
from sys import stderr
print(f"[*] {msg}", file=stderr)
log("Initializing")
options = Options()
options.add_argument("--headless")
service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501
driver = webdriver.Firefox(options=options, service=service)
driver.implicitly_wait(10)
log("Opening homepage")
driver.get("https://server:7890")
def wait_elem(by, query, timeout=10):
wait = WebDriverWait(driver, timeout)
wait.until(EC.presence_of_element_located((by, query)))
def wait_title_contains(title, timeout=10):
wait = WebDriverWait(driver, timeout)
wait.until(EC.title_contains(title))
def find_element(by, query):
return driver.find_element(by, query)
def set_value(elem, value):
script = 'arguments[0].value = arguments[1]'
return driver.execute_script(script, elem, value)
log("Waiting for the homepage to load")
# cockpit sets initial title as hostname
wait_title_contains("server")
wait_elem(By.CSS_SELECTOR, 'input#login-user-input')
log("Homepage loaded!")
log("Filling out username")
login_input = find_element(By.CSS_SELECTOR, 'input#login-user-input')
set_value(login_input, "${user}")
log("Filling out password")
password_input = find_element(By.CSS_SELECTOR, 'input#login-password-input')
set_value(password_input, "${password}")
log("Submitting credentials for login")
driver.find_element(By.CSS_SELECTOR, 'button#login-button').click()
# driver.implicitly_wait(1)
# driver.get("https://server:7890/system")
log("Waiting dashboard to load")
wait_title_contains("${user}@server")
log("Waiting for the frontend to initialize")
sleep(1)
log("Looking for that banner that tells about limited access")
container_iframe = find_element(By.CSS_SELECTOR, 'iframe.container-frame')
driver.switch_to.frame(container_iframe)
assert "Web console is running in limited access mode" in driver.page_source
log("Clicking the sudo button")
for button in driver.find_elements(By.TAG_NAME, "button"):
if 'admin' in button.text:
button.click()
driver.switch_to.default_content()
log("Checking that /nonexistent is not a thing")
assert '/nonexistent' not in driver.page_source
assert len(driver.find_elements(By.CSS_SELECTOR, '#machine-reconnect')) == 0
driver.close()
'';
in
with pkgs;
[
firefox-unwrapped
geckodriver
seleniumScript
];
services.cockpit = {
enable = true;
port = 7890;
openFirewall = true;
allowed-origins = [
"https://server:${toString config.services.cockpit.port}"
];
};
};
};
client =
{ config, ... }:
{
imports = [ ./common/user-account.nix ];
environment.systemPackages =
let
seleniumScript =
pkgs.writers.writePython3Bin "selenium-script"
{
libraries = with pkgs.python3Packages; [ selenium ];
}
''
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from time import sleep
testScript = ''
start_all()
server.wait_for_unit("sockets.target")
server.wait_for_open_port(7890)
def log(msg):
from sys import stderr
print(f"[*] {msg}", file=stderr)
client.succeed("curl -k https://server:7890 -o /dev/stderr")
print(client.succeed("whoami"))
client.succeed('PYTHONUNBUFFERED=1 selenium-script')
'';
}
)
log("Initializing")
options = Options()
options.add_argument("--headless")
service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501
driver = webdriver.Firefox(options=options, service=service)
driver.implicitly_wait(10)
log("Opening homepage")
driver.get("https://server:7890")
def wait_elem(by, query, timeout=10):
wait = WebDriverWait(driver, timeout)
wait.until(EC.presence_of_element_located((by, query)))
def wait_title_contains(title, timeout=10):
wait = WebDriverWait(driver, timeout)
wait.until(EC.title_contains(title))
def find_element(by, query):
return driver.find_element(by, query)
def set_value(elem, value):
script = 'arguments[0].value = arguments[1]'
return driver.execute_script(script, elem, value)
log("Waiting for the homepage to load")
# cockpit sets initial title as hostname
wait_title_contains("server")
wait_elem(By.CSS_SELECTOR, 'input#login-user-input')
log("Homepage loaded!")
log("Filling out username")
login_input = find_element(By.CSS_SELECTOR, 'input#login-user-input')
set_value(login_input, "${user}")
log("Filling out password")
password_input = find_element(By.CSS_SELECTOR, 'input#login-password-input')
set_value(password_input, "${password}")
log("Submitting credentials for login")
driver.find_element(By.CSS_SELECTOR, 'button#login-button').click()
# driver.implicitly_wait(1)
# driver.get("https://server:7890/system")
log("Waiting dashboard to load")
wait_title_contains("${user}@server")
log("Waiting for the frontend to initialize")
sleep(1)
log("Looking for that banner that tells about limited access")
container_iframe = find_element(By.CSS_SELECTOR, 'iframe.container-frame')
driver.switch_to.frame(container_iframe)
assert "Web console is running in limited access mode" in driver.page_source
log("Clicking the sudo button")
for button in driver.find_elements(By.TAG_NAME, "button"):
if 'admin' in button.text:
button.click()
driver.switch_to.default_content()
log("Checking that /nonexistent is not a thing")
assert '/nonexistent' not in driver.page_source
assert len(driver.find_elements(By.CSS_SELECTOR, '#machine-reconnect')) == 0
driver.close()
'';
in
with pkgs;
[
firefox-unwrapped
geckodriver
seleniumScript
];
};
};
testScript = ''
start_all()
server.wait_for_unit("sockets.target")
server.wait_for_open_port(7890)
client.succeed("curl -k https://server:7890 -o /dev/stderr")
print(client.succeed("whoami"))
client.succeed('PYTHONUNBUFFERED=1 selenium-script')
'';
}
+20 -22
View File
@@ -1,26 +1,24 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "code-server";
{ pkgs, lib, ... }:
{
name = "code-server";
nodes = {
machine =
{ pkgs, ... }:
{
services.code-server = {
enable = true;
auth = "none";
};
nodes = {
machine =
{ pkgs, ... }:
{
services.code-server = {
enable = true;
auth = "none";
};
};
};
};
testScript = ''
start_all()
machine.wait_for_unit("code-server.service")
machine.wait_for_open_port(4444)
machine.succeed("curl -k --fail http://localhost:4444", timeout=10)
'';
testScript = ''
start_all()
machine.wait_for_unit("code-server.service")
machine.wait_for_open_port(4444)
machine.succeed("curl -k --fail http://localhost:4444", timeout=10)
'';
meta.maintainers = [ lib.maintainers.drupol ];
}
)
meta.maintainers = [ lib.maintainers.drupol ];
}
+19 -21
View File
@@ -1,25 +1,23 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "coder";
meta.maintainers = pkgs.coder.meta.maintainers;
{ pkgs, ... }:
{
name = "coder";
meta.maintainers = pkgs.coder.meta.maintainers;
nodes.machine =
{ pkgs, ... }:
{
services.coder = {
enable = true;
accessUrl = "http://localhost:3000";
};
nodes.machine =
{ pkgs, ... }:
{
services.coder = {
enable = true;
accessUrl = "http://localhost:3000";
};
};
testScript = ''
machine.start()
machine.wait_for_unit("postgresql.service")
machine.wait_for_unit("coder.service")
machine.wait_for_open_port(3000)
testScript = ''
machine.start()
machine.wait_for_unit("postgresql.service")
machine.wait_for_unit("coder.service")
machine.wait_for_open_port(3000)
machine.succeed("curl --fail http://localhost:3000")
'';
}
)
machine.succeed("curl --fail http://localhost:3000")
'';
}
+33 -35
View File
@@ -1,41 +1,39 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "collectd";
meta = { };
{ pkgs, ... }:
{
name = "collectd";
meta = { };
nodes.machine =
{ pkgs, lib, ... }:
nodes.machine =
{ pkgs, lib, ... }:
{
services.collectd = {
enable = true;
extraConfig = lib.mkBefore ''
Interval 30
{
services.collectd = {
enable = true;
extraConfig = lib.mkBefore ''
Interval 30
'';
plugins = {
rrdtool = ''
DataDir "/var/lib/collectd/rrd"
'';
plugins = {
rrdtool = ''
DataDir "/var/lib/collectd/rrd"
'';
load = "";
};
load = "";
};
environment.systemPackages = [ pkgs.rrdtool ];
};
environment.systemPackages = [ pkgs.rrdtool ];
};
testScript = ''
machine.wait_for_unit("collectd.service")
hostname = machine.succeed("hostname").strip()
file = f"/var/lib/collectd/rrd/{hostname}/load/load.rrd"
machine.wait_for_file(file);
machine.succeed(f"rrdinfo {file} | logger")
# check that this file contains a shortterm metric
machine.succeed(f"rrdinfo {file} | grep -F 'ds[shortterm].min = '")
# check that interval was set before the plugins
machine.succeed(f"rrdinfo {file} | grep -F 'step = 30'")
# check that there are frequent updates
machine.succeed(f"cp {file} before")
machine.wait_until_fails(f"cmp before {file}")
'';
}
)
testScript = ''
machine.wait_for_unit("collectd.service")
hostname = machine.succeed("hostname").strip()
file = f"/var/lib/collectd/rrd/{hostname}/load/load.rrd"
machine.wait_for_file(file);
machine.succeed(f"rrdinfo {file} | logger")
# check that this file contains a shortterm metric
machine.succeed(f"rrdinfo {file} | grep -F 'ds[shortterm].min = '")
# check that interval was set before the plugins
machine.succeed(f"rrdinfo {file} | grep -F 'step = 30'")
# check that there are frequent updates
machine.succeed(f"cp {file} before")
machine.wait_until_fails(f"cmp before {file}")
'';
}
+15 -17
View File
@@ -1,21 +1,19 @@
import ./make-test-python.nix (
{ lib, ... }:
{
name = "commafeed";
{ lib, ... }:
{
name = "commafeed";
nodes.server = {
services.commafeed = {
enable = true;
};
nodes.server = {
services.commafeed = {
enable = true;
};
};
testScript = ''
server.start()
server.wait_for_unit("commafeed.service")
server.wait_for_open_port(8082)
server.succeed("curl --fail --silent http://localhost:8082")
'';
testScript = ''
server.start()
server.wait_for_unit("commafeed.service")
server.wait_for_open_port(8082)
server.succeed("curl --fail --silent http://localhost:8082")
'';
meta.maintainers = [ lib.maintainers.raroh73 ];
}
)
meta.maintainers = [ lib.maintainers.raroh73 ];
}
+74 -76
View File
@@ -1,85 +1,83 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "connman";
meta = with lib.maintainers; {
maintainers = [ rnhmjoj ];
{ pkgs, lib, ... }:
{
name = "connman";
meta = with lib.maintainers; {
maintainers = [ rnhmjoj ];
};
# Router running radvd on VLAN 1
nodes.router =
{ ... }:
{
imports = [ ../modules/profiles/minimal.nix ];
virtualisation.vlans = [ 1 ];
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
networking = {
useDHCP = false;
interfaces.eth1.ipv6.addresses = [
{
address = "fd12::1";
prefixLength = 64;
}
];
};
services.radvd = {
enable = true;
config = ''
interface eth1 {
AdvSendAdvert on;
AdvManagedFlag on;
AdvOtherConfigFlag on;
prefix fd12::/64 {
AdvAutonomous off;
};
};
'';
};
};
# Router running radvd on VLAN 1
nodes.router =
{ ... }:
{
imports = [ ../modules/profiles/minimal.nix ];
# Client running connman, connected to VLAN 1
nodes.client =
{ ... }:
{
virtualisation.vlans = [ 1 ];
virtualisation.vlans = [ 1 ];
# add a virtual wlan interface
boot.kernelModules = [ "mac80211_hwsim" ];
boot.extraModprobeConfig = ''
options mac80211_hwsim radios=1
'';
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
# Note: the overrides are needed because the wifi is
# disabled with mkVMOverride in qemu-vm.nix.
services.connman.enable = lib.mkOverride 0 true;
services.connman.networkInterfaceBlacklist = [ "eth0" ];
networking.wireless.enable = lib.mkOverride 0 true;
networking.wireless.interfaces = [ "wlan0" ];
};
networking = {
useDHCP = false;
interfaces.eth1.ipv6.addresses = [
{
address = "fd12::1";
prefixLength = 64;
}
];
};
testScript = ''
start_all()
services.radvd = {
enable = true;
config = ''
interface eth1 {
AdvSendAdvert on;
AdvManagedFlag on;
AdvOtherConfigFlag on;
prefix fd12::/64 {
AdvAutonomous off;
};
};
'';
};
};
with subtest("Router is ready"):
router.wait_for_unit("radvd.service")
# Client running connman, connected to VLAN 1
nodes.client =
{ ... }:
{
virtualisation.vlans = [ 1 ];
with subtest("Daemons are running"):
client.wait_for_unit("wpa_supplicant-wlan0.service")
client.wait_for_unit("connman.service")
client.wait_until_succeeds("connmanctl state | grep -q ready")
# add a virtual wlan interface
boot.kernelModules = [ "mac80211_hwsim" ];
boot.extraModprobeConfig = ''
options mac80211_hwsim radios=1
'';
with subtest("Wired interface is configured"):
client.wait_until_succeeds("ip -6 route | grep -q fd12::/64")
client.wait_until_succeeds("ping -c 1 fd12::1")
# Note: the overrides are needed because the wifi is
# disabled with mkVMOverride in qemu-vm.nix.
services.connman.enable = lib.mkOverride 0 true;
services.connman.networkInterfaceBlacklist = [ "eth0" ];
networking.wireless.enable = lib.mkOverride 0 true;
networking.wireless.interfaces = [ "wlan0" ];
};
testScript = ''
start_all()
with subtest("Router is ready"):
router.wait_for_unit("radvd.service")
with subtest("Daemons are running"):
client.wait_for_unit("wpa_supplicant-wlan0.service")
client.wait_for_unit("connman.service")
client.wait_until_succeeds("connmanctl state | grep -q ready")
with subtest("Wired interface is configured"):
client.wait_until_succeeds("ip -6 route | grep -q fd12::/64")
client.wait_until_succeeds("ping -c 1 fd12::1")
with subtest("Can set up a wireless access point"):
client.succeed("connmanctl enable wifi")
client.wait_until_succeeds("connmanctl tether wifi on nixos-test reproducibility | grep -q 'Enabled'")
client.wait_until_succeeds("iw wlan0 info | grep -q nixos-test")
'';
}
)
with subtest("Can set up a wireless access point"):
client.succeed("connmanctl enable wifi")
client.wait_until_succeeds("connmanctl tether wifi on nixos-test reproducibility | grep -q 'Enabled'")
client.wait_until_succeeds("iw wlan0 info | grep -q nixos-test")
'';
}
+35 -37
View File
@@ -1,43 +1,41 @@
import ./make-test-python.nix (
{ ... }:
{
name = "consul-template";
{ ... }:
{
name = "consul-template";
nodes.machine =
{ ... }:
{
services.consul-template.instances.example.settings = {
template = [
{
contents = ''
{{ key "example" }}
'';
perms = "0600";
destination = "/example";
}
];
};
services.consul = {
enable = true;
extraConfig = {
server = true;
bootstrap_expect = 1;
bind_addr = "127.0.0.1";
};
};
nodes.machine =
{ ... }:
{
services.consul-template.instances.example.settings = {
template = [
{
contents = ''
{{ key "example" }}
'';
perms = "0600";
destination = "/example";
}
];
};
testScript = ''
machine.wait_for_unit("consul.service")
machine.wait_for_open_port(8500)
services.consul = {
enable = true;
extraConfig = {
server = true;
bootstrap_expect = 1;
bind_addr = "127.0.0.1";
};
};
};
machine.wait_for_unit("consul-template-example.service")
testScript = ''
machine.wait_for_unit("consul.service")
machine.wait_for_open_port(8500)
machine.wait_until_succeeds('consul kv put example example')
machine.wait_for_unit("consul-template-example.service")
machine.wait_for_file("/example")
machine.succeed('grep "example" /example')
'';
}
)
machine.wait_until_succeeds('consul kv put example example')
machine.wait_for_file("/example")
machine.succeed('grep "example" /example')
'';
}
+220 -220
View File
@@ -1,267 +1,267 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{ lib, ... }:
let
# Settings for both servers and agents
webUi = true;
retry_interval = "1s";
raft_multiplier = 1;
let
# Settings for both servers and agents
webUi = true;
retry_interval = "1s";
raft_multiplier = 1;
defaultExtraConfig = {
inherit retry_interval;
performance = {
inherit raft_multiplier;
defaultExtraConfig = {
inherit retry_interval;
performance = {
inherit raft_multiplier;
};
};
allConsensusServerHosts = [
"192.168.1.1"
"192.168.1.2"
"192.168.1.3"
];
allConsensusClientHosts = [
"192.168.2.1"
"192.168.2.2"
];
firewallSettings = {
# See https://www.consul.io/docs/install/ports.html
allowedTCPPorts = [
8301
8302
8600
8500
8300
];
allowedUDPPorts = [
8301
8302
8600
];
};
client =
index:
{ pkgs, ... }:
let
ip = builtins.elemAt allConsensusClientHosts index;
in
{
environment.systemPackages = [ pkgs.consul ];
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
{
address = ip;
prefixLength = 16;
}
];
networking.firewall = firewallSettings;
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ];
services.consul = {
enable = true;
inherit webUi;
extraConfig = defaultExtraConfig // {
server = false;
retry_join = allConsensusServerHosts;
bind_addr = ip;
};
};
};
allConsensusServerHosts = [
"192.168.1.1"
"192.168.1.2"
"192.168.1.3"
];
allConsensusClientHosts = [
"192.168.2.1"
"192.168.2.2"
];
firewallSettings = {
# See https://www.consul.io/docs/install/ports.html
allowedTCPPorts = [
8301
8302
8600
8500
8300
server =
index:
{ pkgs, ... }:
let
numConsensusServers = builtins.length allConsensusServerHosts;
thisConsensusServerHost = builtins.elemAt allConsensusServerHosts index;
ip = thisConsensusServerHost; # since we already use IPs to identify servers
in
{
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
{
address = ip;
prefixLength = 16;
}
];
allowedUDPPorts = [
8301
8302
8600
];
};
networking.firewall = firewallSettings;
client =
index:
{ pkgs, ... }:
let
ip = builtins.elemAt allConsensusClientHosts index;
in
{
environment.systemPackages = [ pkgs.consul ];
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ];
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
{
address = ip;
prefixLength = 16;
}
];
networking.firewall = firewallSettings;
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ];
services.consul = {
services.consul =
assert builtins.elem thisConsensusServerHost allConsensusServerHosts;
{
enable = true;
inherit webUi;
extraConfig = defaultExtraConfig // {
server = false;
retry_join = allConsensusServerHosts;
server = true;
bootstrap_expect = numConsensusServers;
# Tell Consul that we never intend to drop below this many servers.
# Ensures to not permanently lose consensus after temporary loss.
# See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
autopilot.min_quorum = numConsensusServers;
retry_join =
# If there's only 1 node in the network, we allow self-join;
# otherwise, the node must not try to join itself, and join only the other servers.
# See https://github.com/hashicorp/consul/issues/2868
if numConsensusServers == 1 then
allConsensusServerHosts
else
builtins.filter (h: h != thisConsensusServerHost) allConsensusServerHosts;
bind_addr = ip;
};
};
};
server =
index:
{ pkgs, ... }:
let
numConsensusServers = builtins.length allConsensusServerHosts;
thisConsensusServerHost = builtins.elemAt allConsensusServerHosts index;
ip = thisConsensusServerHost; # since we already use IPs to identify servers
in
{
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
{
address = ip;
prefixLength = 16;
}
];
networking.firewall = firewallSettings;
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ];
services.consul =
assert builtins.elem thisConsensusServerHost allConsensusServerHosts;
{
enable = true;
inherit webUi;
extraConfig = defaultExtraConfig // {
server = true;
bootstrap_expect = numConsensusServers;
# Tell Consul that we never intend to drop below this many servers.
# Ensures to not permanently lose consensus after temporary loss.
# See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
autopilot.min_quorum = numConsensusServers;
retry_join =
# If there's only 1 node in the network, we allow self-join;
# otherwise, the node must not try to join itself, and join only the other servers.
# See https://github.com/hashicorp/consul/issues/2868
if numConsensusServers == 1 then
allConsensusServerHosts
else
builtins.filter (h: h != thisConsensusServerHost) allConsensusServerHosts;
bind_addr = ip;
};
};
};
in
{
name = "consul";
nodes = {
server1 = server 0;
server2 = server 1;
server3 = server 2;
client1 = client 0;
client2 = client 1;
};
in
{
name = "consul";
testScript = ''
servers = [server1, server2, server3]
machines = [server1, server2, server3, client1, client2]
node.pkgsReadOnly = false;
for m in machines:
m.wait_for_unit("consul.service")
nodes = {
server1 = server 0;
server2 = server 1;
server3 = server 2;
client1 = client 0;
client2 = client 1;
};
testScript = ''
servers = [server1, server2, server3]
machines = [server1, server2, server3, client1, client2]
for m in machines:
m.wait_for_unit("consul.service")
def wait_for_healthy_servers():
# See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
# for why the `Voter` column of `list-peers` has that info.
# TODO: The `grep true` relies on the fact that currently in
# the output like
# # consul operator raft list-peers
# Node ID Address State Voter RaftProtocol
# server3 ... 192.168.1.3:8300 leader true 3
# server2 ... 192.168.1.2:8300 follower true 3
# server1 ... 192.168.1.1:8300 follower false 3
# `Voter`is the only boolean column.
# Change this to the more reliable way to be defined by
# https://github.com/hashicorp/consul/issues/8118
# once that ticket is closed.
for m in machines:
m.wait_until_succeeds(
"[ $(consul operator raft list-peers | grep true | wc -l) == 3 ]"
)
def wait_for_healthy_servers():
# See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
# for why the `Voter` column of `list-peers` has that info.
# TODO: The `grep true` relies on the fact that currently in
# the output like
# # consul operator raft list-peers
# Node ID Address State Voter RaftProtocol
# server3 ... 192.168.1.3:8300 leader true 3
# server2 ... 192.168.1.2:8300 follower true 3
# server1 ... 192.168.1.1:8300 follower false 3
# `Voter`is the only boolean column.
# Change this to the more reliable way to be defined by
# https://github.com/hashicorp/consul/issues/8118
# once that ticket is closed.
for m in machines:
m.wait_until_succeeds(
"[ $(consul operator raft list-peers | grep true | wc -l) == 3 ]"
)
def wait_for_all_machines_alive():
"""
Note that Serf-"alive" does not mean "Raft"-healthy;
see `wait_for_healthy_servers()` for that instead.
"""
for m in machines:
m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
def wait_for_all_machines_alive():
"""
Note that Serf-"alive" does not mean "Raft"-healthy;
see `wait_for_healthy_servers()` for that instead.
"""
for m in machines:
m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
wait_for_healthy_servers()
# Also wait for clients to be alive.
wait_for_all_machines_alive()
wait_for_healthy_servers()
# Also wait for clients to be alive.
wait_for_all_machines_alive()
client1.succeed("consul kv put testkey 42")
client2.succeed("[ $(consul kv get testkey) == 42 ]")
client1.succeed("consul kv put testkey 42")
client2.succeed("[ $(consul kv get testkey) == 42 ]")
def rolling_restart_test(proper_rolling_procedure=True):
"""
Tests that the cluster can tolearate failures of any single server,
following the recommended rolling upgrade procedure from
https://www.consul.io/docs/upgrading#standard-upgrades.
def rolling_restart_test(proper_rolling_procedure=True):
"""
Tests that the cluster can tolearate failures of any single server,
following the recommended rolling upgrade procedure from
https://www.consul.io/docs/upgrading#standard-upgrades.
Optionally, `proper_rolling_procedure=False` can be given
to wait only for each server to be back `Healthy`, not `Stable`
in the Raft consensus, see Consul setting `ServerStabilizationTime` and
https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040.
"""
Optionally, `proper_rolling_procedure=False` can be given
to wait only for each server to be back `Healthy`, not `Stable`
in the Raft consensus, see Consul setting `ServerStabilizationTime` and
https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040.
"""
for server in servers:
server.block()
server.systemctl("stop consul")
for server in servers:
server.block()
server.systemctl("stop consul")
# Make sure the stopped peer is recognized as being down
client1.wait_until_succeeds(
f"[ $(consul members | grep {server.name} | grep -o -E 'failed|left' | wc -l) == 1 ]"
)
# Make sure the stopped peer is recognized as being down
client1.wait_until_succeeds(
f"[ $(consul members | grep {server.name} | grep -o -E 'failed|left' | wc -l) == 1 ]"
)
# For each client, wait until they have connection again
# using `kv get -recurse` before issuing commands.
client1.wait_until_succeeds("consul kv get -recurse")
client2.wait_until_succeeds("consul kv get -recurse")
# For each client, wait until they have connection again
# using `kv get -recurse` before issuing commands.
client1.wait_until_succeeds("consul kv get -recurse")
client2.wait_until_succeeds("consul kv get -recurse")
# Do some consul actions while one server is down.
client1.succeed("consul kv put testkey 43")
client2.succeed("[ $(consul kv get testkey) == 43 ]")
client2.succeed("consul kv delete testkey")
# Do some consul actions while one server is down.
client1.succeed("consul kv put testkey 43")
client2.succeed("[ $(consul kv get testkey) == 43 ]")
client2.succeed("consul kv delete testkey")
server.unblock()
server.systemctl("start consul")
server.unblock()
server.systemctl("start consul")
if proper_rolling_procedure:
# Wait for recovery.
wait_for_healthy_servers()
else:
# NOT proper rolling upgrade procedure, see above.
wait_for_all_machines_alive()
if proper_rolling_procedure:
# Wait for recovery.
wait_for_healthy_servers()
else:
# NOT proper rolling upgrade procedure, see above.
wait_for_all_machines_alive()
# Wait for client connections.
client1.wait_until_succeeds("consul kv get -recurse")
client2.wait_until_succeeds("consul kv get -recurse")
# Wait for client connections.
client1.wait_until_succeeds("consul kv get -recurse")
client2.wait_until_succeeds("consul kv get -recurse")
# Do some consul actions with server back up.
client1.succeed("consul kv put testkey 44")
client2.succeed("[ $(consul kv get testkey) == 44 ]")
client2.succeed("consul kv delete testkey")
# Do some consul actions with server back up.
client1.succeed("consul kv put testkey 44")
client2.succeed("[ $(consul kv get testkey) == 44 ]")
client2.succeed("consul kv delete testkey")
def all_servers_crash_simultaneously_test():
"""
Tests that the cluster will eventually come back after all
servers crash simultaneously.
"""
def all_servers_crash_simultaneously_test():
"""
Tests that the cluster will eventually come back after all
servers crash simultaneously.
"""
for server in servers:
server.block()
server.systemctl("stop --no-block consul")
for server in servers:
server.block()
server.systemctl("stop --no-block consul")
for server in servers:
# --no-block is async, so ensure it has been stopped by now
server.wait_until_fails("systemctl is-active --quiet consul")
server.unblock()
server.systemctl("start consul")
for server in servers:
# --no-block is async, so ensure it has been stopped by now
server.wait_until_fails("systemctl is-active --quiet consul")
server.unblock()
server.systemctl("start consul")
# Wait for recovery.
wait_for_healthy_servers()
# Wait for recovery.
wait_for_healthy_servers()
# Wait for client connections.
client1.wait_until_succeeds("consul kv get -recurse")
client2.wait_until_succeeds("consul kv get -recurse")
# Wait for client connections.
client1.wait_until_succeeds("consul kv get -recurse")
client2.wait_until_succeeds("consul kv get -recurse")
# Do some consul actions with servers back up.
client1.succeed("consul kv put testkey 44")
client2.succeed("[ $(consul kv get testkey) == 44 ]")
client2.succeed("consul kv delete testkey")
# Do some consul actions with servers back up.
client1.succeed("consul kv put testkey 44")
client2.succeed("[ $(consul kv get testkey) == 44 ]")
client2.succeed("consul kv delete testkey")
# Run the tests.
# Run the tests.
print("rolling_restart_test()")
rolling_restart_test()
print("rolling_restart_test()")
rolling_restart_test()
print("all_servers_crash_simultaneously_test()")
all_servers_crash_simultaneously_test()
print("all_servers_crash_simultaneously_test()")
all_servers_crash_simultaneously_test()
print("rolling_restart_test(proper_rolling_procedure=False)")
rolling_restart_test(proper_rolling_procedure=False)
'';
}
)
print("rolling_restart_test(proper_rolling_procedure=False)")
rolling_restart_test(proper_rolling_procedure=False)
'';
}
+92 -94
View File
@@ -5,110 +5,108 @@ let
containerIp6 = "fc00::2/7";
in
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "containers-bridge";
meta = {
maintainers = with lib.maintainers; [
aristid
aszlig
kampfschlaefer
];
};
{ pkgs, lib, ... }:
{
name = "containers-bridge";
meta = {
maintainers = with lib.maintainers; [
aristid
aszlig
kampfschlaefer
];
};
nodes.machine =
{ pkgs, ... }:
{
imports = [ ../modules/installer/cd-dvd/channel.nix ];
virtualisation.writableStore = true;
nodes.machine =
{ pkgs, ... }:
{
imports = [ ../modules/installer/cd-dvd/channel.nix ];
virtualisation.writableStore = true;
networking.bridges = {
br0 = {
interfaces = [ ];
};
networking.bridges = {
br0 = {
interfaces = [ ];
};
networking.interfaces = {
br0 = {
ipv4.addresses = [
{
address = hostIp;
prefixLength = 24;
}
];
ipv6.addresses = [
{
address = hostIp6;
prefixLength = 7;
}
];
};
};
networking.interfaces = {
br0 = {
ipv4.addresses = [
{
address = hostIp;
prefixLength = 24;
}
];
ipv6.addresses = [
{
address = hostIp6;
prefixLength = 7;
}
];
};
containers.webserver = {
autoStart = true;
privateNetwork = true;
hostBridge = "br0";
localAddress = containerIp;
localAddress6 = containerIp6;
config = {
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
networking.firewall.allowedTCPPorts = [ 80 ];
};
};
containers.web-noip = {
autoStart = true;
privateNetwork = true;
hostBridge = "br0";
config = {
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
networking.firewall.allowedTCPPorts = [ 80 ];
};
};
virtualisation.additionalPaths = [ pkgs.stdenv ];
};
testScript = ''
machine.wait_for_unit("default.target")
assert "webserver" in machine.succeed("nixos-container list")
containers.webserver = {
autoStart = true;
privateNetwork = true;
hostBridge = "br0";
localAddress = containerIp;
localAddress6 = containerIp6;
config = {
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
networking.firewall.allowedTCPPorts = [ 80 ];
};
};
with subtest("Start the webserver container"):
assert "up" in machine.succeed("nixos-container status webserver")
containers.web-noip = {
autoStart = true;
privateNetwork = true;
hostBridge = "br0";
config = {
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
networking.firewall.allowedTCPPorts = [ 80 ];
};
};
with subtest("Bridges exist inside containers"):
machine.succeed(
"nixos-container run webserver -- ip link show eth0",
"nixos-container run web-noip -- ip link show eth0",
)
virtualisation.additionalPaths = [ pkgs.stdenv ];
};
ip = "${containerIp}".split("/")[0]
machine.succeed(f"ping -n -c 1 {ip}")
machine.succeed(f"curl --fail http://{ip}/ > /dev/null")
testScript = ''
machine.wait_for_unit("default.target")
assert "webserver" in machine.succeed("nixos-container list")
ip6 = "${containerIp6}".split("/")[0]
machine.succeed(f"ping -n -c 1 {ip6}")
machine.succeed(f"curl --fail http://[{ip6}]/ > /dev/null")
with subtest("Start the webserver container"):
assert "up" in machine.succeed("nixos-container status webserver")
with subtest(
"nixos-container show-ip works in case of an ipv4 address "
+ "with subnetmask in CIDR notation."
):
result = machine.succeed("nixos-container show-ip webserver").rstrip()
assert result == ip
with subtest("Bridges exist inside containers"):
machine.succeed(
"nixos-container run webserver -- ip link show eth0",
"nixos-container run web-noip -- ip link show eth0",
)
with subtest("Stop the container"):
machine.succeed("nixos-container stop webserver")
machine.fail(
f"curl --fail --connect-timeout 2 http://{ip}/ > /dev/null",
f"curl --fail --connect-timeout 2 http://[{ip6}]/ > /dev/null",
)
ip = "${containerIp}".split("/")[0]
machine.succeed(f"ping -n -c 1 {ip}")
machine.succeed(f"curl --fail http://{ip}/ > /dev/null")
# Destroying a declarative container should fail.
machine.fail("nixos-container destroy webserver")
'';
}
)
ip6 = "${containerIp6}".split("/")[0]
machine.succeed(f"ping -n -c 1 {ip6}")
machine.succeed(f"curl --fail http://[{ip6}]/ > /dev/null")
with subtest(
"nixos-container show-ip works in case of an ipv4 address "
+ "with subnetmask in CIDR notation."
):
result = machine.succeed("nixos-container show-ip webserver").rstrip()
assert result == ip
with subtest("Stop the container"):
machine.succeed("nixos-container stop webserver")
machine.fail(
f"curl --fail --connect-timeout 2 http://{ip}/ > /dev/null",
f"curl --fail --connect-timeout 2 http://[{ip6}]/ > /dev/null",
)
# Destroying a declarative container should fail.
machine.fail("nixos-container destroy webserver")
'';
}
+42 -44
View File
@@ -1,48 +1,46 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
let
{ pkgs, lib, ... }:
let
customPkgs = pkgs.appendOverlays [
(self: super: {
hello = super.hello.overrideAttrs (old: {
name = "custom-hello";
});
})
];
customPkgs = pkgs.appendOverlays [
(self: super: {
hello = super.hello.overrideAttrs (old: {
name = "custom-hello";
});
})
];
in
{
name = "containers-custom-pkgs";
meta = {
maintainers = with lib.maintainers; [ erikarvstedt ];
in
{
name = "containers-custom-pkgs";
meta = {
maintainers = with lib.maintainers; [ erikarvstedt ];
};
nodes.machine =
{ config, ... }:
{
assertions =
let
helloName = (builtins.head config.containers.test.config.system.extraDependencies).name;
in
[
{
assertion = helloName == "custom-hello";
message = "Unexpected value: ${helloName}";
}
];
containers.test = {
autoStart = true;
config =
{ pkgs, config, ... }:
{
nixpkgs.pkgs = customPkgs;
system.extraDependencies = [ pkgs.hello ];
};
};
};
nodes.machine =
{ config, ... }:
{
assertions =
let
helloName = (builtins.head config.containers.test.config.system.extraDependencies).name;
in
[
{
assertion = helloName == "custom-hello";
message = "Unexpected value: ${helloName}";
}
];
containers.test = {
autoStart = true;
config =
{ pkgs, config, ... }:
{
nixpkgs.pkgs = customPkgs;
system.extraDependencies = [ pkgs.hello ];
};
};
};
# This test only consists of evaluating the test machine
testScript = "pass";
}
)
# This test only consists of evaluating the test machine
testScript = "pass";
}
+44 -46
View File
@@ -1,59 +1,57 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "containers-ephemeral";
meta = {
maintainers = with lib.maintainers; [ patryk27 ];
};
{ pkgs, lib, ... }:
{
name = "containers-ephemeral";
meta = {
maintainers = with lib.maintainers; [ patryk27 ];
};
nodes.machine =
{ pkgs, ... }:
{
virtualisation.writableStore = true;
nodes.machine =
{ pkgs, ... }:
{
virtualisation.writableStore = true;
containers.webserver = {
ephemeral = true;
privateNetwork = true;
hostAddress = "10.231.136.1";
localAddress = "10.231.136.2";
config = {
services.nginx = {
enable = true;
virtualHosts.localhost = {
root = pkgs.runCommand "localhost" { } ''
mkdir "$out"
echo hello world > "$out/index.html"
'';
};
containers.webserver = {
ephemeral = true;
privateNetwork = true;
hostAddress = "10.231.136.1";
localAddress = "10.231.136.2";
config = {
services.nginx = {
enable = true;
virtualHosts.localhost = {
root = pkgs.runCommand "localhost" { } ''
mkdir "$out"
echo hello world > "$out/index.html"
'';
};
networking.firewall.allowedTCPPorts = [ 80 ];
};
networking.firewall.allowedTCPPorts = [ 80 ];
};
};
};
testScript = ''
assert "webserver" in machine.succeed("nixos-container list")
testScript = ''
assert "webserver" in machine.succeed("nixos-container list")
machine.succeed("nixos-container start webserver")
machine.succeed("nixos-container start webserver")
with subtest("Container got its own root folder"):
machine.succeed("ls /run/nixos-containers/webserver")
with subtest("Container got its own root folder"):
machine.succeed("ls /run/nixos-containers/webserver")
with subtest("Container persistent directory is not created"):
machine.fail("ls /var/lib/nixos-containers/webserver")
with subtest("Container persistent directory is not created"):
machine.fail("ls /var/lib/nixos-containers/webserver")
# Since "start" returns after the container has reached
# multi-user.target, we should now be able to access it.
ip = machine.succeed("nixos-container show-ip webserver").rstrip()
machine.succeed(f"ping -n -c1 {ip}")
machine.succeed(f"curl --fail http://{ip}/ > /dev/null")
# Since "start" returns after the container has reached
# multi-user.target, we should now be able to access it.
ip = machine.succeed("nixos-container show-ip webserver").rstrip()
machine.succeed(f"ping -n -c1 {ip}")
machine.succeed(f"curl --fail http://{ip}/ > /dev/null")
with subtest("Stop the container"):
machine.succeed("nixos-container stop webserver")
machine.fail(f"curl --fail --connect-timeout 2 http://{ip}/ > /dev/null")
with subtest("Stop the container"):
machine.succeed("nixos-container stop webserver")
machine.fail(f"curl --fail --connect-timeout 2 http://{ip}/ > /dev/null")
with subtest("Container's root folder was removed"):
machine.fail("ls /run/nixos-containers/webserver")
'';
}
)
with subtest("Container's root folder was removed"):
machine.fail("ls /run/nixos-containers/webserver")
'';
}
+97 -99
View File
@@ -1,115 +1,113 @@
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{
name = "containers-extra_veth";
meta = {
maintainers = with lib.maintainers; [ kampfschlaefer ];
};
{ pkgs, lib, ... }:
{
name = "containers-extra_veth";
meta = {
maintainers = with lib.maintainers; [ kampfschlaefer ];
};
nodes.machine =
{ pkgs, ... }:
{
imports = [ ../modules/installer/cd-dvd/channel.nix ];
virtualisation.writableStore = true;
virtualisation.vlans = [ ];
nodes.machine =
{ pkgs, ... }:
{
imports = [ ../modules/installer/cd-dvd/channel.nix ];
virtualisation.writableStore = true;
virtualisation.vlans = [ ];
networking.useDHCP = false;
networking.bridges = {
br0 = {
interfaces = [ ];
};
br1 = {
interfaces = [ ];
};
networking.useDHCP = false;
networking.bridges = {
br0 = {
interfaces = [ ];
};
networking.interfaces = {
br0 = {
ipv4.addresses = [
{
address = "192.168.0.1";
prefixLength = 24;
}
];
ipv6.addresses = [
{
address = "fc00::1";
prefixLength = 7;
}
];
};
br1 = {
ipv4.addresses = [
{
address = "192.168.1.1";
prefixLength = 24;
}
];
};
br1 = {
interfaces = [ ];
};
containers.webserver = {
autoStart = true;
privateNetwork = true;
hostBridge = "br0";
localAddress = "192.168.0.100/24";
localAddress6 = "fc00::2/7";
extraVeths = {
veth1 = {
hostBridge = "br1";
localAddress = "192.168.1.100/24";
};
veth2 = {
hostAddress = "192.168.2.1";
localAddress = "192.168.2.100";
};
};
config = {
networking.firewall.allowedTCPPorts = [ 80 ];
};
};
networking.interfaces = {
br0 = {
ipv4.addresses = [
{
address = "192.168.0.1";
prefixLength = 24;
}
];
ipv6.addresses = [
{
address = "fc00::1";
prefixLength = 7;
}
];
};
br1 = {
ipv4.addresses = [
{
address = "192.168.1.1";
prefixLength = 24;
}
];
};
virtualisation.additionalPaths = [ pkgs.stdenv ];
};
testScript = ''
machine.wait_for_unit("default.target")
assert "webserver" in machine.succeed("nixos-container list")
containers.webserver = {
autoStart = true;
privateNetwork = true;
hostBridge = "br0";
localAddress = "192.168.0.100/24";
localAddress6 = "fc00::2/7";
extraVeths = {
veth1 = {
hostBridge = "br1";
localAddress = "192.168.1.100/24";
};
veth2 = {
hostAddress = "192.168.2.1";
localAddress = "192.168.2.100";
};
};
config = {
networking.firewall.allowedTCPPorts = [ 80 ];
};
};
with subtest("Status of the webserver container is up"):
assert "up" in machine.succeed("nixos-container status webserver")
virtualisation.additionalPaths = [ pkgs.stdenv ];
};
with subtest("Ensure that the veths are inside the container"):
assert "state UP" in machine.succeed(
"nixos-container run webserver -- ip link show veth1"
)
assert "state UP" in machine.succeed(
"nixos-container run webserver -- ip link show veth2"
)
testScript = ''
machine.wait_for_unit("default.target")
assert "webserver" in machine.succeed("nixos-container list")
with subtest("Ensure the presence of the extra veths"):
assert "state UP" in machine.succeed("ip link show veth1")
assert "state UP" in machine.succeed("ip link show veth2")
with subtest("Status of the webserver container is up"):
assert "up" in machine.succeed("nixos-container status webserver")
with subtest("Ensure the veth1 is part of br1 on the host"):
assert "master br1" in machine.succeed("ip link show veth1")
with subtest("Ensure that the veths are inside the container"):
assert "state UP" in machine.succeed(
"nixos-container run webserver -- ip link show veth1"
)
assert "state UP" in machine.succeed(
"nixos-container run webserver -- ip link show veth2"
)
with subtest("Ping on main veth"):
machine.succeed("ping -n -c 1 192.168.0.100")
machine.succeed("ping -n -c 1 fc00::2")
with subtest("Ensure the presence of the extra veths"):
assert "state UP" in machine.succeed("ip link show veth1")
assert "state UP" in machine.succeed("ip link show veth2")
with subtest("Ping on the first extra veth"):
machine.succeed("ping -n -c 1 192.168.1.100 >&2")
with subtest("Ensure the veth1 is part of br1 on the host"):
assert "master br1" in machine.succeed("ip link show veth1")
with subtest("Ping on the second extra veth"):
machine.succeed("ping -n -c 1 192.168.2.100 >&2")
with subtest("Ping on main veth"):
machine.succeed("ping -n -c 1 192.168.0.100")
machine.succeed("ping -n -c 1 fc00::2")
with subtest("Container can be stopped"):
machine.succeed("nixos-container stop webserver")
machine.fail("ping -n -c 1 192.168.1.100 >&2")
machine.fail("ping -n -c 1 192.168.2.100 >&2")
with subtest("Ping on the first extra veth"):
machine.succeed("ping -n -c 1 192.168.1.100 >&2")
with subtest("Destroying a declarative container should fail"):
machine.fail("nixos-container destroy webserver")
'';
}
)
with subtest("Ping on the second extra veth"):
machine.succeed("ping -n -c 1 192.168.2.100 >&2")
with subtest("Container can be stopped"):
machine.succeed("nixos-container stop webserver")
machine.fail("ping -n -c 1 192.168.1.100 >&2")
machine.fail("ping -n -c 1 192.168.2.100 >&2")
with subtest("Destroying a declarative container should fail"):
machine.fail("nixos-container destroy webserver")
'';
}

Some files were not shown because too many files have changed in this diff Show More