Commit 47742f8
Changed files (13)
hosts
modules
nixos
outputs
x86_64-linux
secrets
hosts
hosts/chaser-kevin/default.nix
@@ -9,9 +9,7 @@
# My main computer, with I7-14650HX + RTX4060 Laptop GPU + 48GB memory, for daily use.
#
#############################################################
-let
- hostName = "kevin"; # Define your hostname.
-in {
+{
imports = [
nixos-hardware.nixosModules.common-cpu-intel
nixos-hardware.nixosModules.common-hidpi
@@ -23,13 +21,6 @@ in {
./boot.nix
];
- modules.my-hosts.${hostName}.network = {
- enable = "networkmanager";
- iface = "wlp0s20f3";
- useDHCP = true;
- nameservers = myvars.defaultNameservers;
- };
-
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
hosts/chaser-pardofelis/default.nix
@@ -10,27 +10,13 @@
# My main server hosted by Yecaoyun.
#
#############################################################
-let
- hostName = "pardofelis"; # Define your hostname.
-in {
+{
imports =
(mylib.scanModules ./.)
++ [
disko.nixosModules.default
];
- modules.my-hosts.${hostName} = {
- network = {
- enable = "networkd";
- iface = "eth0";
- useDHCP = false;
- nameservers = ["172.16.36.100"] ++ myvars.defaultNameservers;
- search = ["local"];
- };
- hostPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO56HKTdzGulisPLhpfUmLQNEgwDqwD9SBLRb5aETffV root@pardofelis";
- sshPorts = [23930];
- };
-
systemd.network.enable = true;
# This value determines the NixOS release from which the default
hosts/general.nix
@@ -0,0 +1,26 @@
+{myvars, ...}: {
+ modules.my-hosts = {
+ kevin.network = {
+ enable = "networkmanager";
+ iface = "wlp0s20f3";
+ useDHCP = true;
+ nameservers = myvars.defaultNameservers;
+ };
+
+ pardofelis = {
+ network = {
+ enable = "networkd";
+ iface = "eth0";
+ useDHCP = false;
+ nameservers = ["172.16.36.100"] ++ myvars.defaultNameservers;
+ search = ["local"];
+ ipv4 = {secretName = "pardofelis-ipv4";};
+ ipv6 = {secretName = "pardofelis-ipv6";};
+ defaultGateway = {secretName = "pardofelis-gateway";};
+ defaultGateway6 = {secretName = "pardofelis-gateway6";};
+ };
+ hostPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO56HKTdzGulisPLhpfUmLQNEgwDqwD9SBLRb5aETffV root@pardofelis";
+ sshPorts = [23930];
+ };
+ };
+}
modules/base/hosts.nix
@@ -1,5 +1,14 @@
{lib, ...}:
with lib; let
+ secretType = types.submodule {
+ options = {
+ secretName = mkOption {
+ type = types.str;
+ };
+ };
+ };
+ optSecretType = types.nullOr (types.either types.str secretType);
+
hostModule = types.submodule {
options = {
network = mkOption {
@@ -41,11 +50,11 @@ with lib; let
default = [];
};
ipv4 = mkOption {
- type = types.nullOr types.str;
+ type = optSecretType;
default = null;
};
ipv6 = mkOption {
- type = types.nullOr types.str;
+ type = optSecretType;
default = null;
};
prefixLength4 = mkOption {
@@ -57,11 +66,11 @@ with lib; let
default = 64;
};
defaultGateway = mkOption {
- type = types.nullOr types.str;
+ type = optSecretType;
default = null;
};
defaultGateway6 = mkOption {
- type = types.nullOr types.str;
+ type = optSecretType;
default = null;
};
};
modules/base/users.nix
@@ -5,40 +5,68 @@
...
}: let
hosts = config.modules.my-hosts;
- sshTargetHosts =
+ managedHosts =
lib.filterAttrs (
- n: v: !builtins.isNull v.hostPublicKey && !builtins.isNull v.network.ipv4
+ name: host:
+ !builtins.isNull host.hostPublicKey
+ && (!builtins.isNull host.network.ipv4 || !builtins.isNull host.network.ipv6)
)
hosts;
-in {
- programs.ssh = {
- extraConfig =
- lib.attrsets.foldlAttrs
- (acc: host: val:
- acc
- + ''
- Host ${host}
- HostName ${val.network.ipv4}
- Port ${lib.elemAt val.sshPorts 0}
- '')
- ""
- sshTargetHosts;
- knownHosts =
- lib.mapAttrs'
- (
- host: value:
- lib.attrsets.nameValuePair
- (value.network.ipv4)
- {
- inherit (value) hostPublicKey;
- hostNames = [host];
- }
- )
- sshTargetHosts;
- };
+ secretIpHosts =
+ lib.filterAttrs (
+ name: host:
+ isSecret host.network.ipv4 || isSecret host.network.ipv6
+ )
+ managedHosts;
+ isSecret = v: lib.isAttrs v && v ? "secretName";
+ isPlain = v: lib.isString v;
+in {
users.users.${myvars.username} = {
description = myvars.userfullname;
openssh.authorizedKeys.keys = myvars.sshAuthorizedKeys;
};
+
+ programs.ssh.knownHosts =
+ lib.mapAttrs'
+ (name: host: lib.nameValuePair name {publicKey = host.hostPublicKey;})
+ managedHosts;
+
+ programs.ssh.extraConfig = ''
+ ${lib.concatStringsSep "\n" (
+ lib.mapAttrsToList (
+ name: host: let
+ cfg = host.network;
+ in ''
+ Host ${name}
+ ${lib.optionalString (isPlain cfg.ipv4) "HostName ${cfg.ipv4}"}
+ ${lib.optionalString (isPlain cfg.ipv6) "HostName ${cfg.ipv6}"}
+ ${
+ lib.optionalString (isSecret cfg.ipv4 || isSecret cfg.ipv6)
+ "Include ${config.sops.templates."ssh-config-${name}".path}"
+ }
+ Port ${toString (lib.elemAt host.sshPorts 0)}
+ ''
+ )
+ managedHosts
+ )}
+ '';
+
+ sops.templates =
+ lib.mapAttrs'
+ (name: host:
+ lib.nameValuePair "ssh-config-${name}" {
+ content = ''
+ ${lib.optionalString (isSecret host.network.ipv4) ''
+ HostName ${config.sops.placeholder.${host.network.ipv4.secretName}}
+ ''}
+ ${lib.optionalString (isSecret host.network.ipv6) ''
+ HostName ${config.sops.placeholder.${host.network.ipv6.secretName}}
+ ''}
+ '';
+ owner = "root";
+ group = "ssh-secrets-users";
+ mode = "0440";
+ })
+ secretIpHosts;
}
modules/nixos/base/networking.nix
@@ -1,77 +1,148 @@
{
config,
lib,
- hostName,
...
-}:
-lib.mkMerge [
- {
- # Use an NTP server located in the mainland of China to synchronize the system time
- networking.timeServers = [
- "ntp.aliyun.com" # Aliyun NTP Server
- "ntp.tencent.com" # Tencent NTP Server
- ];
- }
+}: let
+ hostName = config.modules.currentHost;
+in
+ lib.mkMerge [
+ {
+ # Use an NTP server located in the mainland of China to synchronize the system time
+ networking.timeServers = [
+ "ntp.aliyun.com" # Aliyun NTP Server
+ "ntp.tencent.com" # Tencent NTP Server
+ ];
+ }
- (let
- cfg = config.modules.my-hosts.${hostName}.network;
- in
- lib.mkIf cfg.useDHCP {
- assertions = map (x: {
- assertion = cfg.${x} == null;
- message = "my-host.network.useDHCP is confilt with my-host.network.${x}";
- }) ["ipv4" "ipv6"];
- })
+ (let
+ cfg = config.modules.my-hosts.${hostName}.network;
+ in
+ lib.mkIf cfg.useDHCP {
+ assertions = map (x: {
+ assertion = cfg.${x} == null;
+ message = "my-host.network.useDHCP is confilt with my-host.network.${x}";
+ }) ["ipv4" "ipv6"];
+ })
- (let
- cfg = config.modules.my-hosts.${hostName}.network;
- in
- lib.mkIf
- (cfg.enable == "networkmanager")
- {
- networking = with cfg; {
- networkmanager.enable = true;
- useDHCP = lib.mkDefault useDHCP;
- inherit hostName search defaultGateway defaultGateway6 nameservers;
- interfaces.${cfg.iface} = lib.mkIf (!builtins.isNull cfg.ipv4 && !builtins.isNull cfg.ipv6) {
- ipv4.addresses = lib.optional (!builtins.isNull cfg.ipv4) {
- address = cfg.ipv4;
- prefixLength = cfg.prefixLength4;
+ (let
+ cfg = config.modules.my-hosts.${hostName}.network;
+ in
+ lib.mkIf
+ (cfg.enable == "networkmanager")
+ {
+ assertions = map (x: {
+ assertion = !(cfg.${x} ? "secretName");
+ message = "my-host.network.${x} should not be a secret when using networkmanager.";
+ }) ["ipv4" "ipv6" "defaultGateway" "defaultGateway6"];
+ networking = with cfg; {
+ networkmanager.enable = true;
+ useDHCP = lib.mkDefault useDHCP;
+ inherit hostName search defaultGateway defaultGateway6 nameservers;
+ interfaces.${cfg.iface} = lib.mkIf (!builtins.isNull cfg.ipv4 && !builtins.isNull cfg.ipv6) {
+ ipv4.addresses = lib.optional (!builtins.isNull cfg.ipv4) {
+ address = cfg.ipv4;
+ prefixLength = cfg.prefixLength4;
+ };
+ ipv6.addresses = lib.optional (!builtins.isNull cfg.ipv6) {
+ address = cfg.ipv6;
+ prefixLength = cfg.prefixLength6;
+ };
};
- ipv6.addresses = lib.optional (!builtins.isNull cfg.ipv6) {
- address = cfg.ipv6;
- prefixLength = cfg.prefixLength6;
+ };
+ })
+ (let
+ cfg = config.modules.my-hosts.${hostName}.network;
+ isSecret = v: lib.isAttrs v && v ? "secretName";
+ isInEval = x: (!builtins.isNull x && !isSecret x);
+ in
+ lib.mkIf
+ (cfg.enable == "networkd")
+ {
+ networking.useNetworkd = true;
+ networking.hostName = hostName;
+ systemd.network.networks."10-${cfg.iface}" = {
+ matchConfig.Name = [cfg.iface];
+ networkConfig = {
+ Address =
+ (lib.optionals (isInEval cfg.ipv4)
+ ["${cfg.ipv4}/${toString cfg.prefixLength4}"])
+ ++ (lib.optionals (isInEval cfg.ipv6)
+ ["${cfg.ipv6}/${toString cfg.prefixLength6}"]);
+ DNS = cfg.nameservers;
};
+ routes =
+ (lib.optional (isInEval cfg.defaultGateway)
+ {
+ Destination = "0.0.0.0/0";
+ Gateway = cfg.defaultGateway;
+ })
+ ++ (lib.optional (isInEval cfg.defaultGateway6) {
+ Destination = "::/0";
+ Gateway = cfg.defaultGateway6;
+ });
+ linkConfig.RequiredForOnline = "routable";
};
- };
- })
- (let
- cfg = config.modules.my-hosts.${hostName}.network;
- in
- lib.mkIf
- (cfg.enable == "networkd")
- {
- networking.useNetworkd = true;
- networking.hostName = hostName;
- systemd.network.networks."10-${cfg.iface}" = {
- matchConfig.Name = [cfg.iface];
- networkConfig = {
- Address =
- (lib.optionals (!builtins.isNull cfg.ipv4) ["${cfg.ipv4}/${toString cfg.prefixLength4}"])
- ++ (lib.optionals (!builtins.isNull cfg.ipv6) ["${cfg.ipv6}/${toString cfg.prefixLength6}"]);
- DNS = cfg.nameservers;
+
+ environment.etc."systemd/network/10-${cfg.iface}.network.d/99-address.conf" =
+ lib.mkIf
+ (isSecret cfg.ipv4 || isSecret cfg.ipv6)
+ {
+ source = config.sops.templates.networkd-address.path;
+ user = "root";
+ group = "systemd-network";
+ mode = "0440";
+ };
+ environment.etc."systemd/network/10-${cfg.iface}.network.d/99-route.conf" =
+ lib.mkIf
+ (isSecret cfg.defaultGateway || isSecret cfg.defaultGateway6)
+ {
+ source = config.sops.templates.networkd-route.path;
+ user = "root";
+ group = "systemd-network";
+ mode = "0440";
+ };
+
+ sops.templates.networkd-address = {
+ content =
+ lib.mkIf
+ (isSecret cfg.ipv4 || isSecret cfg.ipv6)
+ ''
+ [Network]
+ ${
+ lib.optionalString (isSecret cfg.ipv4)
+ "Address=${config.sops.placeholder.${cfg.ipv4.secretName}}/${toString cfg.prefixLength4}"
+ }
+ ${
+ lib.optionalString (isSecret cfg.ipv6)
+ "Address=${config.sops.placeholder.${cfg.ipv6.secretName}}/${toString cfg.prefixLength6}"
+ }
+ '';
+ owner = "root";
+ group = "systemd-network";
+ mode = "0440";
+ };
+ sops.templates.networkd-route = {
+ content =
+ lib.mkIf
+ (isSecret cfg.defaultGateway || isSecret cfg.defaultGateway6)
+ "${
+ lib.optionalString (isSecret cfg.defaultGateway)
+ ''
+ [Route]
+ Gateway=${config.sops.placeholder.${cfg.defaultGateway.secretName}}
+ Destination=0.0.0.0/0
+ ''
+ }\n${
+ lib.optionalString (isSecret cfg.defaultGateway6)
+ ''
+ [Route]
+ Gateway=${config.sops.placeholder.${cfg.defaultGateway6.secretName}}
+ Destination=::/0
+ ''
+ }";
+ owner = "root";
+ group = "systemd-network";
+ mode = "0440";
};
- routes =
- (lib.optional (!builtins.isNull cfg.defaultGateway)
- {
- Destination = "0.0.0.0/0";
- Gateway = cfg.defaultGateway;
- })
- ++ (lib.optional (!builtins.isNull cfg.defaultGateway6) {
- Destination = "::/0";
- Gateway = cfg.defaultGateway6;
- });
- linkConfig.RequiredForOnline = "routable";
- };
- })
-]
+ })
+ ]
modules/nixos/base/user-group.nix
@@ -8,6 +8,7 @@
users.groups = {
"${myvars.username}" = {};
+ ssh-secrets-users = {};
};
users.users."${myvars.username}" = {
@@ -22,6 +23,7 @@
"networkmanager"
"wheel"
"aria2"
+ "ssh-secrets-users"
];
};
outputs/x86_64-linux/src/kevin.nix
@@ -17,6 +17,7 @@
# common
"secrets/nixos.nix"
"modules/nixos/desktop.nix"
+ "hosts/general.nix"
# host specific
"hosts/chaser-${name}"
];
outputs/x86_64-linux/src/pardofelis.nix
@@ -20,6 +20,7 @@
# common
"secrets/nixos.nix"
"modules/nixos/server/x86_64.nix"
+ "hosts/general.nix"
# host specific
"hosts/chaser-${name}"
];
secrets/hosts/pardofelis/default.nix
@@ -1,23 +1,10 @@
{
- config,
- lib,
- hostName,
- ...
-}:
-lib.mkIf (hostName == "pardofelis") {
- sops.secrets.pardofelis-network = {
- format = "yaml";
- key = "content";
- sopsFile = ./secrets.yaml;
- owner = "root";
- group = "systemd-network";
- mode = "0440";
- path = "/etc/systemd/network/10-${config.modules.my-hosts.${hostName}.network.iface}.network.d/99-secret-ip.conf";
- };
- services.openssh.hostKeys = [
- {
- path = "/etc/ssh/ssh_host_ed25519_key";
- type = "ed25519";
- }
- ];
+ sops.secrets = builtins.listToAttrs (builtins.map (x: {
+ name = "pardofelis-${x}";
+ value = {
+ format = "yaml";
+ sopsFile = ./secrets.yaml;
+ key = x;
+ };
+ }) ["ipv4" "ipv6" "gateway" "gateway6"]);
}
secrets/hosts/pardofelis/secrets.yaml
@@ -1,4 +1,7 @@
-content: ENC[AES256_GCM,data:GWb0KiK4qOIm4ht+kRyAwSZsIysP5d/4ijvxTUUtMSxOc5Mq0j8C24x1VbFWk5n2w6Bx0PSzZwiTL0fBivZRs9bEi0lQAoLiF6szftw2hGcqwoWmNFDajr4i5MaICG1nY/zFelWd7o+45/1Z003hpx8bZEfiTAY=,iv:FXPH4KlhbVFjVvr27jJlFbehYfNeUVqXXHOY0azAJYQ=,tag:R0JPMsSxxpXDt20wlc3W6Q==,type:str]
+ipv4: ENC[AES256_GCM,data:EOyIUXJxIKZIjLjh,iv:fS6HCVpATCrOCleA+2ZqiJpQD/CqkOeFhRcgkVLx45I=,tag:7IqJE9v65SxJMcOW3juBIg==,type:str]
+ipv6: ENC[AES256_GCM,data:0tuG+y2elv10AmyLdTh6o1wggdSm9A==,iv:BzGoHn8JLlGpk/Ifn5Qtf7qHSMUzM3lXl2UOF7Eilts=,tag:mSnjqis1Z39j9+WWPQvB6g==,type:str]
+gateway: ENC[AES256_GCM,data:ScDchbNjK1DPkc4Zvw==,iv:AyMa6YkTyEJclJKOqIbWCc4bfr9IXyTrRNJ0cCv0LiE=,tag:bPwlivyWgcpKBd70Pp+z5A==,type:str]
+gateway6: ENC[AES256_GCM,data:0kNmpzpfa1Px+b8thcPU524SZWM=,iv:Rw9+fe1DvG/eE369zEiivy82aiWXRGvzTLBXEdd3BVY=,tag:nS1v2h/b169Q/7E7ywvu0A==,type:str]
sops:
age:
- recipient: age1l9acz0cuy455nprryeqyv6ckfqgv3tekuk0kxvvxyunsapwmpvnsmaazhy
@@ -10,8 +13,8 @@ sops:
WUd6SkpZSHpEYXdyZm9uV2V5VDdTNXcKO1nnWK+udwvNl6b+mxyTspgpsGxSQ9YP
SpIglDs4+ya3n1UeoSg5JY6s7RjHQj9zFCL2b7FYUJgSrZ/XFVgS5g==
-----END AGE ENCRYPTED FILE-----
- lastmodified: "2025-07-15T09:27:08Z"
- mac: ENC[AES256_GCM,data:I+y24lmIiH1kYljzLr3rWfx/9e+YNK95v6RBfqrwZifKMJbgApwnPzvAhVeYmL2Cx25TV8TLdwS1gQWvi6vje/lrM9mmRNChbXIARqQ4E58oEDpGTX1OWPNYKTb+nFwYmfO+eX1l/NR9LhM75axhLse8nVBblspXdvrcpMAHFuM=,iv:iqFyXxrAYT79l9tiXGZ57XXXmGOiOjCVEDlr4TA4jo0=,tag:23dSgbtwl2WnOYxtXRZLaw==,type:str]
+ lastmodified: "2025-07-15T14:00:16Z"
+ mac: ENC[AES256_GCM,data:1Ozw1la9CaD9b2neHnxTwZem4WsmrgUymBdUbjvDvgcH01yFVOtMs8FC2nkVus5bFuH7/jfnW30bKyIdclePYiqXXqRs3OS04Q6q9lMH71EZYdrAjlgltDFFDo67Z1WyzB02ePKVeZ7i06lDvBgLq8yq8oHUU+gSslOkP8rREUA=,iv:D5hS4VtKrWuoMx5lL0HU6lNUBTnGPGiqVFhcmCHjXYs=,tag:GcV6UnCuKTrZWd8QxT3zTA==,type:str]
pgp:
- created_at: "2025-07-15T09:13:38Z"
enc: |-
secrets/hosts/README.md
@@ -0,0 +1,7 @@
+# Host secrets
+
+This folder contains host-specific settings, typically the IP address of a VPS.
+
+## IP Secret Management
+
+Define some common host configuration options in `/modules/base/hosts.nix` and declare them centrally in `/hosts/general.nix`. In `/modules/base/users.nix`, store IP-related secrets in files and import them into `knownHosts`; in `/modules/nixos/base/networking.nix`, store IP-related secrets in systemd's drop-in.
\ No newline at end of file
secrets/hosts/README.zh-CN.md
@@ -0,0 +1,7 @@
+# Host secrets
+
+此文件夹下存放主机特定的设置,一般而言是 VPS 的 IP 地址。
+
+## IP 机密管理
+
+通过 `/modules/base/hosts.nix` 定义主机的部分通用设置选项,并在 `/hosts/general.nix` 中集中声明。在 `/modules/base/users.nix` 中将 IP 相关机密存入文件并引入 `knownHosts` 中;在 `/modules/nixos/base/networking.nix` 中将 IP 相关机密存入 systemd 的 drop-in 中。
\ No newline at end of file