mirror of
https://github.com/gensokyo-zone/infrastructure.git
synced 2026-02-09 04:19:19 -08:00
refactor: move services out of systems/tewi/
This commit is contained in:
parent
2f68968238
commit
5a661e8809
30 changed files with 992 additions and 638 deletions
63
modules/nixos/cloudflared.nix
Normal file
63
modules/nixos/cloudflared.nix
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{ pkgs, config, utils, lib, ... }: let
|
||||
inherit (lib.attrsets) mapAttrsToList mapAttrs' nameValuePair filterAttrsRecursive;
|
||||
inherit (lib.lists) singleton;
|
||||
inherit (lib.modules) mkIf mkMerge mkForce;
|
||||
inherit (lib.options) mkOption mkEnableOption;
|
||||
cfg = config.services.cloudflared;
|
||||
in {
|
||||
options.services.cloudflared = with lib.types; {
|
||||
tunnels = let
|
||||
tunnelModule = { config, ... }: {
|
||||
options = {
|
||||
extraTunnel = {
|
||||
enable = mkEnableOption "extra tunnels" // {
|
||||
default = config.extraTunnel.ingress != { };
|
||||
};
|
||||
ingress = mkOption {
|
||||
type = attrs;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in mkOption {
|
||||
type = attrsOf (submodule tunnelModule);
|
||||
};
|
||||
};
|
||||
config.systemd.services = let
|
||||
filterConfig = filterAttrsRecursive (_: v: ! builtins.elem v [ null [ ] { } ]);
|
||||
mapIngress = hostname: ingress: {
|
||||
inherit hostname;
|
||||
} // filterConfig (filterConfig ingress);
|
||||
in mkIf cfg.enable (mapAttrs' (uuid: tunnel: let
|
||||
RuntimeDirectory = "cloudflared-tunnel-${uuid}";
|
||||
configPath = "/run/${RuntimeDirectory}/config.yml";
|
||||
settings = {
|
||||
tunnel = uuid;
|
||||
credentials-file = tunnel.credentialsFile;
|
||||
ingress = mapAttrsToList mapIngress tunnel.ingress
|
||||
++ mapAttrsToList mapIngress tunnel.extraTunnel.ingress
|
||||
++ singleton { service = tunnel.default; };
|
||||
};
|
||||
in nameValuePair "cloudflared-tunnel-${uuid}" (mkMerge [
|
||||
{
|
||||
after = mkIf config.services.tailscale.enable [ "tailscale-autoconnect.service" ];
|
||||
serviceConfig = {
|
||||
RestartSec = 10;
|
||||
};
|
||||
}
|
||||
(mkIf tunnel.extraTunnel.enable {
|
||||
serviceConfig = {
|
||||
inherit RuntimeDirectory;
|
||||
ExecStart = mkForce [
|
||||
"${cfg.package}/bin/cloudflared tunnel --config=${configPath} --no-autoupdate run"
|
||||
];
|
||||
ExecStartPre = [
|
||||
(pkgs.writeShellScript "cloudflared-tunnel-${uuid}-prepare" ''
|
||||
${utils.genJqSecretsReplacementSnippet settings configPath}
|
||||
'')
|
||||
];
|
||||
};
|
||||
})
|
||||
])) cfg.tunnels);
|
||||
}
|
||||
193
modules/nixos/home-assistant.nix
Normal file
193
modules/nixos/home-assistant.nix
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
cfg = config.services.home-assistant;
|
||||
inherit (lib.modules) mkIf mkMerge mkBefore mkDefault;
|
||||
inherit (lib.options) mkOption mkEnableOption;
|
||||
inherit (lib.lists) optional elem;
|
||||
in {
|
||||
options.services.home-assistant = with lib.types; {
|
||||
mutableUiConfig = mkEnableOption "UI-editable config files";
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
default = config.networking.domain;
|
||||
};
|
||||
homekit.enable = mkEnableOption "homekit" // {
|
||||
default = cfg.config.homekit or [ ] != [ ];
|
||||
};
|
||||
googleAssistant.enable = mkEnableOption "Google Assistant" // {
|
||||
default = cfg.config.google_assistant or { } != { };
|
||||
};
|
||||
androidTv.enable = mkEnableOption "Android TV" // {
|
||||
default = elem "androidtv" cfg.extraComponents;
|
||||
};
|
||||
cast = {
|
||||
enable = mkEnableOption "Chromecast" // {
|
||||
default = elem "cast" cfg.extraComponents;
|
||||
};
|
||||
openFirewall = mkEnableOption "Chromecast ports" // {
|
||||
default = cfg.openFirewall;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
networking.firewall = mkIf cfg.enable {
|
||||
allowedTCPPorts = mkIf (cfg.openFirewall && cfg.homekit.enable) (
|
||||
map ({ port, ... }: port) cfg.config.homekit or [ ]
|
||||
);
|
||||
|
||||
allowedUDPPortRanges = [
|
||||
(mkIf (cfg.cast.enable && cfg.cast.openFirewall) {
|
||||
from = 32768;
|
||||
to = 60999;
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
# MDNS
|
||||
services.avahi = mkIf (cfg.enable && cfg.homekit.enable) {
|
||||
enable = mkDefault true;
|
||||
publish.enable = false;
|
||||
};
|
||||
|
||||
systemd.services.home-assistant = mkIf (cfg.enable && cfg.mutableUiConfig) {
|
||||
# UI-editable config files
|
||||
preStart = mkBefore ''
|
||||
touch ${cfg.configDir}/{automations,scenes,scripts,manual,homekit_entity_config,homekit_include_entities}.yaml
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config.services.home-assistant = {
|
||||
config = mkMerge [
|
||||
{
|
||||
homeassistant = {
|
||||
external_url = "https://${cfg.domain}";
|
||||
};
|
||||
logger = {
|
||||
default = mkDefault "info";
|
||||
};
|
||||
http = {
|
||||
cors_allowed_origins = [
|
||||
"https://google.com"
|
||||
"https://www.home-assistant.io"
|
||||
];
|
||||
use_x_forwarded_for = "true";
|
||||
trusted_proxies = [
|
||||
"127.0.0.0/24"
|
||||
"200::/7"
|
||||
"100.64.0.0/10"
|
||||
"fd7a:115c:a1e0:ab12::/64"
|
||||
"::1"
|
||||
];
|
||||
};
|
||||
recorder = {
|
||||
db_url = mkIf config.services.postgresql.enable (mkDefault "postgresql://@/hass");
|
||||
};
|
||||
counter = {};
|
||||
device_tracker = {};
|
||||
energy = {};
|
||||
group = {};
|
||||
history = {};
|
||||
input_boolean = {};
|
||||
input_button = {};
|
||||
input_datetime = {};
|
||||
input_number = {};
|
||||
input_select = {};
|
||||
input_text = {};
|
||||
logbook = {};
|
||||
schedule = {};
|
||||
map = {};
|
||||
media_source = {};
|
||||
media_player = [];
|
||||
mobile_app = {};
|
||||
my = {};
|
||||
person = {};
|
||||
ssdp = {};
|
||||
switch = {};
|
||||
stream = {};
|
||||
sun = {};
|
||||
system_health = {};
|
||||
tag = {};
|
||||
template = {};
|
||||
timer = {};
|
||||
webhook = {};
|
||||
zeroconf = {};
|
||||
zone = {};
|
||||
sensor = {};
|
||||
}
|
||||
(mkIf cfg.mutableUiConfig {
|
||||
# https://nixos.wiki/wiki/Home_Assistant#Combine_declarative_and_UI_defined_automations
|
||||
"automation manual" = [];
|
||||
"automation ui" = "!include automations.yaml";
|
||||
# https://nixos.wiki/wiki/Home_Assistant#Combine_declarative_and_UI_defined_scenes
|
||||
"scene manual" = [];
|
||||
"scene ui" = "!include scenes.yaml";
|
||||
"script manual" = [];
|
||||
"script ui" = "!include scripts.yaml";
|
||||
})
|
||||
];
|
||||
package = let
|
||||
inherit (cfg.package) python;
|
||||
hasBrother = elem "brother" cfg.extraComponents;
|
||||
# https://github.com/pysnmp/pysnmp/issues/51
|
||||
needsPyasn1pin = if lib.versionOlder python.pkgs.pysnmplib.version "6.0"
|
||||
then true
|
||||
else lib.warn "pyasn1 pin likely no longer needed" false;
|
||||
pyasn1prefix = "${python.pkgs.pysnmp-pyasn1}/${python.sitePackages}";
|
||||
home-assistant = pkgs.home-assistant.override {
|
||||
packageOverrides = self: super: {
|
||||
};
|
||||
};
|
||||
in home-assistant.overrideAttrs (old: {
|
||||
makeWrapperArgs = old.makeWrapperArgs ++ optional (hasBrother && needsPyasn1pin) "--prefix PYTHONPATH : ${pyasn1prefix}";
|
||||
disabledTests = old.disabledTests or [ ] ++ [
|
||||
"test_check_config"
|
||||
];
|
||||
});
|
||||
extraPackages = python3Packages: with python3Packages; mkMerge [
|
||||
[
|
||||
psycopg2
|
||||
securetar
|
||||
getmac # for upnp integration
|
||||
python-otbr-api
|
||||
protobuf3
|
||||
(aiogithubapi.overrideAttrs (_: {doInstallCheck = false;}))
|
||||
]
|
||||
(mkIf cfg.homekit.enable [
|
||||
aiohomekit
|
||||
])
|
||||
(mkIf cfg.androidTv.enable [
|
||||
adb-shell
|
||||
(callPackage ../../packages/androidtvremote2.nix { })
|
||||
])
|
||||
];
|
||||
extraComponents = mkMerge [
|
||||
[
|
||||
"automation"
|
||||
"scene"
|
||||
"script"
|
||||
"default_config"
|
||||
"environment_canada"
|
||||
"met"
|
||||
"generic_thermostat"
|
||||
"mqtt"
|
||||
"zeroconf"
|
||||
]
|
||||
(mkIf cfg.homekit.enable [
|
||||
"homekit"
|
||||
])
|
||||
(mkIf cfg.googleAssistant.enable [
|
||||
"google"
|
||||
"google_assistant"
|
||||
"google_cloud"
|
||||
])
|
||||
(map ({ platform, ... }: platform) cfg.config.media_player or [ ])
|
||||
(map ({ platform, ... }: platform) cfg.config.tts or [ ])
|
||||
];
|
||||
};
|
||||
}
|
||||
91
modules/nixos/kanidm.nix
Normal file
91
modules/nixos/kanidm.nix
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkIf mkMerge mkDefault mkOptionDefault mkEnableOption mkOption;
|
||||
cfg = config.services.kanidm;
|
||||
in {
|
||||
options.services.kanidm = with lib.types; {
|
||||
server = {
|
||||
openFirewall = mkEnableOption "firewall ports";
|
||||
unencrypted = {
|
||||
enable = mkEnableOption "snake oil certificate";
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
default = cfg.server.frontend.domain;
|
||||
};
|
||||
package = mkOption {
|
||||
type = package;
|
||||
};
|
||||
};
|
||||
frontend = {
|
||||
domain = mkOption {
|
||||
type = nullOr str;
|
||||
default = cfg.serverSettings.domain;
|
||||
};
|
||||
address = mkOption {
|
||||
type = str;
|
||||
default = "127.0.0.1";
|
||||
};
|
||||
port = mkOption {
|
||||
type = port;
|
||||
default = 8081;
|
||||
};
|
||||
};
|
||||
ldap = {
|
||||
enable = mkEnableOption "LDAP interface";
|
||||
address = mkOption {
|
||||
type = str;
|
||||
default = "127.0.0.1";
|
||||
};
|
||||
port = mkOption {
|
||||
type = port;
|
||||
default = 636;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
networking.firewall.allowedTCPPorts = mkIf (cfg.enableServer && cfg.server.openFirewall) [
|
||||
cfg.server.frontend.port
|
||||
cfg.server.ldap.port
|
||||
];
|
||||
|
||||
services.kanidm = {
|
||||
server.unencrypted.package = let
|
||||
cert = pkgs.runCommand "kanidm-cert" {
|
||||
inherit (cfg.server.unencrypted) domain;
|
||||
nativeBuildInputs = [ pkgs.buildPackages.minica ];
|
||||
} ''
|
||||
install -d $out
|
||||
cd $out
|
||||
minica \
|
||||
--ca-key ca.key.pem \
|
||||
--ca-cert ca.cert.pem \
|
||||
--domains $domain
|
||||
cat $domain/cert.pem ca.cert.pem > $domain.pem
|
||||
'';
|
||||
in mkOptionDefault cert;
|
||||
clientSettings = mkIf cfg.enableServer {
|
||||
uri = mkDefault cfg.serverSettings.origin;
|
||||
};
|
||||
serverSettings = mkMerge [
|
||||
{
|
||||
domain = mkDefault config.networking.domain;
|
||||
origin = mkDefault "https://${cfg.server.frontend.domain}";
|
||||
bindaddress = mkDefault "${cfg.server.frontend.address}:${toString cfg.server.frontend.port}";
|
||||
ldapbindaddress = mkIf cfg.server.ldap.enable (
|
||||
mkDefault "${cfg.server.ldap.address}:${toString cfg.server.ldap.port}"
|
||||
);
|
||||
}
|
||||
(mkIf cfg.server.unencrypted.enable {
|
||||
tls_chain = "${cfg.server.unencrypted.package}/${cfg.server.unencrypted.domain}.pem";
|
||||
tls_key = "${cfg.server.unencrypted.package}/${cfg.server.unencrypted.domain}/key.pem";
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
25
modules/nixos/mosquitto.nix
Normal file
25
modules/nixos/mosquitto.nix
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkIf mkMerge mkEnableOption mkOption;
|
||||
cfg = config.services.mosquitto;
|
||||
in {
|
||||
options.services.mosquitto = with lib.types; {
|
||||
listeners = let
|
||||
listenerModule = { ... }: {
|
||||
options = {
|
||||
openFirewall = mkEnableOption "firewall";
|
||||
};
|
||||
};
|
||||
in mkOption {
|
||||
type = listOf (submodule listenerModule);
|
||||
};
|
||||
};
|
||||
config = {
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.enable (mkMerge (
|
||||
map (listener: mkIf listener.openFirewall [ listener.port ]) cfg.listeners
|
||||
));
|
||||
};
|
||||
}
|
||||
75
modules/nixos/nginx-vouch.nix
Normal file
75
modules/nixos/nginx-vouch.nix
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
inherit (config.services) vouch-proxy;
|
||||
in {
|
||||
options = with types; {
|
||||
services.nginx.virtualHosts = let
|
||||
vouchModule = { config, ... }: {
|
||||
options = {
|
||||
vouch = {
|
||||
enable = mkEnableOption "vouch auth proxy";
|
||||
proxyOrigin = mkOption {
|
||||
type = str;
|
||||
};
|
||||
authUrl = mkOption {
|
||||
type = str;
|
||||
};
|
||||
url = mkOption {
|
||||
type = str;
|
||||
};
|
||||
};
|
||||
};
|
||||
config = mkMerge [
|
||||
{
|
||||
vouch = mkIf vouch-proxy.enable {
|
||||
proxyOrigin = let
|
||||
inherit (vouch-proxy.settings.vouch) listen port;
|
||||
in mkOptionDefault "http://${listen}:${toString port}";
|
||||
authUrl = mkOptionDefault vouch-proxy.authUrl;
|
||||
url = mkOptionDefault vouch-proxy.url;
|
||||
};
|
||||
}
|
||||
(mkIf config.vouch.enable {
|
||||
extraConfig = ''
|
||||
auth_request /validate;
|
||||
error_page 401 = @error401;
|
||||
'';
|
||||
locations = {
|
||||
"/" = {
|
||||
extraConfig = ''
|
||||
add_header Access-Control-Allow-Origin ${config.vouch.url};
|
||||
add_header Access-Control-Allow-Origin ${config.vouch.authUrl};
|
||||
proxy_set_header X-Vouch-User $auth_resp_x_vouch_user;
|
||||
'';
|
||||
};
|
||||
"@error401" = {
|
||||
extraConfig = ''
|
||||
return 302 ${config.vouch.url}/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
|
||||
'';
|
||||
};
|
||||
"/validate" = {
|
||||
recommendedProxySettings = false;
|
||||
proxyPass = "${config.vouch.proxyOrigin}/validate";
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
|
||||
auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
|
||||
auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
|
||||
auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
|
||||
'';
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
in mkOption {
|
||||
type = attrsOf (submodule vouchModule);
|
||||
};
|
||||
};
|
||||
}
|
||||
163
modules/nixos/vouch.nix
Normal file
163
modules/nixos/vouch.nix
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
{
|
||||
config,
|
||||
utils,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkIf mkMerge mkDefault mkOptionDefault mkOption mkEnableOption types
|
||||
getExe;
|
||||
nixosConfig = config;
|
||||
cfg = config.services.vouch-proxy;
|
||||
settingsFormat = pkgs.formats.json { };
|
||||
in {
|
||||
options.services.vouch-proxy = with types; {
|
||||
enable = mkEnableOption "vouch";
|
||||
user = mkOption {
|
||||
type = str;
|
||||
default = "vouch-proxy";
|
||||
};
|
||||
group = mkOption {
|
||||
type = str;
|
||||
default = "vouch-proxy";
|
||||
};
|
||||
authUrl = mkOption {
|
||||
type = str;
|
||||
default = config.services.kanidm.serverSettings.origin;
|
||||
};
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
default = config.networking.domain;
|
||||
};
|
||||
url = mkOption {
|
||||
type = str;
|
||||
default = "https://${cfg.domain}";
|
||||
};
|
||||
enableSettingsSecrets = mkEnableOption "genJqSecretsReplacementSnippet";
|
||||
settings = let
|
||||
settingsModule = { ... }: {
|
||||
freeformType = settingsFormat.type;
|
||||
options = {
|
||||
vouch = {
|
||||
cookie = {
|
||||
domain = mkOption {
|
||||
type = nullOr str;
|
||||
default = nixosConfig.networking.domain;
|
||||
};
|
||||
secure = mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
port = mkOption {
|
||||
type = port;
|
||||
default = 30746;
|
||||
};
|
||||
listen = mkOption {
|
||||
type = nullOr str;
|
||||
default = "127.0.0.1";
|
||||
};
|
||||
allowAllUsers = mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
oauth = {
|
||||
auth_url = mkOption {
|
||||
type = str;
|
||||
default = "${cfg.authUrl}/ui/oauth2";
|
||||
};
|
||||
token_url = mkOption {
|
||||
type = str;
|
||||
default = "${cfg.authUrl}/oauth2/token";
|
||||
};
|
||||
user_info_url = mkOption {
|
||||
type = str;
|
||||
default = "${cfg.authUrl}/oauth2/openid/vouch/userinfo";
|
||||
};
|
||||
scopes = mkOption {
|
||||
type = listOf str;
|
||||
default = ["openid" "email" "profile"];
|
||||
};
|
||||
callback_url = mkOption {
|
||||
type = str;
|
||||
default = "${cfg.url}/auth";
|
||||
};
|
||||
provider = mkOption {
|
||||
type = nullOr str;
|
||||
default = "oidc";
|
||||
};
|
||||
code_challenge_method = mkOption {
|
||||
type = str;
|
||||
default = "S256";
|
||||
};
|
||||
client_id = mkOption {
|
||||
type = str;
|
||||
default = "vouch";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in mkOption {
|
||||
type = submodule settingsModule;
|
||||
default = { };
|
||||
};
|
||||
extraSettings = mkOption {
|
||||
inherit (settingsFormat) type;
|
||||
default = { };
|
||||
};
|
||||
settingsPath = mkOption {
|
||||
type = path;
|
||||
};
|
||||
};
|
||||
config = let
|
||||
recursiveMergeAttrs = listOfAttrsets: lib.fold (attrset: acc: lib.recursiveUpdate attrset acc) {} listOfAttrsets;
|
||||
settings = recursiveMergeAttrs [
|
||||
cfg.settings
|
||||
cfg.extraSettings
|
||||
];
|
||||
settingsPath = if cfg.enableSettingsSecrets
|
||||
then "/run/vouch-proxy/vouch-config.json"
|
||||
else settingsFormat.generate "vouch-config.json" settings;
|
||||
in mkMerge [
|
||||
{
|
||||
services.vouch-proxy = {
|
||||
settingsPath = mkOptionDefault settingsPath;
|
||||
};
|
||||
}
|
||||
(mkIf cfg.enable {
|
||||
systemd.services.vouch-proxy = {
|
||||
description = "Vouch-proxy";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStartPre = let
|
||||
preprocess = pkgs.writeShellScript "vouch-proxy-prestart" (
|
||||
utils.genJqSecretsReplacementSnippet settings cfg.settingsPath
|
||||
);
|
||||
in mkIf cfg.enableSettingsSecrets [
|
||||
"${preprocess}"
|
||||
];
|
||||
ExecStart = [
|
||||
"${getExe pkgs.vouch-proxy} -config ${cfg.settingsPath}"
|
||||
];
|
||||
Restart = "on-failure";
|
||||
RestartSec = mkDefault 5;
|
||||
WorkingDirectory = "/var/lib/vouch-proxy";
|
||||
StateDirectory = "vouch-proxy";
|
||||
RuntimeDirectory = "vouch-proxy";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
StartLimitBurst = mkDefault 3;
|
||||
};
|
||||
};
|
||||
|
||||
users.users.${cfg.user} = {
|
||||
inherit (cfg) group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = {};
|
||||
})
|
||||
];
|
||||
}
|
||||
22
modules/nixos/zigbee2mqtt.nix
Normal file
22
modules/nixos/zigbee2mqtt.nix
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkIf mkEnableOption mkOption;
|
||||
cfg = config.services.zigbee2mqtt;
|
||||
in {
|
||||
options.services.zigbee2mqtt = with lib.types; {
|
||||
openFirewall = mkEnableOption "firewall port";
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
default = config.networking.domain;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
networking.firewall.allowedTCPPorts = mkIf (cfg.enable && cfg.openFirewall) [
|
||||
cfg.settings.frontend.port
|
||||
];
|
||||
};
|
||||
}
|
||||
12
nixos/access/gensokyo.nix
Normal file
12
nixos/access/gensokyo.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
services.nginx.virtualHosts.${config.networking.domain} = {
|
||||
locations."/" = {
|
||||
root = pkgs.gensokyoZone;
|
||||
};
|
||||
};
|
||||
}
|
||||
13
nixos/access/nextcloud.nix
Normal file
13
nixos/access/nextcloud.nix
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
meta,
|
||||
...
|
||||
}:
|
||||
with lib; {
|
||||
services.nginx.virtualHosts."cloud.${config.networking.domain}" = {
|
||||
locations = {
|
||||
"/".proxyPass = meta.tailnet.yukari.ppp 4 80 "nextcloud/";
|
||||
};
|
||||
};
|
||||
}
|
||||
33
nixos/access/plex.nix
Normal file
33
nixos/access/plex.nix
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
meta,
|
||||
...
|
||||
}:
|
||||
with lib; {
|
||||
services.nginx.virtualHosts."plex.${config.networking.domain}" = {
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = meta.tailnet.yukari.pp 4 32400;
|
||||
extraConfig = ''
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_redirect off;
|
||||
proxy_buffering off;
|
||||
proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier;
|
||||
proxy_set_header X-Plex-Device $http_x_plex_device;
|
||||
proxy_set_header X-Plex-Device-Name $http_x_plex_device_name;
|
||||
proxy_set_header X-Plex-Platform $http_x_plex_platform;
|
||||
proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version;
|
||||
proxy_set_header X-Plex-Product $http_x_plex_product;
|
||||
proxy_set_header X-Plex-Token $http_x_plex_token;
|
||||
proxy_set_header X-Plex-Version $http_x_plex_version;
|
||||
proxy_set_header X-Plex-Nocache $http_x_plex_nocache;
|
||||
proxy_set_header X-Plex-Provides $http_x_plex_provides;
|
||||
proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor;
|
||||
proxy_set_header X-Plex-Model $http_x_plex_model;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
22
nixos/access/zigbee2mqtt.nix
Normal file
22
nixos/access/zigbee2mqtt.nix
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.services.zigbee2mqtt;
|
||||
in {
|
||||
services.nginx.virtualHosts.${cfg.domain} = {
|
||||
vouch.enable = true;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://127.0.0.1:${toString cfg.settings.frontend.port}";
|
||||
extraConfig = ''
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_http_version 1.1;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
5
nixos/cloudflared.nix
Normal file
5
nixos/cloudflared.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{ config, lib, ... }: let
|
||||
inherit (lib.modules) mkDefault;
|
||||
in {
|
||||
config.services.cloudflared.enable = mkDefault true;
|
||||
}
|
||||
32
nixos/deluge.nix
Normal file
32
nixos/deluge.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{ config, lib, ... }: let
|
||||
inherit (lib) mkDefault;
|
||||
cfg = config.services.deluge;
|
||||
in {
|
||||
sops.secrets.deluge-auth = {
|
||||
inherit (cfg) group;
|
||||
owner = cfg.user;
|
||||
};
|
||||
services.deluge = {
|
||||
enable = mkDefault true;
|
||||
declarative = mkDefault true;
|
||||
openFirewall = mkDefault true;
|
||||
web = {
|
||||
enable = true;
|
||||
};
|
||||
config = {
|
||||
max_upload_speed = 10.0;
|
||||
#share_ratio_limit = 2.0;
|
||||
max_connections_global = 1024;
|
||||
max_connections_per_second = 50;
|
||||
max_active_limit = 100;
|
||||
max_active_downloading = 75;
|
||||
max_upload_slots_global = 25;
|
||||
max_active_seeding = 1;
|
||||
allow_remote = true;
|
||||
daemon_port = 58846;
|
||||
listen_ports = [ 6881 6889 ];
|
||||
random_port = false;
|
||||
};
|
||||
authFile = config.sops.secrets.deluge-auth.path;
|
||||
};
|
||||
}
|
||||
133
nixos/home-assistant.nix
Normal file
133
nixos/home-assistant.nix
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
cfg = config.services.home-assistant;
|
||||
inherit (lib.modules) mkDefault;
|
||||
inherit (lib.lists) optional;
|
||||
in {
|
||||
sops.secrets = {
|
||||
ha-integration = {
|
||||
owner = "hass";
|
||||
path = "${cfg.configDir}/integration.yaml";
|
||||
};
|
||||
ha-secrets = {
|
||||
owner = "hass";
|
||||
path = "${cfg.configDir}/secrets.yaml";
|
||||
};
|
||||
};
|
||||
|
||||
services.home-assistant = {
|
||||
enable = mkDefault true;
|
||||
openFirewall = mkDefault true;
|
||||
mutableUiConfig = mkDefault true;
|
||||
domain = mkDefault "home.${config.networking.domain}";
|
||||
config = {
|
||||
homeassistant = {
|
||||
name = "Gensokyo";
|
||||
unit_system = "metric";
|
||||
latitude = "!secret home_lat";
|
||||
longitude = "!secret home_long";
|
||||
elevation = "!secret home_asl";
|
||||
currency = "CAD";
|
||||
country = "CA";
|
||||
time_zone = "America/Vancouver";
|
||||
packages = {
|
||||
manual = "!include manual.yaml";
|
||||
};
|
||||
};
|
||||
frontend = {
|
||||
themes = "!include_dir_merge_named themes";
|
||||
};
|
||||
powercalc = {
|
||||
};
|
||||
utility_meter = {
|
||||
};
|
||||
withings = {
|
||||
use_webhook = true;
|
||||
};
|
||||
recorder = {
|
||||
auto_purge = true;
|
||||
purge_keep_days = 14;
|
||||
commit_interval = 1;
|
||||
exclude = {
|
||||
domains = [
|
||||
"automation"
|
||||
"updater"
|
||||
];
|
||||
entity_globs = [
|
||||
"sensor.weather_*"
|
||||
"sensor.date_*"
|
||||
];
|
||||
entities = [
|
||||
"sun.sun"
|
||||
"sensor.last_boot"
|
||||
"sensor.date"
|
||||
"sensor.time"
|
||||
];
|
||||
event_types = [
|
||||
"call_service"
|
||||
];
|
||||
};
|
||||
};
|
||||
google_assistant = {
|
||||
project_id = "gensokyo-5cfaf";
|
||||
service_account = "!include integration.yaml";
|
||||
report_state = true;
|
||||
exposed_domains = [
|
||||
"scene"
|
||||
"script"
|
||||
"climate"
|
||||
#"sensor"
|
||||
];
|
||||
entity_config = {};
|
||||
};
|
||||
homekit = [ {
|
||||
name = "Tewi";
|
||||
port = 21063;
|
||||
ip_address = "10.1.1.38";
|
||||
filter = let
|
||||
inherit (cfg.config) google_assistant;
|
||||
in {
|
||||
include_domains = google_assistant.exposed_domains;
|
||||
include_entities = "!include homekit_include_entities.yaml";
|
||||
};
|
||||
entity_config = "!include homekit_entity_config.yaml";
|
||||
} ];
|
||||
tts = [
|
||||
{
|
||||
platform = "google_translate";
|
||||
service_name = "google_say";
|
||||
}
|
||||
];
|
||||
media_player = [
|
||||
{
|
||||
platform = "mpd";
|
||||
name = "Shanghai MPD";
|
||||
host = "shanghai.local.cutie.moe";
|
||||
password = "!secret mpd-shanghai-password";
|
||||
}
|
||||
];
|
||||
prometheus = {};
|
||||
wake_on_lan = {};
|
||||
};
|
||||
extraComponents = [
|
||||
"zha"
|
||||
"esphome"
|
||||
"apple_tv"
|
||||
"spotify"
|
||||
"brother"
|
||||
"ipp"
|
||||
"androidtv"
|
||||
"cast"
|
||||
"plex"
|
||||
"shopping_list"
|
||||
"tile"
|
||||
"wake_on_lan"
|
||||
"withings"
|
||||
"wled"
|
||||
];
|
||||
};
|
||||
}
|
||||
33
nixos/kanidm.nix
Normal file
33
nixos/kanidm.nix
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkDefault;
|
||||
cfg = config.services.kanidm;
|
||||
in {
|
||||
services.kanidm = {
|
||||
enableServer = true;
|
||||
enableClient = true;
|
||||
server = {
|
||||
unencrypted.enable = mkDefault true;
|
||||
openFirewall = mkDefault true;
|
||||
frontend = {
|
||||
domain = mkDefault "id.${cfg.serverSettings.domain}";
|
||||
address = mkDefault "0.0.0.0";
|
||||
};
|
||||
ldap = {
|
||||
enable = mkDefault true;
|
||||
address = mkDefault "0.0.0.0";
|
||||
};
|
||||
};
|
||||
clientSettings = {
|
||||
verify_ca = mkDefault true;
|
||||
verify_hostnames = mkDefault true;
|
||||
};
|
||||
serverSettings = {
|
||||
role = mkDefault "WriteReplica";
|
||||
log_level = mkDefault "info";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -2,11 +2,9 @@
|
|||
config,
|
||||
lib,
|
||||
...
|
||||
}: {
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
1883
|
||||
];
|
||||
|
||||
}: let
|
||||
inherit (lib) mkDefault;
|
||||
in {
|
||||
sops.secrets = {
|
||||
z2m-pass.owner = "mosquitto";
|
||||
systemd-pass.owner = "mosquitto";
|
||||
|
|
@ -15,10 +13,11 @@
|
|||
};
|
||||
|
||||
services.mosquitto = {
|
||||
enable = true;
|
||||
persistence = true;
|
||||
enable = mkDefault true;
|
||||
persistence = mkDefault true;
|
||||
listeners = [
|
||||
{
|
||||
openFirewall = mkDefault true;
|
||||
acl = [
|
||||
"pattern readwrite #"
|
||||
];
|
||||
|
|
@ -49,7 +48,7 @@
|
|||
};
|
||||
};
|
||||
settings = {
|
||||
allow_anonymous = false;
|
||||
allow_anonymous = mkDefault false;
|
||||
};
|
||||
}
|
||||
];
|
||||
|
|
@ -29,7 +29,5 @@ with lib; {
|
|||
#proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
||||
'';
|
||||
clientMaxBodySize = "512m";
|
||||
virtualHosts = {
|
||||
};
|
||||
};
|
||||
}
|
||||
8
nixos/reisen-ct/filesystem.nix
Normal file
8
nixos/reisen-ct/filesystem.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkDefault;
|
||||
in {
|
||||
services.kanidm.serverSettings.db_fs_type = mkDefault "zfs";
|
||||
}
|
||||
26
nixos/vouch.nix
Normal file
26
nixos/vouch.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkDefault;
|
||||
cfg = config.services.vouch-proxy;
|
||||
in {
|
||||
services.vouch-proxy = {
|
||||
enable = mkDefault true;
|
||||
domain = mkDefault "login.${config.networking.domain}";
|
||||
settings = {
|
||||
vouch.cookie.secure = mkDefault false;
|
||||
};
|
||||
enableSettingsSecrets = mkDefault true;
|
||||
extraSettings = {
|
||||
oauth.client_secret._secret = config.sops.secrets.vouch-client-secret.path;
|
||||
vouch.jwt.secret._secret = config.sops.secrets.vouch-jwt.path;
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets = {
|
||||
vouch-jwt.owner = cfg.user;
|
||||
vouch-client-secret.owner = cfg.user;
|
||||
};
|
||||
}
|
||||
|
|
@ -2,28 +2,27 @@
|
|||
config,
|
||||
lib,
|
||||
...
|
||||
}: {
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
# Zigbee2MQTT Frontend
|
||||
8072
|
||||
];
|
||||
|
||||
}: let
|
||||
cfg = config.services.zigbee2mqtt;
|
||||
inherit (lib) mkDefault;
|
||||
in {
|
||||
sops.secrets.z2m-secret = {
|
||||
owner = "zigbee2mqtt";
|
||||
path = "${config.services.zigbee2mqtt.dataDir}/secret.yaml";
|
||||
path = "${cfg.dataDir}/secret.yaml";
|
||||
};
|
||||
|
||||
users.groups.input.members = ["zigbee2mqtt"];
|
||||
users.groups.input.members = [ "zigbee2mqtt" ];
|
||||
|
||||
services.zigbee2mqtt = {
|
||||
enable = true;
|
||||
enable = mkDefault true;
|
||||
openFirewall = mkDefault true;
|
||||
domain = mkDefault "z2m.${config.networking.domain}";
|
||||
settings = {
|
||||
advanced = {
|
||||
log_level = "info";
|
||||
network_key = "!secret network_key";
|
||||
};
|
||||
mqtt = {
|
||||
server = "mqtt://127.0.0.1:1883";
|
||||
user = "z2m";
|
||||
password = "!secret z2m_pass";
|
||||
};
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
meta,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; {
|
||||
services.nginx.virtualHosts = mkMerge [
|
||||
(mkIf (config.networking.hostName == "tewi") {
|
||||
"gensokyo.zone" = {
|
||||
locations."/" = {
|
||||
root = pkgs.gensokyoZone;
|
||||
};
|
||||
};
|
||||
"z2m.gensokyo.zone" = {
|
||||
extraConfig = ''
|
||||
auth_request /validate;
|
||||
error_page 401 = @error401;
|
||||
'';
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://127.0.0.1:8072";
|
||||
extraConfig = ''
|
||||
add_header Access-Control-Allow-Origin https://login.gensokyo.zone;
|
||||
add_header Access-Control-Allow-Origin https://id.gensokyo.zone;
|
||||
proxy_set_header X-Vouch-User $auth_resp_x_vouch_user;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_http_version 1.1;
|
||||
'';
|
||||
};
|
||||
"@error401" = {
|
||||
extraConfig = ''
|
||||
return 302 https://login.gensokyo.zone/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
|
||||
'';
|
||||
};
|
||||
"/validate" = {
|
||||
recommendedProxySettings = false;
|
||||
proxyPass = "http://127.0.0.1:30746/validate";
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
|
||||
auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
|
||||
auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
|
||||
auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
(mkIf (config.networking.hostName != "tewi") {
|
||||
"home.${config.networking.domain}" = {
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = meta.tailnet.yukari.pp 4 8123;
|
||||
extraConfig = ''
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_http_version 1.1;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
"cloud.kittywit.ch" = {
|
||||
locations = {
|
||||
"/".proxyPass = meta.tailnet.yukari.ppp 4 80 "nextcloud/";
|
||||
};
|
||||
};
|
||||
"plex.kittywit.ch" = {
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = meta.tailnet.yukari.pp 4 32400;
|
||||
extraConfig = ''
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_redirect off;
|
||||
proxy_buffering off;
|
||||
proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier;
|
||||
proxy_set_header X-Plex-Device $http_x_plex_device;
|
||||
proxy_set_header X-Plex-Device-Name $http_x_plex_device_name;
|
||||
proxy_set_header X-Plex-Platform $http_x_plex_platform;
|
||||
proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version;
|
||||
proxy_set_header X-Plex-Product $http_x_plex_product;
|
||||
proxy_set_header X-Plex-Token $http_x_plex_token;
|
||||
proxy_set_header X-Plex-Version $http_x_plex_version;
|
||||
proxy_set_header X-Plex-Nocache $http_x_plex_nocache;
|
||||
proxy_set_header X-Plex-Provides $http_x_plex_provides;
|
||||
proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor;
|
||||
proxy_set_header X-Plex-Model $http_x_plex_model;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
|
|
@ -1,75 +1,31 @@
|
|||
{ pkgs, config, utils, lib, ... }: let
|
||||
inherit (lib) mapAttrsToList mapAttrs' nameValuePair splitString last singleton
|
||||
mkIf mkMerge mkForce;
|
||||
{ config, lib, ... }: let
|
||||
inherit (config) services;
|
||||
inherit (services.kanidm.serverSettings) domain;
|
||||
cfg = services.cloudflared;
|
||||
apartment = "131222b0-9db0-4168-96f5-7d45ec51c3be";
|
||||
shadowTunnel = {
|
||||
${apartment}.ingress.deluge = {
|
||||
hostname._secret = config.sops.secrets.cloudflared-tunnel-apartment-deluge.path;
|
||||
service = "http://localhost:${toString services.deluge.web.port}";
|
||||
};
|
||||
};
|
||||
in {
|
||||
sops.secrets.cloudflared-tunnel-apartment.owner = services.cloudflared.user;
|
||||
sops.secrets.cloudflared-tunnel-apartment-deluge.owner = services.cloudflared.user;
|
||||
services.cloudflared = {
|
||||
enable = true;
|
||||
tunnels = {
|
||||
${apartment} = {
|
||||
credentialsFile = config.sops.secrets.cloudflared-tunnel-apartment.path;
|
||||
default = "http_status:404";
|
||||
ingress = mapAttrs' (prefix: nameValuePair "${prefix}${domain}") {
|
||||
"".service = "http://localhost:80";
|
||||
"home.".service = "http://localhost:${toString services.home-assistant.config.http.server_port}";
|
||||
"z2m.".service = "http://localhost:80";
|
||||
"login.".service = "http://localhost:${toString services.vouch-proxy.settings.vouch.port}";
|
||||
"id." = let
|
||||
inherit (services.kanidm.serverSettings) bindaddress;
|
||||
port = last (splitString ":" bindaddress);
|
||||
in {
|
||||
service = "https://127.0.0.1:${port}";
|
||||
ingress = {
|
||||
${config.networking.domain}.service = "http://localhost:80";
|
||||
${services.home-assistant.domain}.service = "http://localhost:${toString services.home-assistant.config.http.server_port}";
|
||||
${services.zigbee2mqtt.domain}.service = "http://localhost:80";
|
||||
${services.vouch-proxy.domain}.service = "http://localhost:${toString services.vouch-proxy.settings.vouch.port}";
|
||||
${services.kanidm.server.frontend.domain} = {
|
||||
service = "https://127.0.0.1:${toString services.kanidm.server.frontend.port}";
|
||||
originRequest.noTLSVerify = true;
|
||||
};
|
||||
};
|
||||
extraTunnel.ingress = {
|
||||
deluge = {
|
||||
hostname._secret = config.sops.secrets.cloudflared-tunnel-apartment-deluge.path;
|
||||
service = "http://localhost:${toString services.deluge.web.port}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
systemd.services = let
|
||||
filterConfig = lib.filterAttrsRecursive (_: v: ! builtins.elem v [ null [ ] { } ]);
|
||||
mapIngress = hostname: ingress: {
|
||||
inherit hostname;
|
||||
} // filterConfig (filterConfig ingress);
|
||||
in mkIf cfg.enable (mapAttrs' (uuid: tunnel: let
|
||||
RuntimeDirectory = "cloudflared-tunnel-${uuid}";
|
||||
configPath = "/run/${RuntimeDirectory}/config.yml";
|
||||
settings = {
|
||||
tunnel = uuid;
|
||||
credentials-file = tunnel.credentialsFile;
|
||||
ingress = mapAttrsToList mapIngress tunnel.ingress
|
||||
++ mapAttrsToList mapIngress shadowTunnel.${uuid}.ingress or { }
|
||||
++ singleton { service = tunnel.default; };
|
||||
};
|
||||
in nameValuePair "cloudflared-tunnel-${uuid}" (mkMerge [
|
||||
{
|
||||
after = [ "tailscale-autoconnect.service" ];
|
||||
serviceConfig = {
|
||||
RestartSec = 10;
|
||||
};
|
||||
}
|
||||
(mkIf (shadowTunnel.${uuid} or { } != { }) {
|
||||
serviceConfig = {
|
||||
inherit RuntimeDirectory;
|
||||
ExecStart = mkForce [
|
||||
"${cfg.package}/bin/cloudflared tunnel --config=${configPath} --no-autoupdate run"
|
||||
];
|
||||
ExecStartPre = [
|
||||
(pkgs.writeShellScript "cloudflared-tunnel-${uuid}-prepare" ''
|
||||
${utils.genJqSecretsReplacementSnippet settings configPath}
|
||||
'')
|
||||
];
|
||||
};
|
||||
})
|
||||
])) cfg.tunnels);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,35 +4,12 @@
|
|||
shadowDir = "/mnt/shadow";
|
||||
mediaDir = "${shadowDir}/deluge";
|
||||
in {
|
||||
sops.secrets.deluge-auth = {
|
||||
inherit (cfg) group;
|
||||
owner = cfg.user;
|
||||
};
|
||||
services.deluge = {
|
||||
enable = true;
|
||||
declarative = true;
|
||||
openFirewall = true;
|
||||
web = {
|
||||
enable = true;
|
||||
};
|
||||
config = {
|
||||
download_location = "${mediaDir}/download";
|
||||
move_completed_path = "${mediaDir}/complete";
|
||||
move_completed = true;
|
||||
max_upload_speed = 10.0;
|
||||
#share_ratio_limit = 2.0;
|
||||
max_connections_global = 1024;
|
||||
max_connections_per_second = 50;
|
||||
max_active_limit = 100;
|
||||
max_active_downloading = 75;
|
||||
max_upload_slots_global = 25;
|
||||
max_active_seeding = 1;
|
||||
allow_remote = true;
|
||||
daemon_port = 58846;
|
||||
listen_ports = [ 6881 6889 ];
|
||||
random_port = false;
|
||||
};
|
||||
authFile = config.sops.secrets.deluge-auth.path;
|
||||
};
|
||||
systemd.services = {
|
||||
deluged = {
|
||||
|
|
|
|||
|
|
@ -1,260 +0,0 @@
|
|||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
cfg = config.services.home-assistant;
|
||||
inherit (lib.attrsets) attrNames filterAttrs mapAttrs' nameValuePair;
|
||||
inherit (lib.strings) hasPrefix;
|
||||
in {
|
||||
# MDNS
|
||||
services.avahi.enable = true;
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
# Home Assistant
|
||||
cfg.config.http.server_port
|
||||
] ++ map ({ port, ... }: port) cfg.config.homekit;
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
# MDNS
|
||||
5353
|
||||
];
|
||||
|
||||
networking.firewall.allowedUDPPortRanges = [
|
||||
# Chromecast
|
||||
{
|
||||
from = 32768;
|
||||
to = 60999;
|
||||
}
|
||||
];
|
||||
|
||||
sops.secrets = {
|
||||
ha-integration = {
|
||||
owner = "hass";
|
||||
path = "${cfg.configDir}/integration.yaml";
|
||||
};
|
||||
ha-secrets = {
|
||||
owner = "hass";
|
||||
path = "${cfg.configDir}/secrets.yaml";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.home-assistant = {
|
||||
# UI-editable config files
|
||||
preStart = lib.mkBefore ''
|
||||
touch ${cfg.configDir}/{automations,scenes,scripts,manual,homekit_entity_config,homekit_include_entities}.yaml
|
||||
'';
|
||||
};
|
||||
|
||||
services.home-assistant = {
|
||||
enable = true;
|
||||
config = {
|
||||
homeassistant = {
|
||||
name = "Gensokyo";
|
||||
unit_system = "metric";
|
||||
latitude = "!secret home_lat";
|
||||
longitude = "!secret home_long";
|
||||
elevation = "!secret home_asl";
|
||||
currency = "CAD";
|
||||
country = "CA";
|
||||
time_zone = "America/Vancouver";
|
||||
external_url = "https://home.gensokyo.zone";
|
||||
packages = {
|
||||
manual = "!include manual.yaml";
|
||||
};
|
||||
};
|
||||
frontend = {
|
||||
themes = "!include_dir_merge_named themes";
|
||||
};
|
||||
powercalc = {
|
||||
};
|
||||
utility_meter = {
|
||||
};
|
||||
withings = {
|
||||
use_webhook = true;
|
||||
};
|
||||
logger = {
|
||||
default = "info";
|
||||
};
|
||||
http = {
|
||||
cors_allowed_origins = [
|
||||
"https://google.com"
|
||||
"https://www.home-assistant.io"
|
||||
];
|
||||
use_x_forwarded_for = "true";
|
||||
trusted_proxies = [
|
||||
"127.0.0.0/24"
|
||||
"200::/7"
|
||||
"100.64.0.0/10"
|
||||
"fd7a:115c:a1e0:ab12::/64"
|
||||
"::1"
|
||||
];
|
||||
};
|
||||
recorder = {
|
||||
db_url = "postgresql://@/hass";
|
||||
auto_purge = true;
|
||||
purge_keep_days = 14;
|
||||
commit_interval = 1;
|
||||
exclude = {
|
||||
domains = [
|
||||
"automation"
|
||||
"updater"
|
||||
];
|
||||
entity_globs = [
|
||||
"sensor.weather_*"
|
||||
"sensor.date_*"
|
||||
];
|
||||
entities = [
|
||||
"sun.sun"
|
||||
"sensor.last_boot"
|
||||
"sensor.date"
|
||||
"sensor.time"
|
||||
];
|
||||
event_types = [
|
||||
"call_service"
|
||||
];
|
||||
};
|
||||
};
|
||||
google_assistant = {
|
||||
project_id = "gensokyo-5cfaf";
|
||||
service_account = "!include integration.yaml";
|
||||
report_state = true;
|
||||
exposed_domains = [
|
||||
"scene"
|
||||
"script"
|
||||
"climate"
|
||||
#"sensor"
|
||||
];
|
||||
entity_config = {};
|
||||
};
|
||||
homekit = [ {
|
||||
name = "Tewi";
|
||||
port = 21063;
|
||||
ip_address = "10.1.1.38";
|
||||
filter = let
|
||||
inherit (cfg.config) google_assistant;
|
||||
in {
|
||||
include_domains = google_assistant.exposed_domains;
|
||||
include_entities = "!include homekit_include_entities.yaml";
|
||||
};
|
||||
entity_config = "!include homekit_entity_config.yaml";
|
||||
} ];
|
||||
tts = [
|
||||
{
|
||||
platform = "google_translate";
|
||||
service_name = "google_say";
|
||||
}
|
||||
];
|
||||
# https://nixos.wiki/wiki/Home_Assistant#Combine_declarative_and_UI_defined_automations
|
||||
"automation manual" = [];
|
||||
"automation ui" = "!include automations.yaml";
|
||||
# https://nixos.wiki/wiki/Home_Assistant#Combine_declarative_and_UI_defined_scenes
|
||||
"scene manual" = [];
|
||||
"scene ui" = "!include scenes.yaml";
|
||||
"script manual" = [];
|
||||
"script ui" = "!include scripts.yaml";
|
||||
counter = {};
|
||||
device_tracker = {};
|
||||
energy = {};
|
||||
group = {};
|
||||
history = {};
|
||||
input_boolean = {};
|
||||
input_button = {};
|
||||
input_datetime = {};
|
||||
input_number = {};
|
||||
input_select = {};
|
||||
input_text = {};
|
||||
logbook = {};
|
||||
schedule = {};
|
||||
map = {};
|
||||
media_source = {};
|
||||
media_player = [
|
||||
{
|
||||
platform = "mpd";
|
||||
name = "Shanghai MPD";
|
||||
host = "10.1.1.32";
|
||||
password = "!secret mpd-shanghai-password";
|
||||
}
|
||||
];
|
||||
mobile_app = {};
|
||||
my = {};
|
||||
person = {};
|
||||
prometheus = {};
|
||||
ssdp = {};
|
||||
switch = {};
|
||||
stream = {};
|
||||
sun = {};
|
||||
system_health = {};
|
||||
tag = {};
|
||||
template = {};
|
||||
timer = {};
|
||||
webhook = {};
|
||||
wake_on_lan = {};
|
||||
zeroconf = {};
|
||||
zone = {};
|
||||
sensor = {};
|
||||
};
|
||||
package = let
|
||||
inherit (lib) warn versionOlder elem;
|
||||
inherit (cfg.package) python;
|
||||
hasBrother = elem "brother" cfg.extraComponents;
|
||||
# https://github.com/pysnmp/pysnmp/issues/51
|
||||
needsPyasn1pin = if versionOlder python.pkgs.pysnmplib.version "6.0"
|
||||
then true
|
||||
else warn "pyasn1 pin likely no longer needed" false;
|
||||
pyasn1prefix = "${python.pkgs.pysnmp-pyasn1}/${python.sitePackages}";
|
||||
home-assistant = pkgs.home-assistant.override {
|
||||
packageOverrides = self: super: {
|
||||
};
|
||||
};
|
||||
in home-assistant.overrideAttrs (old: {
|
||||
makeWrapperArgs = old.makeWrapperArgs ++ lib.optional (hasBrother && needsPyasn1pin) "--prefix PYTHONPATH : ${pyasn1prefix}";
|
||||
disabledTests = old.disabledTests or [ ] ++ [
|
||||
"test_check_config"
|
||||
];
|
||||
});
|
||||
extraPackages = python3Packages:
|
||||
with python3Packages; [
|
||||
psycopg2
|
||||
aiohomekit
|
||||
securetar
|
||||
getmac # for upnp integration
|
||||
python-otbr-api
|
||||
protobuf3
|
||||
adb-shell
|
||||
(callPackage ./androidtvremote2.nix { })
|
||||
(aiogithubapi.overrideAttrs (_: {doInstallCheck = false;}))
|
||||
];
|
||||
extraComponents = [
|
||||
"automation"
|
||||
"scene"
|
||||
"script"
|
||||
"zha"
|
||||
"esphome"
|
||||
"apple_tv"
|
||||
"spotify"
|
||||
"default_config"
|
||||
"brother"
|
||||
"ipp"
|
||||
"androidtv"
|
||||
"cast"
|
||||
"plex"
|
||||
"environment_canada"
|
||||
"met"
|
||||
"generic_thermostat"
|
||||
"google"
|
||||
"google_assistant"
|
||||
"google_cloud"
|
||||
"google_translate"
|
||||
"homekit"
|
||||
"mpd"
|
||||
"mqtt"
|
||||
"shopping_list"
|
||||
"tile"
|
||||
"wake_on_lan"
|
||||
"withings"
|
||||
"wled"
|
||||
"zeroconf"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
{
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
conf = import ./snakeoil-certs.nix;
|
||||
domain = conf.domain;
|
||||
unencryptedCert = with pkgs;
|
||||
runCommand "kanidm-cert" {
|
||||
domain = "id.gensokyo.zone";
|
||||
nativeBuildInputs = [minica];
|
||||
} ''
|
||||
install -d $out
|
||||
cd $out
|
||||
minica \
|
||||
--ca-key ca.key.pem \
|
||||
--ca-cert ca.cert.pem \
|
||||
--domains $domain
|
||||
cat $domain/cert.pem ca.cert.pem > $domain.pem
|
||||
'';
|
||||
in {
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
8081
|
||||
636
|
||||
];
|
||||
|
||||
services.kanidm = {
|
||||
enableServer = true;
|
||||
enablePam = false;
|
||||
enableClient = true;
|
||||
clientSettings = {
|
||||
uri = "https://id.gensokyo.zone";
|
||||
verify_ca = true;
|
||||
verify_hostnames = true;
|
||||
};
|
||||
serverSettings = {
|
||||
domain = "gensokyo.zone";
|
||||
origin = "https://id.gensokyo.zone";
|
||||
role = "WriteReplica";
|
||||
log_level = "info";
|
||||
db_fs_type = "zfs";
|
||||
bindaddress = "0.0.0.0:8081";
|
||||
ldapbindaddress = "0.0.0.0:636";
|
||||
tls_chain = "${unencryptedCert}/${unencryptedCert.domain}.pem";
|
||||
tls_key = "${unencryptedCert}/${unencryptedCert.domain}/key.pem";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -43,16 +43,19 @@ in {
|
|||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
nixos.sops
|
||||
nixos.tailscale
|
||||
nixos.cloudflared
|
||||
nixos.nginx
|
||||
nixos.access.gensokyo
|
||||
nixos.access.zigbee2mqtt
|
||||
nixos.postgres
|
||||
nixos.vouch
|
||||
nixos.kanidm
|
||||
nixos.mosquitto
|
||||
nixos.zigbee2mqtt
|
||||
nixos.deluge
|
||||
nixos.syncplay
|
||||
nixos.home-assistant
|
||||
inputs.systemd2mqtt.nixosModules.default
|
||||
./access.nix
|
||||
./syncplay.nix
|
||||
./kanidm.nix
|
||||
./vouch.nix
|
||||
./home-assistant.nix
|
||||
./zigbee2mqtt.nix
|
||||
./mosquitto.nix
|
||||
./postgres.nix
|
||||
./nginx.nix
|
||||
./mediatomb.nix
|
||||
./deluge.nix
|
||||
./cloudflared.nix
|
||||
|
|
@ -65,6 +68,7 @@ in {
|
|||
'';
|
||||
|
||||
services.cockroachdb.locality = "provider=local,network=gensokyo,host=${config.networking.hostName}";
|
||||
services.kanidm.serverSettings.db_fs_type = "zfs";
|
||||
|
||||
sops.defaultSopsFile = ./secrets.yaml;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
{
|
||||
config,
|
||||
utils,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: {
|
||||
options = with lib; let
|
||||
origin = "https://id.gensokyo.zone";
|
||||
in {
|
||||
services.vouch-proxy = {
|
||||
settings = {
|
||||
vouch = {
|
||||
cookie = {
|
||||
domain = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "gensokyo.zone";
|
||||
};
|
||||
secure = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
port = mkOption {
|
||||
type = lib.types.port;
|
||||
default = 30746;
|
||||
};
|
||||
listen = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "127.0.0.1";
|
||||
};
|
||||
allowAllUsers = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
oauth = {
|
||||
auth_url = mkOption {
|
||||
type = types.str;
|
||||
default = "${origin}/ui/oauth2";
|
||||
};
|
||||
token_url = mkOption {
|
||||
type = types.str;
|
||||
default = "${origin}/oauth2/token";
|
||||
};
|
||||
user_info_url = mkOption {
|
||||
type = types.str;
|
||||
default = "${origin}/oauth2/openid/vouch/userinfo";
|
||||
};
|
||||
scopes = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = ["openid" "email" "profile"];
|
||||
};
|
||||
callback_url = mkOption {
|
||||
type = types.str;
|
||||
default = "https://login.gensokyo.zone/auth";
|
||||
};
|
||||
provider = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "oidc";
|
||||
};
|
||||
code_challenge_method = mkOption {
|
||||
type = types.str;
|
||||
default = "S256";
|
||||
};
|
||||
client_id = mkOption {
|
||||
type = types.str;
|
||||
default = "vouch";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
services.vouch-proxy.settings = {
|
||||
vouch.cookie.secure = false;
|
||||
};
|
||||
|
||||
sops.secrets = {
|
||||
vouch-jwt.owner = "vouch-proxy";
|
||||
vouch-client-secret.owner = "vouch-proxy";
|
||||
};
|
||||
|
||||
systemd.services.vouch-proxy = {
|
||||
description = "Vouch-proxy";
|
||||
after = ["network.target"];
|
||||
wantedBy = ["multi-user.target"];
|
||||
serviceConfig = {
|
||||
ExecStart = let
|
||||
recursiveMergeAttrs = listOfAttrsets: lib.fold (attrset: acc: lib.recursiveUpdate attrset acc) {} listOfAttrsets;
|
||||
settings = recursiveMergeAttrs [
|
||||
config.services.vouch-proxy.settings
|
||||
{
|
||||
oauth.client_secret._secret = config.sops.secrets.vouch-client-secret.path;
|
||||
vouch.jwt.secret._secret = config.sops.secrets.vouch-jwt.path;
|
||||
}
|
||||
];
|
||||
in
|
||||
pkgs.writeShellScript "vouch-proxy-start" ''
|
||||
${utils.genJqSecretsReplacementSnippet settings "/run/vouch-proxy/vouch-config.json"}
|
||||
${pkgs.vouch-proxy}/bin/vouch-proxy -config /run/vouch-proxy/vouch-config.json
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
WorkingDirectory = "/var/lib/vouch-proxy";
|
||||
StateDirectory = "vouch-proxy";
|
||||
RuntimeDirectory = "vouch-proxy";
|
||||
User = "vouch-proxy";
|
||||
Group = "vouch-proxy";
|
||||
StartLimitBurst = 3;
|
||||
};
|
||||
};
|
||||
|
||||
users.users.vouch-proxy = {
|
||||
isSystemUser = true;
|
||||
group = "vouch-proxy";
|
||||
};
|
||||
|
||||
users.groups.vouch-proxy = {};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue