Init infrastructure

Benjamin, I hate you.

Signed-off-by: Ryan Lahfa <federez-infra@lahfa.xyz>
This commit is contained in:
Ryan Lahfa 2024-02-12 04:57:07 +01:00
commit 8acdce99df
23 changed files with 602 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

0
README.md Normal file
View file

14
bootstrap/iso.nix Normal file
View file

@ -0,0 +1,14 @@
{ pkgs, modulesPath, lib, ... }: {
imports = [
"${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix"
];
# Use the latest Linux kernel
boot.kernelPackages = pkgs.linuxPackages_latest;
# Exclude ZFS.
boot.supportedFilesystems = lib.mkForce [ "btrfs" "reiserfs" "vfat" "f2fs" "xfs" "ntfs" "cifs" "bcachefs" ];
users.users.root.openssh.authorizedKeys.keyFiles = [
../pubkeys/raito.keys
];
}

15
default.nix Normal file
View file

@ -0,0 +1,15 @@
{ src ? import ./npins, pkgs ? import src.nixpkgs {} }:
{
shell = pkgs.mkShell {
buildInputs = [
pkgs.colmena
pkgs.npins
pkgs.gitFull
(pkgs.callPackage "${src.agenix}/pkgs/agenix.nix" { })
];
};
bootstrapIso = (pkgs.nixos [
(import ./bootstrap/iso.nix)
]).config.system.build.isoImage;
}

33
disks/ext4.nix Normal file
View file

@ -0,0 +1,33 @@
{ disk ? "/dev/sda", ... }:
{
disko.devices = {
disk = {
${disk} = {
device = "${disk}";
type = "disk";
content = {
type = "gpt";
partitions = {
ESP = {
end = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
}

128
hive.nix Normal file
View file

@ -0,0 +1,128 @@
let
src = import ./npins;
pkgs = import src.nixpkgs { };
disko = (import src.disko { inherit (pkgs) lib; });
diskConfig = import ./disks/ext4.nix {
inherit (pkgs) lib;
};
in
{
meta = {
nixpkgs = src.nixpkgs;
};
defaults = { pkgs, ... }: {
imports = [
./profiles/sysadmin.nix
"${src.agenix}/modules/age.nix"
(disko.config diskConfig)
];
security.acme.defaults.email = "monitoring@federez.net";
security.acme.acceptTerms = true;
systemd.network.enable = true;
networking.useDHCP = false;
services.openssh.enable = true;
# Mot de passe classique qu'on trouvera dans le "trousseau" legacy.
users.users.root.initialHashedPassword = "$y$j9T$RoSZj8ezgR7cI8Le6xqwW/$0BI6G1Nqy/G0g0sNhQhyEedqoHsEyMFVjQgc3TPqE.4";
system.stateVersion = "24.05";
system.build.diskoScript = disko.diskoScript diskConfig pkgs;
time.timeZone = "Europe/Paris";
};
estragon = { name, nodes, ... }: {
deployment.tags = [ "matrix" ];
deployment.targetHost = "estragon.federez.net";
systemd.network.links."10-wan" = {
matchConfig.MACAddress = "BC:24:11:5C:A4:5A";
linkConfig.Name = "wan";
};
systemd.network.networks."10-wan" = {
matchConfig.Name = "wan";
address = [
"172.17.8.227/22"
];
routes = [
{
routeConfig = {
Gateway = "172.17.11.254";
};
}
];
linkConfig.RequiredForOnline = "routable";
};
networking.hostName = name;
imports = [
./profiles/vm.nix
./profiles/matrix-server.nix
./profiles/element.nix
./profiles/telegram-bot.nix
./profiles/irc-bot.nix
];
};
wagon = { name, nodes, ... }: {
deployment.tags = [ "vaultwarden" "pass" "passwords" ];
deployment.targetHost = "wagon.federez.net";
networking.hostName = name;
systemd.network.links."10-wan" = {
matchConfig.MACAddress = "BC:24:11:EA:6C:0B";
linkConfig.Name = "wan";
};
systemd.network.networks."10-wan" = {
matchConfig.Name = "wan";
address = [
"172.17.8.228/22"
];
routes = [
{
routeConfig = {
Gateway = "172.17.11.254";
};
}
];
linkConfig.RequiredForOnline = "routable";
};
imports = [
./profiles/vm.nix
./profiles/vaultwarden.nix
];
};
lagon = { name, nodes, ... }: {
deployment.tags = [ "keycloak" ];
deployment.targetHost = "lagon.federez.net";
networking.hostName = name;
systemd.network.links."10-wan" = {
matchConfig.MACAddress = "BC:24:11:7F:19:60";
linkConfig.Name = "wan";
};
systemd.network.networks."10-wan" = {
matchConfig.Name = "wan";
address = [
"172.17.8.229/22"
];
routes = [
{
routeConfig = {
Gateway = "172.17.11.254";
};
}
];
linkConfig.RequiredForOnline = "routable";
};
imports = [
./profiles/vm.nix
./profiles/keycloak.nix
];
};
}

47
npins/default.nix Normal file
View file

@ -0,0 +1,47 @@
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
mkSource = spec:
assert spec ? type; let
path =
if spec.type == "Git" then mkGitSource spec
else if spec.type == "GitRelease" then mkGitSource spec
else if spec.type == "PyPi" then mkPyPiSource spec
else if spec.type == "Channel" then mkChannelSource spec
else builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = path; };
mkGitSource = { repository, revision, url ? null, hash, ... }:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null then
(builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
})
else assert repository.type == "Git"; builtins.fetchGit {
url = repository.url;
rev = revision;
# hash = hash;
};
mkPyPiSource = { url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource = { url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;
};
in
if version == 3 then
builtins.mapAttrs (_: mkSource) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"

35
npins/sources.json Normal file
View file

@ -0,0 +1,35 @@
{
"pins": {
"agenix": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "ryantm",
"repo": "agenix"
},
"branch": "main",
"revision": "417caa847f9383e111d1397039c9d4337d024bf0",
"url": "https://github.com/ryantm/agenix/archive/417caa847f9383e111d1397039c9d4337d024bf0.tar.gz",
"hash": "0g240g51w62r7j67dcc73xkdnhd26nk07043qsqqavl5mbh40swy"
},
"disko": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "nix-community",
"repo": "disko"
},
"branch": "master",
"revision": "d07de570ba05cec2807d058daaa044f6955720c7",
"url": "https://github.com/nix-community/disko/archive/d07de570ba05cec2807d058daaa044f6955720c7.tar.gz",
"hash": "18rli5h2xmzbbwambrcrg7r22vp0rmnjm55mcqc00n3fq5kscsqy"
},
"nixpkgs": {
"type": "Channel",
"name": "nixos-unstable-small",
"url": "https://releases.nixos.org/nixos/unstable-small/nixos-24.05pre583100.bdc57436da85/nixexprs.tar.xz",
"hash": "1ds8jv7wn5dx5s725jkzk6dk3j54kbhjl7aabc4y45v1m7bp9s3r"
}
},
"version": 3
}

15
profiles/element.nix Normal file
View file

@ -0,0 +1,15 @@
{ pkgs, ... }:
let
clientConfig."m.homeserver".base_url = "https://matrix.federez.net";
in
{
services.nginx.virtualHosts."element.federez.net" = {
enableACME = true;
forceSSL = true;
root = pkgs.element-web.override {
conf = {
default_server_config = clientConfig; # see `clientConfig` from the snippet above.
};
};
};
}

53
profiles/irc-bot.nix Normal file
View file

@ -0,0 +1,53 @@
{ ... }: {
services.matrix-appservice-irc = {
enable = true;
registrationUrl = "http://localhost:8009";
settings = {
homeserver.url = "https://matrix.federez.net";
homeserver.domain = "federez.net";
ircService.servers."irc.rezosup.org" = {
name = "RezoSup";
port = 6667;
# ssl = true;
# botConfig = {
# enabled = true;
# nick = "FederezMatrix";
# username = "federezmatrix";
# };
dynamicChannels = {
enabled = true;
createAlias = true;
# published = true;
aliasTemplate = "#irc_$CHANNEL";
};
matrixClients = {
userTemplate = "@irc_$NICK";
};
ircClients = {
nickTemplate = "$LOCALPART[m]";
allowNickChanges = true;
};
membershipLists = {
enabled = true;
global = {
ircToMatrix = {
initial = true;
incremental = true;
};
matrixToIrc = {
initial = true;
incremental = true;
};
};
};
};
logging = {
level = "debug";
};
};
};
}

34
profiles/keycloak.nix Normal file
View file

@ -0,0 +1,34 @@
{ config, ... }: {
# age.secrets.keycloak-password-file.file = ../secrets/keycloak-password-file.age;
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
virtualHosts."sso.federez.net" = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://127.0.0.1:8080";
};
};
services.keycloak = {
enable = true;
database = {
createLocally = true;
passwordFile = "/etc/secrets/keycloak-password";
};
settings = {
proxy = "edge";
http-host = "127.0.0.1";
http-port = 8080;
hostname = "sso.federez.net";
};
};
}

View file

@ -0,0 +1,77 @@
{ pkgs, lib, config, ... }:
let
fqdn = "matrix.federez.net";
baseUrl = "https://${fqdn}";
in {
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.postgresql.enable = true;
services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
TEMPLATE template0
LC_COLLATE = "C"
LC_CTYPE = "C";
'';
environment.systemPackages = [ pkgs.matrix-synapse ];
services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
virtualHosts = {
"${fqdn}" = {
enableACME = true;
forceSSL = true;
# It's also possible to do a redirect here or something else, this vhost is not
# needed for Matrix. It's recommended though to *not put* element
# here, see also the section about Element.
locations."/".extraConfig = ''
return 404;
'';
# Forward all Matrix API calls to the synapse Matrix homeserver. A trailing slash
# *must not* be used here.
locations."/_matrix".proxyPass = "http://[::1]:8008";
# Forward requests for e.g. SSO and password-resets.
locations."/_synapse/client".proxyPass = "http://[::1]:8008";
};
};
};
services.matrix-synapse = {
enable = true;
settings.server_name = "federez.net";
# The public base URL value must match the `base_url` value set in `clientConfig` above.
# The default value here is based on `server_name`, so if your `server_name` is different
# from the value of `fqdn` above, you will likely run into some mismatched domain names
# in client applications.
settings.public_baseurl = baseUrl;
settings.app_service_config_files = [
"/var/lib/matrix-synapse/telegram-registration.yaml"
"/var/lib/matrix-synapse/irc-registration.yml"
];
settings.listeners = [
{ port = 8008;
bind_addresses = [ "::1" ];
type = "http";
tls = false;
x_forwarded = true;
resources = [ {
names = [ "client" "federation" ];
compress = true;
} ];
}
];
extraConfigFiles = [
config.age.secrets.matrix-shared-secret.path
];
};
age.secrets.matrix-shared-secret = {
file = ../secrets/matrix-shared-secret.age;
owner = "matrix-synapse";
group = "matrix-synapse";
};
}

28
profiles/sysadmin.nix Normal file
View file

@ -0,0 +1,28 @@
{ ... }: {
users.users.root.openssh.authorizedKeys.keyFiles = [
../pubkeys/raito.keys
];
users.ldap.daemon = {
enable = false;
extraConfig = ''
# The location at which the LDAP server(s) should be reachable.
uri ldaps://ldap.federez.net
uri ldaps://ldap-ro.federez.net
# The search base that will be used for all queries.
base dc=federez,dc=net
base passwd cn=Utilisateurs,dc=federez,dc=net
base shadow cn=Utilisateurs,dc=federez,dc=net
base group ou=posix,ou=groups,dc=federez,dc=net
# The LDAP protocol version to use.
ldap_version 3
# The DN to bind with for normal lookups.
binddn cn=nssauth,ou=service-users,dc=federez,dc=net
bindpw ********TOP-SECRET-PASSWORD-THAT-MUST-BE-CHANGED-FOR-A-VALID-ONE********
'';
};
}

32
profiles/telegram-bot.nix Normal file
View file

@ -0,0 +1,32 @@
{ config, lib, ... }: {
systemd.services.mautrix-telegram.serviceConfig.WorkingDirectory = lib.mkForce "/var/lib/mautrix-telegram";
age.secrets.mautrix-telegram.file = ../secrets/mautrix-telegram.age;
services.mautrix-telegram = {
enable = true;
environmentFile = config.age.secrets.mautrix-telegram.path;
settings = {
appservice = {
id = "telegram_cute";
bot_username = "telegram";
bot_displayname = "Telegram";
bot_avatar = "mxc://maunium.net/tJCRmUyJDsgRNgqhOgoiHWbX";
};
homeserver = {
address = "https://matrix.federez.net";
domain = "federez.net";
};
bridge = {
username_template = "telegram_{userid}";
alias_template = "telegram_{groupname}";
displayname_template = "{displayname}";
permissions = {
"*" = "relaybot";
"federez.net" = "full";
"@raitobezarius:federez.net" = "admin";
"@bsomers:resel.fr" = "admin";
};
};
};
};
}

31
profiles/vaultwarden.nix Normal file
View file

@ -0,0 +1,31 @@
{ config, ... }: {
age.secrets.vaultwarden-secrets.file = ../secrets/vaultwarden-secrets.age;
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
virtualHosts."vault.federez.net" = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://127.0.0.1:8222";
};
};
services.vaultwarden = {
enable = true;
environmentFile = config.age.secrets.vaultwarden-secrets.path;
config = {
DOMAIN = "https://vault.federez.net";
SIGNUPS_ALLOWED = false;
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = 8222;
ROCKET_LOG = "critical";
};
};
}

23
profiles/vm.nix Normal file
View file

@ -0,0 +1,23 @@
{ ... }: {
imports = [ <nixpkgs/nixos/modules/profiles/qemu-guest.nix> ];
boot.loader.systemd-boot.enable = true;
boot.initrd.kernelModules =
[ "virtio_balloon" "virtio_console" "virtio_rng" ];
boot.initrd.availableKernelModules = [
"9p"
"9pnet_virtio"
"ata_piix"
"nvme"
"sr_mod"
"uhci_hcd"
"virtio_blk"
"virtio_mmio"
"virtio_net"
"virtio_pci"
"virtio_scsi"
"xhci_pci"
];
}

14
provision-anywhere.sh Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -ex
NODE=$1
DISKO_SCRIPT=$(colmena eval -E "{ nodes, ... }: nodes.\"$NODE\".config.system.build.diskoScript" --show-trace | tr -d '"')
DISKO_SCRIPT_DRV=$(colmena eval -E "{ nodes, ... }: nodes.\"$NODE\".config.system.build.diskoScript.drvPath" --show-trace | tr -d '"')
nix-store -r $DISKO_SCRIPT_DRV
NIXOS_SYSTEM=$(colmena eval -E "{ nodes, ... }: nodes.\"$NODE\".config.system.build.toplevel")
colmena build --on "$NODE" --verbose
echo $NIXOS_SYSTEM $DISKO_SCRIPT
echo nix run github:nix-community/nixos-anywhere -- -s $DISKO_SCRIPT $NIXOS_SYSTEM "root@$NODE.federez.net" --no-reboot

3
pubkeys/raito.keys Normal file
View file

@ -0,0 +1,3 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDcEkYM1r8QVNM/G5CxJInEdoBCWjEHHDdHlzDYNSUIdHHsn04QY+XI67AdMCm8w30GZnLUIj5RiJEWXREUApby0GrfxGGcy8otforygfgtmuUKAUEHdU2MMwrQI7RtTZ8oQ0USRGuqvmegxz3l5caVU7qGvBllJ4NUHXrkZSja2/51vq80RF4MKkDGiz7xUTixI2UcBwQBCA/kQedKV9G28EH+1XfvePqmMivZjl+7VyHsgUVj9eRGA1XWFw59UPZG8a7VkxO/Eb3K9NF297HUAcFMcbY6cPFi9AaBgu3VC4eetDnoN/+xT1owiHi7BReQhGAy/6cdf7C/my5ehZwD
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE0xMwWedkKosax9+7D2OlnMxFL/eV4CvFZLsbLptpXr
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKiXXYkhRh+s7ixZ8rvG8ntIqd6FELQ9hh7HoaHQJRPU

Binary file not shown.

Binary file not shown.

18
secrets/secrets.nix Normal file
View file

@ -0,0 +1,18 @@
let
readKeyFile = keyFile: builtins.filter (x: x != [] && x != "") (builtins.split "\n" (builtins.readFile keyFile));
estragon = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBS1xp/2n5q4u4kDerkXQClnD1xeS6qrj0regbJwjktB root@estragon";
wagon = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJqBgXGbnPPmDHrn05Fr3X66cmgP6zvnMtPL21d4ebfh root@wagon";
lagon = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN8fiqJw9RvVVQghG0OVKsXAkBcWox4JsozfxToLAiIK root@lagon";
# Add yourself.
raito = readKeyFile ../pubkeys/raito.keys;
matrix-admins = raito;
vaultwarden-admins = raito;
keycloak-admins = raito;
in
{
"matrix-shared-secret.age".publicKeys = [ estragon ] ++ matrix-admins;
"mautrix-telegram.age".publicKeys = [ estragon ] ++ matrix-admins;
"vaultwarden-secrets.age".publicKeys = [ wagon ] ++ vaultwarden-admins;
"keycloak-password-file.age".publicKeys = [ lagon ] ++ keycloak-admins;
}

Binary file not shown.

1
shell.nix Normal file
View file

@ -0,0 +1 @@
(import ./default.nix {}).shell