Merge branch 'merge-hectic-lab'

This commit is contained in:
2026-04-26 13:52:17 +00:00
16 changed files with 1130 additions and 12 deletions

View File

@@ -11,7 +11,7 @@ let
hectic.imports = attrValues (
readModulesRecursive' ./hectic { inherit flake self inputs; }
);
# Read generic modules seperately
# Read generic modules separately
generic = readModulesRecursive'
./generic
{ inherit flake self inputs; };

View File

@@ -0,0 +1,169 @@
# INFO(nrv): This is standalone shadowsocks module. Instance-specific is at ./shadowsocks.nix
{
...
}:
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.shadowsocks-rust;
opts = {
server = cfg.localAddress;
server_port = cfg.port;
method = cfg.encryptionMethod;
mode = cfg.mode;
user = "nobody";
fast_open = cfg.fastOpen;
} // optionalAttrs (cfg.plugin != null) {
plugin = cfg.plugin;
plugin_opts = cfg.pluginOpts;
} // optionalAttrs (cfg.password != null) {
password = cfg.password;
} // cfg.extraConfig;
configFile = pkgs.writeText "shadowsocks.json" (builtins.toJSON opts);
in
{
###### interface
options = {
services.shadowsocks-rust = {
enable = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Whether to run shadowsocks-rust shadowsocks server.
'';
};
localAddress = mkOption {
type = types.str;
default = "0.0.0.0";
description = lib.mdDoc ''
Local addresses to which the server binds.
'';
};
port = mkOption {
type = types.port;
default = 8388;
description = lib.mdDoc ''
Port which the server uses.
'';
};
password = mkOption {
type = types.nullOr types.str;
default = null;
description = lib.mdDoc ''
Password for connecting clients.
'';
};
passwordFile = mkOption {
type = types.nullOr types.path;
default = null;
description = lib.mdDoc ''
Password file with a password for connecting clients.
'';
};
mode = mkOption {
type = types.enum [ "tcp_only" "tcp_and_udp" "udp_only" ];
default = "tcp_and_udp";
description = lib.mdDoc ''
Relay protocols.
'';
};
fastOpen = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
use TCP fast-open
'';
};
encryptionMethod = mkOption {
type = types.str;
default = "chacha20-ietf-poly1305";
description = lib.mdDoc ''
Encryption method. See <https://github.com/shadowsocks/shadowsocks-org/wiki/AEAD-Ciphers>.
'';
};
plugin = mkOption {
type = types.nullOr types.str;
default = null;
example = literalExpression ''"''${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin"'';
description = lib.mdDoc ''
SIP003 plugin for shadowsocks
'';
};
pluginOpts = mkOption {
type = types.str;
default = "";
example = "server;host=example.com";
description = lib.mdDoc ''
Options to pass to the plugin if one was specified
'';
};
extraConfig = mkOption {
type = types.attrs;
default = {};
example = {
nameserver = "8.8.8.8";
};
description = lib.mdDoc ''
Additional configuration for shadowsocks that is not covered by the
provided options. The provided attrset will be serialized to JSON and
has to contain valid shadowsocks options. Unfortunately most
additional options are undocumented but it's easy to find out what is
available by looking into the source code of
<https://github.com/shadowsocks/shadowsocks-rust/blob/master/src/jconf.c>
'';
};
};
};
###### implementation
config = mkIf cfg.enable {
assertions = singleton
{ assertion = cfg.password == null || cfg.passwordFile == null;
message = "Cannot use both password and passwordFile for shadowsocks-rust";
};
systemd.services.shadowsocks-rust = {
description = "shadowsocks-rust Daemon";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.shadowsocks-rust ]
++ optional (cfg.plugin != null) cfg.plugin
++ optional (cfg.passwordFile != null) pkgs.jq;
serviceConfig.PrivateTmp = true;
script = ''
${optionalString (cfg.passwordFile != null) ''
cat ${configFile} | jq --arg password "$(cat "${cfg.passwordFile}")" '. + { password: $password }' > /run/shadowsocks.json
''}
exec ssserver --config ${if cfg.passwordFile != null then "/run/shadowsocks.json" else configFile}
'';
};
};
}

View File

@@ -0,0 +1,28 @@
{
...
}:
{
pkgs,
config,
...
}:
{
sops.secrets."ss-bfs/password" = {};
services.shadowsocks-rust = {
enable = true;
plugin = "${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin";
# TODO: setup dnscrypt or a private DNS server for this
# extraConfig = {
# nameserver = "185.12.64.1"; # FIXME: this can vary across instances.
# };
port = 55228;
pluginOpts = "server";
# TODO: setup a TLS certs for this (look: (README.md) https://github.com/shadowsocks/v2ray-plugin/)
#pluginOpts = "server;tls;host=ss.bfs.band";
passwordFile = config.sops.secrets."ss-bfs/password".path;
mode = "tcp_and_udp"; # default
localAddress = "0.0.0.0";
fastOpen = true; # default
encryptionMethod = "chacha20-ietf-poly1305"; # default
};
}

View File

@@ -12,6 +12,7 @@
in {
imports = [
inputs.disko.nixosModules.default
inputs.nixos-mailserver.nixosModules.mailserver
];
options.hectic.archetype.base.enable = lib.mkEnableOption "Enable archetupe.dev";

View File

@@ -0,0 +1,65 @@
{
inputs,
flake,
self,
}:
{
lib,
config,
...
}: let
cfg = config.services.mailserver;
transformLoginAccounts = domain: input:
builtins.listToAttrs (map (key: {
name = key + "@" + domain;
value = input.${key};
}) (builtins.attrNames input));
in {
options = {
services.mailserver.enable = lib.mkEnableOption "Mail server";
services.mailserver.domain = lib.mkOption {
type = lib.types.str;
description = "The domain name of the mail server";
};
services.mailserver.loginAccounts = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
hashedPassword = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
hashedPasswordFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Full path to a file containing the hashed password suitable
for use with `chpasswd -e`.
'';
};
};
});
default = {};
description = "Login accounts for the mail server";
};
};
config = lib.mkIf cfg.enable {
mailserver = {
enable = true;
fqdn = "mail." + cfg.domain;
domains = [ cfg.domain ];
loginAccounts = transformLoginAccounts cfg.domain cfg.loginAccounts;
certificateScheme = "acme-nginx";
};
# NOTE(yukkop): avoid Gmail rejection due to missing IPv6 PTR records
services.postfix.settings.main.inet_protocols = lib.mkDefault "ipv4";
security.acme.acceptTerms = true;
security.acme.defaults.email = "security@" + cfg.domain;
};
}