commit 8acdce99df32743b3d6db9ef364011d907afd2ce Author: Ryan Lahfa Date: Mon Feb 12 04:57:07 2024 +0100 Init infrastructure Benjamin, I hate you. Signed-off-by: Ryan Lahfa diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/bootstrap/iso.nix b/bootstrap/iso.nix new file mode 100644 index 0000000..426fafb --- /dev/null +++ b/bootstrap/iso.nix @@ -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 + ]; +} + diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..8cd059d --- /dev/null +++ b/default.nix @@ -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; +} diff --git a/disks/ext4.nix b/disks/ext4.nix new file mode 100644 index 0000000..c5a2cf2 --- /dev/null +++ b/disks/ext4.nix @@ -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 = "/"; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/hive.nix b/hive.nix new file mode 100644 index 0000000..7283d36 --- /dev/null +++ b/hive.nix @@ -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 + ]; + }; + +} diff --git a/npins/default.nix b/npins/default.nix new file mode 100644 index 0000000..4a7c372 --- /dev/null +++ b/npins/default.nix @@ -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`" diff --git a/npins/sources.json b/npins/sources.json new file mode 100644 index 0000000..f8f1c56 --- /dev/null +++ b/npins/sources.json @@ -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 +} \ No newline at end of file diff --git a/profiles/element.nix b/profiles/element.nix new file mode 100644 index 0000000..2fbf25e --- /dev/null +++ b/profiles/element.nix @@ -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. + }; + }; + }; +} diff --git a/profiles/irc-bot.nix b/profiles/irc-bot.nix new file mode 100644 index 0000000..3557ac9 --- /dev/null +++ b/profiles/irc-bot.nix @@ -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"; + }; + }; +}; +} diff --git a/profiles/keycloak.nix b/profiles/keycloak.nix new file mode 100644 index 0000000..bb8e1f3 --- /dev/null +++ b/profiles/keycloak.nix @@ -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"; + }; + }; + + +} diff --git a/profiles/matrix-server.nix b/profiles/matrix-server.nix new file mode 100644 index 0000000..9a90b3e --- /dev/null +++ b/profiles/matrix-server.nix @@ -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"; + }; +} diff --git a/profiles/sysadmin.nix b/profiles/sysadmin.nix new file mode 100644 index 0000000..ebdcb44 --- /dev/null +++ b/profiles/sysadmin.nix @@ -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******** + ''; + }; +} diff --git a/profiles/telegram-bot.nix b/profiles/telegram-bot.nix new file mode 100644 index 0000000..194fc8c --- /dev/null +++ b/profiles/telegram-bot.nix @@ -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"; + }; + }; + }; + }; +} diff --git a/profiles/vaultwarden.nix b/profiles/vaultwarden.nix new file mode 100644 index 0000000..1c20d6e --- /dev/null +++ b/profiles/vaultwarden.nix @@ -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"; + }; + }; +} diff --git a/profiles/vm.nix b/profiles/vm.nix new file mode 100644 index 0000000..4d3c05c --- /dev/null +++ b/profiles/vm.nix @@ -0,0 +1,23 @@ +{ ... }: { + imports = [ ]; + + 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" + ]; +} diff --git a/provision-anywhere.sh b/provision-anywhere.sh new file mode 100755 index 0000000..32538dc --- /dev/null +++ b/provision-anywhere.sh @@ -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 diff --git a/pubkeys/raito.keys b/pubkeys/raito.keys new file mode 100644 index 0000000..7a717dd --- /dev/null +++ b/pubkeys/raito.keys @@ -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 diff --git a/secrets/matrix-shared-secret.age b/secrets/matrix-shared-secret.age new file mode 100644 index 0000000..793750b Binary files /dev/null and b/secrets/matrix-shared-secret.age differ diff --git a/secrets/mautrix-telegram.age b/secrets/mautrix-telegram.age new file mode 100644 index 0000000..9628e15 Binary files /dev/null and b/secrets/mautrix-telegram.age differ diff --git a/secrets/secrets.nix b/secrets/secrets.nix new file mode 100644 index 0000000..63cb505 --- /dev/null +++ b/secrets/secrets.nix @@ -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; + } diff --git a/secrets/vaultwarden-secrets.age b/secrets/vaultwarden-secrets.age new file mode 100644 index 0000000..ba88754 Binary files /dev/null and b/secrets/vaultwarden-secrets.age differ diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..82fb296 --- /dev/null +++ b/shell.nix @@ -0,0 +1 @@ +(import ./default.nix {}).shell