chore: Abstract machines and modules
All checks were successful
Check workflows / check_workflows (push) Successful in 21s
Run pre-commit on all files / check (push) Successful in 24s
Check meta / check_dns (pull_request) Successful in 19s
Check meta / check_meta (pull_request) Successful in 18s
Check workflows / check_workflows (pull_request) Successful in 19s
Build all the nodes / bridge01 (pull_request) Successful in 1m13s
Build all the nodes / geo01 (pull_request) Successful in 1m14s
Build all the nodes / compute01 (pull_request) Successful in 1m44s
Build all the nodes / geo02 (pull_request) Successful in 1m12s
Build all the nodes / rescue01 (pull_request) Successful in 1m30s
Build all the nodes / storage01 (pull_request) Successful in 1m29s
Build all the nodes / vault01 (pull_request) Successful in 1m26s
Build all the nodes / web02 (pull_request) Successful in 1m19s
Run pre-commit on all files / check (pull_request) Successful in 24s
Build all the nodes / web01 (pull_request) Successful in 1m56s
Build all the nodes / web03 (pull_request) Successful in 1m25s
All checks were successful
Check workflows / check_workflows (push) Successful in 21s
Run pre-commit on all files / check (push) Successful in 24s
Check meta / check_dns (pull_request) Successful in 19s
Check meta / check_meta (pull_request) Successful in 18s
Check workflows / check_workflows (pull_request) Successful in 19s
Build all the nodes / bridge01 (pull_request) Successful in 1m13s
Build all the nodes / geo01 (pull_request) Successful in 1m14s
Build all the nodes / compute01 (pull_request) Successful in 1m44s
Build all the nodes / geo02 (pull_request) Successful in 1m12s
Build all the nodes / rescue01 (pull_request) Successful in 1m30s
Build all the nodes / storage01 (pull_request) Successful in 1m29s
Build all the nodes / vault01 (pull_request) Successful in 1m26s
Build all the nodes / web02 (pull_request) Successful in 1m19s
Run pre-commit on all files / check (pull_request) Successful in 24s
Build all the nodes / web01 (pull_request) Successful in 1m56s
Build all the nodes / web03 (pull_request) Successful in 1m25s
This adds subdirectories for the different types of systems, for the modules and the machines
This commit is contained in:
parent
c3f4e7ade6
commit
ecbad0a638
264 changed files with 49 additions and 38 deletions
77
modules/nixos/default.nix
Normal file
77
modules/nixos/default.nix
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Copyright :
|
||||
# - Tom Hubrecht <tom.hubrecht@dgnum.eu> 2023
|
||||
# - Maurice Debray <maurice.debray@dgnum.eu> 2023
|
||||
#
|
||||
# Ce logiciel est un programme informatique servant à déployer des
|
||||
# configurations de serveurs via NixOS.
|
||||
#
|
||||
# Ce logiciel est régi par la licence CeCILL soumise au droit français et
|
||||
# respectant les principes de diffusion des logiciels libres. Vous pouvez
|
||||
# utiliser, modifier et/ou redistribuer ce programme sous les conditions
|
||||
# de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
|
||||
# sur le site "http://www.cecill.info".
|
||||
#
|
||||
# En contrepartie de l'accessibilité au code source et des droits de copie,
|
||||
# de modification et de redistribution accordés par cette licence, il n'est
|
||||
# offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
|
||||
# seule une responsabilité restreinte pèse sur l'auteur du programme, le
|
||||
# titulaire des droits patrimoniaux et les concédants successifs.
|
||||
#
|
||||
# A cet égard l'attention de l'utilisateur est attirée sur les risques
|
||||
# associés au chargement, à l'utilisation, à la modification et/ou au
|
||||
# développement et à la reproduction du logiciel par l'utilisateur étant
|
||||
# donné sa spécificité de logiciel libre, qui peut le rendre complexe à
|
||||
# manipuler et qui le réserve donc à des développeurs et des professionnels
|
||||
# avertis possédant des connaissances informatiques approfondies. Les
|
||||
# utilisateurs sont donc invités à charger et tester l'adéquation du
|
||||
# logiciel à leurs besoins dans des conditions permettant d'assurer la
|
||||
# sécurité de leurs systèmes et ou de leurs données et, plus généralement,
|
||||
# à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.
|
||||
#
|
||||
# Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
|
||||
# pris connaissance de la licence CeCILL, et que vous en avez accepté les
|
||||
# termes.
|
||||
|
||||
{
|
||||
lib,
|
||||
nodeMeta,
|
||||
sources,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
imports =
|
||||
(lib.extra.mkImports ./. [
|
||||
"dgn-access-control"
|
||||
"dgn-acme"
|
||||
"dgn-backups"
|
||||
"dgn-console"
|
||||
"dgn-chatops"
|
||||
"dgn-firewall"
|
||||
"dgn-hardware"
|
||||
"dgn-netbox-agent"
|
||||
"dgn-network"
|
||||
"dgn-node-monitoring"
|
||||
"dgn-notify"
|
||||
"dgn-records"
|
||||
"dgn-redirections"
|
||||
"dgn-ssh"
|
||||
"dgn-vm-variant"
|
||||
"dgn-web"
|
||||
"django-apps"
|
||||
])
|
||||
++ [
|
||||
"${sources.agenix}/modules/age.nix"
|
||||
"${sources.arkheon}/module.nix"
|
||||
"${sources."microvm.nix"}/nixos-modules/host"
|
||||
]
|
||||
++ ((import sources.nix-modules { inherit lib; }).importModules (
|
||||
[
|
||||
"age-secrets"
|
||||
"services/bupstash"
|
||||
"services/reaction"
|
||||
"services/systemd-notify"
|
||||
]
|
||||
++ nodeMeta.nix-modules
|
||||
));
|
||||
}
|
101
modules/nixos/dgn-access-control.nix
Normal file
101
modules/nixos/dgn-access-control.nix
Normal file
|
@ -0,0 +1,101 @@
|
|||
# Copyright :
|
||||
# - Tom Hubrecht <tom.hubrecht@dgnum.eu> 2023
|
||||
#
|
||||
# Ce logiciel est un programme informatique servant à déployer des
|
||||
# configurations de serveurs via NixOS.
|
||||
#
|
||||
# Ce logiciel est régi par la licence CeCILL soumise au droit français et
|
||||
# respectant les principes de diffusion des logiciels libres. Vous pouvez
|
||||
# utiliser, modifier et/ou redistribuer ce programme sous les conditions
|
||||
# de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
|
||||
# sur le site "http://www.cecill.info".
|
||||
#
|
||||
# En contrepartie de l'accessibilité au code source et des droits de copie,
|
||||
# de modification et de redistribution accordés par cette licence, il n'est
|
||||
# offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
|
||||
# seule une responsabilité restreinte pèse sur l'auteur du programme, le
|
||||
# titulaire des droits patrimoniaux et les concédants successifs.
|
||||
#
|
||||
# A cet égard l'attention de l'utilisateur est attirée sur les risques
|
||||
# associés au chargement, à l'utilisation, à la modification et/ou au
|
||||
# développement et à la reproduction du logiciel par l'utilisateur étant
|
||||
# donné sa spécificité de logiciel libre, qui peut le rendre complexe à
|
||||
# manipuler et qui le réserve donc à des développeurs et des professionnels
|
||||
# avertis possédant des connaissances informatiques approfondies. Les
|
||||
# utilisateurs sont donc invités à charger et tester l'adéquation du
|
||||
# logiciel à leurs besoins dans des conditions permettant d'assurer la
|
||||
# sécurité de leurs systèmes et ou de leurs données et, plus généralement,
|
||||
# à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.
|
||||
#
|
||||
# Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
|
||||
# pris connaissance de la licence CeCILL, et que vous en avez accepté les
|
||||
# termes.
|
||||
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
dgn-keys,
|
||||
meta,
|
||||
nodeMeta,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
mkDefault
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkMerge
|
||||
mkOption
|
||||
|
||||
types
|
||||
;
|
||||
|
||||
admins =
|
||||
meta.organization.groups.root
|
||||
++ nodeMeta.admins
|
||||
++ (builtins.concatMap (g: meta.organization.groups.${g}) nodeMeta.adminGroups);
|
||||
|
||||
cfg = config.dgn-access-control;
|
||||
in
|
||||
|
||||
{
|
||||
options.dgn-access-control = {
|
||||
enable = mkEnableOption "DGNum access control." // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
users = mkOption {
|
||||
type = with types; attrsOf (listOf str);
|
||||
default = { };
|
||||
description = ''
|
||||
Attribute set describing which member has access to which user on the node.
|
||||
Members must be declared in `meta/members.nix`.
|
||||
'';
|
||||
example = ''
|
||||
{
|
||||
user1 = [ "member1" "member2" ];
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (mkMerge [
|
||||
{
|
||||
# Admins have root access to the node
|
||||
dgn-access-control.users.root = mkDefault admins;
|
||||
|
||||
users.users = builtins.mapAttrs (_: members: {
|
||||
openssh.authorizedKeys.keys = dgn-keys.getKeys members;
|
||||
}) cfg.users;
|
||||
}
|
||||
{
|
||||
users = {
|
||||
mutableUsers = false;
|
||||
users.root = {
|
||||
inherit (nodeMeta) hashedPassword;
|
||||
};
|
||||
};
|
||||
}
|
||||
]);
|
||||
}
|
53
modules/nixos/dgn-acme.nix
Normal file
53
modules/nixos/dgn-acme.nix
Normal file
|
@ -0,0 +1,53 @@
|
|||
# Copyright :
|
||||
# - Tom Hubrecht <tom.hubrecht@dgnum.eu> 2023
|
||||
#
|
||||
# Ce logiciel est un programme informatique servant à déployer des
|
||||
# configurations de serveurs via NixOS.
|
||||
#
|
||||
# Ce logiciel est régi par la licence CeCILL soumise au droit français et
|
||||
# respectant les principes de diffusion des logiciels libres. Vous pouvez
|
||||
# utiliser, modifier et/ou redistribuer ce programme sous les conditions
|
||||
# de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
|
||||
# sur le site "http://www.cecill.info".
|
||||
#
|
||||
# En contrepartie de l'accessibilité au code source et des droits de copie,
|
||||
# de modification et de redistribution accordés par cette licence, il n'est
|
||||
# offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
|
||||
# seule une responsabilité restreinte pèse sur l'auteur du programme, le
|
||||
# titulaire des droits patrimoniaux et les concédants successifs.
|
||||
#
|
||||
# A cet égard l'attention de l'utilisateur est attirée sur les risques
|
||||
# associés au chargement, à l'utilisation, à la modification et/ou au
|
||||
# développement et à la reproduction du logiciel par l'utilisateur étant
|
||||
# donné sa spécificité de logiciel libre, qui peut le rendre complexe à
|
||||
# manipuler et qui le réserve donc à des développeurs et des professionnels
|
||||
# avertis possédant des connaissances informatiques approfondies. Les
|
||||
# utilisateurs sont donc invités à charger et tester l'adéquation du
|
||||
# logiciel à leurs besoins dans des conditions permettant d'assurer la
|
||||
# sécurité de leurs systèmes et ou de leurs données et, plus généralement,
|
||||
# à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.
|
||||
#
|
||||
# Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
|
||||
# pris connaissance de la licence CeCILL, et que vous en avez accepté les
|
||||
# termes.
|
||||
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkEnableOption mkIf;
|
||||
|
||||
cfg = config.dgn-acme;
|
||||
in
|
||||
|
||||
{
|
||||
options.dgn-acme.enable = mkEnableOption "ACME settings." // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "acme@dgnum.eu";
|
||||
};
|
||||
};
|
||||
}
|
130
modules/nixos/dgn-backups/default.nix
Normal file
130
modules/nixos/dgn-backups/default.nix
Normal file
|
@ -0,0 +1,130 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
dgn-keys,
|
||||
name,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mkEnableOption mkOption remove;
|
||||
|
||||
inherit (lib.types)
|
||||
attrs
|
||||
attrsOf
|
||||
listOf
|
||||
str
|
||||
submodule
|
||||
;
|
||||
|
||||
cfg = config.dgn-backups;
|
||||
|
||||
homes = {
|
||||
compute01 = "/data/slow/bupstash";
|
||||
geo01 = "/data/bupstash";
|
||||
geo02 = "/data/bupstash";
|
||||
storage01 = "/data/slow/bupstash";
|
||||
};
|
||||
|
||||
starts = {
|
||||
compute01 = "*-*-* *:38:00";
|
||||
storage01 = "*-*-* *:21:00";
|
||||
web01 = "*-*-* *:47:00";
|
||||
};
|
||||
|
||||
mkJobs = builtins.mapAttrs (
|
||||
_:
|
||||
{ to, settings }:
|
||||
{
|
||||
startAt = starts.${name};
|
||||
key = config.age.secrets."bupstash-put_key".path;
|
||||
repositoryCommands = lib.extra.mapSingleFuse (
|
||||
host: "ssh -i /etc/ssh/ssh_host_ed25519_key bupstash-repo@${host}.dgnum"
|
||||
) to;
|
||||
}
|
||||
// settings
|
||||
);
|
||||
in
|
||||
|
||||
{
|
||||
options.dgn-backups = {
|
||||
enable = mkEnableOption "DGNum backup service.";
|
||||
|
||||
postgresDatabases = mkOption {
|
||||
type = listOf str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of postgres databases to dump into bupstash.
|
||||
'';
|
||||
};
|
||||
|
||||
jobs = mkOption {
|
||||
type = attrsOf (submodule {
|
||||
options = {
|
||||
to = mkOption {
|
||||
type = listOf str;
|
||||
default = remove name [
|
||||
"compute01"
|
||||
"geo01"
|
||||
"geo02"
|
||||
"storage01"
|
||||
];
|
||||
description = "Hosts to send the backups to.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = attrs;
|
||||
default = { };
|
||||
description = "Base bupstash job config.";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = { };
|
||||
description = "List of bupstash jobs.";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
dgn-backups.jobs = lib.extra.mapFuse (db: {
|
||||
"${db}-db".settings = {
|
||||
user = "postgres";
|
||||
command = [
|
||||
"${lib.getExe' config.services.postgresql.package "pg_dump"}"
|
||||
db
|
||||
];
|
||||
};
|
||||
}) cfg.postgresDatabases;
|
||||
|
||||
services.bupstash = {
|
||||
repositories = {
|
||||
inherit (cfg) enable;
|
||||
|
||||
home = homes.${name};
|
||||
|
||||
access = [
|
||||
{
|
||||
repo = "default";
|
||||
keys = dgn-keys.getKeys [
|
||||
"compute01"
|
||||
"storage01"
|
||||
"vault01"
|
||||
"web01"
|
||||
];
|
||||
allowed = [ "put" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
jobs = mkJobs cfg.jobs;
|
||||
};
|
||||
|
||||
programs.ssh.knownHosts =
|
||||
lib.extra.mapFuse (host: { "${host}.dgnum".publicKey = builtins.head dgn-keys._keys.${host}; })
|
||||
[
|
||||
"compute01"
|
||||
"geo01"
|
||||
"geo02"
|
||||
"storage01"
|
||||
];
|
||||
};
|
||||
}
|
BIN
modules/nixos/dgn-backups/keys/compute01.key
Normal file
BIN
modules/nixos/dgn-backups/keys/compute01.key
Normal file
Binary file not shown.
5
modules/nixos/dgn-backups/keys/secrets.nix
Normal file
5
modules/nixos/dgn-backups/keys/secrets.nix
Normal file
|
@ -0,0 +1,5 @@
|
|||
(import ../../../../keys).mkSecrets [ ] [
|
||||
"compute01.key"
|
||||
"storage01.key"
|
||||
"web01.key"
|
||||
]
|
BIN
modules/nixos/dgn-backups/keys/storage01.key
Normal file
BIN
modules/nixos/dgn-backups/keys/storage01.key
Normal file
Binary file not shown.
BIN
modules/nixos/dgn-backups/keys/web01.key
Normal file
BIN
modules/nixos/dgn-backups/keys/web01.key
Normal file
Binary file not shown.
1
modules/nixos/dgn-chatops/.envrc
Normal file
1
modules/nixos/dgn-chatops/.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use nix
|
75
modules/nixos/dgn-chatops/default.nix
Normal file
75
modules/nixos/dgn-chatops/default.nix
Normal file
|
@ -0,0 +1,75 @@
|
|||
# Copyright :
|
||||
# - Ryan Lahfa <ryan.lahfa@dgnum.eu> 2024
|
||||
#
|
||||
# Ce logiciel est un programme informatique servant à déployer des
|
||||
# configurations de serveurs via NixOS.
|
||||
#
|
||||
# Ce logiciel est régi par la licence CeCILL soumise au droit français et
|
||||
# respectant les principes de diffusion des logiciels libres. Vous pouvez
|
||||
# utiliser, modifier et/ou redistribuer ce programme sous les conditions
|
||||
# de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
|
||||
# sur le site "http://www.cecill.info".
|
||||
#
|
||||
# En contrepartie de l'accessibilité au code source et des droits de copie,
|
||||
# de modification et de redistribution accordés par cette licence, il n'est
|
||||
# offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
|
||||
# seule une responsabilité restreinte pèse sur l'auteur du programme, le
|
||||
# titulaire des droits patrimoniaux et les concédants successifs.
|
||||
#
|
||||
# A cet égard l'attention de l'utilisateur est attirée sur les risques
|
||||
# associés au chargement, à l'utilisation, à la modification et/ou au
|
||||
# développement et à la reproduction du logiciel par l'utilisateur étant
|
||||
# donné sa spécificité de logiciel libre, qui peut le rendre complexe à
|
||||
# manipuler et qui le réserve donc à des développeurs et des professionnels
|
||||
# avertis possédant des connaissances informatiques approfondies. Les
|
||||
# utilisateurs sont donc invités à charger et tester l'adéquation du
|
||||
# logiciel à leurs besoins dans des conditions permettant d'assurer la
|
||||
# sécurité de leurs systèmes et ou de leurs données et, plus généralement,
|
||||
# à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.
|
||||
#
|
||||
# Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
|
||||
# pris connaissance de la licence CeCILL, et que vous en avez accepté les
|
||||
# termes.
|
||||
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.dgn-chatops;
|
||||
inherit (lib) mkEnableOption mkIf;
|
||||
python3 = pkgs.python311;
|
||||
python3Pkgs = python3.pkgs;
|
||||
ircrobots = python3Pkgs.callPackage ./ircrobots.nix { };
|
||||
tortoise-orm = python3Pkgs.callPackage ./tortoise-orm.nix { };
|
||||
ps = python3Pkgs.makePythonPath [
|
||||
ircrobots
|
||||
tortoise-orm
|
||||
python3Pkgs.aiohttp
|
||||
];
|
||||
in
|
||||
{
|
||||
options.dgn-chatops = {
|
||||
enable = mkEnableOption "the ChatOps layer";
|
||||
};
|
||||
|
||||
# Our ChatOps bot.
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.irc-takumi = {
|
||||
description = "DGNum IRC automation bot, Takumi";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
environment = {
|
||||
PYTHONPATH = ps;
|
||||
};
|
||||
serviceConfig = {
|
||||
RuntimeDirectory = "takumi";
|
||||
StateDirectory = "takumi";
|
||||
DynamicUser = true;
|
||||
ExecStart = "${lib.getExe python3} ${./takumi.py}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
56
modules/nixos/dgn-chatops/ircrobots.nix
Normal file
56
modules/nixos/dgn-chatops/ircrobots.nix
Normal file
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
lib,
|
||||
buildPythonPackage,
|
||||
fetchFromGitea,
|
||||
pythonOlder,
|
||||
anyio,
|
||||
asyncio-rlock,
|
||||
asyncio-throttle,
|
||||
ircstates,
|
||||
async-stagger,
|
||||
async-timeout,
|
||||
python,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "ircrobots";
|
||||
version = "0.7.0";
|
||||
format = "setuptools";
|
||||
disabled = pythonOlder "3.7";
|
||||
|
||||
src = fetchFromGitea {
|
||||
domain = "git.dgnum.eu";
|
||||
owner = "DGNum";
|
||||
repo = pname;
|
||||
# No tag yet :(.
|
||||
rev = "63aa84b40450bd534fc232eee10e8088028c9f6d";
|
||||
hash = "sha256-gXiPy6wjPEtc9v0cG0lb2QVXDlU5Q8ncxJO0lBm2RSE=";
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
# too specific pins https://github.com/jesopo/ircrobots/issues/3
|
||||
sed -iE 's/anyio.*/anyio/' requirements.txt
|
||||
'';
|
||||
|
||||
propagatedBuildInputs = [
|
||||
anyio
|
||||
asyncio-rlock
|
||||
asyncio-throttle
|
||||
ircstates
|
||||
async-stagger
|
||||
async-timeout
|
||||
];
|
||||
|
||||
checkPhase = ''
|
||||
${python.interpreter} -m unittest test
|
||||
'';
|
||||
|
||||
pythonImportsCheck = [ "ircrobots" ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Asynchronous bare-bones IRC bot framework for python3";
|
||||
license = licenses.mit;
|
||||
homepage = "https://github.com/jesopo/ircrobots";
|
||||
maintainers = with maintainers; [ hexa ];
|
||||
};
|
||||
}
|
31
modules/nixos/dgn-chatops/pypika-tortoise.nix
Normal file
31
modules/nixos/dgn-chatops/pypika-tortoise.nix
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
lib,
|
||||
buildPythonPackage,
|
||||
fetchFromGitHub,
|
||||
poetry-core,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "pypika-tortoise";
|
||||
version = "0.1.6";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "tortoise";
|
||||
repo = "pypika-tortoise";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-xx5FUMHh6413fsvwrEA+Q0tBmJWy00h5O6YijvrJyCE=";
|
||||
};
|
||||
|
||||
build-system = [ poetry-core ];
|
||||
|
||||
pythonImportsCheck = [ "pypika" ];
|
||||
|
||||
meta = {
|
||||
description = "";
|
||||
homepage = "https://github.com/tortoise/pypika-tortoise";
|
||||
changelog = "https://github.com/tortoise/pypika-tortoise/blob/${src.rev}/CHANGELOG.md";
|
||||
license = lib.licenses.asl20;
|
||||
maintainers = with lib.maintainers; [ raitobezarius ];
|
||||
};
|
||||
}
|
20
modules/nixos/dgn-chatops/pyproject.toml
Normal file
20
modules/nixos/dgn-chatops/pyproject.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "takumi"
|
||||
version = "1.1.0"
|
||||
authors = [
|
||||
{ name = "Ryan Lahfa", email = "ryan@dgnum.eu" },
|
||||
]
|
||||
description = "Fully automatic day-to-day operations at DGNum"
|
||||
requires-python = ">=3.11"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://git.dgnum.eu/DGNum/infrastructure"
|
||||
Issues = "https://git.dgnum.eu/DGNum/infrastructure/issues"
|
29
modules/nixos/dgn-chatops/shell.nix
Normal file
29
modules/nixos/dgn-chatops/shell.nix
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
pkgs ? import <nixpkgs> { },
|
||||
python3 ? pkgs.python3,
|
||||
}:
|
||||
let
|
||||
takumi = python3.pkgs.buildPythonPackage rec {
|
||||
pname = "takumi";
|
||||
version = "1.1.0";
|
||||
pyproject = true;
|
||||
|
||||
src = ./.;
|
||||
|
||||
build-system = [ python3.pkgs.hatchling ];
|
||||
|
||||
dependencies = [
|
||||
(python3.pkgs.callPackage ./ircrobots.nix { })
|
||||
(python3.pkgs.callPackage ./tortoise-orm.nix { })
|
||||
python3.pkgs.aiohttp
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $out/bin
|
||||
cp -v takumi.py $out/bin/takumi.py
|
||||
chmod +x $out/bin/takumi.py
|
||||
wrapProgram $out/bin/takumi.py --prefix PYTHONPATH : "$PYTHONPATH"
|
||||
'';
|
||||
};
|
||||
in
|
||||
pkgs.mkShell { packages = [ takumi ]; }
|
121
modules/nixos/dgn-chatops/takumi.py
Normal file
121
modules/nixos/dgn-chatops/takumi.py
Normal file
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
|
||||
from irctokens.line import build, Line
|
||||
from ircrobots.bot import Bot as BaseBot
|
||||
from ircrobots.server import Server as BaseServer
|
||||
from ircrobots.params import ConnectionParams
|
||||
|
||||
import aiohttp
|
||||
|
||||
BRIDGE_NICKNAME = "hermes"
|
||||
|
||||
SERVERS = [
|
||||
("dgnum", "irc.dgnum.eu")
|
||||
]
|
||||
|
||||
TEAMS = {
|
||||
"fai": ("tomate", "elias", "JeMaGius", "Luj", "catvayor", "Raito"),
|
||||
"marketing": ("cst1", "elias"),
|
||||
"bureau": ("Raito", "JeMaGius", "Luj", "gdd")
|
||||
}
|
||||
|
||||
# times format is 0700-29092024
|
||||
TRIGGER = '!'
|
||||
async def create_meet(title: str, times: list[str], timezone: str = "UTC") -> str:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
payload = {
|
||||
'name': title,
|
||||
'times': times,
|
||||
'timezone': timezone
|
||||
}
|
||||
async with session.post('https://api.meet.dgnum.eu/event', json=payload) as response:
|
||||
response.raise_for_status()
|
||||
id = (await response.json()).get('id')
|
||||
if not id:
|
||||
raise RuntimeError('No ID attributed to a meet')
|
||||
return f'https://meet.dgnum.eu/{id}'
|
||||
|
||||
def expand_times(times: list[str]) -> list[str]:
|
||||
expanded = []
|
||||
# TODO: verify the date exist in the calendar
|
||||
# TODO: verify that we don't write any duplicates.
|
||||
for time in times:
|
||||
if '-' not in time:
|
||||
for i in range(7, 20):
|
||||
expanded.append(f'{i:02}00-{time}')
|
||||
else:
|
||||
expanded.append(time)
|
||||
return expanded
|
||||
|
||||
def bridge_stripped(possible_command: str, origin_nick: str) -> str | None:
|
||||
if origin_nick.lower() == BRIDGE_NICKNAME:
|
||||
stripped_user = possible_command.split(':')[1].lstrip()
|
||||
return stripped_user if stripped_user.startswith(TRIGGER) else None
|
||||
else:
|
||||
return possible_command if possible_command.startswith(TRIGGER) else None
|
||||
|
||||
class Server(BaseServer):
|
||||
def extract_valid_command(self, line: Line) -> str | None:
|
||||
me = self.nickname_lower
|
||||
if line.command == "PRIVMSG" and \
|
||||
self.has_channel(line.params[0]) and \
|
||||
line.hostmask is not None and \
|
||||
self.casefold(line.hostmask.nickname) != me and \
|
||||
self.has_user(line.hostmask.nickname):
|
||||
return bridge_stripped(line.params[1], line.hostmask.nickname)
|
||||
|
||||
|
||||
async def line_read(self, line: Line):
|
||||
print(f"{self.name} < {line.format()}")
|
||||
if line.command == "001":
|
||||
print(f"connected to {self.isupport.network}")
|
||||
await self.send(build("JOIN", ["#dgnum-bridge-test"]))
|
||||
|
||||
# In case `!probe_meet <title> <team> <time_1> <time_2> … <time_N> [<timezone>]`
|
||||
if (command := self.extract_valid_command(line)) is not None:
|
||||
text = command.lstrip(TRIGGER)
|
||||
if text.startswith('probe_meet') or text.startswith('pm'):
|
||||
args = text.split(' ')
|
||||
if len(args) < 4:
|
||||
await self.send(build("PRIVMSG", [line.params[0], "usage is !probe_meet <title> <team> <time_1> [<time_2> <time_3> … <time_N>] ; time is in [00-hour-]DDMMYYYY format."]))
|
||||
return
|
||||
|
||||
title, team = args[1], args[2]
|
||||
print(f"creating meet '{title}' for team '{team}'")
|
||||
try:
|
||||
times = expand_times(args[3:])
|
||||
link = await create_meet(title, times)
|
||||
if team not in TEAMS:
|
||||
await self.send(build("PRIVMSG", [line.params[0], f"team {team} does not exist"]))
|
||||
return
|
||||
|
||||
targets = TEAMS[team]
|
||||
ping_mentions = ', '.join(targets)
|
||||
await self.send(build("PRIVMSG", [line.params[0], f'{ping_mentions} {link}']))
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
await self.send(build("PRIVMSG", [line.params[0], "time format is [00-hour-]DDMMYYYY, hour is optional, by default it's 07:00 to 19:00 in Europe/Paris timezone"]))
|
||||
except aiohttp.ClientError as e:
|
||||
print(e)
|
||||
await self.send(build("PRIVMSG", [line.params[0], "failed to create the meet on meet.dgnum.eu, API error, check the logs"]))
|
||||
|
||||
|
||||
async def line_send(self, line: Line):
|
||||
print(f"{self.name} > {line.format()}")
|
||||
|
||||
class Bot(BaseBot):
|
||||
def create_server(self, name: str):
|
||||
return Server(self, name)
|
||||
|
||||
async def main():
|
||||
bot = Bot()
|
||||
for name, host in SERVERS:
|
||||
# For IPv4-only connections.
|
||||
params = ConnectionParams("Takumi", host, 6698)
|
||||
await bot.add_server(name, params)
|
||||
|
||||
await bot.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
71
modules/nixos/dgn-chatops/tortoise-orm.nix
Normal file
71
modules/nixos/dgn-chatops/tortoise-orm.nix
Normal file
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
lib,
|
||||
buildPythonPackage,
|
||||
fetchFromGitHub,
|
||||
poetry-core,
|
||||
aiosqlite,
|
||||
iso8601,
|
||||
callPackage,
|
||||
pytz,
|
||||
ciso8601,
|
||||
orjson,
|
||||
uvloop,
|
||||
aiomysql,
|
||||
asyncmy,
|
||||
asyncpg,
|
||||
psycopg,
|
||||
pydantic,
|
||||
pythonRelaxDepsHook,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "tortoise-orm";
|
||||
version = "0.21.6";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "tortoise";
|
||||
repo = "tortoise-orm";
|
||||
rev = version;
|
||||
hash = "sha256-Gu7MSJbPjaGUN6tmHwkmx7Bdy/+V1wZjmTCQrTDDPkw=";
|
||||
};
|
||||
|
||||
buildInputs = [ pythonRelaxDepsHook ];
|
||||
|
||||
pythonRelaxDeps = [
|
||||
"aiosqlite"
|
||||
"iso8601"
|
||||
];
|
||||
|
||||
build-system = [ poetry-core ];
|
||||
|
||||
dependencies = [
|
||||
aiosqlite
|
||||
iso8601
|
||||
pydantic
|
||||
(callPackage ./pypika-tortoise.nix { })
|
||||
pytz
|
||||
];
|
||||
|
||||
optional-dependencies = {
|
||||
accel = [
|
||||
ciso8601
|
||||
orjson
|
||||
uvloop
|
||||
];
|
||||
aiomysql = [ aiomysql ];
|
||||
asyncmy = [ asyncmy ];
|
||||
asyncpg = [ asyncpg ];
|
||||
psycopg = [ psycopg ];
|
||||
};
|
||||
|
||||
pythonImportsCheck = [ "tortoise" ];
|
||||
|
||||
meta = {
|
||||
description = "";
|
||||
homepage = "https://github.com/tortoise/tortoise-orm";
|
||||
changelog = "https://github.com/tortoise/tortoise-orm/blob/${src.rev}/CHANGELOG.rst";
|
||||
license = lib.licenses.asl20;
|
||||
maintainers = with lib.maintainers; [ raitobezarius ];
|
||||
};
|
||||
}
|
120
modules/nixos/dgn-console.nix
Normal file
120
modules/nixos/dgn-console.nix
Normal file
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mkEnableOption mkOption mkIf;
|
||||
|
||||
cfg = config.dgn-console;
|
||||
in
|
||||
{
|
||||
options.dgn-console = {
|
||||
enable = mkEnableOption "DGNum console setup." // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
pg-upgrade-from = mkOption {
|
||||
type = lib.types.package;
|
||||
default = config.services.postgresql.package;
|
||||
};
|
||||
|
||||
pg-upgrade-to = mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.postgresql_15;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
time.timeZone = "Europe/Paris";
|
||||
|
||||
console = {
|
||||
keyMap = "fr";
|
||||
};
|
||||
|
||||
environment.variables.EDITOR = "nvim";
|
||||
|
||||
programs = {
|
||||
neovim.vimAlias = true;
|
||||
rust-motd = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
uptime.prefix = "Up";
|
||||
filesystems.root = "/";
|
||||
memory.swap_pos = "below";
|
||||
last_login.root = 5;
|
||||
};
|
||||
};
|
||||
|
||||
bash.promptInit = ''
|
||||
FQDN="$(hostname).$(domainname)"
|
||||
# Provide a nice prompt if the terminal supports it.
|
||||
if [ "$TERM" != "dumb" ] || [ -n "$INSIDE_EMACS" ]; then
|
||||
PROMPT_COLOR="1;31m"
|
||||
((UID)) && PROMPT_COLOR="1;32m"
|
||||
if [ -n "$INSIDE_EMACS" ] || [ "$TERM" = "eterm" ] || [ "$TERM" = "eterm-color" ]; then
|
||||
# Emacs term mode doesn't support xterm title escape sequence (\e]0;)
|
||||
PS1="\n\[\033[$PROMPT_COLOR\][\u@$FQDN:\w]\\$\[\033[0m\] "
|
||||
else
|
||||
PS1="\n\[\033[$PROMPT_COLOR\][\[\e]0;\u@\H: \w\a\]\u@$FQDN:\w]\\$\[\033[0m\] "
|
||||
fi
|
||||
if test "$TERM" = "xterm"; then
|
||||
PS1="\[\033]2;$FQDN:\u:\w\007\]$PS1"
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
system.activationScripts.diff = {
|
||||
supportsDryActivation = true;
|
||||
text = ''
|
||||
${pkgs.nvd}/bin/nvd --nix-bin-dir=${pkgs.nix}/bin diff /run/current-system "$systemConfig"
|
||||
'';
|
||||
};
|
||||
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
environment.systemPackages =
|
||||
(with pkgs; [
|
||||
neovim
|
||||
wget
|
||||
kitty.terminfo
|
||||
|
||||
# Utilities
|
||||
bcc
|
||||
bottom
|
||||
cpuid
|
||||
dig
|
||||
htop
|
||||
iftop
|
||||
mtr
|
||||
tcpdump
|
||||
])
|
||||
++ [ config.boot.kernelPackages.perf ]
|
||||
++ lib.optional (config.services.postgresql.enable && cfg.pg-upgrade-from != cfg.pg-upgrade-to) (
|
||||
pkgs.writeScriptBin "upgrade-pg-cluster" ''
|
||||
set -eux
|
||||
# XXX it's perhaps advisable to stop all services that depend on postgresql
|
||||
systemctl stop postgresql
|
||||
|
||||
export NEWDATA="/var/lib/postgresql/${cfg.pg-upgrade-to.psqlSchema}"
|
||||
export NEWBIN="${cfg.pg-upgrade-to}/bin"
|
||||
|
||||
export OLDDATA="${config.services.postgresql.dataDir}"
|
||||
export OLDBIN="${cfg.pg-upgrade-from}/bin"
|
||||
|
||||
install -d -m 0700 -o postgres -g postgres "$NEWDATA"
|
||||
cd "$NEWDATA"
|
||||
sudo -u postgres $NEWBIN/initdb -D "$NEWDATA"
|
||||
|
||||
sudo -u postgres $NEWBIN/pg_upgrade \
|
||||
--old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \
|
||||
--old-bindir $OLDBIN --new-bindir $NEWBIN \
|
||||
"$@"
|
||||
''
|
||||
);
|
||||
};
|
||||
}
|
87
modules/nixos/dgn-firewall/default.nix
Normal file
87
modules/nixos/dgn-firewall/default.nix
Normal file
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
name,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
concatStringsSep
|
||||
length
|
||||
replicate
|
||||
splitString
|
||||
;
|
||||
|
||||
inherit (lib.lists) map;
|
||||
|
||||
c4 = "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
|
||||
|
||||
mkV4' = p: concatStringsSep "\\." (p ++ (replicate (4 - (length p)) c4));
|
||||
mkV4 = s: mkV4' (splitString "." s);
|
||||
|
||||
nft = s: [ "nft" ] ++ [ s ];
|
||||
|
||||
streams' = import ./streams.nix;
|
||||
in
|
||||
|
||||
{
|
||||
# Switch to nftables
|
||||
networking.nftables.enable = true;
|
||||
|
||||
services.reaction = {
|
||||
enable = true;
|
||||
|
||||
extraPackages = [ pkgs.nftables ];
|
||||
|
||||
runAsRoot = true;
|
||||
|
||||
logLevel = "WARN";
|
||||
|
||||
settings = {
|
||||
patterns = {
|
||||
ip = {
|
||||
regex = "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|(?:(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))";
|
||||
|
||||
ignoreregex = map mkV4 [
|
||||
"10" # Legacy wireguard
|
||||
"129.199" # ENS
|
||||
"100.80" # Netbird internal
|
||||
];
|
||||
|
||||
ignore = [
|
||||
"127.0.0.1"
|
||||
"::1"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
start = [
|
||||
(nft ''
|
||||
table inet reaction {
|
||||
set ipv4bans {
|
||||
type ipv4_addr
|
||||
flags interval
|
||||
auto-merge
|
||||
}
|
||||
set ipv6bans {
|
||||
type ipv6_addr
|
||||
flags interval
|
||||
auto-merge
|
||||
}
|
||||
chain input {
|
||||
type filter hook input priority 0
|
||||
policy accept
|
||||
ip saddr @ipv4bans drop
|
||||
ip6 saddr @ipv6bans drop
|
||||
}
|
||||
}
|
||||
'')
|
||||
];
|
||||
|
||||
stop = [ (nft "delete table inet reaction") ];
|
||||
|
||||
streams = streams'.default // (streams'.${name} or { });
|
||||
};
|
||||
};
|
||||
}
|
46
modules/nixos/dgn-firewall/streams.nix
Normal file
46
modules/nixos/dgn-firewall/streams.nix
Normal file
|
@ -0,0 +1,46 @@
|
|||
let
|
||||
act = a: [
|
||||
"nft46"
|
||||
"${a} element inet reaction ipvXbans { <ip> }"
|
||||
];
|
||||
|
||||
journalctl = u: [
|
||||
"journalctl"
|
||||
"-fn0"
|
||||
"-u"
|
||||
"${u}.service"
|
||||
];
|
||||
|
||||
ban = after: {
|
||||
ban.cmd = act "add";
|
||||
unban = {
|
||||
inherit after;
|
||||
|
||||
cmd = act "delete";
|
||||
};
|
||||
};
|
||||
|
||||
available = {
|
||||
ssh = {
|
||||
cmd = journalctl "sshd";
|
||||
|
||||
filters = {
|
||||
failedlogin = {
|
||||
regex = [
|
||||
"authentication failure;.*rhost=<ip>"
|
||||
"Connection reset by authenticating user .* <ip>"
|
||||
"Connection closed by invalid user .* <ip> port .*"
|
||||
"Failed password for .* from <ip>"
|
||||
"Invalid user .* from <ip> port .*"
|
||||
"Unable to negotiate with <ip> port .*"
|
||||
];
|
||||
actions = ban "48h";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
builtins.mapAttrs (_: builtins.foldl' (a: s: a // { ${s} = available.${s}; }) { }) {
|
||||
default = [ "ssh" ];
|
||||
}
|
95
modules/nixos/dgn-hardware.nix
Normal file
95
modules/nixos/dgn-hardware.nix
Normal file
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkMerge
|
||||
mkOption
|
||||
;
|
||||
inherit (lib.types) listOf str;
|
||||
|
||||
cfg = config.dgn-hardware;
|
||||
in
|
||||
{
|
||||
options.dgn-hardware = {
|
||||
enable = mkEnableOption "default hardware configuration." // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
useSystemd = mkEnableOption "sytemd boot and configuration." // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
useZfs = mkEnableOption "zfs configuration.";
|
||||
useBcachefs = mkEnableOption "bcachefs configuration";
|
||||
|
||||
zfsPools = mkOption {
|
||||
type = listOf str;
|
||||
default = [
|
||||
"fast01"
|
||||
"work01"
|
||||
];
|
||||
description = ''
|
||||
ZFS pools present on the machine
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (mkMerge [
|
||||
{
|
||||
microvm.host.enable = lib.mkDefault false;
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
hardware.cpu.intel.updateMicrocode = true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
|
||||
boot = {
|
||||
initrd.availableKernelModules = [
|
||||
"ata_piix"
|
||||
"uhci_hcd"
|
||||
"ehci_pci"
|
||||
"virtio_pci"
|
||||
"ahci"
|
||||
"virtio_blk"
|
||||
];
|
||||
kernelModules = [ "kvm-intel" ];
|
||||
kernelParams = [
|
||||
"cgroup_enable=cpu"
|
||||
"cgroup_enable=cpuset"
|
||||
"cgroup_enable=memory"
|
||||
"cgroup_memory=1"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
(mkIf cfg.useSystemd {
|
||||
boot.loader = {
|
||||
systemd-boot.enable = true;
|
||||
efi.canTouchEfiVariables = true;
|
||||
};
|
||||
})
|
||||
|
||||
(mkIf cfg.useBcachefs {
|
||||
boot.supportedFilesystems = [ "bcachefs" ];
|
||||
boot.kernelPackages = pkgs.linuxKernel.packages.linux_6_7;
|
||||
})
|
||||
|
||||
(mkIf cfg.useZfs {
|
||||
boot = {
|
||||
supportedFilesystems = [ "zfs" ];
|
||||
|
||||
zfs = {
|
||||
forceImportRoot = false;
|
||||
extraPools = cfg.zfsPools;
|
||||
package = pkgs.zfs_2_1;
|
||||
};
|
||||
};
|
||||
})
|
||||
]);
|
||||
}
|
51
modules/nixos/dgn-netbox-agent/default.nix
Normal file
51
modules/nixos/dgn-netbox-agent/default.nix
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
nodeMeta,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (config.networking) hostName domain;
|
||||
in
|
||||
|
||||
{
|
||||
imports = [ ./module.nix ];
|
||||
|
||||
options.dgn-netbox-agent = {
|
||||
enable = lib.mkEnableOption "DGNum netbox agent setup." // {
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.dgn-netbox-agent.enable {
|
||||
services.netbox-agent = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
netbox.url = "https://netbox.dgnum.eu/";
|
||||
network.ignore_interfaces = "(lo|dummy.*|docker.*|podman.*)";
|
||||
register = true;
|
||||
update_all = true;
|
||||
virtual = {
|
||||
enabled = nodeMeta.vm-cluster != null;
|
||||
cluster_name = nodeMeta.vm-cluster;
|
||||
};
|
||||
purge_old_devices = true;
|
||||
hostname_cmd = "echo ${hostName}.${domain}";
|
||||
datacenter_location = {
|
||||
driver = "cmd:echo ${nodeMeta.site}";
|
||||
regex = "(.*)";
|
||||
};
|
||||
device = {
|
||||
tags = "netbox-agent";
|
||||
# Default role
|
||||
server_role = "Staging infra";
|
||||
};
|
||||
};
|
||||
randomizedDelaySec = "3h";
|
||||
environmentFile = config.age.secrets."netbox-agent".path;
|
||||
};
|
||||
|
||||
age-secrets.sources = [ ./secrets ];
|
||||
};
|
||||
}
|
115
modules/nixos/dgn-netbox-agent/module.nix
Normal file
115
modules/nixos/dgn-netbox-agent/module.nix
Normal file
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
getExe
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
mkPackageOption
|
||||
;
|
||||
|
||||
inherit (lib.types)
|
||||
either
|
||||
listOf
|
||||
nullOr
|
||||
path
|
||||
str
|
||||
;
|
||||
|
||||
settingsFormat = pkgs.formats.yaml { };
|
||||
|
||||
cfg = config.services.netbox-agent;
|
||||
in
|
||||
{
|
||||
options.services.netbox-agent = {
|
||||
enable = mkEnableOption "Netbox-agent";
|
||||
|
||||
package = (mkPackageOption pkgs "netbox-agent" { }) // {
|
||||
default = pkgs.callPackage ./package.nix { };
|
||||
};
|
||||
|
||||
startAt = mkOption {
|
||||
type = either str (listOf str);
|
||||
default = "*-*-* 00:00:00";
|
||||
description = ''
|
||||
Automatically start this unit at the given date/time, which
|
||||
must be in the format described in
|
||||
{manpage}`systemd.time(7)`.
|
||||
'';
|
||||
};
|
||||
|
||||
randomizedDelaySec = mkOption {
|
||||
type = str;
|
||||
default = "0";
|
||||
example = "45min";
|
||||
description = ''
|
||||
Add a randomized delay before each netbox-agent runs.
|
||||
The delay will be chosen between zero and this value.
|
||||
This value must be a time span in the format specified by
|
||||
{manpage}`systemd.time(7)`
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
inherit (settingsFormat) type;
|
||||
description = ''
|
||||
Settings to be passed to the netbox agent. Will be converted to a YAML
|
||||
config file
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
Environment file to pass to netbox-agent. See `netbox-agent --help` for
|
||||
possible environment variables
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.netbox-agent = {
|
||||
description = "Netbox-agent service. It generates an existing infrastructure on Netbox and have the ability to update it regularly through this service.";
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
# We could link directly into pkgs.tzdata, but at least timedatectl seems
|
||||
# to expect the symlink to point directly to a file in etc.
|
||||
# Setting the "debian timezone file" to point at /dev/null stops it doing anything.
|
||||
ExecStart = utils.escapeSystemdExecArgs [
|
||||
(getExe cfg.package)
|
||||
"-c"
|
||||
(settingsFormat.generate "config.yaml" cfg.settings)
|
||||
];
|
||||
EnvironmentFile = cfg.environmentFile;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = "strict";
|
||||
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
};
|
||||
inherit (cfg) startAt;
|
||||
};
|
||||
|
||||
systemd.timers.netbox-agent.timerConfig.RandomizedDelaySec = cfg.randomizedDelaySec;
|
||||
};
|
||||
}
|
46
modules/nixos/dgn-netbox-agent/netifaces2.nix
Normal file
46
modules/nixos/dgn-netbox-agent/netifaces2.nix
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
lib,
|
||||
buildPythonPackage,
|
||||
fetchFromGitHub,
|
||||
cargo,
|
||||
rustPlatform,
|
||||
rustc,
|
||||
typing-extensions,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "netifaces-2";
|
||||
version = "0.0.22";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "SamuelYvon";
|
||||
repo = "netifaces-2";
|
||||
rev = "V${version}";
|
||||
hash = "sha256-XO3HWq8FOVzvpbK8mIBOup6hFMnhDpqOK/5bPziPZQ8=";
|
||||
};
|
||||
|
||||
cargoDeps = rustPlatform.fetchCargoTarball {
|
||||
inherit src;
|
||||
name = "${pname}-${version}";
|
||||
hash = "sha256-uoUa6DSBuIV3RrE7svT1TVLxPHdx8BFu/C6mbpRmor0=";
|
||||
};
|
||||
|
||||
build-system = [
|
||||
cargo
|
||||
rustPlatform.cargoSetupHook
|
||||
rustPlatform.maturinBuildHook
|
||||
rustc
|
||||
];
|
||||
|
||||
dependencies = [ typing-extensions ];
|
||||
|
||||
pythonImportsCheck = [ "netifaces" ];
|
||||
|
||||
meta = {
|
||||
description = "Netifaces reborn";
|
||||
homepage = "https://github.com/SamuelYvon/netifaces-2.git";
|
||||
license = lib.licenses.mit;
|
||||
maintainers = with lib.maintainers; [ ];
|
||||
};
|
||||
}
|
64
modules/nixos/dgn-netbox-agent/package.nix
Normal file
64
modules/nixos/dgn-netbox-agent/package.nix
Normal file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
lib,
|
||||
python3,
|
||||
fetchgit,
|
||||
ethtool,
|
||||
dmidecode,
|
||||
ipmitool,
|
||||
lldpd,
|
||||
lshw,
|
||||
}:
|
||||
|
||||
python3.pkgs.buildPythonApplication {
|
||||
pname = "netbox-agent";
|
||||
version = "unstable-2023-03-19";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchgit {
|
||||
url = "https://git.dgnum.eu/DGNum/netbox-agent";
|
||||
rev = "424283239658516feb34c0f68496775350b1bf22";
|
||||
hash = "sha256-sp1QVy8AIezR2LRDDYS9G0g0GQRwGKGmEE7ykITPxtY=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = with python3.pkgs; [
|
||||
setuptools
|
||||
wheel
|
||||
pythonRelaxDepsHook
|
||||
];
|
||||
|
||||
pythonRelaxDeps = true;
|
||||
|
||||
propagatedBuildInputs = with python3.pkgs; [
|
||||
distro
|
||||
jsonargparse
|
||||
netaddr
|
||||
(callPackage ./netifaces2.nix { })
|
||||
packaging
|
||||
pynetbox
|
||||
python-slugify
|
||||
pyyaml
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
wrapProgram $out/bin/netbox_agent \
|
||||
--prefix PATH ":" ${
|
||||
lib.makeBinPath [
|
||||
ethtool
|
||||
dmidecode
|
||||
ipmitool
|
||||
lldpd
|
||||
lshw
|
||||
]
|
||||
}
|
||||
'';
|
||||
|
||||
pythonImportsCheck = [ "netbox_agent" ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Netbox agent to run on your infrastructure's servers";
|
||||
homepage = "https://git.dgnum.eu/DGNum/netbox-agent";
|
||||
license = licenses.asl20;
|
||||
maintainers = [ ];
|
||||
mainProgram = "netbox_agent";
|
||||
};
|
||||
}
|
53
modules/nixos/dgn-netbox-agent/secrets/netbox-agent
Normal file
53
modules/nixos/dgn-netbox-agent/secrets/netbox-agent
Normal file
|
@ -0,0 +1,53 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 jIXfPA zyp8jIQ/BGlaOe2hCYdO2/jpiCJO/yASFn2v4yxF3XE
|
||||
tnajUOFI/LeiRRK2+XEmgAhU8PfyerYDPZ3CASAx6uE
|
||||
-> ssh-ed25519 QlRB9Q GTRAu+AUZ2MJs3ZaZR8GcS8U2xyGR0mx1FB78TmVhik
|
||||
PmenwNgQQUd6JWgUU1zmJWF+Lek4QwCKc0MzD/iLGUE
|
||||
-> ssh-ed25519 r+nK/Q 2cOo2pK5KN0keAbW62MaC0/wDysciEZPgY8+3vhx30s
|
||||
ZmjX2vi9qYOVWtctWcEt95l2kBlZH1uNLFUdUxSHyus
|
||||
-> ssh-rsa krWCLQ
|
||||
xNCMgSxO8SA2rQqU14RD2TU5PQyssMlWomoA9VjoT6FsYZleRd7nPeABYqlnzUNj
|
||||
wWk3obSp3AO+NNscnmFrAijYQl0C+hBBplsgEyQ87j60s0ReAZGaURbrxRJr0dr/
|
||||
2JBrPtQ7tiSQYRZG9DH6ASUYrlVCB3Vq18OOa+os8PpqyL6Q6pglx0ePY1wx9irG
|
||||
6qj54LAR34C+uOi620LZuJ3YhZYIp0blmxqrXGeVTY1c7mmELKCdslFpiBvKE5jf
|
||||
71Lj6ihc5Z5kJxi0vPXMXkuGXtmlIr57dre2XWhynuXq9sLj0KEE0GVQa/vMV3Hd
|
||||
4/ATD4bbpkzwkfZIlL1LRQ
|
||||
-> ssh-ed25519 /vwQcQ 63EfH8Eu6Rdyz01sN7yfpaQpxJ2w4VqzQRWMw3AMSAk
|
||||
bj1CFYkCOcoMtuq/mC+vn9YM8aM9rLClcGo1rpytN7k
|
||||
-> ssh-ed25519 0R97PA gOIroiigdZxulsng29mz0o3gLYnMb5YjmBOmTd9UvHw
|
||||
mgvgYedm7U1y5BlRcvPEZhHpPEnczungDuBAEGcJwMw
|
||||
-> ssh-ed25519 JGx7Ng FeQyBpbGZ2WGztFXBpJ5uYXIPIEJqnf2FedleYRQJUc
|
||||
SzbinTIdwa1pvc9AZSWj2GRR86hD+SHY63QzBSv4D3I
|
||||
-> ssh-ed25519 5SY7Kg BgCKJrxjRS8QNCndIfySdq2u+hv3Q7Dg/hToWOE8e3g
|
||||
/rKzCb9fdZTEwTP1/QW4vn1ewQDn5TtV4Ui3MwChdB8
|
||||
-> ssh-ed25519 p/Mg4Q ftfpqvy3TuWoq+Hcmt+oYiJ1GhwYvR+GDh3MzVsfv3s
|
||||
I2dj0FSRGfoBqwSetdKz9NX11zUeHxIizmjctYrmjD8
|
||||
-> ssh-ed25519 DqHxWQ Zs+uNTp/4plSisoBzUpnvlZXLrbYphYvaeogHCyg4As
|
||||
hvXMQNPnJK7ZQrkYIyHW07rWd06QkNpiNuL3oUXxoQo
|
||||
-> ssh-ed25519 tDqJRg hMw/doebsExNtZ9oC1OFrnWOsiPOKh3D76RPfw0If00
|
||||
p5dxioeIt558deMFrRiTMxYocmP6p8kTk/nzSb5yuPA
|
||||
-> ssh-ed25519 9pVK7Q mctwqK3IkQdbeajO9mbvejtG85rFXTmFdptrzIzP9Cc
|
||||
sVG1NKMmTR0Sf60hvPJ4QRypmBT4a6yUZ+gyp/Xf+EQ
|
||||
-> ssh-ed25519 /BRpBQ C6CjF9H+x1fd2s4sjHw0IzKpNvbnr3H0tnxJdwzrzlQ
|
||||
gcrSM7NoHqeFdsTAWpO23cfAISile0uVEHu4fBvqwME
|
||||
-> ssh-ed25519 /x+F2Q t6mrvde1VJP7ARlwQAFOQxg6Uu2+GDDzN8GG/F/C5zA
|
||||
z3jOcIvHjH4TgiMHqABBU5t9bilBtv5rBKHJLMp9CaA
|
||||
-> ssh-ed25519 +MNHsw 5FBjw08c8F2wqrJe8KfWdn5bjzYmXXqLpVIozq8c8WE
|
||||
47oEgYMsl6/JtL1JqOOajHdB22gIdIGhhtcchUK7ZX4
|
||||
-> ssh-ed25519 rHotTw 4/W5DKJCc18KOcJQ1s4DveOVEjf5oy3HeQF5AThpvFM
|
||||
vG9LsTXTFk6TLHNDDS3qtirjm7iyZnhN+FM++xU0qGI
|
||||
-> ssh-ed25519 +mFdtQ bh0b+b2J2dg9hpBVYM3hDUwJOO/xi+dcH41abtVjt2E
|
||||
NPU1M+fXjOSROEWY73hftAniWUpr0ymbfo8mqZTPC/M
|
||||
-> ssh-ed25519 0IVRbA ioMW4UYJ+kKlZBdf430FHnbqdw3BcwWSr2RmOHCv+hA
|
||||
qw0VDAu93LSEZqhs9nRTCMGWsXKjxK65VfkKJbUU5fY
|
||||
-> ssh-ed25519 IY5FSQ 1aD4KWKITo+88CEwuTKq1QH+Pf5qoOXlI+EY2FX9IG4
|
||||
KzOGMeIxLypf7S6WeUM4Zr/S/g9HWXHBGcKkgHMLRJc
|
||||
-> ssh-ed25519 VQSaNw fCt2YDODTAtamSSYH+RNIpWAQ53WPwOeR92rHa89QBE
|
||||
2KAY4EgfxnNxvQGV4lgoGT+sb4nJV1eE50GHRljngEo
|
||||
-> x!p-grease Qza ,IU!}' (fMHX0~ m
|
||||
DGgaSNyr7o+hl8p9viIHBbTdiTdY79TgFsTdM2oBJAqT5P/LkFzg8TYNsH04eReH
|
||||
dmTu9wjN2OM
|
||||
--- +/E2Y1+KnzcreXm8DtJE39wR4dVL6vneloVFzK33c8Y
|
||||
T|ïá+¡ÆTÔŒÄ
|
||||
vΧ“8»,OÔ¸lžÇ±z)/0<30>>hkJMèl öÝ®GØßûGÜ>lU¯1Ÿ}€Š¤£T<C2A3>ÞhèÅý,åÎ8Åç%ßÓ¤lQ‰
|
||||
ëb©,@
|
1
modules/nixos/dgn-netbox-agent/secrets/secrets.nix
Normal file
1
modules/nixos/dgn-netbox-agent/secrets/secrets.nix
Normal file
|
@ -0,0 +1 @@
|
|||
{ netbox-agent.publicKeys = (import ../../../keys).machineKeys; }
|
54
modules/nixos/dgn-network.nix
Normal file
54
modules/nixos/dgn-network.nix
Normal file
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
meta,
|
||||
name,
|
||||
nodeMeta,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mapAttrs' mkEnableOption mkIf;
|
||||
|
||||
net' = meta.network.${name};
|
||||
|
||||
mkAddress = { address, prefixLength, ... }: "${address}/${builtins.toString prefixLength}";
|
||||
mkRoute = gateway: {
|
||||
routeConfig = {
|
||||
Gateway = gateway;
|
||||
GatewayOnLink = true;
|
||||
};
|
||||
};
|
||||
|
||||
mkInterface = interface: net: {
|
||||
name = "10-${interface}";
|
||||
value = {
|
||||
name = interface;
|
||||
address = builtins.map mkAddress (net.ipv4 ++ net.ipv6);
|
||||
routes = builtins.map mkRoute net.gateways;
|
||||
|
||||
inherit (net) DHCP dns;
|
||||
};
|
||||
};
|
||||
|
||||
cfg = config.dgn-network;
|
||||
in
|
||||
{
|
||||
options.dgn-network.enable = mkEnableOption "automatic network configuration based on metadata" // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
networking = {
|
||||
inherit (net') hostId;
|
||||
|
||||
hostName = name;
|
||||
domain = "${nodeMeta.site}.infra.dgnum.eu";
|
||||
useNetworkd = true;
|
||||
|
||||
firewall.logRefusedConnections = false;
|
||||
};
|
||||
|
||||
systemd.network.networks = mapAttrs' mkInterface net'.interfaces;
|
||||
};
|
||||
}
|
43
modules/nixos/dgn-node-monitoring.nix
Normal file
43
modules/nixos/dgn-node-monitoring.nix
Normal file
|
@ -0,0 +1,43 @@
|
|||
{ config, lib, ... }:
|
||||
let
|
||||
inherit (lib)
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
|
||||
types
|
||||
;
|
||||
cfg = config.dgn-node-monitoring;
|
||||
in
|
||||
|
||||
{
|
||||
options.dgn-node-monitoring = {
|
||||
enable = mkEnableOption "DGNum nodes monitoring (needs a valid netbird tunnel)" // {
|
||||
default = true;
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 9002;
|
||||
description = lib.mdDoc ''
|
||||
Port to listen on.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.prometheus = {
|
||||
exporters = {
|
||||
node = {
|
||||
enable = true;
|
||||
enabledCollectors = [
|
||||
"processes"
|
||||
"systemd"
|
||||
];
|
||||
inherit (cfg) port;
|
||||
listenAddress = "0.0.0.0";
|
||||
};
|
||||
};
|
||||
};
|
||||
networking.firewall.interfaces.wt0.allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
}
|
73
modules/nixos/dgn-notify/default.nix
Normal file
73
modules/nixos/dgn-notify/default.nix
Normal file
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
meta,
|
||||
nodeMeta,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
concatStringsSep
|
||||
mkEnableOption
|
||||
mkForce
|
||||
mkIf
|
||||
;
|
||||
|
||||
emails = concatStringsSep ", " (
|
||||
builtins.map (name: meta.organization.members.${name}.email) (
|
||||
builtins.foldl' (
|
||||
admins: group: admins ++ meta.organization.groups.${group}
|
||||
) nodeMeta.admins nodeMeta.adminGroups
|
||||
)
|
||||
);
|
||||
|
||||
cfg = config.dgn-notify;
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options.dgn-notify = {
|
||||
enable = mkEnableOption "DGNum email notification cli" // {
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.mail.sendmailSetuidWrapper.group = mkForce "mail";
|
||||
users.groups.mail = { };
|
||||
|
||||
programs.msmtp = {
|
||||
enable = true;
|
||||
setSendmail = true;
|
||||
accounts.default = {
|
||||
tls = true;
|
||||
tls_starttls = false;
|
||||
port = 465;
|
||||
auth = true;
|
||||
host = "kurisu.lahfa.xyz";
|
||||
from = "noreply@infra.dgnum.eu";
|
||||
user = "web-services@infra.dgnum.eu";
|
||||
passwordeval = "cat ${config.age.secrets.mail.path}";
|
||||
};
|
||||
};
|
||||
|
||||
services.systemd-notify = {
|
||||
enable = true;
|
||||
command = builtins.toString (
|
||||
pkgs.writeShellScript "sendmail" ''
|
||||
${pkgs.msmtp}/bin/sendmail -i -t <<ERRMAIL
|
||||
To: admins+monitoring@dgnum.eu, ${emails}
|
||||
Subject: [$HOSTNAME] Systemd failure: $1
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
|
||||
$(systemctl status --full "$1")
|
||||
ERRMAIL
|
||||
''
|
||||
);
|
||||
};
|
||||
age-secrets.sources = [ ./. ];
|
||||
};
|
||||
}
|
50
modules/nixos/dgn-notify/mail
Normal file
50
modules/nixos/dgn-notify/mail
Normal file
|
@ -0,0 +1,50 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 jIXfPA zVe/xwQCEtVnX8qWShePzBmhfQbENRMn8XgPzEqb1gY
|
||||
CFa4qXtY8lBlSIxnWVbejVta8BFYmsCtp9TdXXexZYE
|
||||
-> ssh-ed25519 QlRB9Q 3EJeDSBkwJU2LaKHygG/a0tfFRXcp8SNJBxyhIOwBDU
|
||||
PI63A4YJFC1XTNPbl73SBlUMV25o0ZeojAr8tr5mtlY
|
||||
-> ssh-ed25519 r+nK/Q /4HQCHPBBk7lD2mwJOMEmTeRpPGnPgTcL+htNRvkxkk
|
||||
oXLQoX+AK8zn82wsnLHK4BOpK3gn5lThWiFXh8+rxUU
|
||||
-> ssh-rsa krWCLQ
|
||||
ox5TBB9buXII2U32S/XpQVdr3r87p9lt/7WwELq//ik7vf4B6mZPbYvIV05JZ5bO
|
||||
4f5khDdw+q1bbniDCBH2aPKM0ni3wBdLkT3jwdQBL5imSQuly/cFMdvVwSTuwN9k
|
||||
8smSavsUYK5q5xgE49oMYJBAhNVGFI7NKlx7/a3VaVybsLAnzp3AeWy1o5BB1fKT
|
||||
7Emt88ht4lymL/gyxSMLT5Dreb5Sm+AcE+gYAK92OnX4Z1k8FqETppTKZDuoZUmv
|
||||
hJPpylXw7/YJU7Q0CluwtcGWFaTuE6AT0IrlCdY3NuMGA9IfpVsZr1kocdq2qB02
|
||||
90/yy51Ulwhfhiy1/3mlfw
|
||||
-> ssh-ed25519 /vwQcQ SGCKP+4m5p9SiYnU3vL0QaKp/3+yztZ0snZ6os+mUAo
|
||||
ZEVgV6jo7tRdM1KxQJ2UJRDEYaiQy9PYzaeSAstHYFQ
|
||||
-> ssh-ed25519 0R97PA Bblz2DxUIBovbFqHhwGSRrs3Hbc1vNMtn8SK976YYAU
|
||||
FtEsqOUChH+uzFuTsATraNyyJXXdkesmbe8T+LeK9nU
|
||||
-> ssh-ed25519 JGx7Ng 3umTe7hK60ghA4fXbapBRjjJ9K6hXLfV5kQrBzwsmS8
|
||||
oRBFSJsVStw2ul6JxdJuan18GriwYF+d8asKXnWDpZA
|
||||
-> ssh-ed25519 5SY7Kg Ft714PG5dVVJWHu0aJh+wdT04vSb/vlDVsWmUhdjUXw
|
||||
qY7OJduSibFheBQOrGnSFUOhou/WyyY/M5tAYGvaTJI
|
||||
-> ssh-ed25519 p/Mg4Q u6ES8PpiDb1OY97sMQ/kL6sTIjBhDk1aqoIEd0I5BgA
|
||||
pbX1Wk+5aTbf5rU2JM0rf4SR/fJGLKcDcqLF1yDXbiE
|
||||
-> ssh-ed25519 DqHxWQ n8qHGzdwY1RfajPN+oZV0Ps44rqbW5tcUFSSPbyZmAw
|
||||
EAK0hA/94/ZxBz0iNaTl2RlpswiO+2eIWugozHrZZfw
|
||||
-> ssh-ed25519 tDqJRg RAEIORbyHLRNkm+mFsq07E1uzbEEIBQ3eG+kpyXLLG0
|
||||
1S7gL5WgXiFZxgH6kSp1zANafDTEKsC4Wo4kT8oB7b8
|
||||
-> ssh-ed25519 9pVK7Q p7tGHwbC3CWap6feMXq2twGHkyszLP0EKwhW4McAoj4
|
||||
7F0zZEON8H2H+v0XRCOiYeUuhJBRUVkFoEP+Cz4vHZo
|
||||
-> ssh-ed25519 /BRpBQ stXNcOvGwPBPz8TtLhQUVgpcvu4BtfUACAZtrEI0eGY
|
||||
FN2yFmvc3GhMGNTUCT+XMr1qsfLvmjHIkYoi5B3MDsE
|
||||
-> ssh-ed25519 /x+F2Q fmGbMAGFJbjR0zVdJqsigKQ28nbDq8Zx1FsgviLWqHc
|
||||
+v09rkeHZTvFQLaXfOnFaZMBc2G2BD5dXWYg/Nlx2Og
|
||||
-> ssh-ed25519 +MNHsw KqIxZ4L1aoqLevCwx6Zp0jBHfTOU7WdrE0UN56/xARE
|
||||
OwQ2/WUEfl/oXxfbv5rlLu4OOdrACzPfSS6HfcLpi60
|
||||
-> ssh-ed25519 rHotTw hwCwUHi/xbAQaWt26kOn3/QSP0m0ZKRdIYs55TDMLSM
|
||||
DCvnBearzyPQ6ErYuawsyobpMsD9SSEhkVmFKyp5jUI
|
||||
-> ssh-ed25519 +mFdtQ ZlEsxLPDfy29aIQ9eNsRkZCHSeRmX8+GsuGtikQF4ms
|
||||
n1N2xQb4oRWaJgLtrXMFasc8u516e1M4Q/qLNLA0e0A
|
||||
-> ssh-ed25519 0IVRbA keVcQ4Vx3Avd97N89nUklRnGMABBenHIi+aufVoTABU
|
||||
yrsC1OitS6sqbUsaIaWeU8vYGOQm9afFfc6DprB8Whc
|
||||
-> ssh-ed25519 IY5FSQ npdYCAEfVSpuDNMZnWS469BgvivTKHRKtEAtxmxDZl8
|
||||
gOB1vpBO8ZqtLVwxCj8V/KrWgnYmZGn5QQJzMhiHH4A
|
||||
-> ssh-ed25519 VQSaNw S3dSnOPVQdMcz1dJYai0DvZATuMBDsG/+a0sJBDc/iE
|
||||
Q1gl1nIpDESMvTBX03i4lStAtdWqlTkVABHZ3cqocDE
|
||||
-> t-grease bvZAq
|
||||
NTQBWWf5UW4zsTEEt7rgmTv+B2rFIk/8WwQPrC/s59Ik
|
||||
--- 46n57xU0XlDQgUM0vIYveqDifz57FrTcRwCEpoh62+4
|
||||
[›07Æ~Þ3dïálÃÅô!f›ãš4ÐHßA‘‡íœ÷<C593>9
×ò@d»BÌ&½L‰{aãþm…X2ã’D‰Å´
|
1
modules/nixos/dgn-notify/secrets.nix
Normal file
1
modules/nixos/dgn-notify/secrets.nix
Normal file
|
@ -0,0 +1 @@
|
|||
{ mail.publicKeys = (import ../../../keys).machineKeys; }
|
50
modules/nixos/dgn-records/__arkheon-token_file
Normal file
50
modules/nixos/dgn-records/__arkheon-token_file
Normal file
|
@ -0,0 +1,50 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 jIXfPA YaucboAId6lgc1Y/jV6hLyovkJQnMBnKhJ2QWAci53U
|
||||
Q8RUPu4GUC5QbzTROgL9xaG3BUWO1QU/q1p0/yimBQ0
|
||||
-> ssh-ed25519 QlRB9Q y1tbd/81NoECRtKwOw41Tlls5y+WSu2jGmeOlC939VM
|
||||
DT1zZgWJkkIWRWxzfu4VgiGpV8CioaDKnVemowH59N4
|
||||
-> ssh-ed25519 r+nK/Q dDmGkZ3Y7xAzZGKvGIyIdhD+P0tkV6SMPx3UxphoTXo
|
||||
tkanRbPfu3/cuMPoTrcWBlNcu6RmK+txif+9aIRLy+s
|
||||
-> ssh-rsa krWCLQ
|
||||
IZGpFoWjQuQzqkS2KbpVr+fP7NLPhyaxS4yQroVEkPEZnXx2c6eH3ul218zytZld
|
||||
YRBCxiCtV6VfOB2N2QGuiK7YCGl6oUfN1DePy0jPrGKsnvWBitTuqzADiGQB7aSI
|
||||
ie7GgblPpi4q3ovJPgf7Bs+Mi2dKW5hiD8Jnped7rEW7SEnESkQa3Cx22Ww/UYcW
|
||||
9Uj7ZaDVVbP0ZWyc41HdoJwEnV6MYMRnXUJ/qrLMCIvRaYk8UdiCDgco+XxqAnbs
|
||||
iyUqCvz8iVNsWbJxK+7jJHXp0dQJRciHzSGStIVRSGx4gvuXOGjsuBMjfwoq1XoR
|
||||
5PE3BnP/atHZg3CkQcC2eA
|
||||
-> ssh-ed25519 /vwQcQ WL0PdIIsSWzw+ar2QNXCp7Xs1NH9gUk2fSPskGC9o2I
|
||||
+kHedFsYHgpsGfILtywJaIrTRj8HtHZvVyhtbRhKYC0
|
||||
-> ssh-ed25519 0R97PA +G7wUHF6NJimsAxe6M9RVVTa3GLPoW1bhsgMsWXKNC8
|
||||
i++lKoe8hFFb1rilkO9lcwBJujRqFsLGDOPvbaiz6Nw
|
||||
-> ssh-ed25519 JGx7Ng o66YGXN0uMC2qZo1tVcEMOa4SwxNZaf4HvnGsgzlqjo
|
||||
Tc4KMMrnJbybrNIkhEJz42PVHc3fVMFFSs96lKsEKCA
|
||||
-> ssh-ed25519 5SY7Kg P8Xp9wVJDcPdj3uSiq0yLnLMDInMeFs6XX30VwlXWlg
|
||||
uJfxXOZl8EX8fjRsHZ61JMKFpYksZJucZwVaRJs7qW8
|
||||
-> ssh-ed25519 p/Mg4Q yUyxue7Oda0b+CjdF9VfUCliWyzXNOsVPH7OFoHzWCw
|
||||
+zi+TSojvSc+VDXZG8XXSsTezxKRNC2XHc/hGGv4baM
|
||||
-> ssh-ed25519 DqHxWQ 7Vnq/xidbguw/PkZPUOTHUBTe8/x4PvTjCusUe10jio
|
||||
7Sl1MptpElvEA9VUj7JiVGuEWC0F3aA2rgYvfIchOB0
|
||||
-> ssh-ed25519 tDqJRg udOCDV4/vszObNxcQhJ6iGiDkxgZlrBDyKt3MbibMx4
|
||||
CDDd0LNCCdYvEww/h8q2z4f5QtjnL+kJsnPFtlbiD28
|
||||
-> ssh-ed25519 9pVK7Q DXqkIewHGpUUDtL2ivAoFwY/HCjoQXjxoHGPGkuFfH0
|
||||
JZ7xC2kdtnRNq8WADL2SNw/Ukezu1s4TuUbQnbP8L4o
|
||||
-> ssh-ed25519 /BRpBQ 9j1+wzO733ej03ra8LQOkpOyvY63UCbO9sfT6bV6+zs
|
||||
2F0UjpAqgCK5JS0y0kkHX30EV8JCcjhnJ1NkW06ww4w
|
||||
-> ssh-ed25519 /x+F2Q wYchtMn7MCGllfiFwTrycdLEY3dl297ns26PHs7l320
|
||||
feRd57Z5k6iJ71JRHud0wyYWo3O56q4rrYZt5y3aoqA
|
||||
-> ssh-ed25519 +MNHsw FHfvx1FQWcsRlKrFF0SRcVZ+XG6LXBwIMcPCVeu/ZCg
|
||||
w9fZGhZpEJHlf8JPcbWcNoAO9S06hi15LZxkv1dJUWk
|
||||
-> ssh-ed25519 rHotTw QDcThfb0AJMQBfQDbbtqm6z7BGxC4/sBioprElUTXFA
|
||||
2JOFoMLcVhMoGzZDDNOTL3PBWsqVnrFx8o/W/cWuzl0
|
||||
-> ssh-ed25519 +mFdtQ tWg17VH1Q4gQj/1IK9yrxjw4kRPzsp4dDHFwDKYxvDE
|
||||
9H4ohD3XN4Xtk15SsZQf5k0db+yIVcWp4EV5jKsZgHI
|
||||
-> ssh-ed25519 0IVRbA rkMPsBgVEaiYtaBN5JzHNCPFYFKr/7dqoY+RX19+03o
|
||||
baQK5t5sG8WabaCuMTZ2ZIfMTRH0jQU4l7JEyJ6H+LU
|
||||
-> ssh-ed25519 IY5FSQ c1+2+CMJFMw/iF2XNx5ma28KhwdKKQ9dNC1nBvFz/B0
|
||||
3AE1FQq+//dNIQfuW9BHcpfNbGn724Ydq7aJc95KmmY
|
||||
-> ssh-ed25519 VQSaNw t9yLak0T7FO8hgGrPWFeR3Jw0D6cPxjR5LOIcMnAmgo
|
||||
869SBp0nM5v/9+Xjib6rkmmelhTBfXcyuHiAXh08AWo
|
||||
-> r32t]I\-grease ka<*
|
||||
nkxH0w1aQ64
|
||||
--- LlTR5EcQzCLJ5trkQcomW0+soQoec/IZZNW+g5dyOo0
|
||||
M"ÏLm“õh]ñÖa£uq±ýÏ4ßÏ+ö“9;ФˆÇ-Z±L»¯H0o1»Eâ<>
|
25
modules/nixos/dgn-records/default.nix
Normal file
25
modules/nixos/dgn-records/default.nix
Normal file
|
@ -0,0 +1,25 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkEnableOption mkIf;
|
||||
|
||||
cfg = config.dgn-records;
|
||||
in
|
||||
|
||||
{
|
||||
options.dgn-records.enable = mkEnableOption "Arkheon deployment recording." // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.arkheon.record = {
|
||||
enable = true;
|
||||
|
||||
tokenFile = config.age.secrets."__arkheon-token_file".path;
|
||||
|
||||
url = "https://arkheon.dgnum.eu";
|
||||
};
|
||||
|
||||
age-secrets.sources = [ ./. ];
|
||||
};
|
||||
}
|
1
modules/nixos/dgn-records/secrets.nix
Normal file
1
modules/nixos/dgn-records/secrets.nix
Normal file
|
@ -0,0 +1 @@
|
|||
{ __arkheon-token_file.publicKeys = (import ../../../keys).machineKeys; }
|
136
modules/nixos/dgn-redirections/default.nix
Normal file
136
modules/nixos/dgn-redirections/default.nix
Normal file
|
@ -0,0 +1,136 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkOption;
|
||||
|
||||
inherit (lib.types)
|
||||
attrsOf
|
||||
ints
|
||||
listOf
|
||||
str
|
||||
submodule
|
||||
;
|
||||
|
||||
mkRetired =
|
||||
hosts:
|
||||
builtins.listToAttrs (
|
||||
builtins.map (name: {
|
||||
inherit name;
|
||||
value = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/".return = "301 https://${cfg.retiredHost}/${name}";
|
||||
};
|
||||
}) hosts
|
||||
);
|
||||
|
||||
mkPermanent = _: globalRedirect: {
|
||||
inherit globalRedirect;
|
||||
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
};
|
||||
|
||||
mkTemporary =
|
||||
_:
|
||||
{
|
||||
to,
|
||||
code,
|
||||
location,
|
||||
}:
|
||||
{
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
locations.${location}.return = "${toString code} ${to}";
|
||||
};
|
||||
|
||||
cfg = config.dgn-redirections;
|
||||
in
|
||||
|
||||
{
|
||||
options.dgn-redirections = {
|
||||
permanent = mkOption {
|
||||
type = attrsOf str;
|
||||
default = { };
|
||||
description = ''
|
||||
Attribute set of redirections, for:
|
||||
{ a = b; },
|
||||
a redirection from a to b will be made.
|
||||
'';
|
||||
};
|
||||
|
||||
temporary = mkOption {
|
||||
type = attrsOf (submodule {
|
||||
options = {
|
||||
to = mkOption {
|
||||
type = str;
|
||||
description = "Target of the redirection";
|
||||
};
|
||||
code = mkOption {
|
||||
type = ints.between 300 399;
|
||||
default = 302;
|
||||
example = 308;
|
||||
description = ''
|
||||
HTTP status used by the redirection. Possible usecases
|
||||
include temporary (302, 307) redirects, keeping the request method and
|
||||
body (307, 308), or explicitly resetting the method to GET (303).
|
||||
See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections>.
|
||||
'';
|
||||
};
|
||||
location = mkOption {
|
||||
type = str;
|
||||
default = "/";
|
||||
description = "nginx-style location for the source of the redirection";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = { };
|
||||
example = {
|
||||
"source.dgnum.eu" = {
|
||||
to = "https://target.dgnum.eu/path_to_page";
|
||||
code = 307;
|
||||
location = "/subpath/";
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Attribute set of temporary redirections. The attribute is the source
|
||||
domain.
|
||||
|
||||
For:
|
||||
```
|
||||
{
|
||||
"source.dgnum.eu" = {
|
||||
to = "https://target.dgnum.eu/path_to_page";
|
||||
code = 307;
|
||||
};
|
||||
}
|
||||
```
|
||||
a 307 redirect from all the urls within the domain `source.dgnum.eu` to
|
||||
`https://target.dgnum.eu/path_to_page` will be made.
|
||||
'';
|
||||
};
|
||||
|
||||
retired = mkOption {
|
||||
type = listOf str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of retired domains, they will we redirected to `retired.dgnum.eu/$host`.
|
||||
'';
|
||||
};
|
||||
|
||||
retiredHost = mkOption {
|
||||
type = str;
|
||||
default = "retired.dgnum.eu";
|
||||
description = ''
|
||||
Host used for the redirections of retired services.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
services.nginx.virtualHosts =
|
||||
(builtins.mapAttrs mkPermanent cfg.permanent // builtins.mapAttrs mkTemporary cfg.temporary)
|
||||
// (mkRetired cfg.retired);
|
||||
};
|
||||
}
|
65
modules/nixos/dgn-ssh.nix
Normal file
65
modules/nixos/dgn-ssh.nix
Normal file
|
@ -0,0 +1,65 @@
|
|||
# Copyright :
|
||||
# - Maurice Debray <maurice.debray@dgnum.eu> 2023
|
||||
# - Tom Hubrecht <tom.hubrecht@dgnum.eu> 2023
|
||||
#
|
||||
# Ce logiciel est un programme informatique servant à déployer des
|
||||
# configurations de serveurs via NixOS.
|
||||
#
|
||||
# Ce logiciel est régi par la licence CeCILL soumise au droit français et
|
||||
# respectant les principes de diffusion des logiciels libres. Vous pouvez
|
||||
# utiliser, modifier et/ou redistribuer ce programme sous les conditions
|
||||
# de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
|
||||
# sur le site "http://www.cecill.info".
|
||||
#
|
||||
# En contrepartie de l'accessibilité au code source et des droits de copie,
|
||||
# de modification et de redistribution accordés par cette licence, il n'est
|
||||
# offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
|
||||
# seule une responsabilité restreinte pèse sur l'auteur du programme, le
|
||||
# titulaire des droits patrimoniaux et les concédants successifs.
|
||||
#
|
||||
# A cet égard l'attention de l'utilisateur est attirée sur les risques
|
||||
# associés au chargement, à l'utilisation, à la modification et/ou au
|
||||
# développement et à la reproduction du logiciel par l'utilisateur étant
|
||||
# donné sa spécificité de logiciel libre, qui peut le rendre complexe à
|
||||
# manipuler et qui le réserve donc à des développeurs et des professionnels
|
||||
# avertis possédant des connaissances informatiques approfondies. Les
|
||||
# utilisateurs sont donc invités à charger et tester l'adéquation du
|
||||
# logiciel à leurs besoins dans des conditions permettant d'assurer la
|
||||
# sécurité de leurs systèmes et ou de leurs données et, plus généralement,
|
||||
# à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.
|
||||
#
|
||||
# Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
|
||||
# pris connaissance de la licence CeCILL, et que vous en avez accepté les
|
||||
# termes.
|
||||
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) mkEnableOption mkIf;
|
||||
|
||||
cfg = config.dgn-ssh;
|
||||
in
|
||||
|
||||
{
|
||||
options.dgn-ssh = {
|
||||
enable = mkEnableOption "ssh default configuration." // {
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
KbdInteractiveAuthentication = false;
|
||||
LoginGraceTime = "30";
|
||||
MaxSessions = "64";
|
||||
MaxStartups = "64";
|
||||
PasswordAuthentication = false;
|
||||
};
|
||||
};
|
||||
|
||||
programs.mosh.enable = true;
|
||||
};
|
||||
}
|
18
modules/nixos/dgn-vm-variant.nix
Normal file
18
modules/nixos/dgn-vm-variant.nix
Normal file
|
@ -0,0 +1,18 @@
|
|||
{ config, lib, ... }:
|
||||
let
|
||||
inherit (lib) mkEnableOption mkIf;
|
||||
|
||||
cfg = config.dgn-vmVariant;
|
||||
in
|
||||
|
||||
{
|
||||
options.dgn-vmVariant.enable = mkEnableOption "ACME settings." // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
virtualisation.vmVariant = {
|
||||
services.resolved.dnssec = "false";
|
||||
};
|
||||
};
|
||||
}
|
150
modules/nixos/dgn-web.nix
Normal file
150
modules/nixos/dgn-web.nix
Normal file
|
@ -0,0 +1,150 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
attrsToList
|
||||
concatStringsSep
|
||||
filterAttrs
|
||||
getAttr
|
||||
mapAttrs
|
||||
mapAttrs'
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
nameValuePair
|
||||
recursiveUpdate
|
||||
;
|
||||
|
||||
inherit (lib.types)
|
||||
attrs
|
||||
attrsOf
|
||||
bool
|
||||
port
|
||||
str
|
||||
submodule
|
||||
;
|
||||
|
||||
cfg = config.dgn-web;
|
||||
in
|
||||
{
|
||||
options.dgn-web = {
|
||||
enable = mkEnableOption "sane defaults for web services.";
|
||||
|
||||
internalPorts = mkOption {
|
||||
type = attrsOf port;
|
||||
default = { };
|
||||
description = ''
|
||||
Map from the web services to their internal ports, it should avoid port clashes.
|
||||
'';
|
||||
};
|
||||
|
||||
simpleProxies = mkOption {
|
||||
type = attrsOf (submodule {
|
||||
options = {
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = ''
|
||||
Port where the service will listen.
|
||||
'';
|
||||
};
|
||||
|
||||
host = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
Hostname of the service.
|
||||
'';
|
||||
};
|
||||
|
||||
proxyWebsockets = mkOption {
|
||||
type = bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to support proxying websocket connections with HTTP/1.1.
|
||||
'';
|
||||
};
|
||||
|
||||
vhostConfig = mkOption {
|
||||
type = attrs;
|
||||
default = { };
|
||||
description = ''
|
||||
Additional virtualHost settings.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = { };
|
||||
description = ''
|
||||
A set of simple localhost redirections.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
(
|
||||
let
|
||||
duplicates = builtins.attrValues (
|
||||
builtins.mapAttrs (p: serv: "${p}: ${concatStringsSep ", " serv}") (
|
||||
filterAttrs (_: ls: builtins.length ls != 1) (
|
||||
builtins.foldl' (
|
||||
rev:
|
||||
{ name, value }:
|
||||
let
|
||||
str = builtins.toString value;
|
||||
in
|
||||
rev // { ${str} = (rev.${str} or [ ]) ++ [ name ]; }
|
||||
) { } (attrsToList cfg.internalPorts)
|
||||
)
|
||||
)
|
||||
);
|
||||
in
|
||||
{
|
||||
assertion = duplicates == [ ];
|
||||
message = ''
|
||||
Internal ports cannot be used for multiple services, the clashes are:
|
||||
${concatStringsSep "\n " duplicates}
|
||||
'';
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
dgn-web.internalPorts = mapAttrs (_: getAttr "port") cfg.simpleProxies;
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
virtualHosts = mapAttrs' (
|
||||
_:
|
||||
{
|
||||
host,
|
||||
port,
|
||||
proxyWebsockets,
|
||||
vhostConfig,
|
||||
}:
|
||||
nameValuePair host (
|
||||
recursiveUpdate {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:${builtins.toString port}";
|
||||
inherit proxyWebsockets;
|
||||
};
|
||||
} vhostConfig
|
||||
)
|
||||
) cfg.simpleProxies;
|
||||
|
||||
recommendedBrotliSettings = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
recommendedZstdSettings = true;
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
};
|
||||
}
|
67
modules/nixos/django-apps/01-webhook.patch
Normal file
67
modules/nixos/django-apps/01-webhook.patch
Normal file
|
@ -0,0 +1,67 @@
|
|||
diff --git a/internal/hook/hook.go b/internal/hook/hook.go
|
||||
index 0510095..0347f26 100644
|
||||
--- a/internal/hook/hook.go
|
||||
+++ b/internal/hook/hook.go
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
- "io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"os"
|
||||
+ "path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -750,14 +750,18 @@ func (h *Hooks) LoadFromFile(path string, asTemplate bool) error {
|
||||
}
|
||||
|
||||
// parse hook file for hooks
|
||||
- file, e := ioutil.ReadFile(path)
|
||||
+ file, e := os.ReadFile(path)
|
||||
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
if asTemplate {
|
||||
- funcMap := template.FuncMap{"getenv": getenv}
|
||||
+ funcMap := template.FuncMap{
|
||||
+ "cat": cat,
|
||||
+ "credential": credential,
|
||||
+ "getenv": getenv,
|
||||
+ }
|
||||
|
||||
tmpl, err := template.New("hooks").Funcs(funcMap).Parse(string(file))
|
||||
if err != nil {
|
||||
@@ -956,3 +960,27 @@ func compare(a, b string) bool {
|
||||
func getenv(s string) string {
|
||||
return os.Getenv(s)
|
||||
}
|
||||
+
|
||||
+// cat provides a template function to retrieve content of files
|
||||
+// Similarly to getenv, if no file is found, it returns the empty string
|
||||
+func cat(s string) string {
|
||||
+ data, e := os.ReadFile(s)
|
||||
+
|
||||
+ if e != nil {
|
||||
+ return ""
|
||||
+ }
|
||||
+
|
||||
+ return strings.TrimSuffix(string(data), "\n")
|
||||
+}
|
||||
+
|
||||
+// credential provides a template function to retreive secrets using systemd's LoadCredential mechanism
|
||||
+func credential(s string) string {
|
||||
+ dir := getenv("CREDENTIALS_DIRECTORY")
|
||||
+
|
||||
+ // If no credential directory is found, fallback to the env variable
|
||||
+ if dir == "" {
|
||||
+ return getenv(s)
|
||||
+ }
|
||||
+
|
||||
+ return cat(path.Join(dir, s))
|
||||
+}
|
710
modules/nixos/django-apps/default.nix
Normal file
710
modules/nixos/django-apps/default.nix
Normal file
|
@ -0,0 +1,710 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
options,
|
||||
pkgs,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
attrNames
|
||||
concatLists
|
||||
concatMapAttrs
|
||||
filterAttrs
|
||||
getExe
|
||||
getExe'
|
||||
literalExpression
|
||||
mapAttrs
|
||||
mapAttrs'
|
||||
mapAttrsToList
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkMerge
|
||||
mkOption
|
||||
mkPackageOption
|
||||
nameValuePair
|
||||
optional
|
||||
optionals
|
||||
recursiveUpdate
|
||||
toUpper
|
||||
;
|
||||
|
||||
inherit (lib.types)
|
||||
attrs
|
||||
attrsOf
|
||||
enum
|
||||
functionTo
|
||||
ints
|
||||
listOf
|
||||
nullOr
|
||||
package
|
||||
path
|
||||
str
|
||||
submodule
|
||||
;
|
||||
|
||||
inherit (utils) escapeSystemdExecArgs;
|
||||
|
||||
cfg = config.services.django-apps;
|
||||
|
||||
# Alias the global config to allow its use when the identifier is shadowed
|
||||
config' = config;
|
||||
systemctl = getExe' config.systemd.package "systemctl";
|
||||
in
|
||||
|
||||
{
|
||||
options.services.django-apps = {
|
||||
enable = mkEnableOption "automatic django apps management";
|
||||
|
||||
webhook = {
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
The domain where the webhook service will listen.
|
||||
'';
|
||||
};
|
||||
|
||||
nginx = mkOption {
|
||||
type = nullOr options.services.nginx.virtualHosts.type.nestedTypes.elemType;
|
||||
default = null;
|
||||
description = ''
|
||||
With this option, you can customize the nginx virtualHost settings.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
# To enable encryption and let Let's Encrypt take care of certificate
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
sites = mkOption {
|
||||
type = attrsOf (
|
||||
submodule (
|
||||
{ name, config, ... }:
|
||||
{
|
||||
options = {
|
||||
source = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
The URI where the source of the app can be publicly fetched via git.
|
||||
'';
|
||||
};
|
||||
|
||||
branch = mkOption {
|
||||
type = str;
|
||||
default = "production";
|
||||
description = ''
|
||||
Branch to follow for updates to the source.
|
||||
'';
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
The domain where the web app will be served.
|
||||
'';
|
||||
};
|
||||
|
||||
nginx = mkOption {
|
||||
type = nullOr options.services.nginx.virtualHosts.type.nestedTypes.elemType;
|
||||
default = null;
|
||||
description = ''
|
||||
With this option, you can customize the nginx virtualHost settings.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
# To enable encryption and let Let's Encrypt take care of certificate
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
env_prefix = mkOption {
|
||||
type = str;
|
||||
default = toUpper name;
|
||||
description = ''
|
||||
The prefix to use for environment settings declaration.
|
||||
'';
|
||||
};
|
||||
|
||||
application = {
|
||||
type = mkOption {
|
||||
type = enum [
|
||||
"asgi"
|
||||
"wsgi"
|
||||
"daphne"
|
||||
];
|
||||
default = "wsgi";
|
||||
description = ''
|
||||
Specification for the django application.
|
||||
'';
|
||||
};
|
||||
|
||||
module = mkOption {
|
||||
type = str;
|
||||
default = "app";
|
||||
description = ''
|
||||
Name of the module containing the application interface.
|
||||
'';
|
||||
};
|
||||
|
||||
settingsModule = mkOption {
|
||||
type = str;
|
||||
default = "${config.application.module}.settings";
|
||||
description = ''
|
||||
The django settings module, will be passed as an environment variable to the app.
|
||||
'';
|
||||
};
|
||||
|
||||
workers = mkOption {
|
||||
type = ints.positive;
|
||||
default = 4;
|
||||
description = ''
|
||||
Number of workers processes to use.
|
||||
'';
|
||||
};
|
||||
|
||||
channelLayer = mkOption {
|
||||
type = str;
|
||||
default = "channel_layer";
|
||||
description = ''
|
||||
Channel layer to use when running the application with daphne.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
python = mkPackageOption pkgs "python3" { };
|
||||
|
||||
django = mkOption {
|
||||
type = functionTo package;
|
||||
default = ps: ps.django;
|
||||
defaultText = literalExpression "ps: ps.django";
|
||||
description = ''
|
||||
The django version to use to run the app.
|
||||
'';
|
||||
};
|
||||
|
||||
djangoEnv = mkOption {
|
||||
type = package;
|
||||
default = config.python.withPackages (
|
||||
ps:
|
||||
[ (config.django ps) ]
|
||||
++ (optional (config.application.type != "daphne") ps.gunicorn)
|
||||
++ (optional (config.application.type == "asgi") ps.uvicorn)
|
||||
++ (optional (config.dbType == "postgresql") ps.psycopg)
|
||||
++ (config.dependencies ps)
|
||||
);
|
||||
description = ''
|
||||
The python version used to run the app, with the correct dependencies.
|
||||
'';
|
||||
};
|
||||
|
||||
dependencies = mkOption {
|
||||
type = functionTo (listOf package);
|
||||
default = _: [ ];
|
||||
example = literalExpression "ps: [ ps.requests ]";
|
||||
description = ''
|
||||
Python dependencies of the app.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPackages = mkOption {
|
||||
type = listOf package;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Packages that will be added to the path of the app.
|
||||
'';
|
||||
};
|
||||
|
||||
credentials = mkOption {
|
||||
type = attrsOf path;
|
||||
default = { };
|
||||
description = ''
|
||||
The files containing credentials to pass through `LoadCredential` to the application.
|
||||
'';
|
||||
};
|
||||
|
||||
environment = mkOption {
|
||||
type = attrsOf (pkgs.formats.json { }).type;
|
||||
default = { };
|
||||
description = ''
|
||||
Environment variables to pass to the app.
|
||||
'';
|
||||
};
|
||||
|
||||
managePath = mkOption {
|
||||
type = str;
|
||||
default = "manage.py";
|
||||
description = ''
|
||||
Path to the manage.py file inside the source
|
||||
'';
|
||||
};
|
||||
|
||||
extraServices = mkOption {
|
||||
type = attrs;
|
||||
default = { };
|
||||
description = ''
|
||||
Extra services to run in parallel of the application.
|
||||
May be used to run background tasks and/or workers.
|
||||
'';
|
||||
};
|
||||
|
||||
manageScript = mkOption {
|
||||
type = package;
|
||||
default = pkgs.writeShellApplication {
|
||||
name = "${name}-manage";
|
||||
|
||||
runtimeInputs = [
|
||||
pkgs.util-linux
|
||||
config'.systemd.package
|
||||
|
||||
config.djangoEnv
|
||||
] ++ config.extraPackages;
|
||||
text = ''
|
||||
MainPID=$(systemctl show -p MainPID --value dj-${name}.service)
|
||||
|
||||
nsenter -e -a -t "$MainPID" -G follow -S follow python /var/lib/django-apps/${name}/source/${config.managePath} "$@"
|
||||
'';
|
||||
};
|
||||
description = ''
|
||||
Script to run manage.py related tasks.
|
||||
'';
|
||||
};
|
||||
|
||||
updateScript = mkOption {
|
||||
type = package;
|
||||
default = pkgs.writeShellApplication {
|
||||
name = "dj-${name}-update-source";
|
||||
|
||||
runtimeInputs = [
|
||||
config.djangoEnv
|
||||
|
||||
pkgs.git
|
||||
];
|
||||
|
||||
text = ''
|
||||
git pull
|
||||
python3 ${config.managePath} migrate
|
||||
python3 ${config.managePath} collectstatic --no-input
|
||||
'';
|
||||
};
|
||||
description = ''
|
||||
Script to run when updating the app source.
|
||||
'';
|
||||
};
|
||||
|
||||
webHookSecret = mkOption {
|
||||
type = path;
|
||||
description = ''
|
||||
Path to the webhook secret.
|
||||
'';
|
||||
};
|
||||
|
||||
dbType = mkOption {
|
||||
type = enum [
|
||||
"manual"
|
||||
"postgresql"
|
||||
"sqlite"
|
||||
];
|
||||
default = "postgresql";
|
||||
description = ''
|
||||
Which database backend to use, set to `manual` for custom declaration.
|
||||
'';
|
||||
};
|
||||
|
||||
baseDirectory = mkOption {
|
||||
type = str;
|
||||
readOnly = true;
|
||||
default = "/var/lib/django-apps/${name}";
|
||||
};
|
||||
|
||||
sourceDirectory = mkOption {
|
||||
type = str;
|
||||
readOnly = true;
|
||||
default = "${config.baseDirectory}/source";
|
||||
};
|
||||
|
||||
staticDirectory = mkOption {
|
||||
type = str;
|
||||
default = "static";
|
||||
description = ''
|
||||
Path to the staticfiles directory.
|
||||
This is relative to the base directory, e.g. the parent of the source directory.
|
||||
'';
|
||||
};
|
||||
|
||||
mediaDirectory = mkOption {
|
||||
type = str;
|
||||
default = "media";
|
||||
description = ''
|
||||
Path to the media files directory.
|
||||
This is relative to the base directory, e.g. the parent of the source directory.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
security.sudo.extraRules = [
|
||||
{
|
||||
users = [ "webhook" ];
|
||||
commands = builtins.map (name: {
|
||||
command = "${systemctl} start dj-${name}-update.service";
|
||||
options = [ "NOPASSWD" ];
|
||||
}) (attrNames cfg.sites);
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages = mapAttrsToList (_: { manageScript, ... }: manageScript) cfg.sites;
|
||||
|
||||
services = {
|
||||
webhook = {
|
||||
enable = true;
|
||||
|
||||
package = pkgs.webhook.overrideAttrs (old: {
|
||||
patches = (old.patches or [ ]) ++ [ ./01-webhook.patch ];
|
||||
});
|
||||
|
||||
# extraArgs = [ "-debug" ];
|
||||
|
||||
# Only listen on localhost
|
||||
ip = "127.0.0.1";
|
||||
|
||||
hooksTemplated = mapAttrs' (
|
||||
name:
|
||||
{ branch, ... }:
|
||||
nameValuePair "dj-${name}" (
|
||||
# Avoid issues when quoting "dj-name" through builtins.toJSON
|
||||
builtins.replaceStrings [ "\\" ] [ "" ] (
|
||||
builtins.toJSON {
|
||||
id = "dj-${name}";
|
||||
execute-command = "/run/wrappers/bin/sudo";
|
||||
pass-arguments-to-command =
|
||||
builtins.map
|
||||
(name: {
|
||||
inherit name;
|
||||
source = "string";
|
||||
})
|
||||
[
|
||||
systemctl
|
||||
"start"
|
||||
"dj-${name}-update.service"
|
||||
];
|
||||
# command-working-directory = "/var/lib/django-apps/${name}";
|
||||
trigger-rule = {
|
||||
and = [
|
||||
{
|
||||
or = [
|
||||
{
|
||||
match = {
|
||||
type = "payload-hmac-sha256";
|
||||
secret = ''{{ credential "dj-${name}" | js }}'';
|
||||
parameter = {
|
||||
source = "header";
|
||||
name = "X-Hub-Signature-256";
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
match = {
|
||||
type = "value";
|
||||
value = ''{{ credential "dj-${name}" | js }}'';
|
||||
parameter = {
|
||||
source = "header";
|
||||
name = "X-Gitlab-Token";
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
match = {
|
||||
type = "value";
|
||||
value = "refs/heads/${branch}";
|
||||
parameter = {
|
||||
source = "payload";
|
||||
name = "ref";
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
)
|
||||
)
|
||||
) cfg.sites;
|
||||
};
|
||||
|
||||
nginx = mkMerge [
|
||||
(mkIf (cfg.webhook.nginx != null) {
|
||||
enable = true;
|
||||
|
||||
virtualHosts = {
|
||||
${cfg.webhook.domain} = mkMerge [
|
||||
{ locations."/".proxyPass = "http://127.0.0.1:${builtins.toString config.services.webhook.port}"; }
|
||||
cfg.webhook.nginx
|
||||
];
|
||||
};
|
||||
})
|
||||
{
|
||||
virtualHosts = mapAttrs' (
|
||||
name:
|
||||
{ domain, nginx, ... }:
|
||||
nameValuePair domain (
|
||||
recursiveUpdate {
|
||||
locations = {
|
||||
"/".proxyPass = "http://unix:/run/django-apps/${name}.sock";
|
||||
"/static/".root = "/run/django-apps/${name}";
|
||||
"/media/".root = "/run/django-apps/${name}";
|
||||
};
|
||||
} nginx
|
||||
)
|
||||
) cfg.sites;
|
||||
}
|
||||
];
|
||||
|
||||
postgresql =
|
||||
let
|
||||
apps = builtins.map (name: "dj-${name}") (
|
||||
attrNames (filterAttrs (_: { dbType, ... }: dbType == "postgresql") cfg.sites)
|
||||
);
|
||||
in
|
||||
mkIf (apps != [ ]) {
|
||||
enable = true;
|
||||
|
||||
ensureDatabases = apps;
|
||||
ensureUsers = builtins.map (name: {
|
||||
inherit name;
|
||||
ensureDBOwnership = true;
|
||||
}) apps;
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
users.nginx.extraGroups = [ "django-apps" ];
|
||||
groups.django-apps = { };
|
||||
};
|
||||
|
||||
systemd = {
|
||||
sockets = mapAttrs' (
|
||||
name: _:
|
||||
nameValuePair "dj-${name}" {
|
||||
description = "Socket for the ${name} Django Application";
|
||||
wantedBy = [ "sockets.target" ];
|
||||
|
||||
socketConfig = {
|
||||
ListenStream = "/run/django-apps/${name}.sock";
|
||||
SocketMode = "600";
|
||||
SocketUser = config'.services.nginx.user;
|
||||
};
|
||||
}
|
||||
) cfg.sites;
|
||||
|
||||
mounts = concatLists (
|
||||
mapAttrsToList (
|
||||
name:
|
||||
{ mediaDirectory, staticDirectory, ... }:
|
||||
[
|
||||
{
|
||||
where = "/run/django-apps/${name}/static";
|
||||
what = "/var/lib/django-apps/${name}/${staticDirectory}";
|
||||
options = "bind";
|
||||
|
||||
after = [ "dj-${name}.service" ];
|
||||
partOf = [ "dj-${name}.service" ];
|
||||
upheldBy = [ "dj-${name}.service" ];
|
||||
}
|
||||
|
||||
{
|
||||
where = "/run/django-apps/${name}/media";
|
||||
what = "/var/lib/django-apps/${name}/${mediaDirectory}";
|
||||
options = "bind";
|
||||
|
||||
after = [ "dj-${name}.service" ];
|
||||
partOf = [ "dj-${name}.service" ];
|
||||
upheldBy = [ "dj-${name}.service" ];
|
||||
}
|
||||
]
|
||||
) cfg.sites
|
||||
);
|
||||
|
||||
services =
|
||||
{
|
||||
webhook.serviceConfig.LoadCredential = mapAttrsToList (
|
||||
name: { webHookSecret, ... }: "dj-${name}:${webHookSecret}"
|
||||
) cfg.sites;
|
||||
}
|
||||
// (concatMapAttrs (
|
||||
name: config:
|
||||
let
|
||||
mkDatabase =
|
||||
name: type:
|
||||
if type == "postgresql" then
|
||||
{
|
||||
ENGINE = "django.db.backends.postgresql";
|
||||
NAME = "dj-${name}";
|
||||
}
|
||||
else if type == "sqlite" then
|
||||
{
|
||||
ENGINE = "django.db.backends.sqlite3";
|
||||
NAME = "/var/lib/django-apps/${name}/db.sqlite3";
|
||||
}
|
||||
else
|
||||
throw "Invalid database type !";
|
||||
|
||||
# Systemd Service Configuration
|
||||
Group = "django-apps";
|
||||
LoadCredential = mapAttrsToList (credential: path: "${credential}:${path}") config.credentials;
|
||||
RuntimeDirectory = "django-apps/${name}";
|
||||
StateDirectory = "django-apps/${name}";
|
||||
UMask = "0027";
|
||||
User = "dj-${name}";
|
||||
WorkingDirectory = "/var/lib/django-apps/${name}";
|
||||
|
||||
environment =
|
||||
let
|
||||
mkValue = v: if builtins.isString v then v else builtins.toJSON v;
|
||||
in
|
||||
(mapAttrs' (key: value: nameValuePair "${config.env_prefix}_${key}" (mkValue value)) {
|
||||
DATABASES =
|
||||
if (config.dbType != "manual") then { default = mkDatabase name config.dbType; } else null;
|
||||
STATIC_ROOT = "/var/lib/django-apps/${name}/${config.staticDirectory}";
|
||||
MEDIA_ROOT = "/var/lib/django-apps/${name}/${config.mediaDirectory}";
|
||||
ALLOWED_HOSTS = [ config.domain ];
|
||||
})
|
||||
// {
|
||||
DJANGO_SETTINGS_MODULE = config.application.settingsModule;
|
||||
}
|
||||
// (mapAttrs (_: mkValue) config.environment);
|
||||
path = config.extraPackages ++ [ config.djangoEnv ];
|
||||
after = [ "network.target" ] ++ (optional (config.dbType == "postgresql") "postgresql.service");
|
||||
in
|
||||
{
|
||||
"dj-${name}" = {
|
||||
inherit after environment path;
|
||||
|
||||
preStart = ''
|
||||
if [ ! -f .initialized ]; then
|
||||
# The previous initialization might have failed, so restart from the beginning
|
||||
rm -rf source
|
||||
|
||||
# We need to download the application source and run the migrations first
|
||||
${lib.getExe pkgs.git} clone --single-branch --branch ${config.branch} ${config.source} source
|
||||
(cd source && python ${config.managePath} migrate --no-input && python ${config.managePath} collectstatic --no-input)
|
||||
touch .initialized
|
||||
fi
|
||||
|
||||
# Create the necessary directory with the correct user/group
|
||||
mkdir -p ${config.mediaDirectory} ${config.staticDirectory}
|
||||
'';
|
||||
|
||||
requires = [ "dj-${name}.socket" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
inherit
|
||||
Group
|
||||
LoadCredential
|
||||
RuntimeDirectory
|
||||
StateDirectory
|
||||
User
|
||||
UMask
|
||||
WorkingDirectory
|
||||
;
|
||||
|
||||
DynamicUser = true;
|
||||
ExecStart = escapeSystemdExecArgs (
|
||||
if (config.application.type == "daphne") then
|
||||
[
|
||||
(getExe' config.djangoEnv "daphne")
|
||||
"-u"
|
||||
"/run/django-apps/${name}.sock"
|
||||
"${config.application.module}.asgi:${config.application.channelLayer}"
|
||||
]
|
||||
else
|
||||
(
|
||||
[
|
||||
(getExe' config.djangoEnv "gunicorn")
|
||||
"--workers"
|
||||
config.application.workers
|
||||
"--bind"
|
||||
"unix:/run/django-apps/${name}.sock"
|
||||
"--pythonpath"
|
||||
"source"
|
||||
]
|
||||
++ (optionals (config.application.type == "asgi") [
|
||||
"--worker-class"
|
||||
"uvicorn.workers.UvicornWorker"
|
||||
])
|
||||
++ [ "${config.application.module}.${config.application.type}" ]
|
||||
)
|
||||
);
|
||||
ExecReload = "${getExe' pkgs.coreutils "kill"} -s HUP $MAINPID";
|
||||
KillMode = "mixed";
|
||||
Type = mkIf (config.application.type != "daphne") "notify";
|
||||
};
|
||||
};
|
||||
|
||||
"dj-${name}-update" = {
|
||||
inherit environment path;
|
||||
|
||||
serviceConfig = {
|
||||
inherit
|
||||
Group
|
||||
LoadCredential
|
||||
StateDirectory
|
||||
UMask
|
||||
User
|
||||
;
|
||||
|
||||
DynamicUser = true;
|
||||
ExecStart = "${getExe config.updateScript}";
|
||||
Type = "oneshot";
|
||||
WorkingDirectory = "/var/lib/django-apps/${name}/source";
|
||||
};
|
||||
|
||||
unitConfig = {
|
||||
After = "dj-${name}.service";
|
||||
Conflicts = "dj-${name}.service";
|
||||
};
|
||||
};
|
||||
}
|
||||
// (mapAttrs' (
|
||||
serviceName: serviceContent:
|
||||
nameValuePair "dj-${name}_${serviceName}" (
|
||||
recursiveUpdate {
|
||||
inherit after environment path;
|
||||
|
||||
partOf = [ "dj-${name}.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
upheldBy = [ "dj-${name}.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
inherit
|
||||
Group
|
||||
LoadCredential
|
||||
RuntimeDirectory
|
||||
StateDirectory
|
||||
UMask
|
||||
User
|
||||
;
|
||||
|
||||
DynamicUser = true;
|
||||
};
|
||||
} serviceContent
|
||||
)
|
||||
) config.extraServices)
|
||||
) cfg.sites);
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue