Self-host your own digital island

Compare changes

Choose any two refs to compare.

+185 -42
flake.lock
···
"type": "gitlab"
}
},
+
"eon": {
+
"inputs": {
+
"flake-utils": "flake-utils",
+
"nixpkgs": [
+
"nixpkgs"
+
],
+
"opam-nix": "opam-nix"
+
},
+
"locked": {
+
"lastModified": 1738666931,
+
"narHash": "sha256-dTF+etN5ZDPVwK8XV/huQByY6JohiVgpCfzVJWAZY1I=",
+
"owner": "RyanGibb",
+
"repo": "eon",
+
"rev": "42523d1d8f720215ab5108a1b42e9c5b7d17d4bf",
+
"type": "github"
+
},
+
"original": {
+
"owner": "RyanGibb",
+
"repo": "eon",
+
"type": "github"
+
}
+
},
"flake-compat": {
"flake": false,
"locked": {
···
"type": "github"
}
},
+
"flake-compat_2": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1696426674,
+
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+
"owner": "edolstra",
+
"repo": "flake-compat",
+
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+
"type": "github"
+
},
+
"original": {
+
"owner": "edolstra",
+
"repo": "flake-compat",
+
"type": "github"
+
}
+
},
+
"flake-utils": {
+
"inputs": {
+
"systems": "systems"
+
},
+
"locked": {
+
"lastModified": 1731533236,
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+
"type": "github"
+
},
+
"original": {
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"type": "github"
+
}
+
},
+
"mirage-opam-overlays": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1710922379,
+
"narHash": "sha256-j4QREQDUf8oHOX7qg6wAOupgsNQoYlufxoPrgagD+pY=",
+
"owner": "dune-universe",
+
"repo": "mirage-opam-overlays",
+
"rev": "797cb363df3ff763c43c8fbec5cd44de2878757e",
+
"type": "github"
+
},
+
"original": {
+
"owner": "dune-universe",
+
"repo": "mirage-opam-overlays",
+
"type": "github"
+
}
+
},
"nixos-mailserver": {
"inputs": {
"blobs": "blobs",
-
"flake-compat": "flake-compat",
-
"nixpkgs": "nixpkgs",
-
"nixpkgs-23_05": "nixpkgs-23_05",
-
"nixpkgs-23_11": "nixpkgs-23_11",
+
"flake-compat": "flake-compat_2",
+
"nixpkgs": [
+
"nixpkgs"
+
],
+
"nixpkgs-24_05": "nixpkgs-24_05",
"utils": "utils"
},
"locked": {
-
"lastModified": 1711069052,
-
"narHash": "sha256-QScBLiWRDmrOhG4/jCrdZTNe8zQdf6gzSZocAYOcG3U=",
+
"lastModified": 1718183756,
+
"narHash": "sha256-m5JQT/RIegSLZJx41Cv7d8Xoa2KKq+5uLkgB5KJR5D0=",
"owner": "RyanGibb",
"repo": "nixos-mailserver",
-
"rev": "435c3a167c52ebe443f6ed1ba3412331ae566d05",
-
"type": "github"
+
"rev": "9dc7a8d40232f600e6ca1e78356cd4398665b46b",
+
"type": "gitlab"
},
"original": {
"owner": "RyanGibb",
-
"ref": "fork-23.11",
+
"ref": "fork-24.05",
"repo": "nixos-mailserver",
-
"type": "github"
+
"type": "gitlab"
}
},
"nixpkgs": {
"locked": {
-
"lastModified": 1709703039,
-
"narHash": "sha256-6hqgQ8OK6gsMu1VtcGKBxKQInRLHtzulDo9Z5jxHEFY=",
-
"owner": "NixOS",
+
"lastModified": 1732981179,
+
"narHash": "sha256-F7thesZPvAMSwjRu0K8uFshTk3ZZSNAsXTIFvXBT+34=",
+
"owner": "nixos",
"repo": "nixpkgs",
-
"rev": "9df3e30ce24fd28c7b3e2de0d986769db5d6225d",
+
"rev": "62c435d93bf046a5396f3016472e8f7c8e2aed65",
"type": "github"
},
"original": {
-
"id": "nixpkgs",
-
"ref": "nixos-unstable",
-
"type": "indirect"
+
"owner": "nixos",
+
"ref": "nixos-24.11",
+
"repo": "nixpkgs",
+
"type": "github"
}
},
-
"nixpkgs-23_05": {
+
"nixpkgs-24_05": {
"locked": {
-
"lastModified": 1704290814,
-
"narHash": "sha256-LWvKHp7kGxk/GEtlrGYV68qIvPHkU9iToomNFGagixU=",
+
"lastModified": 1718086528,
+
"narHash": "sha256-hoB7B7oPgypePz16cKWawPfhVvMSXj4G/qLsfFuhFjw=",
"owner": "NixOS",
"repo": "nixpkgs",
-
"rev": "70bdadeb94ffc8806c0570eb5c2695ad29f0e421",
+
"rev": "47b604b07d1e8146d5398b42d3306fdebd343986",
"type": "github"
},
"original": {
"id": "nixpkgs",
-
"ref": "nixos-23.05",
+
"ref": "nixos-24.05",
"type": "indirect"
}
},
-
"nixpkgs-23_11": {
+
"opam-nix": {
+
"inputs": {
+
"flake-compat": "flake-compat",
+
"flake-utils": [
+
"eon",
+
"flake-utils"
+
],
+
"mirage-opam-overlays": "mirage-opam-overlays",
+
"nixpkgs": [
+
"eon",
+
"nixpkgs"
+
],
+
"opam-overlays": "opam-overlays",
+
"opam-repository": "opam-repository",
+
"opam2json": "opam2json"
+
},
+
"locked": {
+
"lastModified": 1732617437,
+
"narHash": "sha256-jj25fziYrES8Ix6HkfSiLzrN6MZjiwlHUxFSIuLRjgE=",
+
"owner": "tweag",
+
"repo": "opam-nix",
+
"rev": "ea8b9cb81fe94e1fc45c6376fcff15f17319c445",
+
"type": "github"
+
},
+
"original": {
+
"owner": "tweag",
+
"repo": "opam-nix",
+
"type": "github"
+
}
+
},
+
"opam-overlays": {
+
"flake": false,
"locked": {
-
"lastModified": 1710951922,
-
"narHash": "sha256-FOOBJ3DQenLpTNdxMHR2CpGZmYuctb92gF0lpiirZ30=",
-
"owner": "NixOS",
-
"repo": "nixpkgs",
-
"rev": "f091af045dff8347d66d186a62d42aceff159456",
+
"lastModified": 1726822209,
+
"narHash": "sha256-bwM18ydNT9fYq91xfn4gmS21q322NYrKwfq0ldG9GYw=",
+
"owner": "dune-universe",
+
"repo": "opam-overlays",
+
"rev": "f2bec38beca4aea9e481f2fd3ee319c519124649",
+
"type": "github"
+
},
+
"original": {
+
"owner": "dune-universe",
+
"repo": "opam-overlays",
+
"type": "github"
+
}
+
},
+
"opam-repository": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1732612513,
+
"narHash": "sha256-kju4NWEQo4xTxnKeBIsmqnyxIcCg6sNZYJ1FmG/gCDw=",
+
"owner": "ocaml",
+
"repo": "opam-repository",
+
"rev": "3d52b66b04788999a23f22f0d59c2dfc831c4f32",
"type": "github"
},
"original": {
-
"id": "nixpkgs",
-
"ref": "nixos-23.11",
-
"type": "indirect"
+
"owner": "ocaml",
+
"repo": "opam-repository",
+
"type": "github"
}
},
-
"nixpkgs_2": {
+
"opam2json": {
+
"inputs": {
+
"nixpkgs": [
+
"eon",
+
"opam-nix",
+
"nixpkgs"
+
]
+
},
"locked": {
-
"lastModified": 1710272261,
-
"narHash": "sha256-g0bDwXFmTE7uGDOs9HcJsfLFhH7fOsASbAuOzDC+fhQ=",
-
"owner": "nixos",
-
"repo": "nixpkgs",
-
"rev": "0ad13a6833440b8e238947e47bea7f11071dc2b2",
+
"lastModified": 1671540003,
+
"narHash": "sha256-5pXfbUfpVABtKbii6aaI2EdAZTjHJ2QntEf0QD2O5AM=",
+
"owner": "tweag",
+
"repo": "opam2json",
+
"rev": "819d291ea95e271b0e6027679de6abb4d4f7f680",
"type": "github"
},
"original": {
-
"owner": "nixos",
-
"ref": "nixos-unstable",
-
"repo": "nixpkgs",
+
"owner": "tweag",
+
"repo": "opam2json",
"type": "github"
}
},
"root": {
"inputs": {
+
"eon": "eon",
"nixos-mailserver": "nixos-mailserver",
-
"nixpkgs": "nixpkgs_2"
+
"nixpkgs": "nixpkgs"
}
},
"systems": {
···
"type": "github"
}
},
+
"systems_2": {
+
"locked": {
+
"lastModified": 1681028828,
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+
"owner": "nix-systems",
+
"repo": "default",
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+
"type": "github"
+
},
+
"original": {
+
"owner": "nix-systems",
+
"repo": "default",
+
"type": "github"
+
}
+
},
"utils": {
"inputs": {
-
"systems": "systems"
+
"systems": "systems_2"
},
"locked": {
"lastModified": 1709126324,
+21 -10
flake.nix
···
{
inputs = {
-
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
-
nixos-mailserver.url = "github:RyanGibb/nixos-mailserver/fork-23.11";
+
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
+
nixos-mailserver.url = "gitlab:RyanGibb/nixos-mailserver/fork-24.05";
+
eon.url = "github:RyanGibb/eon";
+
+
eon.inputs.nixpkgs.follows = "nixpkgs";
+
nixos-mailserver.inputs.nixpkgs.follows = "nixpkgs";
};
-
outputs = { self, nixpkgs, nixos-mailserver, ... }: rec {
+
outputs = { nixpkgs, nixos-mailserver, eon, ... }: {
packages = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (system:
-
let
-
pkgs = nixpkgs.legacyPackages.${system};
+
let pkgs = nixpkgs.legacyPackages.${system};
in {
manpage = import ./man { inherit pkgs system nixos-mailserver; };
+
packages.mautrix-meta = (pkgs.callPackage ./pkgs/mautrix-meta.nix { });
});
nixosModules.default = {
imports = [
./modules/default.nix
nixos-mailserver.nixosModule
-
({ pkgs, config, ... }: {
-
nixpkgs.overlays = [ (final: prev: {
-
mautrix-meta = (prev.callPackage ./pkgs/mautrix-meta.nix { });
-
}) ];
-
})
+
eon.nixosModules.default
+
eon.nixosModules.acme
+
{
+
nixpkgs.overlays = [
+
(final: prev: {
+
mautrix-meta = (prev.callPackage ./pkgs/mautrix-meta.nix { });
+
})
+
];
+
}
];
};
defaultTemplate.path = ./template;
+
+
formatter = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed
+
(system: nixpkgs.legacyPackages.${system}.nixfmt);
};
}
+28 -36
man/default.nix
···
with pkgs;
let
-
optionsDoc =
-
let
-
eval = import (pkgs.path + "/nixos/lib/eval-config.nix") {
-
inherit system;
-
modules = [
-
../modules/default.nix
-
nixos-mailserver
-
];
-
};
-
in pkgs.nixosOptionsDoc {
-
options = eval.options;
-
# TODO make sure all options have descriptions
-
warningsAreErrors = false;
-
};
+
optionsDoc = let
+
eval = import (pkgs.path + "/nixos/lib/eval-config.nix") {
+
inherit system;
+
modules = [ ../modules/default.nix nixos-mailserver ];
+
};
+
in pkgs.nixosOptionsDoc {
+
options = eval.options;
+
# TODO make sure all options have descriptions
+
warningsAreErrors = false;
+
};
# Generate the `man eliean.nix` package
-
eilean-configuration-manual =
-
runCommand "eilean-reference-manpage"
-
{ nativeBuildInputs = [
-
buildPackages.installShellFiles
-
buildPackages.nixos-render-docs
-
];
-
allowedReferences = [ "out" ];
-
}
-
''
-
# Generate manpages.
-
mkdir -p $out/share/man/man5
-
# filter to only eilean options
-
cat ${optionsDoc.optionsJSON}/share/doc/nixos/options.json \
-
| ${pkgs.jq}/bin/jq 'with_entries(select(.key | test("^eilean")))' \
-
> eilean-options.json
-
nixos-render-docs -j $NIX_BUILD_CORES options manpage \
-
--revision dev \
-
--header ${./eilean-configuration-nix-header.5} \
-
--footer ${./eilean-configuration-nix-footer.5} \
-
eilean-options.json \
-
$out/share/man/man5/eilean-configuration.nix.5
-
'';
+
eilean-configuration-manual = runCommand "eilean-reference-manpage" {
+
nativeBuildInputs =
+
[ buildPackages.installShellFiles buildPackages.nixos-render-docs ];
+
allowedReferences = [ "out" ];
+
} ''
+
# Generate manpages.
+
mkdir -p $out/share/man/man5
+
# filter to only eilean options
+
cat ${optionsDoc.optionsJSON}/share/doc/nixos/options.json \
+
| ${pkgs.jq}/bin/jq 'with_entries(select(.key | test("^eilean")))' \
+
> eilean-options.json
+
nixos-render-docs -j $NIX_BUILD_CORES options manpage \
+
--revision dev \
+
--header ${./eilean-configuration-nix-header.5} \
+
--footer ${./eilean-configuration-nix-footer.5} \
+
eilean-options.json \
+
$out/share/man/man5/eilean-configuration.nix.5
+
'';
in eilean-configuration-manual
+16
modules/acme-eon.nix
···
+
{ pkgs, config, lib, ... }:
+
+
with lib;
+
let cfg = config.eilean;
+
in {
+
options.eilean.acme-eon = mkEnableOption "acme-eon";
+
+
config = mkIf cfg.acme-eon {
+
assertions = [{
+
assertion = cfg.services.dns.server == "eon";
+
message = ''
+
If config.eilean.acme-eon is enabled config.eilean.services.dns.server must be "eon".
+
'';
+
}];
+
};
+
}
+15 -15
modules/default.nix
···
{
imports = [
+
./acme-eon.nix
./services/dns/default.nix
./mastodon.nix
./mailserver.nix
./gitea.nix
./dns.nix
+
./fail2ban.nix
./matrix/synapse.nix
-
./matrix/mautrix-signal.nix
./matrix/mautrix-instagram.nix
./matrix/mautrix-messenger.nix
./turn.nix
./headscale.nix
./wireguard/default.nix
+
./radicale.nix
];
options.eilean = with types; {
-
username = mkOption {
-
type = str;
-
};
-
serverIpv4 = mkOption {
-
type = str;
-
};
-
serverIpv6 = mkOption {
-
type = str;
-
};
-
publicInterface = mkOption {
-
type = str;
+
username = mkOption { type = str; };
+
serverIpv4 = mkOption { type = str; };
+
serverIpv6 = mkOption { type = str; };
+
publicInterface = mkOption { type = str; };
+
domainName = mkOption {
+
type = types.str;
+
default = "vps";
};
};
config = {
# TODO install manpage
-
environment.systemPackages = [
-
];
-
security.acme.defaults.email = "${config.eilean.username}@${config.networking.domain}";
+
environment.systemPackages = [ ];
+
security.acme.defaults.email = lib.mkIf (!config.eilean.acme-eon)
+
"${config.eilean.username}@${config.networking.domain}";
+
security.acme-eon.defaults.email = lib.mkIf config.eilean.acme-eon
+
"${config.eilean.username}@${config.networking.domain}";
networking.firewall.allowedTCPPorts = mkIf config.services.nginx.enable [
80 # HTTP
443 # HTTPS
+16 -22
modules/dns.nix
···
{ config, lib, ... }:
with lib;
-
let cfg = config.eilean; in
-
{
-
+
let cfg = config.eilean;
+
in {
+
options.eilean.dns = {
enable = mkEnableOption "dns";
nameservers = mkOption {
···
default = [ "ns1" "ns2" ];
};
};
-
+
config.eilean.services.dns = mkIf cfg.dns.enable {
enable = true;
zones.${config.networking.domain} = {
···
{
name = "@";
type = "NS";
-
data = ns;
+
value = ns;
}
{
name = ns;
type = "A";
-
data = cfg.serverIpv4;
-
}
-
{
-
name = "@";
-
type = "NS";
-
data = ns;
+
value = cfg.serverIpv4;
}
{
name = ns;
type = "AAAA";
-
data = cfg.serverIpv6;
+
value = cfg.serverIpv6;
}
-
]) cfg.dns.nameservers ++
-
[
+
]) cfg.dns.nameservers ++ [
{
name = "@";
type = "A";
-
data = cfg.serverIpv4;
+
value = cfg.serverIpv4;
}
{
name = "@";
type = "AAAA";
-
data = cfg.serverIpv6;
+
value = cfg.serverIpv6;
}
{
-
name = "vps";
+
name = cfg.domainName;
type = "A";
-
data = cfg.serverIpv4;
+
value = cfg.serverIpv4;
}
{
-
name = "vps";
+
name = cfg.domainName;
type = "AAAA";
-
data = cfg.serverIpv6;
+
value = cfg.serverIpv6;
}
-
+
{
name = "@";
type = "LOC";
-
data = "52 12 40.4 N 0 5 31.9 E 22m 10m 10m 10m";
+
value = "52 12 40.4 N 0 5 31.9 E 22m 10m 10m 10m";
}
];
};
+42
modules/fail2ban.nix
···
+
{ config, pkgs, lib, ... }:
+
+
with lib;
+
let cfg = config.eilean;
+
in {
+
options.eilean.fail2ban = {
+
enable = mkEnableOption "TURN server";
+
radicale = mkOption {
+
type = types.bool;
+
default = cfg.radicale.enable;
+
};
+
};
+
+
config = mkIf cfg.fail2ban.enable {
+
services.fail2ban = {
+
enable = true;
+
bantime = "24h";
+
bantime-increment = {
+
enable = true;
+
multipliers = "1 2 4 8 16 32 64";
+
maxtime = "168h";
+
overalljails = true;
+
};
+
jails."radicale".settings = mkIf cfg.fail2ban.radicale {
+
port = "5232";
+
filter = "radicale";
+
banaction = "%(banaction_allports)s[name=radicale]";
+
backend = "systemd";
+
journalmatch = "_SYSTEMD_UNIT=radicale.service";
+
maxRetry = 2;
+
bantime = -1;
+
findtime = 14400;
+
};
+
};
+
environment.etc = {
+
"fail2ban/filter.d/radicale.local".text = mkIf cfg.fail2ban.radicale ''
+
[Definition]
+
failregex = ^.*Failed\slogin\sattempt\sfrom\s.*\(forwarded for \'<HOST>\'.*\):\s.*
+
'';
+
};
+
};
+
}
+29 -21
modules/gitea.nix
···
let
cfg = config.eilean;
domain = config.networking.domain;
+
subdomain = "git.${domain}";
in {
options.eilean.gitea = {
enable = mkEnableOption "gitea";
···
};
config = mkIf cfg.gitea.enable {
+
security.acme-eon.nginxCerts = [ subdomain ];
+
services.nginx = {
enable = true;
recommendedProxySettings = true;
-
virtualHosts."git.${domain}" = {
-
enableACME = true;
+
virtualHosts."${subdomain}" = {
+
enableACME = lib.mkIf (!cfg.acme-eon) true;
forceSSL = true;
locations."/" = {
-
proxyPass = "http://localhost:${builtins.toString config.services.gitea.settings.server.HTTP_PORT}/";
+
proxyPass = "http://localhost:${
+
builtins.toString config.services.gitea.settings.server.HTTP_PORT
+
}/";
};
};
};
···
mailerPasswordFile = cfg.mailserver.systemAccountPasswordFile;
settings = {
server = {
-
ROOT_URL = "https://git.${domain}/";
-
DOMAIN = "git.${domain}";
+
ROOT_URL = "https://${subdomain}/";
+
DOMAIN = subdomain;
};
mailer = {
ENABLED = true;
···
RestrictRealtime = mkForce false;
RestrictSUIDSGID = mkForce false;
SystemCallArchitectures = mkForce "";
-
SystemCallFilter = mkForce [];
+
SystemCallFilter = mkForce [ ];
};
eilean.dns.enable = true;
-
eilean.services.dns.zones.${config.networking.domain}.records = [
-
{
-
name = "git";
-
type = "CNAME";
-
data = "vps";
-
}
-
];
+
eilean.services.dns.zones.${config.networking.domain}.records = [{
+
name = "git";
+
type = "CNAME";
+
value = cfg.domainName;
+
}];
# proxy port 22 on ethernet interface to internal gitea ssh server
# openssh server remains accessible on port 22 via vpn(s)
···
};
networking.firewall = {
-
allowedTCPPorts = [
-
22
-
cfg.gitea.sshPort
-
];
+
allowedTCPPorts = [ 22 cfg.gitea.sshPort ];
extraCommands = ''
# proxy all traffic on public interface to the gitea SSH server
-
iptables -A PREROUTING -t nat -i ${config.eilean.publicInterface} -p tcp --dport 22 -j REDIRECT --to-port ${builtins.toString cfg.gitea.sshPort}
-
ip6tables -A PREROUTING -t nat -i ${config.eilean.publicInterface} -p tcp --dport 22 -j REDIRECT --to-port ${builtins.toString cfg.gitea.sshPort}
+
iptables -A PREROUTING -t nat -i ${config.eilean.publicInterface} -p tcp --dport 22 -j REDIRECT --to-port ${
+
builtins.toString cfg.gitea.sshPort
+
}
+
ip6tables -A PREROUTING -t nat -i ${config.eilean.publicInterface} -p tcp --dport 22 -j REDIRECT --to-port ${
+
builtins.toString cfg.gitea.sshPort
+
}
# proxy locally originating outgoing packets
-
iptables -A OUTPUT -d ${config.eilean.serverIpv4} -t nat -p tcp --dport 22 -j REDIRECT --to-port ${builtins.toString cfg.gitea.sshPort}
-
ip6tables -A OUTPUT -d ${config.eilean.serverIpv6} -t nat -p tcp --dport 22 -j REDIRECT --to-port ${builtins.toString cfg.gitea.sshPort}
+
iptables -A OUTPUT -d ${config.eilean.serverIpv4} -t nat -p tcp --dport 22 -j REDIRECT --to-port ${
+
builtins.toString cfg.gitea.sshPort
+
}
+
ip6tables -A OUTPUT -d ${config.eilean.serverIpv6} -t nat -p tcp --dport 22 -j REDIRECT --to-port ${
+
builtins.toString cfg.gitea.sshPort
+
}
'';
};
+8 -16
modules/headscale.nix
···
{ pkgs, config, lib, ... }:
with lib;
-
let
-
cfg = config.eilean;
+
let cfg = config.eilean;
in {
options.eilean.headscale = with lib; {
enable = mkEnableOption "headscale";
···
domain = mkOption {
type = types.str;
default = "headscale.${config.networking.domain}";
-
defaultText = "headscale.$${config.networking.domain}";
+
defaultText = "headscale.$\${config.networking.domain}";
};
};
···
settings = {
server_url = "https://${cfg.headscale.domain}";
logtail.enabled = false;
-
ip_prefixes = [ "100.64.0.0/10" ];
-
dns_config = {
-
# magicDns = true;
-
nameservers = config.networking.nameservers;
-
base_domain = "${cfg.headscale.zone}";
-
};
+
ip_prefixes = [ "100.64.0.0/10" "fd7a:115c:a1e0::/48" ];
};
};
···
environment.systemPackages = [ config.services.headscale.package ];
eilean.dns.enable = true;
-
eilean.services.dns.zones.${cfg.headscale.zone}.records = [
-
{
-
name = "${cfg.headscale.domain}.";
-
type = "CNAME";
-
data = "vps";
-
}
-
];
+
eilean.services.dns.zones.${cfg.headscale.zone}.records = [{
+
name = "${cfg.headscale.domain}.";
+
type = "CNAME";
+
value = cfg.domainName;
+
}];
};
}
+36 -18
modules/mailserver.nix
···
let
cfg = config.eilean;
domain = config.networking.domain;
+
subdomain = "mail.${domain}";
in {
options.eilean.mailserver = {
enable = mkEnableOption "mailserver";
···
};
config = mkIf cfg.mailserver.enable {
+
security.acme-eon.certs."${subdomain}" = lib.mkIf cfg.acme-eon {
+
group = "turnserver";
+
reloadServices = [ "postfix.service" "dovecot.service" ];
+
};
+
mailserver = {
enable = true;
-
fqdn = "mail.${domain}";
+
fqdn = subdomain;
domains = [ "${domain}" ];
loginAccounts = {
···
# Use Let's Encrypt certificates. Note that this needs to set up a stripped
# down nginx and opens port 80.
-
certificateScheme = "acme-nginx";
-
+
certificateScheme = if cfg.acme-eon then "manual" else "acme-nginx";
+
certificateFile = lib.mkIf cfg.acme-eon "${
+
config.security.acme-eon.certs.${subdomain}.directory
+
}/fullchain.pem";
+
keyFile = lib.mkIf cfg.acme-eon
+
"${config.security.acme-eon.certs.${subdomain}.directory}/key.pem";
localDnsResolver = false;
};
···
return 301 $scheme://${domain}$request_uri;
'';
+
systemd.services.dovecot2 = lib.mkIf cfg.acme-eon {
+
wants = [ "acme-eon-${subdomain}.service" ];
+
after = [ "acme-eon-${subdomain}.service" ];
+
};
+
+
systemd.services.postfix = lib.mkIf cfg.acme-eon {
+
wants = [ "acme-eon-${subdomain}.service" ];
+
after = [ "acme-eon-${subdomain}.service" ];
+
};
+
services.postfix.config = {
-
smtpd_tls_protocols = mkForce "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
-
smtp_tls_protocols = mkForce "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
-
smtpd_tls_mandatory_protocols = mkForce "TLSv1.3, !TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
-
smtp_tls_mandatory_protocols = mkForce "TLSv1.3, !TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+
smtpd_tls_protocols =
+
mkForce "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+
smtp_tls_protocols =
+
mkForce "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+
smtpd_tls_mandatory_protocols =
+
mkForce "TLSv1.3, !TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+
smtp_tls_mandatory_protocols =
+
mkForce "TLSv1.3, !TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
};
eilean.dns.enable = true;
···
{
name = "mail";
type = "A";
-
data = cfg.serverIpv4;
+
value = cfg.serverIpv4;
}
{
name = "mail";
type = "AAAA";
-
data = cfg.serverIpv6;
+
value = cfg.serverIpv6;
}
{
name = "@";
type = "MX";
-
data = "10 mail";
+
value = "10 mail";
}
{
name = "@";
type = "TXT";
-
data = "\"v=spf1 a:mail.${config.networking.domain} -all\"";
-
}
-
{
-
name = "mail._domainkey";
-
ttl = 10800;
-
type = "TXT";
-
data = "\"v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6YmYYvoFF7VqtGcozpVQa78aaGgZdvc5ZIHqzmkKdCBEyDF2FRbCEK4s2AlC8hhc8O4mSSe3S4AzEhlRgHXbU22GBaUZ3s2WHS8JJwZvWeTjsbXQwjN/U7xpkqXPHLH9IVfOJbHlp4HQmCAXw4NaypgkkxIGK0jaZHm2j6/1izQIDAQAB\"";
+
value = ''"v=spf1 a:mail.${config.networking.domain} -all"'';
}
{
name = "_dmarc";
ttl = 10800;
type = "TXT";
-
data = "\"v=DMARC1; p=reject\"";
+
value = ''"v=DMARC1; p=reject"'';
}
];
};
+24 -24
modules/mastodon.nix
···
let
cfg = config.eilean;
domain = config.networking.domain;
+
subdomain = "mastodon.${domain}";
in {
-
options.eilean.mastodon = {
-
enable = mkEnableOption "mastodon";
-
};
+
options.eilean.mastodon = { enable = mkEnableOption "mastodon"; };
config = mkIf cfg.mastodon.enable {
services.mastodon = {
···
};
extraConfig = {
# override localDomain
-
LOCAL_DOMAIN = "${domain}";
-
WEB_DOMAIN = "mastodon.${domain}";
+
LOCAL_DOMAIN = domain;
+
WEB_DOMAIN = subdomain;
# https://peterbabic.dev/blog/setting-up-smtp-in-mastodon/
-
SMTP_SSL="true";
-
SMTP_ENABLE_STARTTLS="false";
-
SMTP_OPENSSL_VERIFY_MODE="none";
+
SMTP_SSL = "true";
+
SMTP_ENABLE_STARTTLS = "false";
+
SMTP_OPENSSL_VERIFY_MODE = "none";
};
};
-
users.groups.${config.services.mastodon.group}.members = [ config.services.nginx.user ];
+
users.groups.${config.services.mastodon.group}.members =
+
[ config.services.nginx.user ];
+
+
security.acme-eon.nginxCerts = lib.mkIf cfg.acme-eon [ subdomain ];
services.nginx = {
enable = true;
···
# relies on root domain being set up
"${domain}".locations = {
"/.well-known/host-meta".extraConfig = ''
-
return 301 https://mastodon.${domain}$request_uri;
+
return 301 https://${subdomain}$request_uri;
'';
"/.well-known/webfinger".extraConfig = ''
-
return 301 https://mastodon.${domain}$request_uri;
+
return 301 https://${subdomain}$request_uri;
'';
};
-
"mastodon.${domain}" = {
+
"${subdomain}" = {
root = "${config.services.mastodon.package}/public/";
forceSSL = true;
-
enableACME = true;
+
enableACME = lib.mkIf (!cfg.acme-eon) true;
locations."/system/".alias = "/var/lib/mastodon/public-system/";
-
locations."/" = {
-
tryFiles = "$uri @proxy";
-
};
+
locations."/" = { tryFiles = "$uri @proxy"; };
locations."@proxy" = {
-
proxyPass = "http://127.0.0.1:${builtins.toString config.services.mastodon.webPort}";
+
proxyPass = "http://127.0.0.1:${
+
builtins.toString config.services.mastodon.webPort
+
}";
proxyWebsockets = true;
};
};
···
};
eilean.dns.enable = true;
-
eilean.services.dns.zones.${config.networking.domain}.records = [
-
{
-
name = "mastodon";
-
type = "CNAME";
-
data = "vps";
-
}
-
];
+
eilean.services.dns.zones.${config.networking.domain}.records = [{
+
name = "mastodon";
+
type = "CNAME";
+
value = cfg.domainName;
+
}];
};
}
+25 -29
modules/matrix/mautrix-instagram.nix
···
-
{
-
lib,
-
config,
-
pkgs,
-
...
-
}: let
+
{ lib, config, pkgs, ... }:
+
let
cfg = config.services.mautrix-instagram;
dataDir = "/var/lib/mautrix-instagram";
registrationFile = "${dataDir}/instagram-registration.yaml";
settingsFile = "${dataDir}/config.json";
-
settingsFileUnsubstituted = settingsFormat.generate "mautrix-instagram-config-unsubstituted.json" cfg.settings;
-
settingsFormat = pkgs.formats.json {};
+
settingsFileUnsubstituted =
+
settingsFormat.generate "mautrix-instagram-config-unsubstituted.json"
+
cfg.settings;
+
settingsFormat = pkgs.formats.json { };
appservicePort = 29319;
mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
···
};
bridge = {
username_template = "instagram_{{.}}";
-
double_puppet_server_map = {};
-
login_shared_secret_map = {};
+
double_puppet_server_map = { };
+
login_shared_secret_map = { };
permissions."*" = "relay";
relay.enabled = true;
};
···
in {
options.services.mautrix-instagram = {
-
enable = lib.mkEnableOption (lib.mdDoc "mautrix-instagram, a puppeting/relaybot bridge between Matrix and Instagram.");
+
enable = lib.mkEnableOption (lib.mdDoc
+
"mautrix-instagram, a puppeting/relaybot bridge between Matrix and Instagram.");
settings = lib.mkOption {
type = settingsFormat.type;
···
ephemeral_events = false;
};
bridge = {
-
history_sync = {
-
request_full_sync = true;
-
};
+
history_sync = { request_full_sync = true; };
private_chat_portal_meta = true;
mute_bridging = true;
encryption = {
···
default = true;
require = true;
};
-
provisioning = {
-
shared_secret = "disable";
-
};
-
permissions = {
-
"example.com" = "user";
-
};
+
provisioning = { shared_secret = "disable"; };
+
permissions = { "example.com" = "user"; };
};
};
};
serviceDependencies = lib.mkOption {
type = with lib.types; listOf str;
-
default = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
+
default = lib.optional config.services.matrix-synapse.enable
+
config.services.matrix-synapse.serviceUnit;
defaultText = lib.literalExpression ''
optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnits
'';
···
description = "Mautrix-Instagram bridge user";
};
-
users.groups.mautrix-instagram = {};
+
users.groups.mautrix-instagram = { };
services.mautrix-instagram.settings = lib.mkMerge (map mkDefaults [
defaultConfig
# Note: this is defined here to avoid the docs depending on `config`
-
{ homeserver.domain = config.services.matrix-synapse.settings.server_name; }
+
{
+
homeserver.domain = config.services.matrix-synapse.settings.server_name;
+
}
]);
systemd.services.mautrix-instagram = {
description = "Mautrix-Instagram Service - A Instagram bridge for Matrix";
-
wantedBy = ["multi-user.target"];
-
wants = ["network-online.target"] ++ cfg.serviceDependencies;
-
after = ["network-online.target"] ++ cfg.serviceDependencies;
+
wantedBy = [ "multi-user.target" ];
+
wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
+
after = [ "network-online.target" ] ++ cfg.serviceDependencies;
preStart = ''
# substitute the settings file by environment variables
···
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallErrorNumber = "EPERM";
-
SystemCallFilter = ["@system-service"];
+
SystemCallFilter = [ "@system-service" ];
Type = "simple";
-
UMask = 0027;
+
UMask = 27;
};
-
restartTriggers = [settingsFileUnsubstituted];
+
restartTriggers = [ settingsFileUnsubstituted ];
};
};
}
+25 -29
modules/matrix/mautrix-messenger.nix
···
-
{
-
lib,
-
config,
-
pkgs,
-
...
-
}: let
+
{ lib, config, pkgs, ... }:
+
let
cfg = config.services.mautrix-messenger;
dataDir = "/var/lib/mautrix-messenger";
registrationFile = "${dataDir}/messenger-registration.yaml";
settingsFile = "${dataDir}/config.json";
-
settingsFileUnsubstituted = settingsFormat.generate "mautrix-messenger-config-unsubstituted.json" cfg.settings;
-
settingsFormat = pkgs.formats.json {};
+
settingsFileUnsubstituted =
+
settingsFormat.generate "mautrix-messenger-config-unsubstituted.json"
+
cfg.settings;
+
settingsFormat = pkgs.formats.json { };
appservicePort = 29320;
mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
···
};
bridge = {
username_template = "messenger_{{.}}";
-
double_puppet_server_map = {};
-
login_shared_secret_map = {};
+
double_puppet_server_map = { };
+
login_shared_secret_map = { };
permissions."*" = "relay";
relay.enabled = true;
};
···
in {
options.services.mautrix-messenger = {
-
enable = lib.mkEnableOption (lib.mdDoc "mautrix-messenger, a puppeting/relaybot bridge between Matrix and Messenger.");
+
enable = lib.mkEnableOption (lib.mdDoc
+
"mautrix-messenger, a puppeting/relaybot bridge between Matrix and Messenger.");
settings = lib.mkOption {
type = settingsFormat.type;
···
ephemeral_events = false;
};
bridge = {
-
history_sync = {
-
request_full_sync = true;
-
};
+
history_sync = { request_full_sync = true; };
private_chat_portal_meta = true;
mute_bridging = true;
encryption = {
···
default = true;
require = true;
};
-
provisioning = {
-
shared_secret = "disable";
-
};
-
permissions = {
-
"example.com" = "user";
-
};
+
provisioning = { shared_secret = "disable"; };
+
permissions = { "example.com" = "user"; };
};
};
};
serviceDependencies = lib.mkOption {
type = with lib.types; listOf str;
-
default = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
+
default = lib.optional config.services.matrix-synapse.enable
+
config.services.matrix-synapse.serviceUnit;
defaultText = lib.literalExpression ''
optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnits
'';
···
description = "Mautrix-Messenger bridge user";
};
-
users.groups.mautrix-messenger = {};
+
users.groups.mautrix-messenger = { };
services.mautrix-messenger.settings = lib.mkMerge (map mkDefaults [
defaultConfig
# Note: this is defined here to avoid the docs depending on `config`
-
{ homeserver.domain = config.services.matrix-synapse.settings.server_name; }
+
{
+
homeserver.domain = config.services.matrix-synapse.settings.server_name;
+
}
]);
systemd.services.mautrix-messenger = {
description = "Mautrix-Messenger Service - A Messenger bridge for Matrix";
-
wantedBy = ["multi-user.target"];
-
wants = ["network-online.target"] ++ cfg.serviceDependencies;
-
after = ["network-online.target"] ++ cfg.serviceDependencies;
+
wantedBy = [ "multi-user.target" ];
+
wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
+
after = [ "network-online.target" ] ++ cfg.serviceDependencies;
preStart = ''
# substitute the settings file by environment variables
···
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallErrorNumber = "EPERM";
-
SystemCallFilter = ["@system-service"];
+
SystemCallFilter = [ "@system-service" ];
Type = "simple";
-
UMask = 0027;
+
UMask = 27;
};
-
restartTriggers = [settingsFileUnsubstituted];
+
restartTriggers = [ settingsFileUnsubstituted ];
};
};
}
-200
modules/matrix/mautrix-signal.nix
···
-
{
-
lib,
-
config,
-
pkgs,
-
...
-
}: let
-
cfg = config.services.mautrix-signal;
-
dataDir = "/var/lib/mautrix-signal";
-
registrationFile = "${dataDir}/signal-registration.yaml";
-
settingsFile = "${dataDir}/config.json";
-
settingsFileUnsubstituted = settingsFormat.generate "mautrix-signal-config-unsubstituted.json" cfg.settings;
-
settingsFormat = pkgs.formats.json {};
-
appservicePort = 29328;
-
-
mkDefaults = lib.mapAttrsRecursive (n: v: lib.mkDefault v);
-
defaultConfig = {
-
homeserver.address = "http://localhost:8448";
-
signal = {
-
socket_path = config.services.signald.socketPath;
-
outgoing_attachment_dir = "/var/lib/signald/tmp";
-
};
-
appservice = {
-
hostname = "[::]";
-
port = appservicePort;
-
database.type = "sqlite3";
-
database.uri = "${dataDir}/mautrix-signal.db";
-
id = "signal";
-
bot.username = "signalbot";
-
bot.displayname = "Signal Bridge Bot";
-
as_token = "";
-
hs_token = "";
-
};
-
bridge = {
-
username_template = "signal_{{.}}";
-
double_puppet_server_map = {};
-
login_shared_secret_map = {};
-
permissions."*" = "relay";
-
};
-
logging = {
-
min_level = "info";
-
writers = lib.singleton {
-
type = "stdout";
-
format = "pretty-colored";
-
time_format = " ";
-
};
-
};
-
};
-
-
in {
-
options.services.mautrix-signal = {
-
enable = lib.mkEnableOption (lib.mdDoc "mautrix-signal, a puppeting/relaybot bridge between Matrix and Signal.");
-
-
settings = lib.mkOption {
-
type = settingsFormat.type;
-
default = defaultConfig;
-
description = lib.mdDoc ''
-
{file}`config.yaml` configuration as a Nix attribute set.
-
Configuration options should match those described in
-
[example-config.yaml](https://github.com/mautrix/signal/blob/master/example-config.yaml).
-
'';
-
example = {
-
appservice = {
-
database = {
-
type = "postgres";
-
uri = "postgresql:///mautrix_signal?host=/run/postgresql";
-
};
-
id = "signal";
-
ephemeral_events = false;
-
};
-
bridge = {
-
history_sync = {
-
request_full_sync = true;
-
};
-
private_chat_portal_meta = true;
-
mute_bridging = true;
-
encryption = {
-
allow = true;
-
default = true;
-
require = true;
-
};
-
provisioning = {
-
shared_secret = "disable";
-
};
-
permissions = {
-
"example.com" = "user";
-
};
-
};
-
};
-
};
-
-
serviceDependencies = lib.mkOption {
-
type = with lib.types; listOf str;
-
default = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
-
defaultText = lib.literalExpression ''
-
optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnits
-
'';
-
description = lib.mdDoc ''
-
List of Systemd services to require and wait for when starting the application service.
-
'';
-
};
-
};
-
-
config = lib.mkIf cfg.enable {
-
-
services.signald.enable = true;
-
-
users.users.mautrix-signal = {
-
isSystemUser = true;
-
group = "mautrix-signal";
-
home = dataDir;
-
description = "Mautrix-Signal bridge user";
-
};
-
-
users.groups.mautrix-signal = {};
-
-
services.mautrix-signal.settings = lib.mkMerge (map mkDefaults [
-
defaultConfig
-
# Note: this is defined here to avoid the docs depending on `config`
-
{ homeserver.domain = config.services.matrix-synapse.settings.server_name; }
-
]);
-
-
systemd.services.mautrix-signal = {
-
description = "Mautrix-Signal Service - A Signal bridge for Matrix";
-
-
requires = [ "signald.service" ];
-
# voice messages need `ffmpeg`
-
path = [ pkgs.ffmpeg ];
-
-
wantedBy = ["multi-user.target"];
-
wants = ["network-online.target"] ++ cfg.serviceDependencies;
-
after = ["network-online.target" "signald.service"] ++ cfg.serviceDependencies;
-
-
preStart = ''
-
# substitute the settings file by environment variables
-
# in this case read from EnvironmentFile
-
test -f '${settingsFile}' && rm -f '${settingsFile}'
-
old_umask=$(umask)
-
umask 0177
-
${pkgs.envsubst}/bin/envsubst \
-
-o '${settingsFile}' \
-
-i '${settingsFileUnsubstituted}'
-
umask $old_umask
-
-
# generate the appservice's registration file if absent
-
if [ ! -f '${registrationFile}' ]; then
-
${pkgs.mautrix-signal}/bin/mautrix-signal \
-
--generate-registration \
-
--config='${settingsFile}' \
-
--registration='${registrationFile}'
-
fi
-
chmod 640 ${registrationFile}
-
-
umask 0177
-
${pkgs.yq}/bin/yq -s '.[0].appservice.as_token = .[1].as_token
-
| .[0].appservice.hs_token = .[1].hs_token
-
| .[0]' '${settingsFile}' '${registrationFile}' \
-
> '${settingsFile}.tmp'
-
mv '${settingsFile}.tmp' '${settingsFile}'
-
umask $old_umask
-
'';
-
-
serviceConfig = {
-
SupplementaryGroups = [ "signald" ];
-
User = "mautrix-signal";
-
Group = "mautrix-signal";
-
StateDirectory = baseNameOf dataDir;
-
WorkingDirectory = dataDir;
-
ExecStart = ''
-
${pkgs.mautrix-signal}/bin/mautrix-signal \
-
--config='${settingsFile}' \
-
--registration='${registrationFile}'
-
'';
-
LockPersonality = true;
-
MemoryDenyWriteExecute = true;
-
NoNewPrivileges = true;
-
PrivateDevices = true;
-
PrivateTmp = true;
-
PrivateUsers = true;
-
ProtectClock = true;
-
ProtectControlGroups = true;
-
ProtectHome = true;
-
ProtectHostname = true;
-
ProtectKernelLogs = true;
-
ProtectKernelModules = true;
-
ProtectKernelTunables = true;
-
ProtectSystem = "strict";
-
Restart = "on-failure";
-
RestartSec = "30s";
-
RestrictRealtime = true;
-
RestrictSUIDSGID = true;
-
SystemCallArchitectures = "native";
-
SystemCallErrorNumber = "EPERM";
-
SystemCallFilter = ["@system-service"];
-
Type = "simple";
-
UMask = 0027;
-
};
-
restartTriggers = [settingsFileUnsubstituted];
-
};
-
};
-
}
+107 -96
modules/matrix/synapse.nix
···
let
cfg = config.eilean;
turnSharedSecretFile = "/run/matrix-synapse/turn-shared-secret";
-
in
-
{
+
domain = config.networking.domain;
+
subdomain = "matrix.${domain}";
+
in {
options.eilean.matrix = {
enable = mkEnableOption "matrix";
turn = mkOption {
···
LC_CTYPE = "C";
'';
+
security.acme-eon.nginxCerts = lib.mkIf cfg.acme-eon [ domain subdomain ];
+
services.nginx = {
enable = true;
# only recommendedProxySettings and recommendedGzipSettings are strictly required,
···
virtualHosts = {
# This host section can be placed on a different host than the rest,
-
# i.e. to delegate from the host being accessible as ${config.networking.domain}
+
# i.e. to delegate from the host being accessible as ${domain}
# to another host actually running the Matrix homeserver.
-
"${config.networking.domain}" = {
-
enableACME = true;
+
"${domain}" = {
+
enableACME = lib.mkIf (!cfg.acme-eon) true;
forceSSL = true;
-
locations."= /.well-known/matrix/server".extraConfig =
-
let
-
# use 443 instead of the default 8448 port to unite
-
# the client-server and server-server port for simplicity
-
server = { "m.server" = "matrix.${config.networking.domain}:443"; };
-
in ''
-
default_type application/json;
-
return 200 '${builtins.toJSON server}';
-
'';
-
locations."= /.well-known/matrix/client".extraConfig =
-
let
-
client = {
-
"m.homeserver" = { "base_url" = "https://matrix.${config.networking.domain}"; };
-
"m.identity_server" = { "base_url" = "https://vector.im"; };
-
};
+
locations."= /.well-known/matrix/server".extraConfig = let
+
# use 443 instead of the default 8448 port to unite
+
# the client-server and server-server port for simplicity
+
server = { "m.server" = "${subdomain}:443"; };
+
in ''
+
default_type application/json;
+
return 200 '${builtins.toJSON server}';
+
'';
+
locations."= /.well-known/matrix/client".extraConfig = let
+
client = {
+
"m.homeserver" = { "base_url" = "https://${subdomain}"; };
+
"m.identity_server" = { "base_url" = "https://vector.im"; };
+
};
# ACAO required to allow element-web on any URL to request this json file
# set other headers due to https://github.com/yandex/gixy/blob/master/docs/en/plugins/addheaderredefinition.md
-
in ''
-
default_type application/json;
-
add_header Access-Control-Allow-Origin *;
-
add_header Strict-Transport-Security max-age=31536000 always;
-
add_header X-Frame-Options SAMEORIGIN always;
-
add_header X-Content-Type-Options nosniff always;
-
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; frame-src 'self'; frame-ancestors 'self'; form-action 'self';" always;
-
add_header Referrer-Policy 'same-origin';
-
return 200 '${builtins.toJSON client}';
-
'';
+
in ''
+
default_type application/json;
+
add_header Access-Control-Allow-Origin *;
+
add_header Strict-Transport-Security max-age=31536000 always;
+
add_header X-Frame-Options SAMEORIGIN always;
+
add_header X-Content-Type-Options nosniff always;
+
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; frame-src 'self'; frame-ancestors 'self'; form-action 'self';" always;
+
add_header Referrer-Policy 'same-origin';
+
return 200 '${builtins.toJSON client}';
+
'';
};
# Reverse proxy for Matrix client-server and server-server communication
-
"matrix.${config.networking.domain}" = {
-
enableACME = true;
+
"${subdomain}" = {
+
enableACME = lib.mkIf (!cfg.acme-eon) true;
forceSSL = true;
# Or do a redirect instead of the 404, or whatever is appropriate for you.
···
'';
# forward all Matrix API calls to the synapse Matrix homeserver
-
locations."/_matrix" = {
-
proxyPass = "http://127.0.0.1:8008"; # without a trailing /
-
#proxyPassReverse = "http://127.0.0.1:8008"; # without a trailing /
+
locations."~ ^(\\/_matrix|\\/_synapse\\/client)" = {
+
proxyPass = "http://127.0.0.1:8008";
};
};
};
···
enable = true;
settings = mkMerge [
{
-
server_name = config.networking.domain;
+
server_name = domain;
enable_registration = true;
registration_requires_token = true;
registration_shared_secret_path = cfg.matrix.registrationSecretFile;
-
listeners = [
-
{
-
port = 8008;
-
bind_addresses = [ "::1" "127.0.0.1" ];
-
type = "http";
-
tls = false;
-
x_forwarded = true;
-
resources = [
-
{
-
names = [ "client" "federation" ];
-
compress = false;
-
}
-
];
-
}
-
];
+
listeners = [{
+
port = 8008;
+
bind_addresses = [ "::1" "127.0.0.1" ];
+
type = "http";
+
tls = false;
+
x_forwarded = true;
+
resources = [{
+
names = [ "client" "federation" ];
+
compress = false;
+
}];
+
}];
max_upload_size = "100M";
-
app_service_config_files =
-
(optional cfg.matrix.bridges.whatsapp "/var/lib/mautrix-whatsapp/whatsapp-registration.yaml") ++
-
(optional cfg.matrix.bridges.signal "/var/lib/mautrix-signal/signal-registration.yaml") ++
-
(optional cfg.matrix.bridges.instagram "/var/lib/mautrix-instagram/instagram-registration.yaml") ++
-
(optional cfg.matrix.bridges.messenger "/var/lib/mautrix-messenger/messenger-registration.yaml");
+
app_service_config_files = (optional cfg.matrix.bridges.instagram
+
"/var/lib/mautrix-instagram/instagram-registration.yaml")
+
++ (optional cfg.matrix.bridges.messenger
+
"/var/lib/mautrix-messenger/messenger-registration.yaml");
}
(mkIf cfg.matrix.turn {
turn_uris = with config.services.coturn; [
···
turn_user_lifetime = "1h";
})
];
-
extraConfigFiles = mkIf cfg.matrix.turn (
-
[ turnSharedSecretFile ]
-
);
+
extraConfigFiles = mkIf cfg.matrix.turn ([ turnSharedSecretFile ]);
};
-
systemd.services.matrix-synapse-turn-shared-secret-generator = mkIf cfg.matrix.turn {
-
description = "Generate matrix synapse turn shared secret config file";
-
script = ''
-
mkdir -p "$(dirname '${turnSharedSecretFile}')"
-
echo "turn_shared_secret: $(cat '${config.services.coturn.static-auth-secret-file}')" > '${turnSharedSecretFile}'
-
chmod 770 '${turnSharedSecretFile}'
-
chown ${config.systemd.services.matrix-synapse.serviceConfig.User}:${config.systemd.services.matrix-synapse.serviceConfig.Group} '${turnSharedSecretFile}'
+
systemd.services.matrix-synapse-turn-shared-secret-generator =
+
mkIf cfg.matrix.turn {
+
description = "Generate matrix synapse turn shared secret config file";
+
script = ''
+
mkdir -p "$(dirname '${turnSharedSecretFile}')"
+
echo "turn_shared_secret: $(cat '${config.services.coturn.static-auth-secret-file}')" > '${turnSharedSecretFile}'
+
chmod 770 '${turnSharedSecretFile}'
+
chown ${config.systemd.services.matrix-synapse.serviceConfig.User}:${config.systemd.services.matrix-synapse.serviceConfig.Group} '${turnSharedSecretFile}'
'';
-
serviceConfig.Type = "oneshot";
-
serviceConfig.RemainAfterExit = true;
-
after = [ "coturn-static-auth-secret-generator.service" ];
-
requires = [ "coturn-static-auth-secret-generator.service" ];
-
};
-
systemd.services."matrix-synapse".after = mkIf cfg.matrix.turn [ "matrix-synapse-turn-shared-secret-generator.service" ];
-
systemd.services."matrix-synapse".requires = mkIf cfg.matrix.turn [ "matrix-synapse-turn-shared-secret-generator.service" ];
+
serviceConfig.Type = "oneshot";
+
serviceConfig.RemainAfterExit = true;
+
after = [ "coturn-static-auth-secret-generator.service" ];
+
requires = [ "coturn-static-auth-secret-generator.service" ];
+
};
+
systemd.services."matrix-synapse".after = mkIf cfg.matrix.turn
+
[ "matrix-synapse-turn-shared-secret-generator.service" ];
+
systemd.services."matrix-synapse".requires = mkIf cfg.matrix.turn
+
[ "matrix-synapse-turn-shared-secret-generator.service" ];
systemd.services.matrix-synapse.serviceConfig.SupplementaryGroups =
-
(optional cfg.matrix.bridges.whatsapp config.systemd.services.mautrix-whatsapp.serviceConfig.Group) ++
-
(optional cfg.matrix.bridges.signal config.systemd.services.mautrix-signal.serviceConfig.Group) ++
-
(optional cfg.matrix.bridges.instagram config.systemd.services.mautrix-instagram.serviceConfig.Group) ++
-
(optional cfg.matrix.bridges.messenger config.systemd.services.mautrix-messenger.serviceConfig.Group);
+
# remove after https://github.com/NixOS/nixpkgs/pull/311681/files
+
(optional cfg.matrix.bridges.whatsapp
+
config.systemd.services.mautrix-whatsapp.serviceConfig.Group)
+
++ (optional cfg.matrix.bridges.instagram
+
config.systemd.services.mautrix-instagram.serviceConfig.Group)
+
++ (optional cfg.matrix.bridges.messenger
+
config.systemd.services.mautrix-messenger.serviceConfig.Group);
services.mautrix-whatsapp = mkIf cfg.matrix.bridges.whatsapp {
enable = true;
-
settings.homeserver.address = "https://matrix.${config.networking.domain}";
-
settings.homeserver.domain = config.networking.domain;
+
settings.homeserver.address = "https://${subdomain}";
+
settings.homeserver.domain = domain;
settings.appservice.hostname = "localhost";
settings.appservice.address = "http://localhost:29318";
settings.bridge.personal_filtering_spaces = true;
settings.bridge.history_sync.backfill = false;
-
settings.bridge.permissions."@${config.eilean.username}:${config.networking.domain}" = "admin";
+
settings.bridge.permissions."@${config.eilean.username}:${domain}" =
+
"admin";
+
settings.bridge.encryption.allow = true;
+
settings.bridge.encryption.default = true;
};
+
# using https://github.com/NixOS/nixpkgs/pull/277368
services.mautrix-signal = mkIf cfg.matrix.bridges.signal {
enable = true;
-
settings.homeserver.address = "https://matrix.${config.networking.domain}";
-
settings.homeserver.domain = config.networking.domain;
+
settings.homeserver.address = "https://${subdomain}";
+
settings.homeserver.domain = domain;
settings.appservice.hostname = "localhost";
settings.appservice.address = "http://localhost:29328";
settings.bridge.personal_filtering_spaces = true;
-
settings.bridge.permissions."@${config.eilean.username}:${config.networking.domain}" = "admin";
+
settings.bridge.permissions."@${config.eilean.username}:${domain}" =
+
"admin";
+
settings.bridge.encryption.allow = true;
+
settings.bridge.encryption.default = true;
};
+
# TODO replace with upstreamed mautrix-meta
services.mautrix-instagram = mkIf cfg.matrix.bridges.instagram {
enable = true;
-
settings.homeserver.address = "https://matrix.${config.networking.domain}";
-
settings.homeserver.domain = config.networking.domain;
+
settings.homeserver.address = "https://${subdomain}";
+
settings.homeserver.domain = domain;
settings.appservice.hostname = "localhost";
settings.appservice.address = "http://localhost:29319";
settings.bridge.personal_filtering_spaces = true;
settings.bridge.backfill.enabled = false;
-
settings.bridge.permissions."@${config.eilean.username}:${config.networking.domain}" = "admin";
+
settings.bridge.permissions."@${config.eilean.username}:${domain}" =
+
"admin";
+
settings.bridge.encryption.allow = true;
+
settings.bridge.encryption.default = true;
};
services.mautrix-messenger = mkIf cfg.matrix.bridges.messenger {
enable = true;
-
settings.homeserver.address = "https://matrix.${config.networking.domain}";
-
settings.homeserver.domain = config.networking.domain;
+
settings.homeserver.address = "https://${subdomain}";
+
settings.homeserver.domain = domain;
settings.appservice.hostname = "localhost";
settings.appservice.address = "http://localhost:29320";
settings.bridge.personal_filtering_spaces = true;
settings.bridge.backfill.enabled = false;
-
settings.bridge.permissions."@${config.eilean.username}:${config.networking.domain}" = "admin";
+
settings.bridge.permissions."@${config.eilean.username}:${domain}" =
+
"admin";
+
settings.bridge.encryption.allow = true;
+
settings.bridge.encryption.default = true;
};
eilean.turn.enable = mkIf cfg.matrix.turn true;
eilean.dns.enable = true;
-
eilean.services.dns.zones.${config.networking.domain}.records = [
-
{
-
name = "matrix";
-
type = "CNAME";
-
data = "vps";
-
}
-
];
+
eilean.services.dns.zones.${domain}.records = [{
+
name = "matrix";
+
type = "CNAME";
+
value = cfg.domainName;
+
}];
};
}
+81
modules/radicale.nix
···
+
{ pkgs, config, lib, ... }:
+
+
with lib;
+
let
+
cfg = config.eilean;
+
domain = config.networking.domain;
+
passwdDir = "/var/lib/radicale/users";
+
passwdFile = "${passwdDir}/passwd";
+
userOps = { name, ... }: {
+
options = {
+
name = mkOption {
+
type = types.str;
+
readOnly = true;
+
default = name;
+
};
+
passwordFile = mkOption { type = types.nullOr types.str; };
+
};
+
};
+
in {
+
options.eilean.radicale = {
+
enable = mkEnableOption "radicale";
+
users = mkOption {
+
type = with types; nullOr (attrsOf (submodule userOps));
+
default = { };
+
};
+
};
+
+
config = mkIf cfg.radicale.enable {
+
services.radicale = {
+
enable = true;
+
settings = {
+
server = { hosts = [ "0.0.0.0:5232" ]; };
+
auth = {
+
type = "htpasswd";
+
htpasswd_filename = passwdFile;
+
htpasswd_encryption = "bcrypt";
+
};
+
storage = { filesystem_folder = "/var/lib/radicale/collections"; };
+
};
+
};
+
+
systemd.services.radicale = {
+
serviceConfig.ReadWritePaths = [ "/var/lib/radicale" ];
+
preStart = lib.mkIf (cfg.radicale.users != null) ''
+
if (! test -d "${passwdDir}"); then
+
mkdir "${passwdDir}"
+
chmod 755 "${passwdDir}"
+
fi
+
+
umask 077
+
+
cat <<EOF > ${passwdFile}
+
+
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
+
''
+
$(${pkgs.apacheHttpd}/bin/htpasswd -nbB "${name}" "$(head -n 2 ${value.passwordFile})")'')
+
cfg.radicale.users)}
+
EOF
+
'';
+
};
+
+
services.nginx = {
+
enable = true;
+
recommendedProxySettings = true;
+
virtualHosts = {
+
"cal.${domain}" = {
+
forceSSL = true;
+
enableACME = true;
+
locations."/" = { proxyPass = "http://localhost:5232"; };
+
};
+
};
+
};
+
+
eilean.dns.enable = true;
+
eilean.services.dns.zones.${domain}.records = [{
+
name = "cal";
+
type = "CNAME";
+
value = cfg.domainName;
+
}];
+
};
+
}
+22 -20
modules/services/dns/bind.nix
···
{ pkgs, config, lib, ... }:
-
let cfg = config.eilean.services.dns; in
-
lib.mkIf (cfg.enable && cfg.server == "bind") {
+
let cfg = config.eilean.services.dns;
+
in lib.mkIf (cfg.enable && cfg.server == "bind") {
services.bind = {
enable = true;
# recursive resolver
# cacheNetworks = [ "0.0.0.0/0" ];
-
zones =
-
let mapZones = zonename: zone:
-
{
-
master = true;
-
file = "${config.services.bind.directory}/${zonename}";
-
#file = "${import ./zonefile.nix { inherit pkgs config lib zonename zone; }}/${zonename}";
-
# axfr zone transfer
-
slaves = [
-
"127.0.0.1"
-
];
-
};
-
in builtins.mapAttrs mapZones cfg.zones;
+
zones = let
+
mapZones = zonename: zone: {
+
master = true;
+
file = "${config.services.bind.directory}/${zonename}";
+
#file = "${import ./zonefile.nix { inherit pkgs config lib zonename zone; }}/${zonename}";
+
# axfr zone transfer
+
slaves = [ "127.0.0.1" ];
+
};
+
in builtins.mapAttrs mapZones cfg.zones;
};
+
+
users.users = { named.extraGroups = [ config.services.opendkim.group ]; };
### bind prestart copy zonefiles
-
systemd.services.bind.preStart =
-
let ops =
-
let mapZones = zonename: zone:
+
systemd.services.bind.preStart = let
+
ops = let
+
mapZones = zonename: zone:
let
-
zonefile = "${import ./zonefile.nix { inherit pkgs config lib zonename zone; }}/${zonename}";
+
zonefile = "${
+
import ./zonefile.nix { inherit pkgs config lib zonename zone; }
+
}/${zonename}";
path = "${config.services.bind.directory}/${zonename}";
in ''
if ! diff ${zonefile} ${path} > /dev/null; then
cp ${zonefile} ${path}
+
cat ${config.mailserver.dkimKeyDirectory}/*.txt >> ${path}
# remove journal file to avoid 'journal out of sync with zone'
# NB this will reset dynamic updates
rm -f ${path}.signed.jnl
fi
'';
-
in lib.attrsets.mapAttrsToList mapZones cfg.zones;
-
in builtins.concatStringsSep "\n" ops;
+
in lib.attrsets.mapAttrsToList mapZones cfg.zones;
+
in builtins.concatStringsSep "\n" ops;
}
+15 -26
modules/services/dns/default.nix
···
default = "dns";
};
# TODO auto increment
-
serial = mkOption {
-
type = types.int;
-
};
+
serial = mkOption { type = types.int; };
refresh = mkOption {
type = types.int;
default = 3600; # 1hr
···
default = 3600; # 1hr
};
};
-
records =
-
let recordOpts.options = {
-
name = mkOption {
-
type = types.str;
-
};
+
records = let
+
recordOpts.options = {
+
name = mkOption { type = types.str; };
ttl = mkOption {
type = with types; nullOr int;
default = null;
};
-
type = mkOption {
-
type = types.str;
-
};
-
data = mkOption {
-
type = types.str;
-
};
+
type = mkOption { type = types.str; };
+
value = mkOption { type = types.str; };
};
-
in mkOption {
-
type = with types; listOf (submodule recordOpts);
-
default = [ ];
-
};
+
in mkOption {
+
type = with types; listOf (submodule recordOpts);
+
default = [ ];
+
};
};
-
in
-
{
-
imports = [ ./bind.nix ];
+
in {
+
imports = [ ./bind.nix ./eon.nix ];
options.eilean.services.dns = {
enable = mkEnableOption "DNS server";
server = mkOption {
-
type = types.enum [ "bind" ];
-
default = "bind";
+
type = types.enum [ "bind" "eon" ];
+
default = if config.eilean.acme-eon then "eon" else "bind";
};
openFirewall = mkOption {
type = types.bool;
default = true;
};
-
zones = mkOption {
-
type = with types; attrsOf (submodule zoneOptions);
-
};
+
zones = mkOption { type = with types; attrsOf (submodule zoneOptions); };
};
config.networking.firewall = mkIf config.eilean.services.dns.openFirewall {
+42
modules/services/dns/eon.nix
···
+
{ pkgs, config, lib, ... }:
+
+
let cfg = config.eilean.services.dns;
+
in lib.mkIf (cfg.enable && cfg.server == "eon") {
+
services.eon = {
+
enable = true;
+
application = "capd";
+
capnpAddress = lib.mkDefault config.networking.domain;
+
zoneFiles = let
+
mapZonefile = zonename: zone:
+
"${
+
import ./zonefile.nix { inherit pkgs config lib zonename zone; }
+
}/${zonename}";
+
in lib.attrsets.mapAttrsToList mapZonefile cfg.zones;
+
};
+
+
users.users = { eon.extraGroups = [ config.services.opendkim.group ]; };
+
+
### bind prestart copy zonefiles
+
systemd.services.eon.postStart = let
+
update = ''
+
update() {
+
local file="$1"
+
local domain="$2"
+
local input=$(tr -d '\n' < "$file")
+
local record_name=$(echo "$input" | ${pkgs.gawk}/bin/awk '{print $1}')
+
local record_type=$(echo "$input" | ${pkgs.gawk}/bin/awk '{print $3}')
+
local ttl=3600
+
local record_value=$(echo "$input" | ${pkgs.gnused}/bin/sed -E 's/[^"]*"([^"]*)"[^"]*/\1/g')
+
${config.services.eon.package}/bin/capc update /var/lib/eon/caps/domain/''${domain}.cap -u "add|''${record_name}.''${domain}|''${record_type}|''${record_value}|''${ttl}" || exit 0
+
}
+
shopt -s nullglob
+
'';
+
ops = let
+
mapZones = zonename: zone: ''
+
for f in ${config.mailserver.dkimKeyDirectory}/${zonename}.*.txt; do
+
update $f ${zonename}
+
done
+
'';
+
in lib.attrsets.mapAttrsToList mapZones cfg.zones;
+
in update + builtins.concatStringsSep "\n" ops;
+
}
+4 -12
modules/services/dns/zonefile.nix
···
-
{
-
pkgs,
-
config,
-
lib,
-
zonename,
-
zone,
-
...
-
}:
+
{ pkgs, config, lib, zonename, zone, ... }:
pkgs.writeTextFile {
name = "zonefile-${zonename}";
···
${builtins.toString zone.soa.expire}
${builtins.toString zone.soa.negativeCacheTtl}
)
-
${
-
lib.strings.concatStringsSep "\n"
-
(builtins.map (rr: "${rr.name} IN ${builtins.toString rr.ttl} ${rr.type} ${rr.data}") zone.records)
-
}
+
${lib.strings.concatStringsSep "\n" (builtins.map
+
(rr: "${rr.name} IN ${builtins.toString rr.ttl} ${rr.type} ${rr.value}")
+
zone.records)}
'';
}
+41 -32
modules/turn.nix
···
let
cfg = config.eilean;
domain = config.networking.domain;
+
subdomain = "turn.${domain}";
staticAuthSecretFile = "/run/coturn/static-auth-secret";
-
in
-
{
-
options.eilean.turn = {
-
enable = mkEnableOption "TURN server";
-
};
+
in {
+
options.eilean.turn = { enable = mkEnableOption "TURN server"; };
config = mkIf cfg.turn.enable {
-
services.coturn = rec {
+
security.acme-eon.certs."${subdomain}" = lib.mkIf cfg.acme-eon {
+
group = "turnserver";
+
reloadServices = [ "coturn" ];
+
};
+
+
services.coturn = let
+
certDir = if cfg.acme-eon then
+
config.security.acme-eon.certs.${subdomain}.directory
+
else
+
config.security.acme.certs.${subdomain}.directory;
+
in {
enable = true;
no-cli = true;
no-tcp-relay = true;
secure-stun = true;
use-auth-secret = true;
static-auth-secret-file = staticAuthSecretFile;
-
realm = "turn.${domain}";
-
relay-ips = with config.eilean; [
-
serverIpv4
-
serverIpv6
-
];
-
cert = "${config.security.acme.certs.${realm}.directory}/full.pem";
-
pkey = "${config.security.acme.certs.${realm}.directory}/key.pem";
+
realm = subdomain;
+
relay-ips = with config.eilean; [ serverIpv4 serverIpv6 ];
+
cert = "${certDir}/fullchain.pem";
+
pkey = "${certDir}/key.pem";
};
systemd.services = {
···
script = ''
if [ ! -f '${staticAuthSecretFile}' ]; then
umask 077
+
DIR="$(dirname '${staticAuthSecretFile}')"
+
mkdir -p "$DIR"
tr -dc A-Za-z0-9 </dev/urandom | head -c 32 > '${staticAuthSecretFile}'
-
chown ${config.systemd.services.coturn.serviceConfig.User}:${config.systemd.services.coturn.serviceConfig.Group} '${staticAuthSecretFile}'
+
chown -R ${config.systemd.services.coturn.serviceConfig.User}:${config.systemd.services.coturn.serviceConfig.Group} "$DIR"
fi
'';
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
};
"coturn" = {
-
after = [ "coturn-static-auth-secret-generator.service" ];
+
after = [ "coturn-static-auth-secret-generator.service" ]
+
++ lib.lists.optional cfg.acme-eon "acme-eon-${subdomain}.service";
requires = [ "coturn-static-auth-secret-generator.service" ];
+
wants = lib.lists.optional cfg.acme-eon "acme-eon-${subdomain}.service";
};
};
-
networking.firewall =
-
with config.services.coturn;
+
networking.firewall = with config.services.coturn;
let
turn-range = {
from = min-port;
···
allowedTCPPortRanges = [ turn-range ];
allowedUDPPorts = stun-ports;
allowedUDPPortRanges = [ turn-range ];
-
};
+
};
-
security.acme.certs.${config.services.coturn.realm} = {
-
postRun = "systemctl reload nginx.service; systemctl restart coturn.service";
-
group = "turnserver";
-
};
-
services.nginx.enable = true;
-
services.nginx.virtualHosts = {
+
security.acme.certs.${config.services.coturn.realm} =
+
lib.mkIf (!cfg.acme-eon) {
+
postRun =
+
"systemctl reload nginx.service; systemctl restart coturn.service";
+
group = "turnserver";
+
};
+
services.nginx.enable = lib.mkIf (!cfg.acme-eon) true;
+
services.nginx.virtualHosts = lib.mkIf (!cfg.acme-eon) {
"${config.services.coturn.realm}" = {
forceSSL = true;
enableACME = true;
};
};
-
users.groups."turnserver".members = [ config.services.nginx.user ];
+
users.groups."turnserver".members =
+
lib.mkIf (!cfg.acme-eon) [ config.services.nginx.user ];
eilean.dns.enable = true;
-
eilean.services.dns.zones.${config.networking.domain}.records = [
-
{
-
name = "turn";
-
type = "CNAME";
-
data = "vps";
-
}
-
];
+
eilean.services.dns.zones.${config.networking.domain}.records = [{
+
name = "turn";
+
type = "CNAME";
+
value = cfg.domainName;
+
}];
};
}
+40 -52
modules/wireguard/default.nix
···
{ pkgs, config, lib, ... }:
with lib;
-
let cfg = config.wireguard; in
-
{
+
let cfg = config.wireguard;
+
in {
options.wireguard = {
enable = mkEnableOption "wireguard";
server = mkOption {
type = with types; bool;
-
default =
-
if cfg.hosts ? config.networking.hostName then
-
cfg.hosts.${config.networking.hostName}.server
-
else false;
+
default = if cfg.hosts ? config.networking.hostName then
+
cfg.hosts.${config.networking.hostName}.server
+
else
+
false;
};
-
hosts =
-
let hostOps = { ... }: {
+
hosts = let
+
hostOps = { ... }: {
options = {
-
ip = mkOption {
-
type = types.str;
-
};
-
publicKey = mkOption {
-
type = types.str;
-
};
+
ip = mkOption { type = types.str; };
+
publicKey = mkOption { type = types.str; };
server = mkOption {
type = types.bool;
default = false;
···
};
};
};
-
in mkOption {
-
type = with types; attrsOf (submodule hostOps);
-
default = {};
-
};
+
in mkOption {
+
type = with types; attrsOf (submodule hostOps);
+
default = { };
+
};
};
config = mkIf cfg.enable {
···
networking = mkMerge [
{
# populate /etc/hosts with hostnames and IPs
-
extraHosts = builtins.concatStringsSep "\n" (
-
attrsets.mapAttrsToList (
-
hostName: values: "${values.ip} ${hostName}"
-
) cfg.hosts
-
);
+
extraHosts = builtins.concatStringsSep "\n" (attrsets.mapAttrsToList
+
(hostName: values: "${values.ip} ${hostName}") cfg.hosts);
firewall = {
allowedUDPPorts = [ 51820 ];
···
wireguard = {
enable = true;
-
interfaces.wg0 = let hostName = config.networking.hostName; in {
-
ips =
-
if cfg.hosts ? hostname then
-
[ "${cfg.hosts."${hostName}".ip}/24" ]
-
else [ ];
-
listenPort = 51820;
+
interfaces.wg0 = let hostName = config.networking.hostName;
+
in {
+
ips = if cfg.hosts ? hostname then
+
[ "${cfg.hosts."${hostName}".ip}/24" ]
+
else
+
[ ];
+
listenPort = 51820;
privateKeyFile = cfg.hosts."${hostName}".privateKeyFile;
-
peers =
-
let
-
serverPeers = attrsets.mapAttrsToList
-
(hostName: values:
-
if values.server then
-
{
-
allowedIPs = [ "10.0.0.0/24" ];
-
publicKey = values.publicKey;
-
endpoint = "${values.endpoint}:51820";
-
persistentKeepalive = values.persistentKeepalive;
-
}
-
else {})
-
cfg.hosts;
-
# remove empty elements
-
cleanedServerPeers = lists.remove { } serverPeers;
-
in mkIf (!cfg.server) cleanedServerPeers;
+
peers = let
+
serverPeers = attrsets.mapAttrsToList (hostName: values:
+
if values.server then {
+
allowedIPs = [ "10.0.0.0/24" ];
+
publicKey = values.publicKey;
+
endpoint = "${values.endpoint}:51820";
+
persistentKeepalive = values.persistentKeepalive;
+
} else
+
{ }) cfg.hosts;
+
# remove empty elements
+
cleanedServerPeers = lists.remove { } serverPeers;
+
in mkIf (!cfg.server) cleanedServerPeers;
};
};
}
···
# add clients
peers = with lib.attrsets;
-
mapAttrsToList (
-
hostName: values: {
-
allowedIPs = [ "${values.ip}/32" ];
-
publicKey = values.publicKey;
-
persistentKeepalive = values.persistentKeepalive;
-
}
-
) cfg.hosts;
+
mapAttrsToList (hostName: values: {
+
allowedIPs = [ "${values.ip}/32" ];
+
publicKey = values.publicKey;
+
persistentKeepalive = values.persistentKeepalive;
+
}) cfg.hosts;
};
})
];
+8 -5
pkgs/mautrix-meta.nix
···
{ lib, buildGoModule, fetchFromGitHub, olm }:
-
buildGoModule rec {
+
let version = "0.4.4";
+
in buildGoModule rec {
name = "mautrix-meta";
+
inherit version;
src = fetchFromGitHub {
owner = "mautrix";
repo = "meta";
-
rev = "7941e937055b792d2cbfde5d9c8c4df75e68ff0a";
-
hash = "sha256-QDqN6AAaEngWo4UxKAyIXB7BwCEJqsMTeuMb2fKu/9o=";
+
rev = "v${version}";
+
hash = "sha256-S8x3TGQEs+oh/3Q1Gz00M8dOcjjuHSgzVhqlbikZ8QE=";
};
buildInputs = [ olm ];
-
vendorHash = "sha256-ClHg3OEKgXYsmBm/aFKWZXbaLOmKdNyvw42QGhtTRik=";
+
vendorHash = "sha256-sUnvwPJQOoVzxbo2lS3CRcTrWsPjgYPsKClVw1wZJdM=";
doCheck = false;
···
meta = with lib; {
homepage = "https://github.com/mautrix/meta";
-
description = " A Matrix-Facebook Messenger and Instagram DM puppeting bridge.";
+
description =
+
" A Matrix-Facebook Messenger and Instagram DM puppeting bridge.";
license = licenses.agpl3Plus;
mainProgram = "mautrix-meta";
};
+4 -5
template/configuration.nix
···
{ pkgs, config, lib, ... }:
{
-
imports = [
-
./hardware-configuration.nix
-
];
+
imports = [ ./hardware-configuration.nix ];
boot.loader = {
systemd-boot.enable = true;
···
# TODO replace this with domain
networking.domain = "example.org";
-
security.acme.acceptTerms = true;
+
security.acme.acceptTerms = lib.mkIf (!config.eilean.acme-eon) true;
+
security.acme-eon.acceptTerms = lib.mkIf config.eilean.acme-eon true;
# TODO select internationalisation properties
i18n.defaultLocale = "en_GB.UTF-8";
···
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "23.05"; # Did you read the comment?
-
}
+
}
+19 -20
template/flake.nix
···
{
inputs = {
-
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
-
eilean.url ="github:RyanGibb/eilean-nix/main";
-
# replace the below line to manage the Nixpkgs instance yourself
-
nixpkgs.follows = "eilean/nixpkgs";
-
#eilean.inputs.nixpkgs.follows = "nixpkgs";
+
nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11";
+
eilean.url = "github:RyanGibb/eilean-nix/main";
+
eilean.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, eilean, ... }@inputs:
-
let hostname = "eilean"; in
-
rec {
-
nixosConfigurations.${hostname} = nixpkgs.lib.nixosSystem {
+
let hostname = "eilean";
+
in rec {
+
nixosConfigurations.${hostname} = nixpkgs.lib.nixosSystem {
system = null;
pkgs = null;
modules = [
-
./configuration.nix
-
eilean.nixosModules.default
-
{
-
networking.hostName = hostname;
-
# pin nix command's nixpkgs flake to the system flake to avoid unnecessary downloads
-
nix.registry.nixpkgs.flake = nixpkgs;
-
# record git revision (can be queried with `nixos-version --json)
-
system.configurationRevision = nixpkgs.lib.mkIf (self ? rev) self.rev;
-
}
-
];
-
};
+
./configuration.nix
+
eilean.nixosModules.default
+
{
+
networking.hostName = hostname;
+
# pin nix command's nixpkgs flake to the system flake to avoid unnecessary downloads
+
nix.registry.nixpkgs.flake = nixpkgs;
+
# record git revision (can be queried with `nixos-version --json)
+
system.configurationRevision =
+
nixpkgs.lib.mkIf (self ? rev) self.rev;
+
}
+
];
};
-
}
+
};
+
}