feat: stalwart mail-server

This commit is contained in:
danny 2025-09-17 15:24:07 +08:00
parent 85feeb7b3f
commit a565033341
16 changed files with 1192 additions and 442 deletions

View file

@ -8,6 +8,7 @@ creation_rules:
key_groups: key_groups:
- age: - age:
- *dn_server - *dn_server
- *dn_pre7780
- path_regex: system/dev/dn-pre7780/secret.yaml - path_regex: system/dev/dn-pre7780/secret.yaml
key_groups: key_groups:
- age: - age:

113
flake.lock generated
View file

@ -491,6 +491,24 @@
} }
}, },
"flake-parts_4": { "flake-parts_4": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
},
"locked": {
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_5": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": [
"nvf", "nvf",
@ -511,7 +529,7 @@
"type": "github" "type": "github"
} }
}, },
"flake-parts_5": { "flake-parts_6": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": [
"stylix", "stylix",
@ -532,6 +550,21 @@
"type": "github" "type": "github"
} }
}, },
"flake-root": {
"locked": {
"lastModified": 1723604017,
"narHash": "sha256-rBtQ8gg+Dn4Sx/s+pvjdq3CB2wQNzx9XGFq/JVGCB6k=",
"owner": "srid",
"repo": "flake-root",
"rev": "b759a56851e10cb13f6b8e5698af7b59c44be26e",
"type": "github"
},
"original": {
"owner": "srid",
"repo": "flake-root",
"type": "github"
}
},
"flake-schemas": { "flake-schemas": {
"locked": { "locked": {
"lastModified": 1721999734, "lastModified": 1721999734,
@ -1405,6 +1438,22 @@
"type": "github" "type": "github"
} }
}, },
"marks-nvim": {
"flake": false,
"locked": {
"lastModified": 1747179163,
"narHash": "sha256-ho2b2Ulh+GTqY0QvW7zjFOSlF5g/kaxWyOjKWhTFq7c=",
"owner": "chentoast",
"repo": "marks.nvim",
"rev": "f353e8c08c50f39e99a9ed474172df7eddd89b72",
"type": "github"
},
"original": {
"owner": "chentoast",
"repo": "marks.nvim",
"type": "github"
}
},
"microvm": { "microvm": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_4", "flake-utils": "flake-utils_4",
@ -1563,6 +1612,29 @@
"type": "github" "type": "github"
} }
}, },
"nixd": {
"inputs": {
"flake-parts": "flake-parts_4",
"flake-root": "flake-root",
"nixpkgs": [
"nixpkgs"
],
"treefmt-nix": "treefmt-nix_2"
},
"locked": {
"lastModified": 1756563652,
"narHash": "sha256-0MvTa6l071JAbePgP3qTkNXr1CbeGDmqyDyvVHxetqg=",
"owner": "nix-community",
"repo": "nixd",
"rev": "15a3376f65de9e7984429b975777f3569430b8a6",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixd",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1755027561, "lastModified": 1755027561,
@ -1594,6 +1666,18 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-lib_2": {
"locked": {
"lastModified": 1733096140,
"narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz"
}
},
"nixpkgs-stable": { "nixpkgs-stable": {
"locked": { "locked": {
"lastModified": 1730741070, "lastModified": 1730741070,
@ -1795,7 +1879,7 @@
"nvf": { "nvf": {
"inputs": { "inputs": {
"flake-compat": "flake-compat_8", "flake-compat": "flake-compat_8",
"flake-parts": "flake-parts_4", "flake-parts": "flake-parts_5",
"mnw": "mnw", "mnw": "mnw",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
@ -1903,11 +1987,13 @@
"hyprlock": "hyprlock", "hyprlock": "hyprlock",
"hyprtasking": "hyprtasking", "hyprtasking": "hyprtasking",
"lanzaboote": "lanzaboote", "lanzaboote": "lanzaboote",
"marks-nvim": "marks-nvim",
"microvm": "microvm", "microvm": "microvm",
"neovim-nightly-overlay": "neovim-nightly-overlay", "neovim-nightly-overlay": "neovim-nightly-overlay",
"nix-index-database": "nix-index-database", "nix-index-database": "nix-index-database",
"nix-minecraft": "nix-minecraft", "nix-minecraft": "nix-minecraft",
"nix-tmodloader": "nix-tmodloader", "nix-tmodloader": "nix-tmodloader",
"nixd": "nixd",
"nixpkgs": "nixpkgs_7", "nixpkgs": "nixpkgs_7",
"nvf": "nvf", "nvf": "nvf",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
@ -2042,7 +2128,7 @@
"base16-helix": "base16-helix", "base16-helix": "base16-helix",
"base16-vim": "base16-vim", "base16-vim": "base16-vim",
"firefox-gnome-theme": "firefox-gnome-theme", "firefox-gnome-theme": "firefox-gnome-theme",
"flake-parts": "flake-parts_5", "flake-parts": "flake-parts_6",
"gnome-shell": "gnome-shell", "gnome-shell": "gnome-shell",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
@ -2371,6 +2457,27 @@
"type": "github" "type": "github"
} }
}, },
"treefmt-nix_2": {
"inputs": {
"nixpkgs": [
"nixd",
"nixpkgs"
]
},
"locked": {
"lastModified": 1734704479,
"narHash": "sha256-MMi74+WckoyEWBRcg/oaGRvXC9BVVxDZNRMpL+72wBI=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "65712f5af67234dad91a5a4baee986a8b62dbf8f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
},
"xdph": { "xdph": {
"inputs": { "inputs": {
"hyprland-protocols": [ "hyprland-protocols": [

603
flake.nix
View file

@ -117,307 +117,317 @@
url = "github:NotAShelf/nvf"; url = "github:NotAShelf/nvf";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nixd = {
url = "github:nix-community/nixd";
inputs.nixpkgs.follows = "nixpkgs";
};
marks-nvim = {
url = "github:chentoast/marks.nvim";
flake = false;
};
}; };
outputs = { outputs =
self, {
nixpkgs, self,
nix-index-database, nixpkgs,
lanzaboote, nix-index-database,
home-manager, lanzaboote,
... home-manager,
} @ inputs: let ...
system = "x86_64-linux"; }@inputs:
nix-version = "25.05"; let
system = "x86_64-linux";
nix-version = "25.05";
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
};
inherit (pkgs) lib;
helper = import ./helper {inherit pkgs lib;};
# Declare COMMON modules here
common-settings = {
modules = [
home-manager.nixosModules.default
nix-index-database.nixosModules.nix-index
inputs.sops-nix.nixosModules.sops
inputs.chaotic.nixosModules.default
inputs.actual-budget-api.nixosModules.default
inputs.stylix.nixosModules.stylix
];
args = {
inherit
helper
inputs
system
nix-version
self
;
};
};
# Declaring All Devices
devices = {
# Home Computer
dn-pre7780 = {
hostname = "dn-pre7780";
domain = "net.dn";
username = "danny";
extra-modules = [
lanzaboote.nixosModules.lanzaboote
./system/dev/dn-pre7780
# VM
inputs.microvm.nixosModules.host
{
networking.useNetworkd = true;
systemd.network.enable = true;
systemd.network.networks."10-lan" = {
matchConfig.Name = [
"enp0s31f6"
"vm-*"
];
networkConfig = {
Bridge = "br0";
};
};
systemd.network.netdevs."br0" = {
netdevConfig = {
Name = "br0";
Kind = "bridge";
};
};
systemd.network.networks."10-lan-bridge" = {
matchConfig.Name = "br0";
networkConfig = {
Address = ["192.168.0.5/24"];
Gateway = "192.168.0.1";
DNS = ["192.168.0.1"];
};
linkConfig.RequiredForOnline = "routable";
};
microvm.vms = {
vm-1 = {
flake = self;
updateFlake = "git+file:///etc/nixos";
autostart = false;
};
vm-2 = {
flake = self;
updateFlake = "git+file:///etc/nixos";
autostart = false;
};
};
}
];
overlays = [];
}; };
# Laptop inherit (pkgs) lib;
dn-lap = {
hostname = "dn-lap"; helper = import ./helper { inherit pkgs lib; };
username = "danny";
domain = "net.dn"; # Declare COMMON modules here
extra-modules = [ common-settings = {
lanzaboote.nixosModules.lanzaboote modules = [
./system/dev/dn-lap home-manager.nixosModules.default
]; nix-index-database.nixosModules.nix-index
overlays = [ inputs.sops-nix.nixosModules.sops
inputs.chaotic.nixosModules.default
inputs.actual-budget-api.nixosModules.default
inputs.stylix.nixosModules.stylix
]; ];
args = {
inherit
helper
inputs
system
nix-version
self
;
};
}; };
# Server # Declaring All Devices
dn-server = { devices = {
hostname = "dn-server"; # Home Computer
username = "danny"; dn-pre7780 = {
domain = "net.dn"; hostname = "dn-pre7780";
extra-modules = [ domain = "net.dn";
inputs.nix-minecraft.nixosModules.minecraft-servers username = "danny";
inputs.nix-tmodloader.nixosModules.tmodloader extra-modules = [
./system/dev/dn-server lanzaboote.nixosModules.lanzaboote
./pkgs/options/dovecot.nix ./system/dev/dn-pre7780
];
overlays = [ # VM
inputs.nix-minecraft.overlay inputs.microvm.nixosModules.host
inputs.nix-tmodloader.overlay {
(import ./pkgs/overlays/dovecot.nix) networking.useNetworkd = true;
]; systemd.network.enable = true;
systemd.network.networks."10-lan" = {
matchConfig.Name = [
"enp0s31f6"
"vm-*"
];
networkConfig = {
Bridge = "br0";
};
};
systemd.network.netdevs."br0" = {
netdevConfig = {
Name = "br0";
Kind = "bridge";
};
};
systemd.network.networks."10-lan-bridge" = {
matchConfig.Name = "br0";
networkConfig = {
Address = [ "192.168.0.5/24" ];
Gateway = "192.168.0.1";
DNS = [ "192.168.0.1" ];
};
linkConfig.RequiredForOnline = "routable";
};
microvm.vms = {
vm-1 = {
flake = self;
updateFlake = "git+file:///etc/nixos";
autostart = false;
};
vm-2 = {
flake = self;
updateFlake = "git+file:///etc/nixos";
autostart = false;
};
};
}
];
overlays = [ ];
};
# Laptop
dn-lap = {
hostname = "dn-lap";
username = "danny";
domain = "net.dn";
extra-modules = [
lanzaboote.nixosModules.lanzaboote
./system/dev/dn-lap
];
overlays = [
];
};
# Server
dn-server = {
hostname = "dn-server";
username = "danny";
domain = "net.dn";
extra-modules = [
inputs.nix-minecraft.nixosModules.minecraft-servers
inputs.nix-tmodloader.nixosModules.tmodloader
./system/dev/dn-server
./pkgs/options/dovecot.nix
];
overlays = [
inputs.nix-minecraft.overlay
inputs.nix-tmodloader.overlay
(import ./pkgs/overlays/dovecot.nix)
];
};
}; };
}; in
in { {
nixosConfigurations = nixosConfigurations =
(builtins.mapAttrs ( (builtins.mapAttrs (
dev: conf: let dev: conf:
domain = let
if conf.domain != null domain = if conf.domain != null then conf.domain else "local";
then conf.domain
else "local";
inherit (conf) username hostname; inherit (conf) username hostname;
in in
nixpkgs.lib.nixosSystem { nixpkgs.lib.nixosSystem {
modules = modules = [
[ {
{ system.stateVersion = nix-version;
system.stateVersion = nix-version; home-manager = {
home-manager = { backupFileExtension = "backup-hm";
backupFileExtension = "backup-hm"; useUserPackages = true;
useUserPackages = true; useGlobalPkgs = true;
useGlobalPkgs = true; extraSpecialArgs = {
extraSpecialArgs = { inherit
inherit helper
helper inputs
inputs system
system nix-version
nix-version devices
devices username
username ;
; };
}; users."${username}" = lib.mkIf (!((conf ? isVM) && (conf.isVM))) {
users."${username}" = lib.mkIf (!((conf ? isVM) && (conf.isVM))) { imports = [
imports = [ inputs.hyprland.homeManagerModules.default
inputs.hyprland.homeManagerModules.default inputs.caelestia-shell.homeManagerModules.default
inputs.caelestia-shell.homeManagerModules.default inputs.zen-browser.homeManagerModules.${system}.default
inputs.zen-browser.homeManagerModules.${system}.default inputs.nvf.homeManagerModules.default
inputs.nvf.homeManagerModules.default {
{ home = {
home = { homeDirectory = "/home/${username}";
homeDirectory = "/home/${username}"; stateVersion = nix-version;
stateVersion = nix-version; };
}; # Let Home Manager install and manage itself.
# Let Home Manager install and manage itself. programs.home-manager.enable = true;
programs.home-manager.enable = true; }
} ];
]; };
}; };
}; networking = {
networking = { inherit domain;
inherit domain; hostName = hostname;
hostName = hostname; };
}; nixpkgs.hostPlatform = system;
nixpkgs.hostPlatform = system; nixpkgs.config.allowUnfree = true;
nixpkgs.config.allowUnfree = true; nixpkgs.overlays = (import ./pkgs/overlays) ++ conf.overlays;
nixpkgs.overlays = (import ./pkgs/overlays) ++ conf.overlays; }
} ]
] ++ common-settings.modules
++ common-settings.modules ++ conf.extra-modules;
++ conf.extra-modules; specialArgs = {
specialArgs = inherit username;
{
inherit username;
}
// common-settings.args;
} }
) // common-settings.args;
devices) }
// ) devices)
# VM For k8s //
( # VM For k8s
let (
vmList = let let
kubeMasterIP = "192.168.0.6"; vmList =
kubeMasterHostname = "api.kube"; let
kubeMasterAPIServerPort = 6443; kubeMasterIP = "192.168.0.6";
kubeApi = "https://${kubeMasterHostname}:${toString kubeMasterAPIServerPort}"; kubeMasterHostname = "api.kube";
in { kubeMasterAPIServerPort = 6443;
# master kubeApi = "https://${kubeMasterHostname}:${toString kubeMasterAPIServerPort}";
vm-1 = { in
ip = "192.168.0.6"; {
mac = "02:00:00:00:00:01"; # master
extraConfig = { vm-1 = {
networking.extraHosts = "${kubeMasterIP} ${kubeMasterHostname}"; ip = "192.168.0.6";
environment.systemPackages = with pkgs; [ mac = "02:00:00:00:00:01";
kompose extraConfig = {
kubectl networking.extraHosts = "${kubeMasterIP} ${kubeMasterHostname}";
kubernetes environment.systemPackages = with pkgs; [
]; kompose
kubectl
kubernetes
];
services.kubernetes = { services.kubernetes = {
roles = [ roles = [
"master" "master"
"node" "node"
]; ];
masterAddress = kubeMasterHostname; masterAddress = kubeMasterHostname;
apiserverAddress = kubeApi; apiserverAddress = kubeApi;
easyCerts = true; easyCerts = true;
apiserver = { apiserver = {
securePort = kubeMasterAPIServerPort; securePort = kubeMasterAPIServerPort;
advertiseAddress = kubeMasterIP; advertiseAddress = kubeMasterIP;
};
addons.dns.enable = true;
};
systemd.services.link-kube-config = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.writeShellScript "link-kube-config.sh" ''
target="/etc/kubernetes/cluster-admin.kubeconfig"
if [ -e "$target" ]; then
[ ! -d "/root/.kube" ] && mkdir -p "/root/.kube"
ln -sf $target /root/.kube/config
fi
''}";
};
};
};
}; };
# Node
vm-2 = {
ip = "192.168.0.7";
mac = "02:00:00:00:00:02";
extraConfig = {
networking.extraHosts = "${kubeMasterIP} ${kubeMasterHostname}";
addons.dns.enable = true; environment.systemPackages = with pkgs; [
}; kompose
kubectl
kubernetes
];
systemd.services.link-kube-config = { services.kubernetes = {
wantedBy = ["multi-user.target"]; roles = [ "node" ];
serviceConfig = { masterAddress = kubeMasterHostname;
Type = "oneshot"; easyCerts = true;
ExecStart = "${pkgs.writeShellScript "link-kube-config.sh" ''
target="/etc/kubernetes/cluster-admin.kubeconfig" kubelet.kubeconfig.server = kubeApi;
if [ -e "$target" ]; then apiserverAddress = kubeApi;
[ ! -d "/root/.kube" ] && mkdir -p "/root/.kube" addons.dns.enable = true;
ln -sf $target /root/.kube/config };
fi };
''}";
}; };
}; };
};
};
# Node
vm-2 = {
ip = "192.168.0.7";
mac = "02:00:00:00:00:02";
extraConfig = {
networking.extraHosts = "${kubeMasterIP} ${kubeMasterHostname}";
environment.systemPackages = with pkgs; [ mkMicrovm = name: value: {
kompose hypervisor = "qemu";
kubectl vcpu = 4;
kubernetes mem = 8192;
interfaces = [
{
type = "tap";
id = "${name}";
mac = value.mac;
}
];
shares = [
{
tag = "ro-store";
source = "/nix/store";
mountPoint = "/nix/.ro-store";
}
]; ];
services.kubernetes = {
roles = ["node"];
masterAddress = kubeMasterHostname;
easyCerts = true;
kubelet.kubeconfig.server = kubeApi;
apiserverAddress = kubeApi;
addons.dns.enable = true;
};
}; };
}; in
}; lib.mapAttrs' (
name: value:
mkMicrovm = name: value: {
hypervisor = "qemu";
vcpu = 4;
mem = 8192;
interfaces = [
{
type = "tap";
id = "${name}";
mac = value.mac;
}
];
shares = [
{
tag = "ro-store";
source = "/nix/store";
mountPoint = "/nix/.ro-store";
}
];
};
in
lib.mapAttrs' (
name: value:
lib.nameValuePair name ( lib.nameValuePair name (
nixpkgs.lib.nixosSystem { nixpkgs.lib.nixosSystem {
inherit system; inherit system;
@ -448,15 +458,15 @@
systemd.network.networks."20-lan" = { systemd.network.networks."20-lan" = {
matchConfig.Type = "ether"; matchConfig.Type = "ether";
networkConfig = { networkConfig = {
Address = ["${value.ip}/24"]; Address = [ "${value.ip}/24" ];
Gateway = "192.168.0.1"; Gateway = "192.168.0.1";
DNS = ["192.168.0.1"]; DNS = [ "192.168.0.1" ];
DHCP = "no"; DHCP = "no";
}; };
}; };
systemd.services.br-netfilter = { systemd.services.br-netfilter = {
wantedBy = ["multi-user.target"]; wantedBy = [ "multi-user.target" ];
serviceConfig = { serviceConfig = {
ExecStart = "/run/current-system/sw/bin/modprobe br_netfilter"; ExecStart = "/run/current-system/sw/bin/modprobe br_netfilter";
}; };
@ -479,23 +489,22 @@
]; ];
} }
) )
) vmList
) )
vmList // {
) vps = nixpkgs.lib.nixosSystem {
// { inherit system;
vps = nixpkgs.lib.nixosSystem { specialArgs = common-settings.args;
inherit system; modules = [
specialArgs = common-settings.args; inputs.disko.nixosModules.disko
modules = [ ./system/dev/generic
inputs.disko.nixosModules.disko ];
./system/dev/generic };
];
}; };
};
packages."${system}" = { packages."${system}" = {
vm-1 = self.nixosConfigurations.vm-1.config.microvm.declaredRunner; vm-1 = self.nixosConfigurations.vm-1.config.microvm.declaredRunner;
vm-2 = self.nixosConfigurations.vm-2.config.microvm.declaredRunner; vm-2 = self.nixosConfigurations.vm-2.config.microvm.declaredRunner;
};
}; };
};
} }

View file

@ -2,8 +2,11 @@
pkgs, pkgs,
lib, lib,
osConfig, osConfig,
inputs,
system,
... ...
}: let }:
let
inherit (lib.generators) mkLuaInline; inherit (lib.generators) mkLuaInline;
suda-nvim = pkgs.vimUtils.buildVimPlugin { suda-nvim = pkgs.vimUtils.buildVimPlugin {
@ -15,7 +18,13 @@
hash = "sha256-46sy3rAdOCULVt1RkIoGdweoV3MqQaB33Et9MrxI6Lk="; hash = "sha256-46sy3rAdOCULVt1RkIoGdweoV3MqQaB33Et9MrxI6Lk=";
}; };
}; };
in {
marks-nvim = pkgs.vimUtils.buildVimPlugin {
name = "marks-nvim";
src = inputs.marks-nvim;
};
in
{
programs.nvf = { programs.nvf = {
enable = true; enable = true;
settings = { settings = {
@ -69,6 +78,12 @@ in {
suda = { suda = {
package = suda-nvim; package = suda-nvim;
}; };
marks = {
package = marks-nvim;
setup = ''
require("marks").setup {}
'';
};
}; };
keymaps = [ keymaps = [
@ -76,16 +91,16 @@ in {
# Explorer # Explorer
{ {
key = "<leader>e"; key = "<leader>e";
mode = ["n"]; mode = [ "n" ];
action = ":Neotree toggle<CR>"; action = ":Neotree toggle<CR>";
silent = true; silent = true;
desc = "Toggle file explorer"; desc = "Toggle file explorer";
} }
# Fzf lua # === Fzf lua === #
{ {
key = "<Leader><Space>"; key = "<Leader><Space>";
silent = true; silent = true;
mode = ["n"]; mode = [ "n" ];
action = ":FzfLua files<CR>"; action = ":FzfLua files<CR>";
nowait = true; nowait = true;
unique = true; unique = true;
@ -93,7 +108,7 @@ in {
} }
{ {
key = "<Leader>/"; key = "<Leader>/";
mode = ["n"]; mode = [ "n" ];
action = ":FzfLua live_grep<CR>"; action = ":FzfLua live_grep<CR>";
nowait = true; nowait = true;
unique = true; unique = true;
@ -103,7 +118,7 @@ in {
{ {
key = "<Leader>ss"; key = "<Leader>ss";
silent = true; silent = true;
mode = ["n"]; mode = [ "n" ];
action = ":FzfLua lsp_document_symbols<CR>"; action = ":FzfLua lsp_document_symbols<CR>";
nowait = true; nowait = true;
unique = true; unique = true;
@ -113,23 +128,37 @@ in {
{ {
key = "<Leader>sS"; key = "<Leader>sS";
silent = true; silent = true;
mode = ["n"]; mode = [ "n" ];
action = ":FzfLua lsp_workspace_symbols<CR>"; action = ":FzfLua lsp_workspace_symbols<CR>";
unique = true; unique = true;
nowait = true; nowait = true;
desc = "Find symbols (workspace)"; desc = "Find symbols (workspace)";
} }
# Registers
{
key = ''""'';
mode = [ "n" ];
action = ":FzfLua registers<CR>";
desc = "Registers";
}
# Marks
{
key = "''";
mode = [ "n" ];
action = ":FzfLua marks<CR>";
desc = "Marks";
}
# === Buffer === # # === Buffer === #
{ {
key = "<Leader>bo"; key = "<Leader>bo";
mode = ["n"]; mode = [ "n" ];
action = ":BufferLineCloseOther<CR>"; action = ":BufferLineCloseOther<CR>";
desc = "Close other buffer"; desc = "Close other buffer";
} }
{ {
key = "<Leader>bS"; key = "<Leader>bS";
mode = ["n"]; mode = [ "n" ];
action = ":SudaWrite<CR>"; action = ":SudaWrite<CR>";
desc = "Save file as root"; desc = "Save file as root";
} }
@ -148,48 +177,48 @@ in {
} }
{ {
key = "<S-Tab>"; key = "<S-Tab>";
mode = ["i"]; mode = [ "i" ];
action = "<C-d>"; action = "<C-d>";
desc = "Shift left"; desc = "Shift left";
} }
{ {
key = "gd"; key = "gd";
mode = ["n"]; mode = [ "n" ];
action = ":FzfLua lsp_definitions<CR>"; action = ":FzfLua lsp_definitions<CR>";
nowait = true; nowait = true;
desc = "Go to definition"; desc = "Go to definition";
} }
{ {
key = "gD"; key = "gD";
mode = ["n"]; mode = [ "n" ];
action = ":FzfLua lsp_declarations<CR>"; action = ":FzfLua lsp_declarations<CR>";
nowait = true; nowait = true;
desc = "Go to declaration"; desc = "Go to declaration";
} }
{ {
key = "gi"; key = "gi";
mode = ["n"]; mode = [ "n" ];
action = ":FzfLua lsp_implementations<CR>"; action = ":FzfLua lsp_implementations<CR>";
nowait = true; nowait = true;
desc = "Go to implementation"; desc = "Go to implementation";
} }
{ {
key = "gr"; key = "gr";
mode = ["n"]; mode = [ "n" ];
action = ":FzfLua lsp_references<CR>"; action = ":FzfLua lsp_references<CR>";
nowait = true; nowait = true;
desc = "List references"; desc = "List references";
} }
{ {
key = "<Leader>n"; key = "<Leader>n";
mode = ["n"]; mode = [ "n" ];
action = ":NoiceAll<CR>"; action = ":NoiceAll<CR>";
nowait = true; nowait = true;
desc = "Notifications"; desc = "Notifications";
} }
{ {
key = "<ESC><ESC>"; key = "<ESC><ESC>";
mode = ["n"]; mode = [ "n" ];
action = ":noh<CR>"; action = ":noh<CR>";
desc = "Clear highlight"; desc = "Clear highlight";
} }
@ -197,14 +226,14 @@ in {
# === Tab === # # === Tab === #
{ {
key = ">"; key = ">";
mode = ["v"]; mode = [ "v" ];
action = ">gv"; action = ">gv";
silent = true; silent = true;
desc = "Shift right"; desc = "Shift right";
} }
{ {
key = "<"; key = "<";
mode = ["v"]; mode = [ "v" ];
action = "<gv"; action = "<gv";
silent = true; silent = true;
desc = "Shift left"; desc = "Shift left";
@ -213,22 +242,22 @@ in {
# === Terminal === # # === Terminal === #
{ {
key = "<C-/>"; key = "<C-/>";
mode = ["t"]; mode = [ "t" ];
action = "<C-\\><C-n>:ToggleTerm<CR>"; action = "<C-\\><C-n>:ToggleTerm<CR>";
} }
{ {
key = "<C-_>"; key = "<C-_>";
mode = ["t"]; mode = [ "t" ];
action = "<C-\\><C-n>:ToggleTerm<CR>"; action = "<C-\\><C-n>:ToggleTerm<CR>";
} }
{ {
key = "<C-_>"; key = "<C-_>";
mode = ["n"]; mode = [ "n" ];
action = ":ToggleTerm<CR>"; action = ":ToggleTerm<CR>";
} }
{ {
key = "<ESC><ESC>"; key = "<ESC><ESC>";
mode = ["t"]; mode = [ "t" ];
action = "<C-\\><C-n>"; action = "<C-\\><C-n>";
} }
{ {
@ -270,7 +299,7 @@ in {
# New Term # New Term
{ {
key = "<Leader>tn"; key = "<Leader>tn";
mode = ["n"]; mode = [ "n" ];
action = ":TermNew<CR>"; action = ":TermNew<CR>";
nowait = true; nowait = true;
desc = "Spawn new terminal"; desc = "Spawn new terminal";
@ -278,7 +307,7 @@ in {
# Select Term # Select Term
{ {
key = "<Leader>tt"; key = "<Leader>tt";
mode = ["n"]; mode = [ "n" ];
action = ":TermSelect<CR>"; action = ":TermSelect<CR>";
nowait = true; nowait = true;
desc = "Select terminal"; desc = "Select terminal";
@ -286,7 +315,7 @@ in {
# Send current selection to Term # Send current selection to Term
{ {
key = "<Leader>ts"; key = "<Leader>ts";
mode = ["v"]; mode = [ "v" ];
action = ":ToggleTermSendVisualSelection<CR>"; action = ":ToggleTermSendVisualSelection<CR>";
nowait = true; nowait = true;
desc = "Send current selection to terminal"; desc = "Send current selection to terminal";
@ -295,7 +324,7 @@ in {
# === Fold (nvim-ufo) === # # === Fold (nvim-ufo) === #
{ {
key = "zR"; key = "zR";
mode = ["n"]; mode = [ "n" ];
action = '' action = ''
require("ufo").openAllFolds require("ufo").openAllFolds
''; '';
@ -303,7 +332,7 @@ in {
} }
{ {
key = "zM"; key = "zM";
mode = ["n"]; mode = [ "n" ];
action = '' action = ''
require("ufo").closeAllFolds require("ufo").closeAllFolds
''; '';
@ -313,29 +342,29 @@ in {
autocmds = [ autocmds = [
{ {
event = ["TextYankPost"]; event = [ "TextYankPost" ];
callback = callback =
mkLuaInline mkLuaInline
# lua # lua
'' ''
function() function()
vim.highlight.on_yank({ higroup = "IncSearch", timeout = 150 }) vim.highlight.on_yank({ higroup = "IncSearch", timeout = 150 })
end end
''; '';
desc = "Highlight yanked"; desc = "Highlight yanked";
} }
{ {
event = ["BufWritePost"]; event = [ "BufWritePost" ];
callback = callback =
mkLuaInline mkLuaInline
# lua # lua
'' ''
function(args) function(args)
local bufname = vim.api.nvim_buf_get_name(args.buf) local bufname = vim.api.nvim_buf_get_name(args.buf)
local info = string.format("Saved %s", vim.fn.fnamemodify(bufname, ":t")) local info = string.format("Saved %s", vim.fn.fnamemodify(bufname, ":t"))
require("fidget").notify(info, vim.log.levels.INFO) require("fidget").notify(info, vim.log.levels.INFO)
end end
''; '';
desc = "Fidget notify file saved"; desc = "Fidget notify file saved";
} }
]; ];
@ -391,12 +420,12 @@ in {
}; };
virtual_text.format = virtual_text.format =
mkLuaInline mkLuaInline
# lua # lua
'' ''
function(diagnostic) function(diagnostic)
return string.format("%s (%s)", diagnostic.message, diagnostic.source) return string.format("%s (%s)", diagnostic.message, diagnostic.source)
end end
''; '';
}; };
}; };
@ -427,17 +456,21 @@ in {
}; };
nix = { nix = {
enable = true; enable = true;
extraDiagnostics.enable = false;
format.type = "nixfmt"; format.type = "nixfmt";
lsp = { lsp = {
server = "nixd"; server = "nil";
options = { options = {
nixos.expr = nix.flake.autoArchive = true;
# nix
''(builtins.getFlake (builtins.toString ./.)).nixosConfigurations.${osConfig.networking.hostName}.options'';
home_manager.expr =
# nix
''(builtins.getFlake (builtins.toString ./.)).nixosConfigurations.${osConfig.networking.hostName}.options.home-manager.users.type.getSubOptions []'';
}; };
# options = {
# nixos.expr =
# # nix
# ''(builtins.getFlake (builtins.toString ./.)).nixosConfigurations.${osConfig.networking.hostName}.options'';
# home_manager.expr =
# # nix
# ''(builtins.getFlake (builtins.toString ./.)).nixosConfigurations.${osConfig.networking.hostName}.options.home-manager.users.type.getSubOptions []'';
# };
}; };
}; };
sql.enable = true; sql.enable = true;
@ -450,7 +483,12 @@ in {
}; };
}; };
python.enable = true; python.enable = true;
markdown.enable = true; markdown = {
enable = true;
extensions = {
render-markdown-nvim.enable = true;
};
};
html.enable = true; html.enable = true;
lua.enable = true; lua.enable = true;
}; };
@ -667,7 +705,7 @@ in {
snippets.luasnip = { snippets.luasnip = {
enable = true; enable = true;
providers = ["blink-cmp"]; providers = [ "blink-cmp" ];
setupOpts.enable_autosnippets = true; setupOpts.enable_autosnippets = true;
}; };
@ -716,7 +754,85 @@ in {
whichKey.enable = true; whichKey.enable = true;
}; };
fzf-lua.enable = true; fzf-lua = {
enable = true;
setupOpts = {
previewers = {
builtin = {
extensions = {
"jpg" = {
"kitty" = "";
};
};
snacks_image = {
enabled = false;
render_inline = false;
};
};
};
winopts = {
preview = {
hidden = "hidden";
};
border = "rounded";
};
fzf_opts = {
"--no-header" = "";
"--no-scrollbar" = "";
};
files = {
formatter = "path.filename_first";
prompt = ":";
no_header = true;
cwd_header = false;
cwd_prompt = false;
winopts = {
title = " files 📑 ";
title_pos = "center";
title_flags = false;
};
};
buffers = {
formatter = "path.filename_first";
prompt = ":";
no_header = true;
fzf_opts = {
"--delimiter" = " ";
"--with-nth" = "-1..";
};
winopts = {
title = " buffers 📝 ";
title_pos = "center";
};
};
lsp = {
symbols = {
cwd_only = true;
no_header = true;
prompt = ":";
winopts = {
title = " symbols ";
title_pos = "center";
height = 0.6;
preview = {
hidden = "nohidden";
horizontal = "down:40%";
wrap = "wrap";
};
};
};
};
registers = {
prompt = "registers:";
filter = "%a";
winopts = {
title = " registers 🏷 ";
title_pos = "center";
};
};
};
};
dashboard = { dashboard = {
alpha.enable = true; alpha.enable = true;
@ -745,7 +861,12 @@ in {
}; };
images = { images = {
img-clip.enable = true; image-nvim = {
enable = true;
setupOpts = {
backend = "kitty";
};
};
}; };
}; };
@ -762,12 +883,12 @@ in {
}; };
setupOpts.winbar.name_formatter = setupOpts.winbar.name_formatter =
mkLuaInline mkLuaInline
# lua # lua
'' ''
function(term) function(term)
return " " .. term.id return " " .. term.id
end end
''; '';
}; };
}; };
@ -782,7 +903,7 @@ in {
event = "notify"; event = "notify";
kind = "info"; kind = "info";
any = [ any = [
{find = "hidden";} { find = "hidden"; }
]; ];
}; };
} }
@ -800,7 +921,7 @@ in {
filter = { filter = {
event = "msg_show"; event = "msg_show";
any = [ any = [
{find = "written";} { find = "written"; }
]; ];
}; };
} }
@ -814,45 +935,45 @@ in {
setupOpts = { setupOpts = {
fold_virt_text_handler = fold_virt_text_handler =
mkLuaInline mkLuaInline
# lua # lua
'' ''
function(virtText, lnum, endLnum, width, truncate) function(virtText, lnum, endLnum, width, truncate)
local newVirtText = {} local newVirtText = {}
local suffix = (' 󰁂 %d '):format(endLnum - lnum) local suffix = (' 󰁂 %d '):format(endLnum - lnum)
local sufWidth = vim.fn.strdisplaywidth(suffix) local sufWidth = vim.fn.strdisplaywidth(suffix)
local targetWidth = width - sufWidth local targetWidth = width - sufWidth
local curWidth = 0 local curWidth = 0
for _, chunk in ipairs(virtText) do for _, chunk in ipairs(virtText) do
local chunkText = chunk[1] local chunkText = chunk[1]
local chunkWidth = vim.fn.strdisplaywidth(chunkText) local chunkWidth = vim.fn.strdisplaywidth(chunkText)
if targetWidth > curWidth + chunkWidth then if targetWidth > curWidth + chunkWidth then
table.insert(newVirtText, chunk) table.insert(newVirtText, chunk)
else else
chunkText = truncate(chunkText, targetWidth - curWidth) chunkText = truncate(chunkText, targetWidth - curWidth)
local hlGroup = chunk[2] local hlGroup = chunk[2]
table.insert(newVirtText, {chunkText, hlGroup}) table.insert(newVirtText, {chunkText, hlGroup})
chunkWidth = vim.fn.strdisplaywidth(chunkText) chunkWidth = vim.fn.strdisplaywidth(chunkText)
-- str width returned from truncate() may less than 2nd argument, need padding -- str width returned from truncate() may less than 2nd argument, need padding
if curWidth + chunkWidth < targetWidth then if curWidth + chunkWidth < targetWidth then
suffix = suffix .. (' '):rep(targetWidth - curWidth - chunkWidth) suffix = suffix .. (' '):rep(targetWidth - curWidth - chunkWidth)
end end
break break
end end
curWidth = curWidth + chunkWidth curWidth = curWidth + chunkWidth
end end
table.insert(newVirtText, {suffix, 'MoreMsg'}) table.insert(newVirtText, {suffix, 'MoreMsg'})
return newVirtText return newVirtText
end end
''; '';
provider_selector = provider_selector =
mkLuaInline mkLuaInline
# lua # lua
'' ''
function(bufnr, filetype, buftype) function(bufnr, filetype, buftype)
return {'treesitter', 'indent'} return {'treesitter', 'indent'}
end end
''; '';
}; };
}; };
borders = { borders = {

View file

@ -1,4 +1,5 @@
[ [
(import ./vesktop.nix) (import ./vesktop.nix)
(import ./powerdns-admin.nix) (import ./powerdns-admin.nix)
(import ./stalwart-mail)
] ]

View file

@ -0,0 +1,7 @@
final: prev: {
stalwart-mail = prev.stalwart-mail.overrideAttrs (oldAttrs: {
patches = [
./enable_root_ca.patch
];
});
}

View file

@ -0,0 +1,291 @@
diff --git a/crates/common/src/config/server/tls.rs b/crates/common/src/config/server/tls.rs
index 603cf889..58e38c84 100644
--- a/crates/common/src/config/server/tls.rs
+++ b/crates/common/src/config/server/tls.rs
@@ -5,7 +5,7 @@
*/
use std::{
- io::Cursor,
+ io::{Cursor, Read},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
sync::Arc,
time::Duration,
@@ -35,7 +35,8 @@ use x509_parser::{
use crate::listener::{
acme::{
- AcmeProvider, ChallengeSettings, EabSettings, directory::LETS_ENCRYPT_PRODUCTION_DIRECTORY,
+ AcmeProvider, ChallengeSettings, EabSettings,
+ directory::{CABundle, LETS_ENCRYPT_PRODUCTION_DIRECTORY},
},
tls::AcmeProviders,
};
@@ -66,6 +67,31 @@ impl AcmeProviders {
.property_or_default(("acme", acme_id, "renew-before"), "30d")
.unwrap_or_else(|| Duration::from_secs(30 * 24 * 60 * 60));
+ let ca_bundle: CABundle = config
+ .value(("acme", acme_id, "ca_bundle"))
+ .map(|s| s.to_string())
+ .and_then(|path| {
+ let buf = std::fs::read(&path)
+ .map_err(|err| {
+ config.new_parse_error(
+ format!("acme.{acme_id}.ca_bundle"),
+ err.to_string(),
+ );
+ })
+ .ok()?;
+
+ reqwest::Certificate::from_pem_bundle(&buf)
+ .map_err(|err| {
+ config.new_parse_error(
+ format!("acme.{acme_id}.ca_bundle"),
+ err.to_string(),
+ );
+
+ err
+ })
+ .ok()
+ });
+
if directory.is_empty() {
config.new_parse_error(format!("acme.{acme_id}.directory"), "Missing property");
continue;
@@ -167,6 +193,7 @@ impl AcmeProviders {
contact,
challenge,
eab,
+ ca_bundle,
renew_before,
default,
) {
@@ -304,7 +331,7 @@ fn build_dns_updater(config: &mut Config, acme_id: &str) -> Option<DnsUpdater> {
.map_err(|err| {
config.new_build_error(
("acme", acme_id, "provider"),
- format!("Failed to create Desec DNS updater: {err}"),
+ format!("Failed to create OVH DNS updater: {err}"),
)
})
.ok(),
diff --git a/crates/common/src/listener/acme/directory.rs b/crates/common/src/listener/acme/directory.rs
index 21640cd6..bbefd7aa 100644
--- a/crates/common/src/listener/acme/directory.rs
+++ b/crates/common/src/listener/acme/directory.rs
@@ -20,6 +20,8 @@ use store::write::Archiver;
use trc::AddContext;
use trc::event::conv::AssertSuccess;
+pub type CABundle = Option<Vec<reqwest::Certificate>>;
+
pub const LETS_ENCRYPT_STAGING_DIRECTORY: &str =
"https://acme-staging-v02.api.letsencrypt.org/directory";
pub const LETS_ENCRYPT_PRODUCTION_DIRECTORY: &str =
@@ -31,6 +33,7 @@ pub struct Account {
pub key_pair: EcdsaKeyPair,
pub directory: Directory,
pub kid: String,
+ pub ca_bundle: CABundle,
}
#[derive(Debug, serde::Serialize)]
@@ -89,16 +92,23 @@ impl Account {
let body = sign(
&key_pair,
None,
- directory.nonce().await?,
+ directory.nonce(&provider.ca_bundle).await?,
&directory.new_account,
&payload,
)?;
- let response = https(&directory.new_account, Method::POST, Some(body)).await?;
+ let response = https(
+ &directory.new_account,
+ Method::POST,
+ Some(body),
+ &provider.ca_bundle,
+ )
+ .await?;
let kid = get_header(&response, "Location")?;
Ok(Account {
key_pair,
kid,
directory,
+ ca_bundle: provider.ca_bundle.clone(),
})
}
@@ -106,15 +116,16 @@ impl Account {
&self,
url: impl AsRef<str>,
payload: &str,
+ ca_bundle: &CABundle,
) -> trc::Result<(Option<String>, String)> {
let body = sign(
&self.key_pair,
Some(&self.kid),
- self.directory.nonce().await?,
+ self.directory.nonce(ca_bundle).await?,
url.as_ref(),
payload,
)?;
- let response = https(url.as_ref(), Method::POST, Some(body)).await?;
+ let response = https(url.as_ref(), Method::POST, Some(body), ca_bundle).await?;
let location = get_header(&response, "Location").ok();
let body = response
.text()
@@ -130,7 +141,9 @@ impl Account {
serde_json::to_string(&domains)
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))?
);
- let response = self.request(&self.directory.new_order, &payload).await?;
+ let response = self
+ .request(&self.directory.new_order, &payload, &self.ca_bundle)
+ .await?;
let url = response.0.ok_or(
trc::EventType::Acme(trc::AcmeEvent::Error)
.caused_by(trc::location!())
@@ -143,30 +156,30 @@ impl Account {
}
pub async fn auth(&self, url: impl AsRef<str>) -> trc::Result<Auth> {
- let response = self.request(url, "").await?;
+ let response = self.request(url, "", &self.ca_bundle).await?;
serde_json::from_str(&response.1)
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))
}
pub async fn challenge(&self, url: impl AsRef<str>) -> trc::Result<()> {
- self.request(&url, "{}").await.map(|_| ())
+ self.request(&url, "{}", &self.ca_bundle).await.map(|_| ())
}
pub async fn order(&self, url: impl AsRef<str>) -> trc::Result<Order> {
- let response = self.request(&url, "").await?;
+ let response = self.request(&url, "", &self.ca_bundle).await?;
serde_json::from_str(&response.1)
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))
}
pub async fn finalize(&self, url: impl AsRef<str>, csr: Vec<u8>) -> trc::Result<Order> {
let payload = format!("{{\"csr\":\"{}\"}}", URL_SAFE_NO_PAD.encode(csr));
- let response = self.request(&url, &payload).await?;
+ let response = self.request(&url, &payload, &self.ca_bundle).await?;
serde_json::from_str(&response.1)
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))
}
pub async fn certificate(&self, url: impl AsRef<str>) -> trc::Result<String> {
- Ok(self.request(&url, "").await?.1)
+ Ok(self.request(&url, "", &self.ca_bundle).await?.1)
}
pub fn http_proof(&self, challenge: &Challenge) -> trc::Result<Vec<u8>> {
@@ -218,9 +231,9 @@ pub struct Directory {
}
impl Directory {
- pub async fn discover(url: impl AsRef<str>) -> trc::Result<Self> {
+ pub async fn discover(url: impl AsRef<str>, ca_bundle: &CABundle) -> trc::Result<Self> {
serde_json::from_str(
- &https(url, Method::GET, None)
+ &https(url, Method::GET, None, ca_bundle)
.await?
.text()
.await
@@ -228,9 +241,9 @@ impl Directory {
)
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))
}
- pub async fn nonce(&self) -> trc::Result<String> {
+ pub async fn nonce(&self, ca_bundle: &CABundle) -> trc::Result<String> {
get_header(
- &https(&self.new_nonce.as_str(), Method::HEAD, None).await?,
+ &https(&self.new_nonce.as_str(), Method::HEAD, None, ca_bundle).await?,
"replay-nonce",
)
}
@@ -316,6 +329,7 @@ async fn https(
url: impl AsRef<str>,
method: Method,
body: Option<String>,
+ ca_bundle: &CABundle,
) -> trc::Result<Response> {
let url = url.as_ref();
let mut builder = reqwest::Client::builder()
@@ -329,6 +343,15 @@ async fn https(
);
}
+ match ca_bundle {
+ Some(certs) => {
+ for cert in certs {
+ builder = builder.add_root_certificate(cert.clone());
+ }
+ }
+ None => {}
+ };
+
let mut request = builder
.build()
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_http_error(err))?
diff --git a/crates/common/src/listener/acme/mod.rs b/crates/common/src/listener/acme/mod.rs
index a6d9b978..2674dd3a 100644
--- a/crates/common/src/listener/acme/mod.rs
+++ b/crates/common/src/listener/acme/mod.rs
@@ -16,7 +16,7 @@ use arc_swap::ArcSwap;
use dns_update::DnsUpdater;
use rustls::sign::CertifiedKey;
-use crate::Server;
+use crate::{Server, listener::acme::directory::CABundle};
use self::directory::{Account, ChallengeType};
@@ -27,6 +27,7 @@ pub struct AcmeProvider {
pub contact: Vec<String>,
pub challenge: ChallengeSettings,
pub eab: Option<EabSettings>,
+ pub ca_bundle: CABundle,
renew_before: chrono::Duration,
account_key: ArcSwap<Vec<u8>>,
default: bool,
@@ -64,6 +65,7 @@ impl AcmeProvider {
contact: Vec<String>,
challenge: ChallengeSettings,
eab: Option<EabSettings>,
+ ca_bundle: CABundle,
renew_before: Duration,
default: bool,
) -> trc::Result<Self> {
@@ -85,6 +87,7 @@ impl AcmeProvider {
account_key: Default::default(),
challenge,
eab,
+ ca_bundle,
default,
})
}
@@ -153,6 +156,7 @@ impl Clone for AcmeProvider {
renew_before: self.renew_before,
account_key: ArcSwap::from_pointee(self.account_key.load().as_ref().clone()),
eab: self.eab.clone(),
+ ca_bundle: self.ca_bundle.clone(),
default: self.default,
}
}
diff --git a/crates/common/src/listener/acme/order.rs b/crates/common/src/listener/acme/order.rs
index 6def86a4..8ba3b185 100644
--- a/crates/common/src/listener/acme/order.rs
+++ b/crates/common/src/listener/acme/order.rs
@@ -85,7 +85,7 @@ impl Server {
}
async fn order(&self, provider: &AcmeProvider) -> trc::Result<Vec<u8>> {
- let directory = Directory::discover(&provider.directory_url).await?;
+ let directory = Directory::discover(&provider.directory_url, &provider.ca_bundle).await?;
let account = Account::create_with_keypair(directory, provider).await?;
let mut params = CertificateParams::new(provider.domains.clone());

View file

@ -59,6 +59,33 @@ in
]; ];
}) })
(import ../../modules/stalwart.nix {
enableNginx = true;
domain = "pre7780.dn";
adminPassFile = config.sops.secrets."stalwart/adminPassword".path;
dbPassFile = config.sops.secrets."stalwart/db".path;
acmeConf = {
directory = "https://ca.net.dn/acme/acme/directory";
ca_bundle = "${"" + ../../extra/ca.crt}";
challenge = "dns-01";
origin = "pre7780.dn";
contact = "admin@pre7780.dn";
domains = [
"pre7780.dn"
"mx1.pre7780.dn"
];
default = true;
provider = "rfc2136-tsig";
host = "10.0.0.1";
renew-before = "1d";
port = 5359;
cache = "${config.services.stalwart-mail.dataDir}/acme";
key = "stalwart";
tsig-algorithm = "hmac-sha512";
secret = "%{file:${config.sops.secrets."stalwart/tsig".path}}%";
};
})
../../modules/davinci-resolve.nix ../../modules/davinci-resolve.nix
../../modules/webcam.nix ../../modules/webcam.nix
../../modules/postgresql.nix ../../modules/postgresql.nix

View file

@ -1,13 +1,20 @@
{ ... }: { config, ... }:
{ {
networking.firewall.allowedTCPPorts = [
443
80
];
security.acme = { security.acme = {
acceptTerms = true; acceptTerms = true;
defaults = { defaults = {
validMinDays = 2; validMinDays = 2;
server = "https://10.0.0.1:${toString 8443}/acme/acme/directory"; server = "https://ca.net.dn/acme/acme/directory";
renewInterval = "daily"; renewInterval = "daily";
email = "danny@net.dn"; email = "danny@net.dn";
webroot = "/var/lib/acme/acme-challenge"; dnsProvider = "pdns";
dnsPropagationCheck = false;
environmentFile = config.sops.secrets."acme/pdns".path;
}; };
}; };

View file

@ -8,6 +8,12 @@ openldap:
adminPassword: ENC[AES256_GCM,data:jEGuzgs5QTWfdyJenC3t3g==,iv:StfFOcvbDapnma6eAlpaGiBWnqiD3I/wfQsMBzufol0=,tag:892q7N4KrsSQoZYGy6CQrA==,type:str] adminPassword: ENC[AES256_GCM,data:jEGuzgs5QTWfdyJenC3t3g==,iv:StfFOcvbDapnma6eAlpaGiBWnqiD3I/wfQsMBzufol0=,tag:892q7N4KrsSQoZYGy6CQrA==,type:str]
lam: lam:
env: ENC[AES256_GCM,data:f1LlC/VvilH8o2Ra7MrSHsMEGlGw3LOV2O9JJf9f,iv:u7cXM8n3jJeLBfxXtA0QMyijBqTcC+yJeW/OO9JuZMI=,tag:QL5FkcCPI5Gxudi0NmCZWg==,type:str] env: ENC[AES256_GCM,data:f1LlC/VvilH8o2Ra7MrSHsMEGlGw3LOV2O9JJf9f,iv:u7cXM8n3jJeLBfxXtA0QMyijBqTcC+yJeW/OO9JuZMI=,tag:QL5FkcCPI5Gxudi0NmCZWg==,type:str]
stalwart:
adminPassword: ENC[AES256_GCM,data:6tUL7b2s3gLtF4Ors9CgYQ==,iv:9UQowgXKr9HR/poELP6SZijp3c2HVTHzEfwf1tZI/3w=,tag:KIOiYEwLsZLH31E2Xb478A==,type:str]
tsig: ENC[AES256_GCM,data:wxsM/dbkW2fNf86b6TsLRNAce19h7mBEuSzFT84aIlaVZA/S29g1U4/CAwD4b+h/XfBgpZQCJf/9yT3yo6dbGAIAk5UgjV2cNY9pO1/uF1T6xoKDgfRZxA==,iv:9BvP8vQkTTEaNgYUPfQcfEMcWqDyD045EPBr7NyHmO4=,tag:coBBAe62kpe/L0S6V8NhXg==,type:str]
db: ENC[AES256_GCM,data:ZRZ2ZzUotYMe2GfkMS7o7dz0aGg=,iv:ys6ogueueESp0y6A+hUG9zTnqmCVobuIzyqA4WVtewo=,tag:p74G+8XhMcpgDnIfh1aXTg==,type:str]
acme:
pdns: ENC[AES256_GCM,data:+InGSnaGIFVtDRlVltzWbZfquzodHUQrPeMRBnVNB9mrajlKr5dFK6DD8dXAvN7UjZFBfrgZefOPkmLR2ncLXGOV2Kl7jorVw50Y0f0iKl7mqwHaZKaQdk7cpGDkCrt/LvfbP9x7gVrs6pQpsU+c/P5rbBLRyejchh/WtiyzgowYIJxYohggeG09+l7YI3FR6U5wiymIRISpNBGEhwG0q17qdAhdtc49qP/K,iv:JcSlxAwHwU528S7iSpAnSbUZw7iO+LMjR3qGwRHp+Zk=,tag:twf2WOQb/yZ3GtN/hlikMA==,type:str]
sops: sops:
age: age:
- recipient: age1uvsvf5ljaezh5wze32p685kfentyle0l2mvysc67yvgct2h4850qqph9lv - recipient: age1uvsvf5ljaezh5wze32p685kfentyle0l2mvysc67yvgct2h4850qqph9lv
@ -19,7 +25,7 @@ sops:
MEdmWkFwNXZoR1ZVRnQ0aWlkYzZwSmsK0EFecUIdqlDKX08oRCoDQQ3QCX1wzb8w MEdmWkFwNXZoR1ZVRnQ0aWlkYzZwSmsK0EFecUIdqlDKX08oRCoDQQ3QCX1wzb8w
lghDJhWlfuKr+X24GoE4UK04aJVLqVMRRI4BJW+LQXeHS+dWKu3mQA== lghDJhWlfuKr+X24GoE4UK04aJVLqVMRRI4BJW+LQXeHS+dWKu3mQA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-06-15T08:18:50Z" lastmodified: "2025-09-16T04:39:12Z"
mac: ENC[AES256_GCM,data:sq+/fpOeNO5wn9S1kFqzRy6xCOVkSBcAkral7MTn4UxRebBDa78KF76Nsba0+o5bzwCchoGl/TC6vySIzGq8FUYwd1tQ9nH5DlqYBVVRgRlKLRyhxXf14BTyYgzHzFuRWdFyY8i4j0flZtlDHk4dVQrE4OhHvhLQ2Zvet5HQ20I=,iv:qoPZ+8tAHJxcR53M2PNwukYgdguSRrAVB+FtKYbf+aM=,tag:FYaPzh6o0ZI27Ul5jEhgVg==,type:str] mac: ENC[AES256_GCM,data:yRVAJz73AqlBm6fxeTehfSqlTLyRYIsPjC/5igpnGC8URUiK66SUtHJSE3196AaPV+CWJrxrXfNWoCmZsP85Rr5V9nw31ZF1boaAc0YzRQBxVmBBlAK7+9Z5KADShAetYNwk9qtCrXd6S8mCwmZjNJaN/Rthy3hchxzAB0/79R4=,iv:QeNUZfmnCx4QF/0wjU/JJRu6umNFC/weW2BJx+7OaPo=,tag:KsityLnPYhugFL4c6wrs6Q==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.10.2 version: 3.10.2

View file

@ -15,6 +15,36 @@
group = config.services.dovecot2.group; group = config.services.dovecot2.group;
mode = "0660"; mode = "0660";
}; };
"stalwart/adminPassword" =
let
inherit (config.users.users.stalwart-mail) name group;
in
lib.mkIf config.services.stalwart-mail.enable {
inherit group;
owner = name;
};
"stalwart/tsig" =
let
inherit (config.users.users.stalwart-mail) name group;
in
lib.mkIf config.services.stalwart-mail.enable {
inherit group;
owner = name;
};
"stalwart/db" =
let
inherit (config.users.users.stalwart-mail) name group;
in
lib.mkIf config.services.stalwart-mail.enable {
inherit group;
owner = name;
};
"acme/pdns" = {
mode = "0660";
owner = "acme";
group = "acme";
};
}; };
}; };
} }

View file

@ -64,6 +64,14 @@
locations."/".proxyPass = "http://10.0.0.130:8001/phone.html"; locations."/".proxyPass = "http://10.0.0.130:8001/phone.html";
}; };
"ca.net.dn" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "https://10.0.0.1:8443/";
};
};
}; };
}; };
} }

View file

@ -33,11 +33,20 @@ sops:
- recipient: age1z6f643a6vqm7cqh6fna5dhmxfkgwxgqy8kg9s0vf9uxhaswtngtspmqsjw - recipient: age1z6f643a6vqm7cqh6fna5dhmxfkgwxgqy8kg9s0vf9uxhaswtngtspmqsjw
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuKzJObXlPVUJzUkEyZXlV YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuYWpiZ0h3VURrcW0rV0Vj
Q0tEbzBPTy9kUXIwVmJkckUyWklUMzhCcTE0Ckh3bXIwRkpESTJYeTBPMGhQYk9y SFJwMlRUMHUyS1FGTEo3cHZJc1Z6a3FWbmtRCkdoZXhwOGJQNlV2dU8wRFRMUHVv
L2NQTWFuMWVqYzJHZGhTaHpDRE5CRGMKLS0tIEsybHdPMk9JeEM2cXFwdlpOeXRj QzhxU3RiVHl5UVpUNk10S2VRVy95OHMKLS0tIE9zbUNUU3ZINU1JNGtmd2trS2tI
Qm0wbmNGZDZwZlNTOVl0WVh5RXNxK2cK1Fwbgl5kKAFyrIIhBP+X4ZKFS4Xl39QY d3YxREtHcTBJYU1sNU9vMGZTUGh6NXMKtGKMnnamCAeftkQ0+Ygb/yg1NdyKDz1W
11qkglNgro/JBFJ/W7Hj5wtEd8QToiJM1RW0lQaI25sneQ2v6L5pDA== UjYvW2PYKzkx8IWmIgzdAI3fWDOiE7tmBTMlX9C3/2PKR6dCc/a+SQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1uvsvf5ljaezh5wze32p685kfentyle0l2mvysc67yvgct2h4850qqph9lv
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxc3pna3R3aG85bmt2WERa
aG9TaDBKTlNMTUVwaFlIdkV0UmFJQStYSHdvCmNuYWJpN2M3QjRkV2s0MHJ4TzZP
ZkhKc0xPUFBrblVFR1U4SUdjYzQ2cm8KLS0tIDVuNW9tRGoxanVKOUJYa2QwNFNz
OTRiU0cxeXp5K1FjaWRGTnBHcnpUYmcKVVlueEj/DELe9Xi9iaBddpPPRmoUmD48
wyjtlvKzS20zishE/D7GkHZ2ZdNsLD3AOnYZ6r6ATAndssC2YT/SXA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-08-29T07:40:00Z" lastmodified: "2025-08-29T07:40:00Z"
mac: ENC[AES256_GCM,data:QeQ5NOrcq3uNmt+MiVF+Jr3JWWBNGPw5A8pSdd1WR426WWqHTRP7NHAaVbS3st9VSmoYY5NI6JKeizuAq/NCvzOZL3Idy9mP+3HD9VZwn1GNSEGfhn+KZT02AY0JHq29KxcZlAYiWZOL4p+blG2aWfGm9+zy1GHoEXoo3OVhaEg=,iv:8uIoOE0ZJZYGZoQaskCXQKr7vl6wjsmJ4iudhvtgqtY=,tag:fRLGalP92dDyF8q+zT97BQ==,type:str] mac: ENC[AES256_GCM,data:QeQ5NOrcq3uNmt+MiVF+Jr3JWWBNGPw5A8pSdd1WR426WWqHTRP7NHAaVbS3st9VSmoYY5NI6JKeizuAq/NCvzOZL3Idy9mP+3HD9VZwn1GNSEGfhn+KZT02AY0JHq29KxcZlAYiWZOL4p+blG2aWfGm9+zy1GHoEXoo3OVhaEg=,iv:8uIoOE0ZJZYGZoQaskCXQKr7vl6wjsmJ4iudhvtgqtY=,tag:fRLGalP92dDyF8q+zT97BQ==,type:str]

View file

@ -4,7 +4,6 @@
username, username,
... ...
}: }:
let let
inherit username; inherit username;
@ -13,10 +12,10 @@ let
sshPortsString = builtins.concatStringsSep ", " (builtins.map (p: builtins.toString p) sshPorts); sshPortsString = builtins.concatStringsSep ", " (builtins.map (p: builtins.toString p) sshPorts);
personal = { personal = {
inherit (config.networking) domain;
ip = "10.0.0.1/24"; ip = "10.0.0.1/24";
interface = "wg0"; interface = "wg0";
port = 51820; port = 51820;
domain = config.networking.domain;
range = "10.0.0.0/24"; range = "10.0.0.0/24";
full = "10.0.0.1/25"; full = "10.0.0.1/25";
restrict = "10.0.0.128/25"; restrict = "10.0.0.128/25";
@ -160,11 +159,13 @@ in
kube.port kube.port
25565 25565
kube.masterAPIServerPort kube.masterAPIServerPort
5359
]; ];
allowedTCPPorts = sshPorts ++ [ allowedTCPPorts = sshPorts ++ [
53 53
25565 25565
kube.masterAPIServerPort kube.masterAPIServerPort
5359
]; ];
}; };
@ -237,8 +238,7 @@ in
listenPort = personal.port; listenPort = personal.port;
privateKeyFile = config.sops.secrets."wireguard/privateKey".path; privateKeyFile = config.sops.secrets."wireguard/privateKey".path;
peers = builtins.map (r: { peers = builtins.map (r: {
publicKey = r.publicKey; inherit (r) publicKey allowedIPs;
allowedIPs = r.allowedIPs;
}) (fullRoute ++ meshRoute); }) (fullRoute ++ meshRoute);
}; };
@ -254,31 +254,31 @@ in
extraHosts = "${kube.masterIP} ${kube.masterHostname}"; extraHosts = "${kube.masterIP} ${kube.masterHostname}";
}; };
services.postgresql = {
enable = lib.mkDefault true;
authentication = ''
host powerdnsadmin powerdnsadmin 127.0.0.1/32 trust
'';
ensureUsers = [
{
name = "powerdnsadmin";
ensureDBOwnership = true;
}
{
name = "pdns";
ensureDBOwnership = true;
}
];
ensureDatabases = [
"powerdnsadmin"
"pdns"
];
};
services = { services = {
dbus.enable = true; dbus.enable = true;
blueman.enable = true; blueman.enable = true;
postgresql = {
enable = lib.mkDefault true;
authentication = ''
host powerdnsadmin powerdnsadmin 127.0.0.1/32 trust
'';
ensureUsers = [
{
name = "powerdnsadmin";
ensureDBOwnership = true;
}
{
name = "pdns";
ensureDBOwnership = true;
}
];
ensureDatabases = [
"powerdnsadmin"
"pdns"
];
};
openssh = { openssh = {
enable = true; enable = true;
ports = sshPorts; ports = sshPorts;
@ -293,6 +293,7 @@ in
enable = true; enable = true;
extraConfig = '' extraConfig = ''
launch=gpgsql launch=gpgsql
loglevel=6
webserver-password=$WEB_PASSWORD webserver-password=$WEB_PASSWORD
api=yes api=yes
api-key=$WEB_PASSWORD api-key=$WEB_PASSWORD
@ -302,6 +303,8 @@ in
webserver=yes webserver=yes
webserver-port=8081 webserver-port=8081
local-port=5359 local-port=5359
dnsupdate=yes
allow-dnsupdate-from=10.0.0.0/24
''; '';
secretFile = config.sops.secrets.powerdns.path; secretFile = config.sops.secrets.powerdns.path;
}; };
@ -310,6 +313,7 @@ in
enable = true; enable = true;
forwardZones = { forwardZones = {
"${config.networking.domain}." = "127.0.0.1:5359"; "${config.networking.domain}." = "127.0.0.1:5359";
"pre7780.dn." = "127.0.0.1:5359";
}; };
forwardZonesRecurse = { forwardZonesRecurse = {
"." = "8.8.8.8"; "." = "8.8.8.8";
@ -380,11 +384,16 @@ in
}; };
}; };
systemd.services.raspamd-trainer = {
after = [ "pdns-recursor.service" ];
};
services.nginx.virtualHosts = { services.nginx.virtualHosts = {
"powerdns.${config.networking.domain}" = { "powerdns.${config.networking.domain}" = {
enableACME = true; enableACME = true;
forceSSL = true; forceSSL = true;
locations."/".proxyPass = "http://localhost:8000"; locations."/api".proxyPass = "http://127.0.0.1:8081";
locations."/".proxyPass = "http://127.0.0.1:8000";
}; };
"uptime.${config.networking.domain}" = { "uptime.${config.networking.domain}" = {

View file

@ -41,7 +41,6 @@ in
postRun = '' postRun = ''
systemctl restart postfix.service systemctl restart postfix.service
systemctl restart dovecot.service systemctl restart dovecot.service
systemctl restart rspamd-trainer.service
''; '';
}; };
"${cfg.domain}" = { "${cfg.domain}" = {

118
system/modules/stalwart.nix Normal file
View file

@ -0,0 +1,118 @@
{
adminPassFile,
dbPassFile,
domain ? null,
acmeConf ? null,
enableNginx ? true,
}:
{
config,
lib,
...
}:
let
inherit (lib) mkIf;
in
{
services.postgresql = {
enable = true;
ensureDatabases = [
"stalwart"
];
ensureUsers = [
{
name = "stalwart";
ensureDBOwnership = true;
}
];
};
services.stalwart-mail = {
enable = true;
openFirewall = true;
settings = {
server = {
hostname = if (domain != null) then "mx1.${domain}" else config.networking.fqdn;
auto-ban.scan.rate = "1000/1d";
tls = {
enable = true;
implicit = true;
};
listener = {
smtp = {
protocol = "smtp";
bind = "[::]:25";
};
submissions = {
protocol = "smtp";
bind = "[::]:465";
tls.implicit = true;
};
imaps = {
protocol = "imap";
bind = "[::]:993";
tls.implicit = true;
};
management = {
protocol = "http";
bind = [ "127.0.0.1:8080" ];
};
};
};
lookup.default = {
hostname = "mx1.${domain}";
domain = "${domain}";
};
acme."step-ca" = mkIf (acmeConf != null) acmeConf;
session.auth = {
mechanisms = "[plain]";
directory = "'in-memory'";
require = true;
allow-plain-text = true;
};
storage.data = "db";
store."db" = {
type = "postgresql";
host = "localhost";
port = 5432;
database = "stalwart";
user = "stalwart";
password = "%{file:${dbPassFile}}%";
};
directory = {
"imap".lookup.domains = [ domain ];
"in-memory" = {
type = "memory";
principals = [
{
name = "admin";
class = "admin";
secret = "%{file:${adminPassFile}}%";
email = [ "admin@${domain}" ];
}
];
};
};
authentication.fallback-admin = {
user = "admin";
secret = "%{file:${adminPassFile}}%";
};
tracer."stdout" = {
enable = true;
type = "console";
level = "debug";
};
};
};
services.nginx = mkIf enableNginx {
enable = true;
virtualHosts = {
"mail.${domain}" = {
locations."/".proxyPass = "http://127.0.0.1:8080";
enableACME = true;
forceSSL = true;
};
};
};
}