Compare commits

...
Sign in to create a new pull request.

17 commits

Author SHA1 Message Date
567368816c Automated break down of Nix flake checks for CI (#512)
picking up from #492 to fix the CI issue.

note that i left the non-flake checks duplicating with flake ones (`pre-commit`, `panel`) in on both sides to get a sense of which way might be preferable.

Co-authored-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
Reviewed-on: Fediversity/Fediversity#512
2025-09-06 17:00:11 +02:00
da77c4c8eb fix formatting (#513)
Reviewed-on: Fediversity/Fediversity#513
2025-09-04 12:36:48 +02:00
e30b052cfc ensure path availability in deployments (#511)
Reviewed-on: Fediversity/Fediversity#511
2025-09-03 17:15:35 +02:00
0f7433abde data model: add TF test (#506)
add a data model deployment test by VM using opentofu.
builds upon #505.

Reviewed-on: Fediversity/Fediversity#506
2025-09-03 15:14:40 +02:00
ccbf82d546 test data model thru VM (#505)
test deployments using data obtained through the data model in VMs.

caveats:

- SSH currently has a `run` abstraction that the nixops4 model still lacks
- the deployed (trivial) configuration on activation has not facilitated new ssh connections (for subsequent) updates, i.e. a more sophisticated configuration would be needed for real-life usage.

Reviewed-on: Fediversity/Fediversity#505
2025-09-03 14:13:13 +02:00
27f96b2504 fix data model issues introduced at #501 (#509)
Signed-off-by: Kiara Grouwstra <kiara@procolix.eu>
Reviewed-on: Fediversity/Fediversity#509
2025-09-03 14:07:53 +02:00
09db4d6217 factor out function wrapper to module function (#503)
lifts the `{ input output }` function wrapper out of the user interface to `function.nix`, simplifying usage.

Reviewed-on: Fediversity/Fediversity#503
2025-09-03 13:06:04 +02:00
c3027eb7d1 move function submodule type (#501)
Reviewed-on: Fediversity/Fediversity#501
2025-09-03 12:52:57 +02:00
4049ca8156 allow different deployment types (#498)
Reviewed-on: Fediversity/Fediversity#498
2025-09-03 12:44:44 +02:00
40e8a0a3c5 CI: allow manual trigger (#495)
Reviewed-on: Fediversity/Fediversity#495
2025-09-03 12:43:25 +02:00
f954ed5389 add proxmox repro test (#345)
closes #325

Reviewed-on: Fediversity/Fediversity#345
Reviewed-by: Nicolas Jeannerod <nicolas.jeannerod@moduscreate.com>
Co-authored-by: Kiara Grouwstra <kiara@procolix.eu>
Co-committed-by: Kiara Grouwstra <kiara@procolix.eu>
2025-09-01 14:31:50 +02:00
9d903f3ef7 Complete the data model with a runtime environment and end-to-end test (#481)
Closes #103

At last, a fully fledged data model for what Fediversity really is and does. This comes with a test that exercises a very simple but functionally complete arrangement with all ingredients fo the business logic: a dummy resource (login shell), a dummy application (`hello`, which needs a shell to live in), a dummy environment (a single NixOS VM that allows for one, the operator's, login shell), and a deployment of that environment given a dummy configuration (that enables `hello`).

The next step will be to lift this purely evaluation-level test into a VM test which verifies that the resulting VM indeed has `hello` deployed to the operator's user account.

Caveats:
- The exact naming has a bit of room for improvement, and may have diverged from the design document
- The test is not as pedantically type safe as it could be, since we simply use `types.raw` for resources such as NixOS users settings which *could* be more finely delineated

Co-authored-by: Kiara Grouwstra <kiara@procolix.eu>
Co-authored-by: kiara Grouwstra <kiara@procolix.eu>
Reviewed-on: Fediversity/Fediversity#481
Reviewed-by: kiara Grouwstra <kiara@procolix.eu>
Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
Co-committed-by: Valentin Gagarin <valentin.gagarin@tweag.io>
2025-08-27 00:45:49 +02:00
1f99a4c6c3 listToAttrs o map o attrsToListmapAttrs' (#489)
Reviewed-on: Fediversity/Fediversity#489
Reviewed-by: kiara Grouwstra <kiara@procolix.eu>
Co-authored-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
Co-committed-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
2025-08-01 13:09:26 +02:00
588bb77a94 Infra: expose and use checks for vmOptions and nixosConfigurations (#488)
Following Fediversity/Fediversity#478 (comment), here is a PR that plugs the infra's `vmOptions` and `nixosConfigurations` outputs into flake checks, instead of calling random Nix commands from the CI. There is still a bit of magic in the CI, but that's because we don't have yet a Nix-aware CI that exposes one job per flake check.

Reviewed-on: Fediversity/Fediversity#488
Reviewed-by: kiara Grouwstra <kiara@procolix.eu>
Co-authored-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
Co-committed-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
2025-07-31 15:41:02 +02:00
df3a070fa4 Infra: get rid of makeResourceModule (#485)
Reviewed-on: Fediversity/Fediversity#485
Reviewed-by: Valentin Gagarin <valentin.gagarin@tweag.io>
Reviewed-by: kiara Grouwstra <kiara@procolix.eu>
Co-authored-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
Co-committed-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
2025-07-31 00:49:00 +02:00
be72b82875 Link to upstreaming PR for lib.types.fileset (#487)
Reviewed-on: Fediversity/Fediversity#487
Reviewed-by: kiara Grouwstra <kiara@procolix.eu>
Co-authored-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
Co-committed-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
2025-07-30 13:25:22 +02:00
1b66028f32 Fix infra and add more tests (#478)
This PR contains a bunch of small fixes having to do with infra code. The goal is not to fix everything as that would require a full rewrite. Instead, we fix just what is necessary to get some testing going on. Once that is available, we will be able to work on a full refactor with more guarantees. Something of note is that most of the difficulty was to find code that would make both `nixops4 apply` _and_ `nix build .#nixosConfigurations.<machine>` happy. The takeaway is that the tests that we are adding now will not catch a whole class of tests having to do with how NixOps4 wires up the resources. Still, this is probably less significant as we are supposed to use NixOps4 every now and then.

The commits should be read separately.

Reviewed-on: Fediversity/Fediversity#478
Reviewed-by: kiara Grouwstra <kiara@procolix.eu>
Co-authored-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
Co-committed-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
2025-07-30 12:31:03 +02:00
69 changed files with 2161 additions and 267 deletions

View file

@ -1,4 +1,5 @@
on:
workflow_dispatch: # allows manual triggering
pull_request:
types:
- opened
@ -21,38 +22,8 @@ jobs:
- uses: actions/checkout@v4
- run: nix-shell --run 'nix-unit ./deployment/data-model-test.nix'
check-mastodon:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.test-mastodon-service -L
check-peertube:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.test-peertube-service -L
check-panel:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix-build -A tests.panel
check-deployment-basic:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-basic -L
check-deployment-cli:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-cli -L
check-deployment-panel:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-panel -L

View file

@ -0,0 +1,61 @@
#!/bin/sh
set -euC
cd "$(dirname "$0")" || exit 3
nix_eval () { nix eval --impure --raw --expr "with builtins; $1"; }
system=$(nix_eval "currentSystem")
checks=$(nix_eval "toJSON (attrNames (getFlake (toString ../..)).checks.$system)")
output=$(mktemp)
{
cat <<EOF
name: Nix flake checks
on:
pull_request:
types:
- opened
- synchronize
- reopened
push:
branches:
- main
jobs:
_checks:
needs: $checks
runs-on: native
steps:
- run: true
_complete:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix-shell --run '.forgejo/workflows/nix-flake-check.sh check'
EOF
for check in $(echo "$checks" | jq -r .[]); do
cat <<EOF
$check:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.$system.$check -vL
EOF
done
} >| "$output"
target=$(basename "$0" .sh).yaml
if [ $# -eq 1 ] && [ "$1" = "check" ]; then
if ! diff_output=$(diff --color=always "$target" "$output"); then
printf >&2 'Changes detected (\e[31m< current\e[0m | \e[32m> generated\e[0m):\n%s\n' "$diff_output"
exit 1
fi
else
mv "$output" "$target"
fi

View file

@ -0,0 +1,294 @@
name: Nix flake checks
on:
pull_request:
types:
- opened
- synchronize
- reopened
push:
branches:
- main
jobs:
_checks:
needs: ["deployment-basic","deployment-cli","deployment-model-nixops4","deployment-model-ssh","deployment-model-tf","deployment-panel","nixops-deployment-providers-default","nixops-deployment-providers-fedi200","nixops-deployment-providers-fedi201","nixops-deployment-providers-forgejo-ci","nixops-deployment-providers-test","nixops-deployment-providers-vm02116","nixops-deployment-providers-vm02187","nixosConfigurations-fedi200","nixosConfigurations-fedi201","nixosConfigurations-forgejo-ci","nixosConfigurations-test01","nixosConfigurations-test02","nixosConfigurations-test03","nixosConfigurations-test04","nixosConfigurations-test05","nixosConfigurations-test06","nixosConfigurations-test11","nixosConfigurations-test12","nixosConfigurations-test13","nixosConfigurations-test14","nixosConfigurations-vm02116","nixosConfigurations-vm02187","panel","pre-commit","proxmox-basic","test-mastodon-service","test-peertube-service","vmOptions-fedi200","vmOptions-fedi201","vmOptions-test01","vmOptions-test02","vmOptions-test03","vmOptions-test04","vmOptions-test05","vmOptions-test06","vmOptions-test11","vmOptions-test12","vmOptions-test13","vmOptions-test14"]
runs-on: native
steps:
- run: true
_complete:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix-shell --run '.forgejo/workflows/nix-flake-check.sh check'
deployment-basic:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-basic -vL
deployment-cli:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-cli -vL
deployment-model-nixops4:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-model-nixops4 -vL
deployment-model-ssh:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-model-ssh -vL
deployment-model-tf:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-model-tf -vL
deployment-panel:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.deployment-panel -vL
nixops-deployment-providers-default:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-default -vL
nixops-deployment-providers-fedi200:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-fedi200 -vL
nixops-deployment-providers-fedi201:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-fedi201 -vL
nixops-deployment-providers-forgejo-ci:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-forgejo-ci -vL
nixops-deployment-providers-test:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-test -vL
nixops-deployment-providers-vm02116:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-vm02116 -vL
nixops-deployment-providers-vm02187:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixops-deployment-providers-vm02187 -vL
nixosConfigurations-fedi200:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-fedi200 -vL
nixosConfigurations-fedi201:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-fedi201 -vL
nixosConfigurations-forgejo-ci:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-forgejo-ci -vL
nixosConfigurations-test01:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test01 -vL
nixosConfigurations-test02:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test02 -vL
nixosConfigurations-test03:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test03 -vL
nixosConfigurations-test04:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test04 -vL
nixosConfigurations-test05:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test05 -vL
nixosConfigurations-test06:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test06 -vL
nixosConfigurations-test11:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test11 -vL
nixosConfigurations-test12:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test12 -vL
nixosConfigurations-test13:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test13 -vL
nixosConfigurations-test14:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-test14 -vL
nixosConfigurations-vm02116:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-vm02116 -vL
nixosConfigurations-vm02187:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.nixosConfigurations-vm02187 -vL
panel:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.panel -vL
pre-commit:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.pre-commit -vL
proxmox-basic:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.proxmox-basic -vL
test-mastodon-service:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.test-mastodon-service -vL
test-peertube-service:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.test-peertube-service -vL
vmOptions-fedi200:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-fedi200 -vL
vmOptions-fedi201:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-fedi201 -vL
vmOptions-test01:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test01 -vL
vmOptions-test02:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test02 -vL
vmOptions-test03:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test03 -vL
vmOptions-test04:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test04 -vL
vmOptions-test05:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test05 -vL
vmOptions-test06:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test06 -vL
vmOptions-test11:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test11 -vL
vmOptions-test12:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test12 -vL
vmOptions-test13:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test13 -vL
vmOptions-test14:
runs-on: native
steps:
- uses: actions/checkout@v4
- run: nix build .#checks.x86_64-linux.vmOptions-test14 -vL

View file

@ -11,7 +11,8 @@ let
;
inherit (pkgs) lib;
inherit (import sources.flake-inputs) import-flake;
inherit ((import-flake { src = ./.; }).inputs) nixops4;
inputs = (import-flake { src = ./.; }).inputs;
inherit (inputs) nixops4;
panel = import ./panel { inherit sources system; };
pre-commit-check =
(import "${git-hooks}/nix" {
@ -64,6 +65,7 @@ in
pkgs.openssh
pkgs.httpie
pkgs.jq
pkgs.diffutils
pkgs.nix-unit
test-loop
nixops4.packages.${system}.default
@ -78,6 +80,7 @@ in
# re-export inputs so they can be overridden granularly
# (they can't be accessed from the outside any other way)
inherit
inputs
sources
system
pkgs

View file

@ -5,4 +5,5 @@
];
pathToRoot = ../../..;
pathFromRoot = ./.;
useFlake = true;
}

View file

@ -10,5 +10,10 @@ runNixOSTest {
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix) targetMachines pathToRoot pathFromRoot;
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
useFlake
;
}

View file

@ -1,4 +1,9 @@
{ inputs, lib, ... }:
{
inputs,
lib,
config,
...
}:
{
_class = "nixosTest";
@ -8,6 +13,7 @@
sourceFileset = lib.fileset.unions [
./constants.nix
./deployment.nix
(config.pathToCwd + "/flake-under-test.nix")
];
nodes.deployer =

View file

@ -8,4 +8,5 @@
pathToRoot = ../../..;
pathFromRoot = ./.;
enableAcme = true;
useFlake = true;
}

View file

@ -15,5 +15,6 @@ runNixOSTest {
pathToRoot
pathFromRoot
enableAcme
useFlake
;
}

View file

@ -1,6 +1,7 @@
{
inputs,
hostPkgs,
config,
lib,
...
}:
@ -19,6 +20,7 @@ in
sourceFileset = lib.fileset.unions [
./constants.nix
./deployments.nix
(config.pathToCwd + "/flake-under-test.nix")
# REVIEW: I would like to be able to grab all of `/deployment` minus
# `/deployment/check`, but I can't because there is a bunch of other files

View file

@ -0,0 +1,25 @@
{
lib,
...
}:
let
inherit (lib) mkOption types;
in
{
options = {
host = mkOption {
type = types.str;
description = "name of the host to deploy to";
};
targetSystem = mkOption {
type = types.str;
description = "name of the host to deploy to";
};
sshOpts = mkOption {
description = "Extra SSH options (`-o`) to use.";
type = types.listOf types.str;
default = [ ];
example = "ConnectTimeout=60";
};
};
}

View file

@ -0,0 +1,256 @@
{
config,
system,
inputs ? (import ../../../default.nix { }).inputs, # XXX can't be serialized
sources ? import ../../../npins,
...
}@args:
let
# having this module's location (`self`) and (serializable) `args`, we know
# enough to make it re-call itself to extract different info elsewhere later.
# we use this to make a deployment script using the desired nixos config,
# which would otherwise not be serializable, while nix also makes it hard to
# produce its derivation to pass thru without a `nix-instantiate` call,
# which in turn would need to be passed the (unserializable) nixos config.
self = "deployment/check/common/data-model.nix";
inherit (sources) nixpkgs;
pkgs = import nixpkgs { inherit system; };
inherit (pkgs) lib;
deployment-config = config;
inherit (deployment-config)
nodeName
pathToRoot
targetSystem
sshOpts
;
inherit (lib) mkOption types;
eval =
module:
(lib.evalModules {
specialArgs = {
inherit pkgs inputs;
};
modules = [
module
../../data-model.nix
];
}).config;
fediversity = eval (
{ config, ... }:
{
config = {
resources.login-shell = {
description = "The operator needs to be able to log into the shell";
request =
{ ... }:
{
_class = "fediversity-resource-request";
options = {
wheel = mkOption {
description = "Whether the login user needs root permissions";
type = types.bool;
default = false;
};
packages = mkOption {
description = "Packages that need to be available in the user environment";
type = with types; attrsOf package;
};
};
};
policy =
{ config, ... }:
{
_class = "fediversity-resource-policy";
options = {
username = mkOption {
description = "Username for the operator";
type = types.str; # TODO: use the proper constraints from NixOS
};
wheel = mkOption {
description = "Whether to allow login with root permissions";
type = types.bool;
default = false;
};
};
config = {
resource-type = types.raw; # TODO: splice out the user type from NixOS
apply =
requests:
let
# Filter out requests that need wheel if policy doesn't allow it
validRequests = lib.filterAttrs (
_name: req: !req.login-shell.wheel || config.wheel
) requests.resources;
in
lib.optionalAttrs (validRequests != { }) {
${config.username} = {
isNormalUser = true;
packages =
with lib;
attrValues (concatMapAttrs (_name: request: request.login-shell.packages) validRequests);
extraGroups = lib.optional config.wheel "wheel";
};
};
};
};
};
applications.hello =
{ ... }:
{
description = ''Command-line tool that will print "Hello, world!" on the terminal'';
module =
{ ... }:
{
options.enable = lib.mkEnableOption "Hello in the shell";
};
implementation = cfg: {
resources = lib.optionalAttrs cfg.enable {
hello.login-shell.packages.hello = pkgs.hello;
};
};
};
environments =
let
mkNixosConfiguration =
environment: requests:
{ ... }:
{
imports = [
./data-model-options.nix
../common/sharedOptions.nix
../common/targetNode.nix
"${nixpkgs}/nixos/modules/profiles/qemu-guest.nix"
];
users.users = environment.config.resources."operator-environment".login-shell.apply {
resources = lib.filterAttrs (_name: value: value ? login-shell) (
lib.concatMapAttrs (
k': req: lib.mapAttrs' (k: lib.nameValuePair "${k'}.${k}") req.resources
) requests
);
};
};
in
{
single-nixos-vm-ssh = environment: {
resources."operator-environment".login-shell.username = "operator";
implementation =
{
required-resources,
deployment-name,
}:
{
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;
};
};
};
single-nixos-vm-nixops4 = environment: {
resources."operator-environment".login-shell.username = "operator";
implementation =
{
required-resources,
...
}:
{
nixops4 =
{ providers, ... }:
{
providers = {
inherit (inputs.nixops4.modules.nixops4Provider) local;
};
resources.${nodeName} = {
type = providers.local.exec;
imports = [
inputs.nixops4-nixos.modules.nixops4Resource.nixos
../common/targetResource.nix
];
nixos.module = mkNixosConfiguration environment required-resources;
_module.args = { inherit inputs sources; };
inherit (deployment-config) nodeName pathToRoot pathFromRoot;
};
};
};
};
single-nixos-vm-tf = environment: {
resources."operator-environment".login-shell.username = "operator";
implementation =
{
required-resources,
deployment-name,
}:
{
tf-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;
};
};
};
};
};
options = {
"example-configuration" = mkOption {
type = config.configuration;
default = {
enable = true;
applications.hello.enable = true;
};
};
"ssh-deployment" =
let
env = config.environments."single-nixos-vm-ssh";
in
mkOption {
type = env.resource-mapping.output-type;
default = env.deployment {
deployment-name = "ssh-deployment";
configuration = config."example-configuration";
};
};
"nixops4-deployment" =
let
env = config.environments."single-nixos-vm-nixops4";
in
mkOption {
type = env.resource-mapping.output-type;
default = env.deployment {
deployment-name = "nixops4-deployment";
configuration = config."example-configuration";
};
};
"tf-deployment" =
let
env = config.environments."single-nixos-vm-tf";
in
mkOption {
type = env.resource-mapping.output-type;
default = env.deployment {
deployment-name = "tf-deployment";
configuration = config."example-configuration";
};
};
};
}
);
in
fediversity

View file

@ -59,6 +59,7 @@ in
inputs.nixpkgs
sources.flake-parts
sources.nixpkgs
sources.flake-inputs
sources.git-hooks

View file

@ -48,7 +48,8 @@ in
extraTestScript = mkOption { };
sourceFileset = mkOption {
## REVIEW: Upstream to nixpkgs?
## FIXME: grab `lib.types.fileset` from NixOS, once upstreaming PR
## https://github.com/NixOS/nixpkgs/pull/428293 lands.
type = types.mkOptionType {
name = "fileset";
description = "fileset";
@ -75,8 +76,6 @@ in
./sharedOptions.nix
./targetNode.nix
./targetResource.nix
(config.pathToCwd + "/flake-under-test.nix")
];
acmeNodeIP = config.nodes.acme.networking.primaryIPAddress;
@ -163,31 +162,38 @@ in
deployer.succeed(f"echo '{host_key}' > ${config.pathFromRoot}/${tm}_host_key.pub")
'')}
## NOTE: This is super slow. It could probably be optimised in Nix, for
## instance by allowing to grab things directly from the host's store.
##
## NOTE: We use the repository as-is (cf `src` above), overriding only
## `flake.nix` by our `flake-under-test.nix`. We also override the flake
## lock file to use locally available inputs, as we cannot download them.
##
with subtest("Override the flake and its lock"):
deployer.succeed("cp ${config.pathFromRoot}/flake-under-test.nix flake.nix")
deployer.succeed("""
nix flake lock --extra-experimental-features 'flakes nix-command' \
--offline -v \
--override-input nixops4 ${inputs.nixops4.packages.${system}.flake-in-a-bottle} \
\
--override-input nixops4-nixos ${inputs.nixops4-nixos} \
--override-input nixops4-nixos/flake-parts ${inputs.nixops4-nixos.inputs.flake-parts} \
--override-input nixops4-nixos/flake-parts/nixpkgs-lib ${inputs.nixops4-nixos.inputs.flake-parts.inputs.nixpkgs-lib} \
--override-input nixops4-nixos/nixops4-nixos ${emptyFlake} \
--override-input nixops4-nixos/nixpkgs ${inputs.nixops4-nixos.inputs.nixpkgs} \
--override-input nixops4-nixos/nixops4 ${
inputs.nixops4-nixos.inputs.nixops4.packages.${system}.flake-in-a-bottle
} \
--override-input nixops4-nixos/git-hooks-nix ${emptyFlake} \
;
""")
${
if config.useFlake then
''
## NOTE: This is super slow. It could probably be optimised in Nix, for
## instance by allowing to grab things directly from the host's store.
##
## NOTE: We use the repository as-is (cf `src` above), overriding only
## `flake.nix` by our `flake-under-test.nix`. We also override the flake
## lock file to use locally available inputs, as we cannot download them.
##
with subtest("Override the flake and its lock"):
deployer.succeed("cp ${config.pathFromRoot}/flake-under-test.nix flake.nix")
deployer.succeed("""
nix flake lock --extra-experimental-features 'flakes nix-command' \
--offline -v \
--override-input nixops4 ${inputs.nixops4.packages.${system}.flake-in-a-bottle} \
\
--override-input nixops4-nixos ${inputs.nixops4-nixos} \
--override-input nixops4-nixos/flake-parts ${inputs.nixops4-nixos.inputs.flake-parts} \
--override-input nixops4-nixos/flake-parts/nixpkgs-lib ${inputs.nixops4-nixos.inputs.flake-parts.inputs.nixpkgs-lib} \
--override-input nixops4-nixos/nixops4-nixos ${emptyFlake} \
--override-input nixops4-nixos/nixpkgs ${inputs.nixops4-nixos.inputs.nixpkgs} \
--override-input nixops4-nixos/nixops4 ${
inputs.nixops4-nixos.inputs.nixops4.packages.${system}.flake-in-a-bottle
} \
--override-input nixops4-nixos/git-hooks-nix ${emptyFlake} \
;
""")
''
else
""
}
${optionalString config.enableAcme ''
with subtest("Set up handmade DNS"):

View file

@ -32,11 +32,11 @@ in
};
pathFromRoot = mkOption {
type = types.path;
type = types.either types.path types.str;
description = ''
Path from the root of the repository to the working directory.
'';
apply = x: lib.path.removePrefix config.pathToRoot x;
apply = x: if lib.isString x then x else lib.path.removePrefix config.pathToRoot x;
};
pathToCwd = mkOption {
@ -64,5 +64,7 @@ in
during the test to the correct value.
'';
};
useFlake = lib.mkEnableOption "Use a flake in the test.";
};
}

View file

@ -28,6 +28,8 @@ in
system.switch.enable = true;
nix = {
# short-cut network time-outs
settings.download-attempts = 1;
## Not used; save a large copy operation
channel.enable = false;
registry = lib.mkForce { };

View file

@ -0,0 +1,9 @@
{
targetMachines = [
"nixops4"
];
pathToRoot = ../../..;
pathFromRoot = ./.;
enableAcme = true;
useFlake = true;
}

View file

@ -0,0 +1,22 @@
{
runNixOSTest,
inputs,
sources,
}:
runNixOSTest {
imports = [
../../data-model.nix
../../function.nix
../common/nixosTest.nix
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
useFlake
;
}

View file

@ -0,0 +1,29 @@
{
inputs = {
nixops4.follows = "nixops4-nixos/nixops4";
nixops4-nixos.url = "github:nixops4/nixops4-nixos";
};
outputs =
inputs:
import ./mkFlake.nix inputs (
{ inputs, ... }:
let
system = "x86_64-linux";
in
{
imports = [
inputs.nixops4.modules.flake.default
];
nixops4Deployments.check-deployment-model =
(import ./deployment/check/common/data-model.nix {
inherit system inputs;
config = {
inherit (import ./deployment/check/data-model-nixops4/constants.nix) pathToRoot pathFromRoot;
nodeName = "nixops4";
};
})."nixops4-deployment".nixops4;
}
);
}

View file

@ -0,0 +1,52 @@
{
lib,
config,
inputs,
...
}:
{
_class = "nixosTest";
imports = [
../common/data-model-options.nix
];
name = "deployment-model";
sourceFileset = lib.fileset.unions [
../../data-model.nix
../../function.nix
../common/data-model.nix
../common/data-model-options.nix
./constants.nix
(config.pathToCwd + "/flake-under-test.nix")
];
nodes.deployer =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
inputs.nixops4.packages.${system}.default
jq
];
# FIXME: sad times
system.extraDependencies = with pkgs; [
jq
jq.inputDerivation
];
system.extraDependenciesFromModule =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
hello
];
};
};
extraTestScript = ''
with subtest("nixops4"):
nixops4.fail("hello 1>&2")
deployer.succeed("nixops4 apply check-deployment-model --show-trace --verbose --no-interactive 1>&2")
nixops4.succeed("su - operator -c hello 1>&2")
'';
}

View file

@ -0,0 +1,12 @@
{
targetMachines = [
"ssh"
];
# stablize path, as just the path would yield distinct paths when applied multiple times
pathToRoot = builtins.path {
path = ../../..;
name = "root";
};
pathFromRoot = "/deployment/check/data-model-ssh";
enableAcme = true;
}

View file

@ -0,0 +1,21 @@
{
runNixOSTest,
inputs,
sources,
}:
runNixOSTest {
imports = [
../../data-model.nix
../../function.nix
../common/nixosTest.nix
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
;
}

View file

@ -0,0 +1,68 @@
{
lib,
pkgs,
...
}:
let
inherit (import ./constants.nix) pathToRoot pathFromRoot;
inherit (pkgs) system;
deployment-config = {
inherit pathToRoot pathFromRoot;
nodeName = "ssh";
targetSystem = system;
sshOpts = [ ];
};
deploy =
(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
})."ssh-deployment".ssh-host.run;
in
{
_class = "nixosTest";
imports = [
../common/data-model-options.nix
];
name = "deployment-model";
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
];
nodes.deployer =
{ ... }:
{
environment.systemPackages = [
deploy
];
system.extraDependenciesFromModule =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
hello
];
};
};
extraTestScript = ''
with subtest("Check the status before deployment"):
ssh.fail("hello 1>&2")
with subtest("Run the deployment"):
deployer.succeed("""
${lib.getExe deploy}
""")
ssh.wait_for_unit("multi-user.target")
ssh.succeed("su - operator -c hello 1>&2")
'';
}

View file

@ -0,0 +1,11 @@
{
targetMachines = [
"target"
];
pathToRoot = builtins.path {
path = ../../..;
name = "root";
};
pathFromRoot = "/deployment/check/data-model-tf";
enableAcme = true;
}

View file

@ -0,0 +1,21 @@
{
runNixOSTest,
inputs,
sources,
}:
runNixOSTest {
imports = [
../../data-model.nix
../../function.nix
../common/nixosTest.nix
./nixosTest.nix
];
_module.args = { inherit inputs sources; };
inherit (import ./constants.nix)
targetMachines
pathToRoot
pathFromRoot
enableAcme
;
}

View file

@ -0,0 +1,61 @@
{
lib,
pkgs,
...
}:
let
inherit (import ./constants.nix) pathToRoot pathFromRoot;
inherit (pkgs) system;
deployment-config = {
inherit pathToRoot pathFromRoot;
nodeName = "target";
targetSystem = system;
sshOpts = [ ];
};
deploy =
(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;
in
{
_class = "nixosTest";
imports = [
../common/data-model-options.nix
];
name = "deployment-model";
sourceFileset = lib.fileset.unions [
../../run/tf-single-host/run.sh
];
nodes.deployer =
{ ... }:
{
environment.systemPackages = [
deploy
];
# needed only when building from deployer
system.extraDependenciesFromModule =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
hello
];
};
};
extraTestScript = ''
with subtest("Check the status before deployment"):
target.fail("hello 1>&2")
with subtest("Run the deployment"):
deployer.succeed("""
${lib.getExe deploy}
""")
target.wait_for_unit("multi-user.target")
target.succeed("su - operator -c hello 1>&2")
'';
}

View file

@ -8,4 +8,5 @@
pathToRoot = ../../..;
pathFromRoot = ./.;
enableAcme = true;
useFlake = true;
}

View file

@ -15,5 +15,6 @@ runNixOSTest {
pathToRoot
pathFromRoot
enableAcme
useFlake
;
}

View file

@ -128,6 +128,7 @@ in
sourceFileset = lib.fileset.unions [
./constants.nix
./deployment.nix
(config.pathToCwd + "/flake-under-test.nix")
# REVIEW: I would like to be able to grab all of `/deployment` minus
# `/deployment/check`, but I can't because there is a bunch of other files

View file

@ -0,0 +1,37 @@
{
runNixOSTest,
sources,
system,
}:
let
pkgs = import sources.nixpkgs-stable {
inherit system;
overlays = [ overlay ];
};
overlay = _: _: {
inherit
(import "${sources.proxmox-nixos}/pkgs" {
craneLib = pkgs.callPackage "${sources.crane}/lib" { };
# breaks from https://github.com/NixOS/nixpkgs/commit/06b354eb2dc535c57e9b4caaa16d79168f117a26,
# which updates libvncserver to 0.9.15, which was not yet patched at https://git.proxmox.com/?p=vncterm.git.
inherit pkgs;
# not so picky about version for our purposes
pkgs-unstable = pkgs;
})
proxmox-ve
pve-ha-manager
;
};
in
runNixOSTest {
node.specialArgs = {
inherit
sources
pkgs
;
};
imports = [
./proxmoxTest.nix
];
}

View file

@ -0,0 +1,87 @@
# https://github.com/SaumonNet/proxmox-nixos/blob/main/tests/vm.nix
{
pkgs,
...
}:
let
# tracking non-tarball downloads seems unsupported still in npins:
# https://github.com/andir/npins/issues/163
minimalIso = pkgs.fetchurl {
url = "https://releases.nixos.org/nixos/24.05/nixos-24.05.7139.bcba2fbf6963/nixos-minimal-24.05.7139.bcba2fbf6963-x86_64-linux.iso";
hash = "sha256-plre/mIHdIgU4xWU+9xErP+L4i460ZbcKq8iy2n4HT8=";
};
in
{
name = "proxmox-basic";
nodes.mypve =
{ sources, ... }:
{
imports = [
"${sources.proxmox-nixos}/modules/proxmox-ve"
];
services.proxmox-ve = {
enable = true;
ipAddress = "192.168.1.1";
vms = {
myvm1 = {
vmid = 100;
memory = 1024;
cores = 1;
sockets = 1;
kvm = true;
scsi = [ { file = "local:16"; } ];
cdrom = "local:iso/minimal.iso";
};
};
};
virtualisation = {
additionalPaths = [ minimalIso ];
diskSize = 4096;
memorySize = 2048;
};
};
testScript = ''
machine.start()
machine.wait_for_unit("pveproxy.service")
assert "running" in machine.succeed("pveproxy status")
# Copy Iso
machine.succeed("mkdir -p /var/lib/vz/template/iso/")
machine.succeed("cp ${minimalIso} /var/lib/vz/template/iso/minimal.iso")
# Declarative VM creation
machine.wait_for_unit("multi-user.target")
machine.succeed("qm stop 100 --timeout 0")
# Seabios VM creation
machine.succeed(
"qm create 101 --kvm 0 --bios seabios -cdrom local:iso/minimal.iso",
"qm start 101",
"qm stop 101 --timeout 0"
)
# Legacy ovmf vm creation
machine.succeed(
"qm create 102 --kvm 0 --bios ovmf -cdrom local:iso/minimal.iso",
"qm start 102",
"qm stop 102 --timeout 0"
)
# UEFI ovmf vm creation
machine.succeed(
"qm create 103 --kvm 0 --bios ovmf --efidisk0 local:4,efitype=4m -cdrom local:iso/minimal.iso",
"qm start 103",
"qm stop 103 --timeout 0"
)
# UEFI ovmf vm creation with secure boot
machine.succeed(
"qm create 104 --kvm 0 --bios ovmf --efidisk0 local:4,efitype=4m,pre-enrolled-keys=1 -cdrom local:iso/minimal.iso",
"qm start 104",
"qm stop 104 --timeout 0"
)
'';
}

View file

@ -1,29 +1,94 @@
let
inherit (import ../default.nix { }) pkgs inputs;
inherit (pkgs) lib;
inherit (lib) mkOption;
inherit (lib) mkOption types;
eval =
module:
(lib.evalModules {
specialArgs = {
inherit inputs;
inherit pkgs inputs;
};
modules = [
module
./data-model.nix
];
}).config;
inherit (inputs.nixops4.lib) mkDeployment;
in
{
_class = "nix-unit";
test-eval = {
/**
This tests a very simple arrangement that features all ingredients of the Fediversity business logic:
application, resource, environment, deployment; and wires it all up in one end-to-end exercise.
- The dummy resource is a login shell made available for some user.
- The dummy application is `hello` that requires a shell to be deployed.
- The dummy environment is a single NixOS VM that hosts one login shell, for the operator.
- The dummy configuration enables the `hello` application.
This will produce a NixOps4 deployment for a NixOS VM with a login shell for the operator and `hello` available.
*/
expr =
let
fediversity = eval (
{ config, ... }:
{
config = {
resources.login-shell = {
description = "The operator needs to be able to log into the shell";
request =
{ ... }:
{
_class = "fediversity-resource-request";
options = {
wheel = mkOption {
description = "Whether the login user needs root permissions";
type = types.bool;
default = false;
};
packages = mkOption {
description = "Packages that need to be available in the user environment";
type = with types; attrsOf package;
};
};
};
policy =
{ config, ... }:
{
_class = "fediversity-resource-policy";
options = {
username = mkOption {
description = "Username for the operator";
type = types.str; # TODO: use the proper constraints from NixOS
};
wheel = mkOption {
description = "Whether to allow login with root permissions";
type = types.bool;
default = false;
};
};
config = {
resource-type = types.raw; # TODO: splice out the user type from NixOS
apply =
requests:
let
# Filter out requests that need wheel if policy doesn't allow it
validRequests = lib.filterAttrs (
_name: req: !req.login-shell.wheel || config.wheel
) requests.resources;
in
lib.optionalAttrs (validRequests != { }) {
${config.username} = {
isNormalUser = true;
packages =
with lib;
attrValues (concatMapAttrs (_name: request: request.login-shell.packages) validRequests);
extraGroups = lib.optional config.wheel "wheel";
};
};
};
};
};
applications.hello =
{ ... }:
{
@ -31,19 +96,49 @@ in
module =
{ ... }:
{
options = {
enable = lib.mkEnableOption "Hello in the shell";
};
options.enable = lib.mkEnableOption "Hello in the shell";
};
implementation =
cfg:
lib.optionalAttrs cfg.enable {
dummy.login-shell.packages.hello = pkgs.hello;
implementation = cfg: {
resources = lib.optionalAttrs cfg.enable {
hello.login-shell.packages.hello = pkgs.hello;
};
};
};
environments.single-nixos-vm =
{ config, ... }:
{
resources."operator-environment".login-shell.username = "operator";
implementation = requests: {
nixops4 = (
{ providers, ... }:
{
providers = {
inherit (inputs.nixops4.modules.nixops4Provider) local;
};
resources.the-machine = {
type = providers.local.exec;
imports = [
inputs.nixops4-nixos.modules.nixops4Resource.nixos
];
nixos.module =
{ ... }:
{
users.users = config.resources."operator-environment".login-shell.apply {
resources = lib.filterAttrs (_name: value: value ? login-shell) (
lib.concatMapAttrs (
k': req: lib.mapAttrs' (k: lib.nameValuePair "${k'}.${k}") req.resources
) requests
);
};
};
};
}
);
};
};
};
options = {
example-configuration = mkOption {
"example-configuration" = mkOption {
type = config.configuration;
readOnly = true;
default = {
@ -51,20 +146,66 @@ in
applications.hello.enable = true;
};
};
"example-deployment" = mkOption {
type = config.environments.single-nixos-vm.resource-mapping.output-type;
readOnly = true;
default = config.environments.single-nixos-vm.deployment config."example-configuration";
};
};
}
);
resources =
fediversity.applications.hello.resources
fediversity."example-configuration".applications.hello;
hello-shell = resources.resources.hello.login-shell;
environment = fediversity.environments.single-nixos-vm.resources."operator-environment".login-shell;
result = mkDeployment {
modules = [
(fediversity.environments.single-nixos-vm.deployment fediversity."example-configuration")
];
};
in
{
inherit (fediversity)
example-configuration
;
number-of-resources = with lib; length (attrNames fediversity.resources);
inherit (fediversity) example-configuration;
hello-package-exists = hello-shell.packages ? hello;
wheel-required = hello-shell.wheel;
wheel-allowed = environment.wheel;
operator-shell =
let
operator = (environment.apply resources).operator;
in
{
inherit (operator) isNormalUser;
packages = map (p: "${p.pname}") operator.packages;
extraGroups = operator.extraGroups;
};
deployment = {
inherit (result) _type;
deploymentFunction = lib.isFunction result.deploymentFunction;
getProviders = lib.isFunction result.getProviders;
};
};
expected = {
number-of-resources = 1;
example-configuration = {
enable = true;
applications.hello.enable = true;
};
hello-package-exists = true;
wheel-required = false;
wheel-allowed = false;
operator-shell = {
isNormalUser = true;
packages = [ "hello" ];
extraGroups = [ ];
};
deployment = {
_type = "nixops4Deployment";
deploymentFunction = true;
getProviders = true;
};
};
};
}

View file

@ -1,33 +1,303 @@
{
lib,
config,
inputs,
pkgs,
sources ? import ../npins,
...
}:
let
inherit (lib) mkOption types;
inherit (lib.types)
attrsOf
attrTag
attrsOf
deferredModuleWith
submodule
optionType
functionTo
nullOr
optionType
raw
str
submodule
;
toBash =
v:
lib.replaceStrings [ "\"" ] [ "\\\"" ] (
if lib.isPath v || builtins.isNull v then
toString v
else if lib.isString v then
v
else
lib.strings.toJSON v
);
functionType = import ./function.nix;
application-resources = {
functionType = submodule ./function.nix;
application-resources = submodule {
options.resources = mkOption {
# TODO: maybe transpose, and group the resources by type instead
type = attrsOf (
attrTag (lib.mapAttrs (_name: resource: mkOption { type = resource.request; }) config.resources)
attrTag (
lib.mapAttrs (_name: resource: mkOption { type = submodule resource.request; }) config.resources
)
);
};
};
nixops4Deployment = types.deferredModuleWith {
staticModules = [
inputs.nixops4.modules.nixops4Deployment.default
{
_class = "nixops4Deployment";
_module.args = {
resourceProviderSystem = pkgs.system;
resources = { };
};
}
];
};
nixos-configuration = mkOption {
description = "A NixOS configuration.";
type = raw;
};
host-ssh = mkOption {
description = "SSH connection info to connect to a single host.";
type = submodule {
options = {
host = mkOption {
description = "the host to access by SSH";
type = str;
};
username = mkOption {
description = "the SSH user to use";
type = nullOr str;
default = null;
};
key-file = mkOption {
description = "path to the user's SSH private key";
type = nullOr str;
example = "/root/.ssh/id_ed25519";
};
sshOpts = mkOption {
description = "Extra SSH options (`-o`) to use.";
type = types.listOf str;
default = [ ];
example = "ConnectTimeout=60";
};
};
};
};
deployment-type = attrTag {
ssh-host = mkOption {
description = "A deployment by SSH to update a single existing NixOS host.";
type = submodule (ssh-host: {
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.writers.writeBashBin "deploy-sh.sh"
{
makeWrapperArgs = [
"--prefix"
"PATH"
":"
"${lib.makeBinPath [
pkgs.jq
]}"
];
}
''
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.";
type = nixops4Deployment;
};
tf-host = mkOption {
description = "A Terraform deployment by SSH to update a single existing NixOS host.";
type = submodule (tf-host: {
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 `tf-deployment.tf-host.run' is read-only, but it's set multiple times.
# readOnly = true;
default =
let
inherit (tf-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 = "tf-host";
};
tf-env = pkgs.callPackage ./run/tf-single-host/tf-env.nix { };
in
pkgs.writers.writeBashBin "deploy-tf.sh"
{
makeWrapperArgs = [
"--prefix"
"PATH"
":"
"${lib.makeBinPath [
pkgs.jq
(pkgs.callPackage ./run/tf-single-host/tf.nix { inherit sources; })
]}"
];
}
''
env ${toString (lib.mapAttrsToList (k: v: "TF_VAR_${k}=\"${toBash v}\"") environment)} \
tf_env=${tf-env} bash ./deployment/run/tf-single-host/run.sh
'';
};
};
});
};
};
in
{
_class = "nixops4Deployment";
options = {
resources = mkOption {
description = "Collection of deployment resources that can be required by applications and policed by hosting providers";
type = attrsOf (
submodule (
{ ... }:
{
_class = "fediversity-resource";
options = {
description = mkOption {
description = "Description of the resource to help application module authors and hosting providers to work with it";
type = types.str;
};
request = mkOption {
description = "Options for declaring resource requirements by an application, a description of how the resource is consumed or accessed";
type = deferredModuleWith { staticModules = [ { _class = "fediversity-resource-request"; } ]; };
};
policy = mkOption {
description = "Options for configuring the resource policy for the hosting provider, a description of how the resource is made available";
type = deferredModuleWith {
staticModules = [
(policy: {
_class = "fediversity-resource-policy";
options.resource-type = mkOption {
description = "The type of resource this policy configures";
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;
};
})
];
};
};
};
}
)
);
};
applications = mkOption {
description = "Collection of Fediversity applications";
type = attrsOf (
@ -48,23 +318,110 @@ in
};
resources = mkOption {
description = "Compute resources required by an application";
type = functionTo application.config.config-mapping.output-type;
type = application.config.config-mapping.function-type;
readOnly = true;
default = input: (application.config.implementation input).output;
default = application.config.config-mapping.apply;
};
# TODO(@fricklerhandwerk): this needs a better name
config-mapping = mkOption {
description = "Function type for the mapping from application configuration to required resources";
type = submodule functionType;
type = functionType;
readOnly = true;
default = {
input-type = application.config.module;
input-type = submodule application.config.module;
output-type = application-resources;
implementation = application.config.implementation;
};
};
};
})
);
};
environments = mkOption {
description = "Run-time environments for Fediversity applications to be deployed to";
type = attrsOf (
submodule (environment: {
_class = "fediversity-environment";
options = {
resources = mkOption {
description = ''
Resources made available by the hosting provider, and their policies.
Setting this is optional, but provides a place to declare that information for programmatic use in the resource mapping.
'';
# TODO: maybe transpose, and group the resources by type instead
type = attrsOf (
attrTag (
lib.mapAttrs (_name: resource: mkOption { type = submodule resource.policy; }) config.resources
)
);
};
implementation = mkOption {
description = "Mapping of resources required by applications to available resources; the result can be deployed";
type = environment.config.resource-mapping.function-type;
};
resource-mapping = mkOption {
description = "Function type for the mapping from resources to a deployment";
type = functionType;
readOnly = true;
default = {
input-type = submodule {
options = {
deployment-name = mkOption {
type = types.str;
};
required-resources = mkOption {
type = attrsOf application-resources;
};
};
};
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`,
# which makes explicit which applications and environments are available.
# 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;
readOnly = true;
default = environment.config.config-mapping.apply;
};
};
})
);
};
configuration = mkOption {
description = "Configuration type declaring options to be set by operators";
type = optionType;

View file

@ -4,9 +4,14 @@
_class = "flake";
perSystem =
{ pkgs, ... }:
{ pkgs, system, ... }:
{
checks = {
proxmox-basic = import ./check/proxmox {
inherit (pkgs.testers) runNixOSTest;
inherit sources system;
};
deployment-basic = import ./check/basic {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
@ -21,6 +26,21 @@
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
};
deployment-model-ssh = import ./check/data-model-ssh {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
};
deployment-model-nixops4 = import ./check/data-model-nixops4 {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
};
deployment-model-tf = import ./check/data-model-tf {
inherit (pkgs.testers) runNixOSTest;
inherit inputs sources;
};
};
};
}

View file

@ -5,7 +5,6 @@
let
inherit (lib) mkOption types;
inherit (types)
deferredModule
submodule
functionTo
optionType
@ -14,24 +13,46 @@ in
{
options = {
input-type = mkOption {
type = deferredModule;
type = optionType;
};
output-type = mkOption {
type = deferredModule;
type = optionType;
};
function-type = mkOption {
type = optionType;
readOnly = true;
default = functionTo config.output-type;
};
wrapper-type = mkOption {
type = optionType;
readOnly = true;
default = functionTo (submodule {
options = {
input = mkOption {
type = submodule config.input-type;
type = config.input-type;
};
output = mkOption {
type = submodule config.output-type;
type = config.output-type;
};
};
});
};
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;
};
};
}

25
deployment/nixos.nix Normal file
View file

@ -0,0 +1,25 @@
{
configuration,
system,
sources ? import ../npins,
}:
let
eval = import "${sources.nixpkgs}/nixos/lib/eval-config.nix" {
inherit system;
specialArgs = {
inherit sources;
};
modules = [ configuration ];
};
toplevel =
{
inherit (eval) pkgs config options;
system = eval.config.system.build.toplevel;
inherit (eval.config.system.build) vm vmWithBootLoader;
}
.config.system.build.toplevel;
in
{
drv_path = toplevel.drvPath;
out_path = toplevel;
}

View file

@ -0,0 +1,49 @@
#! /usr/bin/env bash
set -xeuo pipefail
declare username host system module args deployment_name deployment_type key_file root_path ssh_opts
IFS=" " read -r -a ssh_opts <<< "$( (echo "$ssh_opts" | jq -r '@sh') | tr -d \'\")"
# DEPLOY
sshOpts=(
-o BatchMode=yes
-o StrictHostKeyChecking=no
)
for ssh_opt in "${ssh_opts[@]}"; do
sshOpts+=(
-o "$ssh_opt"
)
done
if [[ -n "$key_file" ]]; then
sshOpts+=(
-i "$key_file"
)
fi
destination="$username@$host"
command=(nix-instantiate --show-trace --expr "
import $root_path/deployment/nixos.nix {
system = \"$system\";
configuration = (import \"$root_path/$module\" (builtins.fromJSON ''$args'')).$deployment_name.$deployment_type.nixos-configuration;
}
")
# INSTANTIATE
# instantiate the config in /nix/store
"${command[@]}" -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 "$destination" "$outPath" --gzip --use-substitutes
# switch the remote host to the config
# 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: $?"
echo "output: $output"
if [[ $output != *"Timeout, server $host not responding"* ]]; then
echo "non-timeout error: $output"
exit 1
else
exit 0
fi

View file

@ -0,0 +1,52 @@
# hash of our code directory, used to trigger re-deploy
# FIXME calculate separately to reduce false positives
data "external" "hash" {
program = ["sh", "-c", "echo \"{\\\"hash\\\":\\\"$(nix-hash ../../..)\\\"}\""]
}
# TF resource to build and deploy NixOS instances.
resource "terraform_data" "nixos" {
# trigger rebuild/deploy if (FIXME?) any potentially used config/code changed,
# preventing these (20+s, build being bottleneck) when nothing changed.
# terraform-nixos separates these to only deploy if instantiate changed,
# yet building even then - which may be not as bad using deploy on remote.
# having build/deploy one resource reflects wanting to prevent no-op rebuilds
# over preventing (with less false positives) no-op deployments,
# as i could not find a way to do prevent no-op rebuilds without merging them:
# - generic resources cannot have outputs, while we want info from the instantiation (unless built on host?).
# - `data` always runs, which is slow for deploy and especially build.
triggers_replace = [
data.external.hash.result,
var.host,
var.module,
var.args,
var.root_path,
var.deployment_type,
]
provisioner "local-exec" {
# directory to run the script from. we use the TF project root dir,
# here as a path relative from where TF is run from,
# matching calling modules' expectations on config_nix locations.
# note that absolute paths can cause false positives in triggers,
# so are generally discouraged in TF.
working_dir = path.root
environment = {
system = var.system
username = var.username
host = var.host
module = var.module
host = var.host
args = var.args
key_file = var.key_file
deployment_name = var.deployment_name
root_path = var.root_path
ssh_opts = var.ssh_opts
deployment_type = var.deployment_type
}
# TODO: refactor back to command="ignoreme" interpreter=concat([]) to protect sensitive data from error logs?
# TODO: build on target?
command = "sh ../ssh-single-host/run.sh"
}
}

View file

@ -0,0 +1,9 @@
#! /usr/bin/env bash
set -xeuo pipefail
declare tf_env
export TF_LOG=info
cd "${tf_env}/deployment/run/tf-single-host"
# parallelism=1: limit OOM risk
tofu apply --auto-approve -lock=false -parallelism=1

View file

@ -0,0 +1,16 @@
{
pkgs,
lib,
sources,
}:
pkgs.writeScriptBin "setup" ''
set -xe
# calculated pins
echo '${lib.strings.toJSON sources}' > ./.npins.json
# generate TF lock for nix's TF providers
rm -rf .terraform/
rm -f .terraform.lock.hcl
# suppress warning on architecture-specific generated lock file:
# `Warning: Incomplete lock file information for providers`.
tofu init -input=false 1>/dev/null
''

View file

@ -0,0 +1,31 @@
{
lib,
pkgs,
sources ? import ../../../npins,
}:
pkgs.stdenv.mkDerivation {
name = "tf-repo";
src =
with lib.fileset;
toSource {
root = ../../../.;
# don't copy ignored files
fileset = intersection (gitTracked ../../../.) ../../../.;
};
buildInputs = [
(pkgs.callPackage ./tf.nix { })
(pkgs.callPackage ./setup.nix { inherit sources; })
];
buildPhase = ''
runHook preBuild
pushd deployment/run/tf-single-host
source setup
popd
runHook postBuild
'';
installPhase = ''
runHook preInstall
cp -r . $out
runHook postInstall
'';
}

View file

@ -0,0 +1,11 @@
# FIXME: use overlays so this gets imported just once?
{
pkgs,
...
}:
let
tf = pkgs.opentofu;
in
tf.withPlugins (p: [
p.external
])

View file

@ -0,0 +1,54 @@
variable "system" {
description = "The architecture of the system to deploy to."
type = string
default = "x86_64-linux"
}
variable "username" {
description = "the SSH user to use"
type = string
default = "root"
}
variable "host" {
description = "the host to access by SSH"
type = string
}
variable "module" {
description = "The module to call to obtain the NixOS configuration from."
type = string
}
variable "args" {
description = "The arguments with which to call the module to obtain the NixOS configuration."
type = string
default = "{}"
}
variable "key_file" {
description = "path to the user's SSH private key"
type = string
}
variable "deployment_name" {
description = "The name of the deployment for which to obtain the NixOS configuration."
type = string
}
variable "root_path" {
description = "The path to the root of the repository."
type = string
}
variable "ssh_opts" {
description = "Extra SSH options (`-o`) to use."
type = string
default = "[]"
}
variable "deployment_type" {
description = "A `deployment-type` from the Fediversity data model, for grabbing the desired NixOS configuration."
type = string
default = "tf-host"
}

View file

@ -6,7 +6,13 @@
outputs =
inputs:
import ./mkFlake.nix inputs (
{
nixConfig = {
extra-trusted-substituters = "https://cache.saumon.network/proxmox-nixos";
extra-trusted-public-keys = "proxmox-nixos:D9RYSWpQQC/msZUWphOY2I5RLH5Dd6yQcaHIuug7dWM=";
};
}
// import ./mkFlake.nix inputs (
{ inputs, sources, ... }:
{
imports = [

View file

@ -20,16 +20,13 @@ in
'';
};
proxmox = mkOption {
type = types.nullOr (
types.enum [
"procolix"
"fediversity"
]
);
isFediversityVm = mkOption {
type = types.bool;
description = ''
The Proxmox instance. This is used for provisioning only and should be
set to `null` if the machine is not a VM.
Whether the machine is a Fediversity VM or not. This is used to
determine whether the machine should be provisioned via Proxmox or not.
Machines that are _not_ Fediversity VM could be physical machines, or
VMs that live outside Fediversity, eg. on Procolix's Proxmox.
'';
};

View file

@ -1,10 +1,14 @@
{ sources, ... }:
{ ... }:
{
_class = "nixos";
imports = [
"${sources.nixpkgs}/nixos/modules/profiles/qemu-guest.nix"
];
## FIXME: It would be nice, but the following leads to infinite recursion
## in the way we currently plug `sources` in.
##
# imports = [
# "${sources.nixpkgs}/nixos/modules/profiles/qemu-guest.nix"
# ];
boot = {
initrd = {

View file

@ -2,7 +2,6 @@
inputs,
lib,
config,
sources,
keys,
secrets,
...
@ -33,10 +32,9 @@ in
## should go into the `./nixos` subdirectory.
nixos.module = {
imports = [
"${sources.agenix}/modules/age.nix"
"${sources.disko}/module.nix"
./options.nix
./nixos
./proxmox-qemu-vm.nix
];
## Inject the shared options from the resource's `config` into the NixOS

View file

@ -14,90 +14,55 @@ let
mkOption
evalModules
filterAttrs
mapAttrs'
deepSeq
;
inherit (lib.attrsets) genAttrs;
## Given a machine's name and whether it is a test VM, make a resource module,
## except for its missing provider. (Depending on the use of that resource, we
## will provide a different one.)
makeResourceModule =
{ vmName, isTestVm }:
{
nixos.module.imports = [
./common/proxmox-qemu-vm.nix
];
nixos.specialArgs = {
inherit
sources
inputs
keys
secrets
;
};
imports =
[
./common/resource.nix
]
++ (
if isTestVm then
[
../machines/operator/${vmName}
{
nixos.module.users.users.root.openssh.authorizedKeys.keys = [
# allow our panel vm access to the test machines
keys.panel
];
}
]
else
[
../machines/dev/${vmName}
]
);
fediversityVm.name = vmName;
commonResourceModule = {
# TODO(@fricklerhandwerk): this is terrible but IMO we should just ditch
# flake-parts and have our own data model for how the project is organised
# internally
_module.args = {
inherit
inputs
keys
secrets
sources
;
};
## FIXME: It would be preferrable to have those `sources`-related imports in
## the modules that use them. However, doing so triggers infinite recursions
## because of the way we propagate `sources`. `sources` must be propagated by
## means of `specialArgs`, but this requires a bigger change.
nixos.module.imports = [
"${sources.nixpkgs}/nixos/modules/profiles/qemu-guest.nix"
"${sources.agenix}/modules/age.nix"
"${sources.disko}/module.nix"
"${sources.home-manager}/nixos"
];
imports = [
./common/resource.nix
];
};
## Given a list of machine names, make a deployment with those machines'
## configurations as resources.
makeDeployment =
vmNames:
{ providers, ... }:
{
# XXX: this type merge is for adding `specialArgs` to resource modules
options.resources = mkOption {
type =
with lib.types;
lazyAttrsOf (submoduleWith {
class = "nixops4Resource";
modules = [ ];
# TODO(@fricklerhandwerk): we may want to pass through all of `specialArgs`
# once we're sure it's sane. leaving it here for better control during refactoring.
specialArgs = {
inherit
sources
inputs
keys
secrets
;
};
});
};
config = {
providers.local = inputs.nixops4.modules.nixops4Provider.local;
resources = genAttrs vmNames (vmName: {
type = providers.local.exec;
imports = [
inputs.nixops4-nixos.modules.nixops4Resource.nixos
(makeResourceModule {
inherit vmName;
isTestVm = false;
})
];
});
};
providers.local = inputs.nixops4.modules.nixops4Provider.local;
resources = genAttrs vmNames (vmName: {
type = providers.local.exec;
imports = [
inputs.nixops4-nixos.modules.nixops4Resource.nixos
commonResourceModule
../machines/dev/${vmName}
];
});
};
makeDeployment' = vmName: makeDeployment [ vmName ];
@ -112,21 +77,29 @@ let
fediversity = import ../services/fediversity;
}
{
garageConfigurationResource = makeResourceModule {
vmName = "test01";
isTestVm = true;
garageConfigurationResource = {
imports = [
commonResourceModule
../machines/operator/test01
];
};
mastodonConfigurationResource = makeResourceModule {
vmName = "test06"; # somehow `test02` has a problem - use test06 instead
isTestVm = true;
mastodonConfigurationResource = {
imports = [
commonResourceModule
../machines/operator/test06 # somehow `test02` has a problem - use test06 instead
];
};
peertubeConfigurationResource = makeResourceModule {
vmName = "test05";
isTestVm = true;
peertubeConfigurationResource = {
imports = [
commonResourceModule
../machines/operator/test05
];
};
pixelfedConfigurationResource = makeResourceModule {
vmName = "test04";
isTestVm = true;
pixelfedConfigurationResource = {
imports = [
commonResourceModule
../machines/operator/test04
];
};
};
@ -139,54 +112,63 @@ let
## this is only needed to expose NixOS configurations for provisioning
## purposes, and eventually all of this should be handled by NixOps4.
options = {
nixos.module = mkOption { }; # NOTE: not just `nixos` otherwise merging will go wrong
nixos.module = mkOption { type = lib.types.deferredModule; }; # NOTE: not just `nixos` otherwise merging will go wrong
nixpkgs = mkOption { };
ssh = mkOption { };
};
};
makeResourceConfig =
vm:
{ vmName, isTestVm }:
(evalModules {
modules = [
nixops4ResourceNixosMockOptions
(makeResourceModule vm)
commonResourceModule
(if isTestVm then ../machines/operator/${vmName} else ../machines/dev/${vmName})
];
}).config;
## Given a VM name, make a NixOS configuration for this machine.
makeConfiguration =
isTestVm: vmName:
let
inherit (sources) nixpkgs;
in
import "${nixpkgs}/nixos" {
modules = [
(makeResourceConfig { inherit vmName isTestVm; }).nixos.module
];
import "${sources.nixpkgs}/nixos" {
configuration = (makeResourceConfig { inherit vmName isTestVm; }).nixos.module;
system = "x86_64-linux";
};
makeVmOptions = isTestVm: vmName: {
inherit ((makeResourceConfig { inherit vmName isTestVm; }).fediversityVm)
proxmox
vmId
description
sockets
cores
memory
diskSize
hostPublicKey
unsafeHostPrivateKey
;
};
makeVmOptions =
isTestVm: vmName:
let
config = (makeResourceConfig { inherit vmName isTestVm; }).fediversityVm;
in
if config.isFediversityVm then
{
inherit (config)
vmId
description
sockets
cores
memory
diskSize
hostPublicKey
unsafeHostPrivateKey
;
}
else
null;
listSubdirectories = path: attrNames (filterAttrs (_: type: type == "directory") (readDir path));
machines = listSubdirectories ../machines/dev;
testMachines = listSubdirectories ../machines/operator;
nixosConfigurations =
genAttrs machines (makeConfiguration false)
// genAttrs testMachines (makeConfiguration true);
vmOptions =
filterAttrs (_: value: value != null) # Filter out non-Fediversity VMs
(genAttrs machines (makeVmOptions false) // genAttrs testMachines (makeVmOptions true));
in
{
_class = "flake";
@ -210,10 +192,23 @@ in
)
);
};
flake.nixosConfigurations =
genAttrs machines (makeConfiguration false)
// genAttrs testMachines (makeConfiguration true);
flake.vmOptions =
genAttrs machines (makeVmOptions false)
// genAttrs testMachines (makeVmOptions true);
flake = { inherit nixosConfigurations vmOptions; };
perSystem =
{ pkgs, ... }:
{
checks =
mapAttrs' (name: nixosConfiguration: {
name = "nixosConfigurations-${name}";
value = nixosConfiguration.config.system.build.toplevel;
}) nixosConfigurations
// mapAttrs' (name: vmOptions: {
name = "vmOptions-${name}";
## Check that VM options builds/evaluates correctly. `deepSeq e1
## e2` evaluates `e1` strictly in depth before returning `e2`. We
## use this trick because checks need to be derivations, which VM
## options are not.
value = deepSeq vmOptions pkgs.hello;
}) vmOptions;
};
}

View file

@ -179,15 +179,9 @@ grab_vm_options () {
--log-format raw --quiet
)
proxmox=$(echo "$options" | jq -r .proxmox)
vm_id=$(echo "$options" | jq -r .vmId)
description=$(echo "$options" | jq -r .description)
if [ "$proxmox" != fediversity ]; then
die "I do not know how to provision things that are not Fediversity VMs,
but I got proxmox = '%s' for VM %s." "$proxmox" "$vm_name"
fi
sockets=$(echo "$options" | jq -r .sockets)
cores=$(echo "$options" | jq -r .cores)
memory=$(echo "$options" | jq -r .memory)

View file

@ -167,16 +167,10 @@ grab_vm_options () {
--log-format raw --quiet
)
proxmox=$(echo "$options" | jq -r .proxmox)
vm_id=$(echo "$options" | jq -r .vmId)
if [ "$proxmox" != fediversity ]; then
die "I do not know how to remove things that are not Fediversity VMs,
but I got proxmox = '%s' for VM %s." "$proxmox" "$vm_name"
fi
printf 'done grabing VM options for VM %s. Found VM %d on %s Proxmox.\n' \
"$vm_name" "$vm_id" "$proxmox"
printf 'done grabing VM options for VM %s. Got id: %d.\n' \
"$vm_name" "$vm_id"
fi
}

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "fedi200";
isFediversityVm = true;
vmId = 200;
proxmox = "fediversity";
description = "Testing machine for Hans";
domain = "abundos.eu";
@ -16,10 +17,4 @@
gateway = "2a00:51c0:13:1305::1";
};
};
nixos.module = {
imports = [
../../../infra/common/proxmox-qemu-vm.nix
];
};
}

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "fedi201";
isFediversityVm = true;
vmId = 201;
proxmox = "fediversity";
description = "FediPanel";
domain = "abundos.eu";
@ -19,7 +20,6 @@
nixos.module = {
imports = [
../../../infra/common/proxmox-qemu-vm.nix
./fedipanel.nix
];
};

View file

@ -1,6 +1,5 @@
{
config,
sources,
...
}:
let
@ -11,7 +10,6 @@ in
imports = [
(import ../../../panel { }).module
"${sources.home-manager}/nixos"
];
security.acme = {

View file

@ -20,7 +20,9 @@ in
ssh.host = mkForce "forgejo-ci";
fediversityVm = {
name = "forgejo-ci";
domain = "procolix.com";
isFediversityVm = false;
ipv4 = {
interface = "enp1s0f0";

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "vm02116";
isFediversityVm = false;
vmId = 2116;
proxmox = "procolix";
description = "Forgejo";
ipv4.address = "185.206.232.34";
@ -14,7 +15,6 @@
{ lib, ... }:
{
imports = [
../../../infra/common/proxmox-qemu-vm.nix
./forgejo.nix
];

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "vm02187";
isFediversityVm = false;
vmId = 2187;
proxmox = "procolix";
description = "Wiki";
ipv4.address = "185.206.232.187";
@ -14,7 +15,6 @@
{ lib, ... }:
{
imports = [
../../../infra/common/proxmox-qemu-vm.nix
./wiki.nix
];

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test01";
isFediversityVm = true;
vmId = 7001;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test02";
isFediversityVm = true;
vmId = 7002;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test03";
isFediversityVm = true;
vmId = 7003;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test04";
isFediversityVm = true;
vmId = 7004;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test05";
isFediversityVm = true;
vmId = 7005;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test06";
isFediversityVm = true;
vmId = 7006;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test11";
isFediversityVm = true;
vmId = 7011;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test12";
isFediversityVm = true;
vmId = 7012;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test13";
isFediversityVm = true;
vmId = 7013;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

View file

@ -2,8 +2,9 @@
_class = "nixops4Resource";
fediversityVm = {
name = "test14";
isFediversityVm = true;
vmId = 7014;
proxmox = "fediversity";
hostPublicKey = builtins.readFile ./ssh_host_ed25519_key.pub;
unsafeHostPrivateKey = builtins.readFile ./ssh_host_ed25519_key;

4
nixmoxer.conf Normal file
View file

@ -0,0 +1,4 @@
host=192.168.51.81
verify_ssl=0
user=kiara@ProcoliX
password=

View file

@ -25,6 +25,22 @@
"url": null,
"hash": "1w2gsy6qwxa5abkv8clb435237iifndcxq0s79wihqw11a5yb938"
},
"crane": {
"type": "GitRelease",
"repository": {
"type": "GitHub",
"owner": "ipetkov",
"repo": "crane"
},
"pre_releases": false,
"version_upper_bound": null,
"release_prefix": null,
"submodules": false,
"version": "v0.20.3",
"revision": "8468a0c46f81d806fd643ffe389fa80328b21cf4",
"url": "https://api.github.com/repos/ipetkov/crane/tarball/v0.20.3",
"hash": "0zw4275c3a6572w6vjmn850yddw6n3qagwfcq6ns247cx72fdfx0"
},
"disko": {
"type": "GitRelease",
"repository": {
@ -150,6 +166,32 @@
"revision": "f33a4d26226c05d501b9d4d3e5e60a3a59991921",
"url": "https://github.com/nixos/nixpkgs/archive/f33a4d26226c05d501b9d4d3e5e60a3a59991921.tar.gz",
"hash": "1b6dm1sn0bdpcsmxna0zzspjaixa2dald08005fry5jrbjvwafdj"
},
"nixpkgs-stable": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "nixos",
"repo": "nixpkgs"
},
"branch": "nixos-25.05",
"submodules": false,
"revision": "a1ae8ef72f64a845ecce5c6dcf65d546bf7deeb4",
"url": "https://github.com/nixos/nixpkgs/archive/a1ae8ef72f64a845ecce5c6dcf65d546bf7deeb4.tar.gz",
"hash": "0d7lp30wyy5647gpm8rnihvdcpmgmfr9c5yg4fhl31lsg8mlbg16"
},
"proxmox-nixos": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "SaumonNet",
"repo": "proxmox-nixos"
},
"branch": "main",
"submodules": false,
"revision": "48f39fbe2e8f90f9ac160dd4b6929f3ac06d8223",
"url": "https://github.com/SaumonNet/proxmox-nixos/archive/48f39fbe2e8f90f9ac160dd4b6929f3ac06d8223.tar.gz",
"hash": "0606qcs8x1jwckd1ivf52rqdmi3lkn66iiqh6ghd4kqx0g2bw3nv"
}
},
"version": 5

View file

@ -7,7 +7,6 @@
{
checks = {
test-mastodon-service = pkgs.testers.runNixOSTest ./mastodon.nix;
test-pixelfed-garage-service = pkgs.testers.runNixOSTest ./pixelfed-garage.nix;
test-peertube-service = pkgs.testers.runNixOSTest ./peertube.nix;
};
};