Compare commits

..

74 commits

Author SHA1 Message Date
59ef1e2a05
fix nixops4 for adjusted arguments 2025-09-03 09:25:07 +02:00
7f1dabe7cb
restore path-based behavior for non-data-model tests 2025-09-01 15:20:07 +02:00
253352616b
reusable TF deployment
note that, other than being easier to call, this maintains the TF
deployment's status of remaining a glorified wrapper of the SSH
deployment.
2025-09-01 15:20:07 +02:00
de19210d1d
stablize pathToRoot for TF 2025-09-01 15:20:07 +02:00
a3ffd6d23b
fix pathFromRoot to work on strings, as its removePrefix does not actually work with store versions of sub-folders 2025-09-01 15:20:07 +02:00
dd6e8850f3
stablize pathToRoot by builtins.path 2025-09-01 15:20:07 +02:00
d35de0b457
add data model test for TF 2025-09-01 15:20:07 +02:00
51b72c79c7
simplify deployment/nixos.nix 2025-09-01 15:20:07 +02:00
76a07a6cf1
split tests to allow running the faster ssh test separately 2025-09-01 15:20:07 +02:00
0aeac419e4
factor out data model 2025-09-01 15:20:07 +02:00
220fe52612
add nixops4 data model test 2025-09-01 15:20:07 +02:00
d6731bbc7d
adjust deployment type
this is a cop-out possible until
fricklerhandwerk/Fediversity#15.
after that, this will require actually figuring out how to get `options`
for `deployment.nix` - which may need `evalModules` with
`data-model.nix`.
2025-09-01 15:20:07 +02:00
a245f52e5b
restore data model with { resources } wrappers, this time working 2025-09-01 15:20:07 +02:00
2ae4ca3f68
simpler data model, not sure it's desirable but at least it's consistent 2025-09-01 15:20:06 +02:00
9907404e94
actually rely on user package from data model 2025-09-01 15:20:06 +02:00
22accb50c0
pass system 2025-09-01 15:20:06 +02:00
6e70640caa
update test 2025-09-01 15:20:06 +02:00
5ce2f2e8ed
update deployment 2025-09-01 15:20:06 +02:00
733c500cd1
simplify auth to not accept password 2025-09-01 15:20:06 +02:00
c818e55194
rename deployment to deployment-type, disambiguating from environments' deployment 2025-09-01 15:20:06 +02:00
f705e56707
fix attrTag by adding mkOption 2025-09-01 15:20:06 +02:00
0249324d86
wrap application resources to match the input of apply 2025-09-01 15:20:06 +02:00
4d348fb9cb
stylize user-specified names by quotes to clarify their status 2025-09-01 15:20:06 +02:00
06fc1e8666
fix a bug of mismatching names in data model test
matches the name of `shell` to `operator-environment`.
2025-09-01 15:20:06 +02:00
dc07eb68c3
try and use deployment 2025-09-01 15:20:06 +02:00
871384d51f
spacing 2025-09-01 15:20:06 +02:00
d37a90723f
simplify imputDerivations 2025-09-01 15:20:06 +02:00
ebfe19ab5c
unimport qemu-guest 2025-09-01 15:20:06 +02:00
95a450023a
simplify inputDerivations 2025-09-01 15:20:06 +02:00
f75fb5eec0
simplify deployment 2025-09-01 15:20:06 +02:00
1f35ca5fe8
skip is-active sshd 2025-09-01 15:20:06 +02:00
a76b3cc4a3
- auto 2025-09-01 15:20:06 +02:00
871b6bd906
move fail in 2025-09-01 15:20:06 +02:00
eec987af06
- BatchMode 2025-09-01 15:20:06 +02:00
240d68617e
rm unused ssh settings 2025-09-01 15:20:06 +02:00
c9dc6ee392
dedupe inputDerivations 2025-09-01 15:20:06 +02:00
98599cebf4
rm cowsay 2025-09-01 15:20:06 +02:00
9746ad0e92
remove unused JSON-serialized args (sources) 2025-09-01 15:20:06 +02:00
bcb0fd5318
factor out to nixos.nix 2025-09-01 15:20:06 +02:00
41b4fa6476
rm users 2025-09-01 15:20:06 +02:00
13a97eadaf
simplify grub 2025-09-01 15:20:06 +02:00
a0e330eb85
rm users 2025-09-01 15:20:06 +02:00
fe4916c854
reenable ipv6 2025-09-01 15:20:06 +02:00
1c362d83b9
reenable firewall 2025-09-01 15:20:06 +02:00
4c360d2cd9
rm comments 2025-09-01 15:20:06 +02:00
4db88cf8df
rm getty 2025-09-01 15:20:06 +02:00
e6c590b4d7
mv attempts 2025-09-01 15:20:06 +02:00
03ea2730b0
download-attempts: settle for just targetNode 2025-09-01 15:20:06 +02:00
f8b508fa43
rm comment 2025-09-01 15:20:06 +02:00
2b66f15e7c
restore imports 2025-09-01 15:20:06 +02:00
cac911a16b
dedupe nixosTest.nix 2025-09-01 15:20:06 +02:00
4249a64c10
qemu guest 2025-09-01 15:20:06 +02:00
c1897a3684
grub 2025-09-01 15:20:06 +02:00
562e511ed8
auto login 2025-09-01 15:20:06 +02:00
c7f2e2b7aa
networking 2025-09-01 15:20:06 +02:00
d363957e37
users 2025-09-01 15:20:06 +02:00
9e3c3b9ee0
handle test outcome 2025-09-01 15:20:06 +02:00
d5bd886757
specialArgs: sources 2025-09-01 15:20:06 +02:00
67484b70ee
nix in tests: download-attempts = 1 2025-09-01 15:20:06 +02:00
767ffd9f87
ensure inputs 2025-09-01 15:20:06 +02:00
80f2bbcc4d
rm paste 2025-09-01 15:20:06 +02:00
8d5c9781d5
move stuff not needed in test out 2025-09-01 15:20:06 +02:00
ea78e850af
ensure availability of needed inputs 2025-09-01 15:20:06 +02:00
2a550e6963
reduce download attempts in test 2025-09-01 15:20:06 +02:00
34a5a62ba3
settle for hello, ditching cowsay 2025-09-01 15:20:06 +02:00
c0a5f28adf
move imports from paste to targetNode to increase parity between paste and nixosTest 2025-09-01 15:20:06 +02:00
9adfb3eae9
ditch superfluous substituters 2025-09-01 15:20:06 +02:00
7d3afbb469
pasteable command for trying without rebuilding vm 2025-09-01 15:20:06 +02:00
ff5fd5047f
add keys 2025-09-01 15:20:06 +02:00
d2b5d7e607
wip: use ssh in test 2025-09-01 15:20:06 +02:00
382bcda9d2
add deployment method: ssh 2025-09-01 15:20:06 +02:00
35e49c04f4
un-nixops 2025-09-01 15:20:06 +02:00
bb79f366e9
scaffold deployment/check/data-model from ./basic
modelify
2025-09-01 15:20:06 +02:00
63c6221479
allow different deployment types 2025-09-01 15:20:06 +02:00
10 changed files with 103 additions and 175 deletions

View file

@ -1,5 +1,4 @@
on:
workflow_dispatch: # allows manual triggering
pull_request:
types:
- opened
@ -64,12 +63,6 @@ jobs:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-panel -L
check-deployment-model:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-model-ssh -L
check-deployment-model-ssh:
runs-on: native
steps:

View file

@ -105,7 +105,8 @@ let
options.enable = lib.mkEnableOption "Hello in the shell";
};
implementation = cfg: {
resources = lib.optionalAttrs cfg.enable {
input = cfg;
output.resources = lib.optionalAttrs cfg.enable {
hello.login-shell.packages.hello = pkgs.hello;
};
};
@ -138,21 +139,17 @@ let
implementation =
{
required-resources,
deployment-name,
...
}:
{
ssh-host = {
input = required-resources;
output.ssh-host = {
nixos-configuration = mkNixosConfiguration environment required-resources;
system = targetSystem;
ssh = {
username = "root";
host = nodeName;
key-file = null;
inherit sshOpts;
};
module = self;
inherit args deployment-name;
root-path = pathToRoot;
};
};
};
@ -164,7 +161,8 @@ let
...
}:
{
nixops4 =
input = required-resources;
output.nixops4 =
{ providers, ... }:
{
providers = {
@ -191,7 +189,8 @@ let
deployment-name,
}:
{
tf-host = {
input = required-resources;
output.tf-host = {
nixos-configuration = mkNixosConfiguration environment required-resources;
system = targetSystem;
ssh = {

View file

@ -58,9 +58,6 @@ in
inputs.nixops4-nixos
inputs.nixpkgs
sources.flake-parts
sources.nixpkgs
sources.flake-parts
sources.nixpkgs
sources.flake-inputs

View file

@ -1,23 +1,30 @@
{
lib,
config,
pkgs,
inputs,
...
}:
let
inherit (import ./constants.nix) pathToRoot pathFromRoot;
inherit (pkgs) system;
escapedJson = v: lib.replaceStrings [ "\"" ] [ "\\\\\"" ] (lib.strings.toJSON v);
deployment-config = {
inherit pathToRoot pathFromRoot;
inherit (config) enableAcme;
acmeNodeIP = if config.enableAcme then config.nodes.acme.networking.primaryIPAddress else null;
nodeName = "ssh";
targetSystem = system;
sshOpts = [ ];
};
deploy =
(import ../common/data-model.nix {
inherit system;
inherit
((import ../common/data-model.nix {
inherit system inputs;
config = deployment-config;
# opt not to pass `inputs`, as we could only pass serializable arguments through to its self-call
})."ssh-deployment".ssh-host.run;
})."ssh-deployment".ssh-host.ssh
)
host
username
key-file
;
in
{
_class = "nixosTest";
@ -29,10 +36,6 @@ in
sourceFileset = lib.fileset.unions [
../../data-model.nix
../../function.nix
../../nixos.nix
../../run/ssh-single-host/run.sh
../../../npins/default.nix
../../../npins/sources.json
../common/data-model.nix
../common/data-model-options.nix
./constants.nix
@ -43,7 +46,6 @@ in
{
environment.systemPackages = with pkgs; [
jq
deploy
];
system.extraDependenciesFromModule =
@ -61,7 +63,46 @@ in
with subtest("Run the deployment"):
deployer.succeed("""
${lib.getExe deploy}
set -euo pipefail
# INSTANTIATE
command=(nix-instantiate --show-trace --expr '
let
system = "${pkgs.system}"; # FIXME: what system are we deploying to?
in
import ${pathToRoot}/deployment/nixos.nix {
inherit system;
configuration = (
import ${pathToRoot}/deployment/check/common/data-model.nix {
inherit system;
config = builtins.fromJSON "${escapedJson deployment-config}";
}
)."ssh-deployment".ssh-host.nixos-configuration;
}
')
# DEPLOY
host="${lib.defaultTo "root" username}@${host}"
sshOpts=(
${if key-file == null then "" else "-i ${key-file}"}
-o StrictHostKeyChecking=no
-o "ConnectTimeout=1"
-o "ServerAliveInterval=1"
)
# instantiate the config in /nix/store
"''${command[@]}" --show-trace -A out_path
# get the realized derivation to deploy
outPath=$(nix-store --realize "$("''${command[@]}" --show-trace --eval --strict --json | jq -r '.drv_path')")
# deploy the config by nix-copy-closure
NIX_SSHOPTS="''${sshOpts[*]}" nix-copy-closure --to "$host" "$outPath" --gzip --use-substitutes
# switch the remote host to the config
output=$(ssh "''${sshOpts[@]}" "$host" "nix-env --profile /nix/var/nix/profiles/system --set $outPath; nohup $outPath/bin/switch-to-configuration switch &" 2>&1) || echo "status code: $?"
echo "output: $output"
if [[ $output != *"Timeout, server ssh not responding"* ]]; then
echo "non-timeout error: $output"
exit 1
else
exit 0
fi
""")
ssh.wait_for_unit("multi-user.target")
ssh.succeed("su - operator -c hello 1>&2")

View file

@ -10,14 +10,17 @@ let
inherit pathToRoot pathFromRoot;
nodeName = "target";
targetSystem = system;
sshOpts = [ ];
sshOpts = [
"ConnectTimeout=1"
"ServerAliveInterval=1"
];
};
deploy =
deployment =
(import ../common/data-model.nix {
inherit system;
config = deployment-config;
# opt not to pass `inputs`, as we could only pass serializable arguments through to its self-call
})."tf-deployment".tf-host.run;
})."tf-deployment".tf-host;
in
{
_class = "nixosTest";
@ -36,7 +39,6 @@ in
environment.systemPackages = with pkgs; [
(pkgs.callPackage ../../run/tf-single-host/tf.nix { })
jq
deploy
];
# needed only when building from deployer
@ -55,7 +57,7 @@ in
with subtest("ssh: Run the deployment"):
deployer.succeed("""
${lib.getExe deploy}
${deployment.run}
""")
target.wait_for_unit("multi-user.target")
target.succeed("su - operator -c hello 1>&2")

View file

@ -98,9 +98,9 @@ in
{
options.enable = lib.mkEnableOption "Hello in the shell";
};
implementation =
cfg: {
resources = lib.optionalAttrs cfg.enable {
implementation = cfg: {
input = cfg;
output.resources = lib.optionalAttrs cfg.enable {
hello.login-shell.packages.hello = pkgs.hello;
};
};
@ -110,7 +110,8 @@ in
{
resources."operator-environment".login-shell.username = "operator";
implementation = requests: {
nixops4 = (
input = requests;
output.nixops4 =
{ providers, ... }:
{
providers = {
@ -133,8 +134,7 @@ in
};
};
};
}
);
};
};
};
};

View file

@ -20,7 +20,7 @@ let
;
toBash =
v:
lib.replaceStrings [ "\"" ] [ "\\\"" ] (
lib.replaceStrings [ "\"" ] [ "\\\\\"" ] (
if lib.isPath v || builtins.isNull v then
toString v
else if lib.isString v then
@ -29,7 +29,7 @@ let
lib.strings.toJSON v
);
functionType = submodule ./function.nix;
functionType = import ./function.nix;
application-resources = submodule {
options.resources = mkOption {
# TODO: maybe transpose, and group the resources by type instead
@ -87,73 +87,12 @@ let
deployment-type = attrTag {
ssh-host = mkOption {
description = "A deployment by SSH to update a single existing NixOS host.";
type = submodule (ssh-host: {
type = submodule {
options = {
system = mkOption {
description = "The architecture of the system to deploy to.";
type = types.str;
};
inherit nixos-configuration;
ssh = host-ssh;
module = mkOption {
description = "The module to call to obtain the NixOS configuration from.";
type = types.str;
};
args = mkOption {
description = "The arguments with which to call the module to obtain the NixOS configuration.";
type = types.attrs;
};
deployment-name = mkOption {
description = "The name of the deployment for which to obtain the NixOS configuration.";
type = types.str;
};
root-path = mkOption {
description = "The path to the root of the repository.";
type = types.path;
};
run = mkOption {
type = types.package;
# error: The option `ssh-deployment.ssh-host.run' is read-only, but it's set multiple times.
# readOnly = true;
default =
let
inherit (ssh-host.config)
system
ssh
module
args
deployment-name
root-path
;
inherit (ssh)
host
username
key-file
sshOpts
;
environment = {
key_file = key-file;
deployment_name = deployment-name;
root_path = root-path;
ssh_opts = sshOpts;
inherit
system
host
username
module
args
;
deployment_type = "ssh-host";
};
in
pkgs.writeShellScriptBin "deploy-ssh.sh" ''
env ${
toString (lib.mapAttrsToList (k: v: "${k}=\"${toBash v}\"") environment)
} bash ./deployment/run/ssh-single-host/run.sh
'';
};
};
});
};
};
nixops4 = mkOption {
description = "A NixOps4 NixOS deployment. For an example, see https://github.com/nixops4/nixops4-nixos/blob/main/example/deployment.nix.";
@ -186,7 +125,7 @@ let
type = types.path;
};
run = mkOption {
type = types.package;
type = types.str;
# error: The option `tf-deployment.tf-host.run' is read-only, but it's set multiple times.
# readOnly = true;
default =
@ -221,7 +160,7 @@ let
};
tf-env = pkgs.callPackage ./run/tf-single-host/tf-env.nix { };
in
pkgs.writeShellScriptBin "deploy-ssh.sh" ''
''
env ${toString (lib.mapAttrsToList (k: v: "TF_VAR_${k}=\"${toBash v}\"") environment)} \
tf_env=${tf-env} bash ./deployment/run/tf-single-host/run.sh
'';
@ -260,7 +199,6 @@ in
type = types.optionType;
};
# TODO(@fricklerhandwerk): we may want to make the function type explicit here: `application-resources -> resource-type`
# and then also rename this to be consistent with the application's resource mapping
options.apply = mkOption {
description = "Apply the policy to a request";
type = functionTo policy.config.resource-type;
@ -294,19 +232,18 @@ in
};
resources = mkOption {
description = "Compute resources required by an application";
type = application.config.config-mapping.function-type;
type = functionTo application.config.config-mapping.output-type;
readOnly = true;
default = application.config.config-mapping.apply;
default = input: (application.config.implementation input).output;
};
# TODO(@fricklerhandwerk): this needs a better name
# TODO(@fricklerhandwerk): this needs a better name, it's just the type
config-mapping = mkOption {
description = "Function type for the mapping from application configuration to required resources";
type = functionType;
type = submodule functionType;
readOnly = true;
default = {
input-type = submodule application.config.module;
output-type = application-resources;
implementation = application.config.implementation;
};
};
};
@ -338,7 +275,7 @@ in
};
resource-mapping = mkOption {
description = "Function type for the mapping from resources to a deployment";
type = functionType;
type = submodule functionType;
readOnly = true;
default = {
input-type = submodule {
@ -352,37 +289,6 @@ in
};
};
output-type = deployment-type;
implementation = environment.config.implementation;
};
};
config-mapping = mkOption {
description = "Mapping from a configuration to a deployment";
type = functionType;
readOnly = true;
default = {
input-type = submodule {
options = {
deployment-name = mkOption {
type = types.str;
};
configuration = mkOption {
type = config.configuration;
};
};
};
output-type = deployment-type;
implementation =
{
deployment-name,
configuration,
}:
# TODO: check cfg.enable.true
let
required-resources = lib.mapAttrs (
name: application-settings: config.applications.${name}.resources application-settings
) configuration.applications;
in
environment.config.resource-mapping.apply { inherit required-resources deployment-name; };
};
};
# TODO(@fricklerhandwerk): maybe this should be a separate thing such as `fediversity-setup`,
@ -390,9 +296,21 @@ in
# then the deployments can simply be the result of the function application baked into this module.
deployment = mkOption {
description = "Generate a deployment from a configuration, by applying an environment's resource policies to the applications' resource mappings";
type = environment.config.config-mapping.function-type;
type = functionTo (environment.config.resource-mapping.output-type);
readOnly = true;
default = environment.config.config-mapping.apply;
default =
{
deployment-name,
configuration,
}:
# TODO: check cfg.enable.true
let
required-resources = lib.mapAttrs (
name: application-settings: config.applications.${name}.resources application-settings
) configuration.applications;
in
(environment.config.implementation { inherit required-resources deployment-name; }).output;
};
};
})

View file

@ -38,7 +38,7 @@
};
deployment-model-tf = import ./check/data-model-tf {
inherit (pkgs.testers) runNixOSTest;
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
};
};

View file

@ -19,11 +19,6 @@ in
type = optionType;
};
function-type = mkOption {
type = optionType;
readOnly = true;
default = functionTo config.output-type;
};
wrapper-type = mkOption {
type = optionType;
readOnly = true;
default = functionTo (submodule {
@ -37,22 +32,5 @@ in
};
});
};
implementation = mkOption {
type = config.function-type;
default = _: { };
};
wrapper = mkOption {
type = config.wrapper-type;
readOnly = true;
default = input: fn: {
inherit input;
output = config.implementation fn.config.input;
};
};
apply = mkOption {
type = config.function-type;
readOnly = true;
default = input: (config.wrapper input).output;
};
};
}

2
deployment/run/ssh-single-host/run.sh Executable file → Normal file
View file

@ -39,7 +39,7 @@ NIX_SSHOPTS="${sshOpts[*]}" nix-copy-closure --to "$destination" "$outPath" --gz
# shellcheck disable=SC2029
ssh "${sshOpts[@]}" "$destination" "nix-env --profile /nix/var/nix/profiles/system --set $outPath"
# shellcheck disable=SC2029
output=$(ssh -o "ConnectTimeout=1" -o "ServerAliveInterval=1" "${sshOpts[@]}" "$destination" "nohup $outPath/bin/switch-to-configuration switch &" 2>&1) || echo "status code: $?"
output=$(ssh "${sshOpts[@]}" "$destination" "nohup $outPath/bin/switch-to-configuration switch &" 2>&1) || echo "status code: $?"
echo "output: $output"
if [[ $output != *"Timeout, server $host not responding"* ]]; then
echo "non-timeout error: $output"