mirror of
https://github.com/rubenhensen/k8scd.git
synced 2026-09-19 02:52:56 +02:00
Add nix-infra-machine
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
appName = "n8n";
|
||||
defaultPort = 5678;
|
||||
|
||||
cfg = config.infrastructure.${appName};
|
||||
|
||||
# Build the custom n8n package with version selection
|
||||
n8nPackage = if cfg.package != null then cfg.package else
|
||||
pkgs.callPackage ./package.nix {
|
||||
version = cfg.version;
|
||||
buildMemoryMB = cfg.buildMemoryMB;
|
||||
};
|
||||
|
||||
# Environment variables for n8n configuration
|
||||
n8nEnvironment = {
|
||||
# Network settings
|
||||
N8N_PORT = toString cfg.bindToPort;
|
||||
N8N_LISTEN_ADDRESS = cfg.bindToIp;
|
||||
|
||||
# Execution settings
|
||||
EXECUTIONS_DATA_PRUNE = if cfg.executions.pruneData then "true" else "false";
|
||||
EXECUTIONS_DATA_MAX_AGE = toString cfg.executions.pruneDataMaxAge;
|
||||
EXECUTIONS_DATA_PRUNE_MAX_COUNT = toString cfg.executions.pruneDataMaxCount;
|
||||
} // (lib.optionalAttrs (cfg.webhookUrl != "") {
|
||||
# Webhook URL (if specified)
|
||||
WEBHOOK_URL = cfg.webhookUrl;
|
||||
}) // (lib.optionalAttrs (cfg.database.type == "postgresdb") {
|
||||
# Database settings (only set if using PostgreSQL)
|
||||
DB_TYPE = "postgresdb";
|
||||
DB_POSTGRESDB_HOST = cfg.database.postgresdb.host;
|
||||
DB_POSTGRESDB_PORT = toString cfg.database.postgresdb.port;
|
||||
DB_POSTGRESDB_DATABASE = cfg.database.postgresdb.database;
|
||||
DB_POSTGRESDB_USER = cfg.database.postgresdb.user;
|
||||
}) // (lib.optionalAttrs (cfg.database.type == "postgresdb" && cfg.database.postgresdb.ssl) {
|
||||
DB_POSTGRESDB_SSL_ENABLED = "true";
|
||||
}) // cfg.settings;
|
||||
in
|
||||
{
|
||||
options.infrastructure.${appName} = {
|
||||
enable = lib.mkEnableOption "infrastructure.n8n";
|
||||
|
||||
# ==========================================================================
|
||||
# Package and Version Configuration
|
||||
# ==========================================================================
|
||||
|
||||
version = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
n8n version to install.
|
||||
|
||||
Supported versions are defined in package.nix. To add a new version,
|
||||
you need to compute the source and pnpm dependency hashes.
|
||||
|
||||
See package.nix for instructions on adding new versions.
|
||||
'';
|
||||
default = "2.1.5";
|
||||
example = "1.120.4";
|
||||
};
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.package;
|
||||
description = ''
|
||||
Custom n8n package to use. If null, the package will be built
|
||||
using the version specified in 'version' option.
|
||||
|
||||
Use this to provide a completely custom n8n build.
|
||||
'';
|
||||
default = null;
|
||||
example = lib.literalExpression "pkgs.n8n";
|
||||
};
|
||||
|
||||
buildMemoryMB = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = ''
|
||||
Maximum Node.js heap size in MB for building n8n.
|
||||
Increase this if you encounter "JavaScript heap out of memory" errors during build.
|
||||
'';
|
||||
default = 4096;
|
||||
example = 8192;
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Network Configuration
|
||||
# ==========================================================================
|
||||
|
||||
bindToIp = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "IP address to bind n8n to.";
|
||||
default = "127.0.0.1";
|
||||
example = "0.0.0.0";
|
||||
};
|
||||
|
||||
bindToPort = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = "Port for n8n web interface.";
|
||||
default = defaultPort;
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Open firewall for n8n.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Webhook Configuration
|
||||
# ==========================================================================
|
||||
|
||||
webhookUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
WEBHOOK_URL for n8n, used when running behind a reverse proxy.
|
||||
This is the external URL where webhooks can reach n8n.
|
||||
'';
|
||||
default = "";
|
||||
example = "https://n8n.example.com/";
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Data Directory
|
||||
# ==========================================================================
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "Directory where n8n data is stored.";
|
||||
default = "/var/lib/n8n";
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Database Configuration
|
||||
# ==========================================================================
|
||||
database = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "sqlite" "postgresdb" ];
|
||||
description = "Database type to use. SQLite is default, PostgreSQL recommended for production.";
|
||||
default = "sqlite";
|
||||
};
|
||||
|
||||
postgresdb = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "PostgreSQL host.";
|
||||
default = "localhost";
|
||||
example = "/run/postgresql";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = "PostgreSQL port.";
|
||||
default = 5432;
|
||||
};
|
||||
|
||||
database = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "PostgreSQL database name.";
|
||||
default = "n8n";
|
||||
};
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "PostgreSQL user.";
|
||||
default = "n8n";
|
||||
};
|
||||
|
||||
passwordSecretName = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Name of the secret containing the PostgreSQL password.
|
||||
The secret should be placed at /run/secrets/<n>.
|
||||
If null, peer/socket authentication is assumed.
|
||||
'';
|
||||
default = null;
|
||||
example = "n8n-db-password";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Enable SSL for PostgreSQL connection.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Whether to create the database user locally.
|
||||
This requires PostgreSQL to be running locally with trust or peer authentication.
|
||||
The database itself should be created via infrastructure.postgresql.initialDatabases.
|
||||
'';
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# Execution Configuration
|
||||
# ==========================================================================
|
||||
|
||||
executions = {
|
||||
pruneData = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Enable automatic pruning of old execution data.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
pruneDataMaxAge = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = "Maximum age of execution data in hours before pruning.";
|
||||
default = 336; # 14 days
|
||||
};
|
||||
|
||||
pruneDataMaxCount = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = "Maximum number of executions to keep.";
|
||||
default = 10000;
|
||||
};
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# n8n Settings (pass-through as environment variables)
|
||||
# ==========================================================================
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
description = ''
|
||||
Additional n8n configuration as environment variables.
|
||||
These are passed directly to the n8n service.
|
||||
See https://docs.n8n.io/hosting/environment-variables/environment-variables/
|
||||
'';
|
||||
default = {};
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
GENERIC_TIMEZONE = "Europe/London";
|
||||
WORKFLOWS_DEFAULT_NAME = "My Workflow";
|
||||
N8N_METRICS = "true";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# Reverse Proxy Configuration
|
||||
# ==========================================================================
|
||||
|
||||
reverseProxy = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Enable nginx reverse proxy for n8n.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
hostName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Hostname for the reverse proxy.";
|
||||
default = "localhost";
|
||||
example = "n8n.example.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Enable SSL/HTTPS for the reverse proxy.";
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# ==========================================================================
|
||||
# Disable the native n8n service (we'll configure our own systemd service)
|
||||
# ==========================================================================
|
||||
|
||||
# Do NOT enable services.n8n - we create our own service to have full control
|
||||
|
||||
# ==========================================================================
|
||||
# n8n User and Group
|
||||
# ==========================================================================
|
||||
|
||||
users.users.n8n = {
|
||||
isSystemUser = true;
|
||||
group = "n8n";
|
||||
home = cfg.dataDir;
|
||||
createHome = true;
|
||||
description = "n8n service user";
|
||||
};
|
||||
|
||||
users.groups.n8n = {};
|
||||
|
||||
# ==========================================================================
|
||||
# n8n Systemd Service
|
||||
# ==========================================================================
|
||||
|
||||
systemd.services.n8n = {
|
||||
description = "n8n - Workflow Automation";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ] ++
|
||||
lib.optionals cfg.reverseProxy.enable [ "nginx.service" ] ++
|
||||
lib.optionals (cfg.database.type == "postgresdb" && cfg.database.postgresdb.createLocally) [
|
||||
"postgresql.service"
|
||||
"n8n-db-setup.service"
|
||||
];
|
||||
wants = lib.optionals (cfg.database.type == "postgresdb" && cfg.database.postgresdb.createLocally) [
|
||||
"n8n-db-setup.service"
|
||||
];
|
||||
requires = lib.optionals (cfg.database.type == "postgresdb" && cfg.database.postgresdb.createLocally) [
|
||||
"postgresql.service"
|
||||
];
|
||||
|
||||
environment = n8nEnvironment;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "n8n";
|
||||
Group = "n8n";
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
ExecStart = "${n8nPackage}/bin/n8n";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
|
||||
# Hardening
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ReadWritePaths = [ cfg.dataDir ];
|
||||
};
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Nginx Reverse Proxy (Optional)
|
||||
# ==========================================================================
|
||||
|
||||
services.nginx = lib.mkIf cfg.reverseProxy.enable {
|
||||
enable = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = cfg.reverseProxy.ssl;
|
||||
|
||||
virtualHosts.${cfg.reverseProxy.hostName} = {
|
||||
forceSSL = cfg.reverseProxy.ssl;
|
||||
enableACME = cfg.reverseProxy.ssl;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://${cfg.bindToIp}:${toString cfg.bindToPort}";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_buffering off;
|
||||
chunked_transfer_encoding off;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Firewall Configuration
|
||||
# ==========================================================================
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall (
|
||||
[ cfg.bindToPort ] ++
|
||||
(lib.optionals cfg.reverseProxy.enable [ 80 443 ])
|
||||
);
|
||||
|
||||
# ==========================================================================
|
||||
# Service Dependencies
|
||||
# ==========================================================================
|
||||
|
||||
systemd.services.nginx = lib.mkIf cfg.reverseProxy.enable {
|
||||
wants = [ "n8n.service" ];
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# PostgreSQL Database Setup (Optional)
|
||||
# ==========================================================================
|
||||
|
||||
systemd.services.n8n-db-setup = lib.mkIf (cfg.database.type == "postgresdb" && cfg.database.postgresdb.createLocally) {
|
||||
description = "Create n8n database user";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "postgresql.service" ];
|
||||
requires = [ "postgresql.service" ];
|
||||
before = [ "n8n.service" ];
|
||||
requiredBy = [ "n8n.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
User = "postgres";
|
||||
};
|
||||
script = let
|
||||
dbUser = cfg.database.postgresdb.user;
|
||||
dbName = cfg.database.postgresdb.database;
|
||||
dbHost = cfg.database.postgresdb.host;
|
||||
dbPort = toString cfg.database.postgresdb.port;
|
||||
in ''
|
||||
# Wait for PostgreSQL to be ready
|
||||
until ${pkgs.postgresql}/bin/pg_isready -h ${dbHost} -p ${dbPort}; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Create database user if it doesn't exist
|
||||
${pkgs.postgresql}/bin/psql -h ${dbHost} -p ${dbPort} -c "SELECT 1 FROM pg_roles WHERE rolname='${dbUser}'" | grep -q 1 || \
|
||||
${pkgs.postgresql}/bin/psql -h ${dbHost} -p ${dbPort} -c "CREATE USER ${dbUser}"
|
||||
|
||||
# Grant privileges on database
|
||||
${pkgs.postgresql}/bin/psql -h ${dbHost} -p ${dbPort} -c "GRANT ALL PRIVILEGES ON DATABASE ${dbName} TO ${dbUser}"
|
||||
${pkgs.postgresql}/bin/psql -h ${dbHost} -p ${dbPort} -d ${dbName} -c "GRANT ALL ON SCHEMA public TO ${dbUser}"
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# Utilities
|
||||
# ==========================================================================
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
curl
|
||||
jq
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
# Custom n8n package with version selection
|
||||
# Based on: https://github.com/NixOS/nixpkgs/blob/nixos-25.11/pkgs/by-name/n8/n8n/package.nix
|
||||
#
|
||||
# To add a new version:
|
||||
# 1. Get the source hash:
|
||||
# nix-prefetch-url --unpack https://github.com/n8n-io/n8n/archive/refs/tags/n8n@VERSION.tar.gz
|
||||
# 2. Get the pnpm deps hash by running a build with lib.fakeHash and copying the correct hash from error
|
||||
# 3. Add entry to versionHashes below
|
||||
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
nodejs,
|
||||
pnpm_10,
|
||||
fetchPnpmDeps,
|
||||
pnpmConfigHook,
|
||||
python3,
|
||||
node-gyp,
|
||||
cctools,
|
||||
xcbuild,
|
||||
libkrb5,
|
||||
libmongocrypt,
|
||||
libpq,
|
||||
makeWrapper,
|
||||
# Custom parameters
|
||||
version ? "2.1.5",
|
||||
buildMemoryMB ? 4096,
|
||||
}:
|
||||
|
||||
let
|
||||
# Known version hashes
|
||||
# To add a new version, run:
|
||||
# nix-prefetch-url --unpack https://github.com/n8n-io/n8n/archive/refs/tags/n8n@VERSION.tar.gz
|
||||
# Then build with lib.fakeHash for pnpmDepsHash to get the correct hash
|
||||
versionHashes = {
|
||||
"2.1.5" = {
|
||||
srcHash = "sha256-/MPY3j/2I3CgX5rRhzj3v7bHjaQEDMNnkVfk3taCrYA=";
|
||||
pnpmDepsHash = "sha256-FRoZIINONy0kFPQAJhOwnCUv7HHwdgqm3r5SJmq4UYk=";
|
||||
};
|
||||
"2.1.4" = {
|
||||
srcHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
pnpmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
"2.0.0" = {
|
||||
srcHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
pnpmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
"1.120.4" = {
|
||||
srcHash = "sha256-gUqQM/eA7GnvFYiduSGkj/MCvgWNQPhDLExAJz67bHg=";
|
||||
pnpmDepsHash = "sha256-UWiN3NvI8We16KwY5JspyX0ok1PJWVg0T5zw+0SnrWk=";
|
||||
};
|
||||
"1.91.3" = {
|
||||
srcHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
pnpmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
};
|
||||
|
||||
# Get hashes for the requested version
|
||||
hashes = versionHashes.${version} or (throw ''
|
||||
n8n version ${version} is not supported.
|
||||
|
||||
Supported versions: ${builtins.concatStringsSep ", " (builtins.attrNames versionHashes)}
|
||||
|
||||
To add support for version ${version}:
|
||||
1. Get source hash: nix-prefetch-url --unpack https://github.com/n8n-io/n8n/archive/refs/tags/n8n@${version}.tar.gz
|
||||
2. Add entry to versionHashes in app_modules/_unstable/n8n/package.nix
|
||||
3. Build once with placeholder pnpmDepsHash to get the correct hash from the error message
|
||||
'');
|
||||
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "n8n";
|
||||
inherit version;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "n8n-io";
|
||||
repo = "n8n";
|
||||
tag = "n8n@${finalAttrs.version}";
|
||||
hash = hashes.srcHash;
|
||||
};
|
||||
|
||||
pnpmDeps = fetchPnpmDeps {
|
||||
inherit (finalAttrs) pname version src;
|
||||
pnpm = pnpm_10;
|
||||
fetcherVersion = 2;
|
||||
hash = hashes.pnpmDepsHash;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
pnpmConfigHook
|
||||
pnpm_10
|
||||
python3 # required to build sqlite3 bindings
|
||||
node-gyp # required to build sqlite3 bindings
|
||||
makeWrapper
|
||||
] ++ lib.optionals stdenv.hostPlatform.isDarwin [
|
||||
cctools
|
||||
xcbuild
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
nodejs
|
||||
libkrb5
|
||||
libmongocrypt
|
||||
libpq
|
||||
];
|
||||
|
||||
# Set memory limit for Node.js during build
|
||||
env = {
|
||||
NODE_OPTIONS = "--max-old-space-size=${toString buildMemoryMB}";
|
||||
};
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
pushd node_modules/sqlite3
|
||||
node-gyp rebuild
|
||||
popd
|
||||
|
||||
# TODO: use deploy after resolved https://github.com/pnpm/pnpm/issues/5315
|
||||
pnpm build --filter=n8n
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
preInstall = ''
|
||||
echo "Removing non-deterministic and unnecessary files"
|
||||
|
||||
find -type d -name .turbo -exec rm -rf {} +
|
||||
rm node_modules/.modules.yaml
|
||||
rm -f packages/nodes-base/dist/types/nodes.json
|
||||
|
||||
CI=true pnpm --ignore-scripts prune --prod
|
||||
find -type f \( -name "*.ts" -o -name "*.map" \) -exec rm -rf {} +
|
||||
rm -rf node_modules/.pnpm/{typescript*,prettier*}
|
||||
shopt -s globstar
|
||||
# https://github.com/pnpm/pnpm/issues/3645
|
||||
find node_modules packages/**/node_modules -xtype l -delete
|
||||
|
||||
echo "Removed non-deterministic and unnecessary files"
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/{bin,lib/n8n}
|
||||
mv {packages,node_modules} $out/lib/n8n
|
||||
|
||||
makeWrapper $out/lib/n8n/packages/cli/bin/n8n $out/bin/n8n \
|
||||
--set N8N_RELEASE_TYPE "stable"
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
# this package has ~80000 files, these take too long and seem to be unnecessary
|
||||
dontStrip = true;
|
||||
dontPatchELF = true;
|
||||
dontRewriteSymlinks = true;
|
||||
|
||||
meta = {
|
||||
description = "Free and source-available fair-code licensed workflow automation tool";
|
||||
longDescription = ''
|
||||
Free and source-available fair-code licensed workflow automation tool.
|
||||
Easily automate tasks across different services.
|
||||
'';
|
||||
homepage = "https://n8n.io";
|
||||
changelog = "https://github.com/n8n-io/n8n/releases/tag/n8n@${finalAttrs.version}";
|
||||
maintainers = with lib.maintainers; [
|
||||
gepbird
|
||||
AdrienLemaire
|
||||
];
|
||||
license = lib.licenses.sustainableUse;
|
||||
mainProgram = "n8n";
|
||||
platforms = lib.platforms.unix;
|
||||
};
|
||||
})
|
||||
Reference in New Issue
Block a user