Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 9 additions & 8 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
description = "NGI Forge";

nixConfig = {
extra-substituters = [ "https://ngi-forge.cachix.org" ];
extra-trusted-public-keys = [
"ngi-forge.cachix.org-1:PK0qK+LhWt4GQVpUtPapyXWxJSM1GhtmPW6CRCoygz0="
];
};
# nixConfig = {
# extra-substituters = [ "https://ngi-forge.cachix.org" ];
# extra-trusted-public-keys = [
# "ngi-forge.cachix.org-1:PK0qK+LhWt4GQVpUtPapyXWxJSM1GhtmPW6CRCoygz0="
# ];
# };

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
Expand Down Expand Up @@ -34,7 +34,8 @@
};

nimi = {
url = "github:ngi-nix/nimi/ngi-patches";
# TODO: https://github.com/ngi-nix/nimi/pull/3
url = "github:eljamm/nimi/feat/service-ordering-take-2";
inputs.nixpkgs.follows = "nixpkgs";
};
};
Expand All @@ -45,7 +46,7 @@
flake-parts.lib.mkFlake { inherit inputs; } {
# Uncomment this to enable flake-parts debug.
# https://flake.parts/options/flake-parts.html?highlight=debug#opt-debug
# debug = true;
debug = true;

systems = [
"x86_64-linux"
Expand Down
1 change: 1 addition & 0 deletions forge/modules/apps/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ in
})
// lib.optionalAttrs app.services.runtimes.container.enable {
container = app.services.runtimes.container.result.build;
bin = app.services.runtimes.container.result.bin;
}
// lib.optionalAttrs app.services.runtimes.nixos.enable {
vm = app.services.runtimes.nixos.result.build;
Expand Down
46 changes: 46 additions & 0 deletions forge/modules/apps/services/component.nix
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,51 @@
default = null;
apply = self: if self != null then pkgs.writeShellScript "${name}-pre-start" self else null;
};

readyCheck = lib.mkOption {
description = ''
Path to an executable to run to determine if the service is ready.

The executable should exit 0 when the service is ready.
It will be polled repeatedly until it succeeds.
Required for services that will be used as `afterReady` targets.

Set to `null` to disable.
'';
type = lib.types.nullOr lib.types.pathInStore;
default = null;
example = lib.literalExpression ''
lib.getExe (
pkgs.writeShellApplication {
name = "example-ready-check";
text = '''
curl -f http://localhost:8080/health || exit 1
''';
}
)
'';
};

type = lib.mkOption {
description = ''
Service type, similar to systemd's Type=.

- `simple` (default): Service runs continuously, expected to stay running.
- `oneshot`: Service runs once and exits. Considered "started" on successful exit.
- `notify`: Service sends READY=1 via sd_notify when ready (not yet implemented).
- `dbus`: Service registers a name on D-Bus (not yet implemented).

For oneshot services, the restart policy is ignored - the service runs once
and is considered successful if it exits with code 0.
'';
default = "simple";
example = lib.literalExpression ''"oneshot"'';
type = lib.types.enum [
"simple"
"oneshot"
"notify"
"dbus"
];
};
};
}
117 changes: 117 additions & 0 deletions forge/modules/apps/services/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
[ command ] ++ service.argv;
configData = service.configData;
preStart = service.preStart;
readyCheck = service.readyCheck;
type = service.type;
};
}
) self;
Expand All @@ -53,5 +55,120 @@
default = { };
description = "Portable services runtimes.";
};

ordering = lib.mkOption {
description = ''
Service startup ordering constraints.

Each attribute names a service and declares which other services
it must wait for before starting. Services without ordering
constraints (or not mentioned here) start immediately.

This only controls startup order inside a single nimi instance.
It applies equally to containers, NixOS, Home Manager, and
local development runs.
'';
example = lib.literalExpression ''
{
backend.after = [ "database" ];
frontend.after = [ "database" "backend" ];
}
'';
type = lib.types.attrsOf (
lib.types.submodule {
options.after = lib.mkOption {
description = ''
List of service names that must have started before this
service is spawned (soft dependency, failure is okay).
'';
type = lib.types.listOf lib.types.str;
default = [ ];
};

options.afterReady = lib.mkOption {
description = ''
List of service names that must be ready before this
service is spawned. Each target must declare a
readiness check.
'';
type = lib.types.listOf lib.types.str;
default = [ ];
};

options.before = lib.mkOption {
description = ''
List of service names that must start after this service.
Inverse of `after`: adds an implicit `after` on the target.
'';
type = lib.types.listOf lib.types.str;
default = [ ];
};

options.wants = lib.mkOption {
description = ''
List of soft dependencies: services that should be started,
but failure is acceptable (matching systemd `Wants=`).
'';
type = lib.types.listOf lib.types.str;
default = [ ];
};

options.requires = lib.mkOption {
description = ''
List of hard dependencies: services that must start successfully
before this service. If a required service fails, this service
will not start (matching systemd `Requires=`).
'';
type = lib.types.listOf lib.types.str;
default = [ ];
};

# options.afterReady = lib.mkOption {
# description = ''
# List of service names that must be ready before this
# service is spawned. Each target must declare a
# readiness check.
# '';
# type = lib.types.listOf lib.types.str;
# default = [ ];
# };
# options.requires = lib.mkOption {
# description = ''
# Hard dependencies. If any required dependency fails, this
# service is stopped immediately.
# '';
# type = lib.types.listOf lib.types.str;
# default = [ ];
# };
# options.wants = lib.mkOption {
# description = ''
# Soft dependencies. If a wanted dependency fails, this service
# continues running. The failure is logged but not acted upon.
# '';
# type = lib.types.listOf lib.types.str;
# default = [ ];
# };
# options.bindsTo = lib.mkOption {
# description = ''
# Lifecycle-coupled dependencies. If a bound dependency stops
# or fails, this service is stopped. If it restarts and becomes
# ready, this service is also restarted.
# '';
# type = lib.types.listOf lib.types.str;
# default = [ ];
# };
# options.partOf = lib.mkOption {
# description = ''
# Stop-propagation dependencies. If the target service is
# stopped, this service is also stopped. Does not react to
# failures, only to explicit stops.
# '';
# type = lib.types.listOf lib.types.str;
# default = [ ];
# };
}
);
default = { };
};
};
}
11 changes: 11 additions & 0 deletions forge/modules/apps/services/runtimes/container/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@
description = "Script that builds container image.";
};

bin = lib.mkOption {
internal = true;
type = lib.types.nullOr lib.types.package;
default = null;
description = "Script that builds container image.";
};

# HACK:
# Prevent toJSON conversion from attempting to convert the `eval` option,
# which won't work because it's a whole NixOS evaluation.
Expand Down Expand Up @@ -143,12 +150,16 @@
}
];
}) app.services.components;

ordering = app.services.ordering;
};

result.eval = nimi.passthru.evalNimiModule { config = config.result.modules; };

result.recipe = nimi.mkContainerImage { config = config.result.modules; };

result.bin = nimi.mkNimiBin { config = config.result.modules; };

result.build = pkgs.runCommand "build-oci-image" { meta.mainProgram = "build-oci-image"; } ''
mkdir -p $out/bin

Expand Down
15 changes: 15 additions & 0 deletions forge/modules/apps/services/runtimes/nixos/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@

environment.variables = lib.concatMapAttrs (_: value: value.environment) app.services.components;
}
# nimi service ordering
{
systemd.services = lib.mapAttrs' (
name: _:
let
order = app.services.ordering.${name} or null;
in
lib.nameValuePair name {
serviceConfig = lib.mkIf (order != null) {
After = order.after or [ ];
Requires = order.requires or [ ];
};
}
) app.services.components;
}
(lib.mkIf (config.setup != "") {
systemd.services."${app.name}-setup" = {
description = "Setup service for ${app.name}.";
Expand Down
20 changes: 18 additions & 2 deletions recipes/apps/python-web-app/recipe.nix
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,28 @@
python-web = {
command = pkgs.mypkgs.python-web;
};

python-web-hello = {
command = pkgs.writeShellScriptBin "test" ''
# exit 1
${pkgs.hello}/bin/hello
'';
type = "oneshot";
};
};

ordering.python-web.after = [ "python-web-hello" ];
ordering.python-web.requires = [ "python-web-hello" ];

runtimes = {
container = {
enable = true;
packages = [ pkgs.mypkgs.python-web ];
packages = [
pkgs.mypkgs.python-web
pkgs.bash
pkgs.curl
pkgs.coreutils
];
# Alternatively, we can re-use attributes with `config`:
#packages = [ config.services.python-web.command ];
composeFile = ./compose.yaml;
Expand All @@ -80,7 +96,7 @@
'';
};
vm.forwardPorts = [
"5000:5000"
"9000:5000"
];
};
};
Expand Down