Merge ee2477b0b5 into haskell-updates
This commit is contained in:
+1
-1
@@ -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/**/*
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
```
|
||||
|
||||
@@ -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 backwards‐incompatible changes; see the NEWS for details.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -309,6 +309,7 @@ let
|
||||
stringLength
|
||||
substring
|
||||
isString
|
||||
replaceString
|
||||
replaceStrings
|
||||
intersperse
|
||||
concatStringsSep
|
||||
|
||||
+2
-2
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
+8
-8
@@ -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]
|
||||
+38
-6
@@ -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);
|
||||
|
||||
+47
-17
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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" ];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
];
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+111
-114
@@ -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
@@ -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
@@ -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
@@ -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}"
|
||||
)
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
'';
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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"')
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user