diff --git a/flake.lock b/flake.lock
index 47e4e38..3da6f9e 100644
--- a/flake.lock
+++ b/flake.lock
@@ -52,6 +52,25 @@
"type": "github"
}
},
+ "deploy-rs": {
+ "inputs": {
+ "flake-compat": "flake-compat_2",
+ "nixpkgs": "nixpkgs_2",
+ "utils": "utils_3"
+ },
+ "locked": {
+ "lastModified": 1648475189,
+ "narHash": "sha256-gAGAS6IagwoUr1B0ohE3iR6sZ8hP4LSqzYLC8Mq3WGU=",
+ "owner": "serokell",
+ "repo": "deploy-rs",
+ "rev": "83e0c78291cd08cb827ba0d553ad9158ae5a95c3",
+ "type": "github"
+ },
+ "original": {
+ "id": "deploy-rs",
+ "type": "indirect"
+ }
+ },
"emacs-overlay": {
"inputs": {
"flake-utils": "flake-utils",
@@ -88,6 +107,37 @@
"type": "github"
}
},
+ "flake-compat_2": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1648199409,
+ "narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "64a525ee38886ab9028e6f61790de0832aa3ef03",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-compat_3": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1627913399,
+ "narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2",
+ "type": "github"
+ },
+ "original": {
+ "id": "flake-compat",
+ "type": "indirect"
+ }
+ },
"flake-utils": {
"locked": {
"lastModified": 1656928814,
@@ -118,6 +168,36 @@
"type": "github"
}
},
+ "flake-utils_3": {
+ "locked": {
+ "lastModified": 1631561581,
+ "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19",
+ "type": "github"
+ },
+ "original": {
+ "id": "flake-utils",
+ "type": "indirect"
+ }
+ },
+ "gitignore-nix": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1611672876,
+ "narHash": "sha256-qHu3uZ/o9jBHiA3MEKHJ06k7w4heOhA+4HCSIvflRxo=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "211907489e9f198594c0eb0ca9256a1949c9d412",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -157,6 +237,40 @@
"type": "github"
}
},
+ "lowdown-src": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1632468475,
+ "narHash": "sha256-NNOm9CbdA8cuwbvaBHslGbPTiU6bh1Ao+MpEPx4rSGo=",
+ "owner": "kristapsdz",
+ "repo": "lowdown",
+ "rev": "6bd668af3fd098bdd07a1bedd399564141e275da",
+ "type": "github"
+ },
+ "original": {
+ "owner": "kristapsdz",
+ "repo": "lowdown",
+ "type": "github"
+ }
+ },
+ "nix": {
+ "inputs": {
+ "lowdown-src": "lowdown-src",
+ "nixpkgs": "nixpkgs_3"
+ },
+ "locked": {
+ "lastModified": 1633098935,
+ "narHash": "sha256-UtuBczommNLwUNEnfRI7822z4vPA7OoRKsgAZ8zsHQI=",
+ "owner": "nixos",
+ "repo": "nix",
+ "rev": "4f496150eb4e0012914c11f0a3ff4df2412b1d09",
+ "type": "github"
+ },
+ "original": {
+ "id": "nix",
+ "type": "indirect"
+ }
+ },
"nixos-hardware": {
"locked": {
"lastModified": 1656933710,
@@ -294,6 +408,37 @@
"type": "github"
}
},
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1648219316,
+ "narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_3": {
+ "locked": {
+ "lastModified": 1632864508,
+ "narHash": "sha256-d127FIvGR41XbVRDPVvozUPQ/uRHbHwvfyKHwEt5xFM=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "82891b5e2c2359d7e58d08849e4c89511ab94234",
+ "type": "github"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "ref": "nixos-21.05-small",
+ "type": "indirect"
+ }
+ },
"root": {
"inputs": {
"agenix": "agenix",
@@ -304,7 +449,33 @@
"nixpkgs": "nixpkgs",
"nixpkgs-mozilla": "nixpkgs-mozilla",
"nixpkgs-stable": "nixpkgs-stable",
- "nixpkgs-wayland": "nixpkgs-wayland"
+ "nixpkgs-wayland": "nixpkgs-wayland",
+ "serokell-nix": "serokell-nix"
+ }
+ },
+ "serokell-nix": {
+ "inputs": {
+ "deploy-rs": "deploy-rs",
+ "flake-compat": "flake-compat_3",
+ "flake-utils": "flake-utils_3",
+ "gitignore-nix": "gitignore-nix",
+ "nix": "nix",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1655492394,
+ "narHash": "sha256-bmiET3gLIv6T3+jPJi0hm9GurjwrddlmtuxTpFDYJZw=",
+ "owner": "serokell",
+ "repo": "serokell.nix",
+ "rev": "ca5235bd249a64991d2e813084aea5d521ca0088",
+ "type": "github"
+ },
+ "original": {
+ "owner": "serokell",
+ "repo": "serokell.nix",
+ "type": "github"
}
},
"utils": {
@@ -336,6 +507,21 @@
"repo": "flake-utils",
"type": "github"
}
+ },
+ "utils_3": {
+ "locked": {
+ "lastModified": 1648297722,
+ "narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
}
},
"root": "root",
diff --git a/flake.nix b/flake.nix
index dc69cfd..5219a74 100644
--- a/flake.nix
+++ b/flake.nix
@@ -12,10 +12,13 @@
nixos-mailserver.inputs.nixpkgs.follows = "nixpkgs";
agenix.url = "github:ryantm/agenix";
agenix.inputs.nixpkgs.follows = "nixpkgs";
-
+ serokell-nix.url = "github:serokell/serokell.nix";
+ serokell-nix.inputs = {
+ nixpkgs.follows = "nixpkgs";
+ };
};
outputs = inputs@{ nixpkgs, home-manager, nixpkgs-mozilla, emacs-overlay
- , nixpkgs-wayland, nixpkgs-stable, nixos-hardware, agenix, self, ... }: {
+ , nixpkgs-wayland, nixpkgs-stable, nixos-hardware, agenix, serokell-nix, self, ... }: {
overlays.default = nixpkgs.lib.composeManyExtensions [
nixpkgs-wayland.overlay
#nixpkgs-mozilla.overlay
diff --git a/nixos/logical/frumar.nix b/nixos/logical/frumar.nix
index 5384060..a17fb94 100644
--- a/nixos/logical/frumar.nix
+++ b/nixos/logical/frumar.nix
@@ -14,12 +14,34 @@
};
}) ];
- services.nginx.enable = false;
- # services.nginx.virtualHosts."${config.networking.hostName}" = {
- # enableACME = lib.mkForce false;
- # forceSSL = lib.mkForce false;
- # default = true;
- # };
+ services.nginx = let
+ cert = config.services.acme-sh.certs.wildcard-yori-cc;
+ sslCertificate = cert.certPath;
+ sslCertificateKey = cert.keyPath;
+ in {
+ enable = true;
+ recommendedOptimisation = true;
+ recommendedTlsSettings = true;
+ recommendedProxySettings = true;
+ recommendedGzipSettings = true;
+ virtualHosts."unifi.yori.cc" = {
+ onlySSL = true;
+ inherit sslCertificate sslCertificateKey;
+ locations."/" = {
+ proxyPass = "https://[::1]:8443";
+ proxyWebsockets = true;
+ extraConfig = ''
+ proxy_ssl_verify off;
+ proxy_ssl_session_reuse on;
+ '';
+ };
+ };
+ virtualHosts."frumar.yori.cc" = {
+ enableACME = lib.mkForce false;
+ forceSSL = lib.mkForce false;
+ default = true;
+ };
+ };
boot.supportedFilesystems = [ "zfs" ];
services.yorick.torrent-vpn = {
enable = true;
@@ -49,12 +71,12 @@
# victoriametrics
remoteWrite = [{ url = "http://127.0.0.1:8428/api/v1/write"; }];
scrapeConfigs = [
- {
- job_name = "smartmeter";
- # prometheus doesn't support mdns :thinking_face:
- static_configs = [{ targets = [ "192.168.178.30" ]; }];
- scrape_interval = "10s";
- }
+ # {
+ # job_name = "smartmeter";
+ # # prometheus doesn't support mdns :thinking_face:
+ # static_configs = [{ targets = [ "192.168.178.30" ]; }];
+ # scrape_interval = "10s";
+ # }
{
job_name = "node";
static_configs = [{ targets = [ "localhost:9100" ]; }];
@@ -62,14 +84,14 @@
# job_name = "unifi";
# static_configs = [ { targets = [ "localhost:9130" ]; } ];
}
- {
- job_name = "thermometer";
- static_configs = [{ targets = [ "192.168.178.21:8000" ]; }];
- }
- {
- job_name = "esphome";
- static_configs = [{ targets = [ "192.168.178.77" ]; }];
- }
+ # {
+ # job_name = "thermometer";
+ # static_configs = [{ targets = [ "192.168.178.21:8000" ]; }];
+ # }
+ # {
+ # job_name = "esphome";
+ # static_configs = [{ targets = [ "192.168.178.77" ]; }];
+ # }
];
exporters.node.enable = true;
# exporters.unifi = {
@@ -82,7 +104,7 @@
};
boot.zfs.requestEncryptionCredentials = false;
networking.firewall.interfaces.wg-y.allowedTCPPorts = [ 3000 9090 8443 ];
- networking.firewall.allowedTCPPorts = [ 1883 5357 ];
+ networking.firewall.allowedTCPPorts = [ 1883 5357 443 ];
networking.firewall.allowedUDPPorts = [ 1883 3702 ];
services.rabbitmq = {
enable = true;
@@ -100,7 +122,61 @@
AUTH_GOOGLE_ALLOW_SIGN_UP = "false";
};
};
- age.secrets.grafana.file = ../../secrets/grafana.env.age;
+ services.zigbee2mqtt = {
+ enable = true;
+ settings.availability = true;
+ settings.device_options = {
+ retain = true;
+ legacy = false;
+ };
+ settings.serial.port = "/dev/ttyUSB0";
+ };
+ services.home-assistant = {
+ enable = true;
+ openFirewall = true;
+ extraComponents = [
+ "default_config"
+ "androidtv"
+ "esphome"
+ "met"
+ "unifi" "yeelight" "plex" "frontend"
+ "automation" "device_automation"
+ ];
+ config = {
+ media_player = [
+ {
+ platform = "androidtv";
+ host = "192.168.2.181";
+ name = "shield";
+ device_class = "androidtv";
+ }
+ ];
+ mobile_app = {};
+ default_config = {};
+ system_log = {};
+ "map" = {};
+
+ frontend.themes = "!include_dir_merge_named themes";
+ automation = "!include automations.yaml";
+ homeassistant = {
+ name = "Home";
+ latitude = "51.84";
+ longitude = "5.85";
+ elevation = "0";
+ unit_system = "metric";
+ time_zone = "Europe/Amsterdam";
+ };
+ };
+ };
+ age.secrets = {
+ grafana.file = ../../secrets/grafana.env.age;
+ transip-key = {
+ file = ../../secrets/transip-key.age;
+ mode = "770";
+ owner = "nginx";
+ group = "nginx";
+ };
+ };
systemd.services.grafana.serviceConfig.EnvironmentFile = config.age.secrets.grafana.path;
services.zfs = {
trim.enable = false; # no ssd's
@@ -110,8 +186,8 @@
};
};
services.samba = {
- enable = true;
- openFirewall = true;
+ enable = false;
+ openFirewall = false;
shares.public = {
path = "/data/plexmedia";
browseable = "yes";
@@ -124,7 +200,7 @@
};
services.samba-wsdd = {
enable = true;
- interface = "eno2";
+ interface = "eno1";
hostname = "NAS";
};
services.sonarr = {
@@ -166,4 +242,16 @@
pyroscope
rtorrent
];
+ services.acme-sh.certs.wildcard-yori-cc = {
+ mainDomain = "*.yori.cc";
+ dns = "dns_transip";
+ production = true;
+ postRun = "systemctl reload nginx || true";
+ inherit (config.services.nginx) user group;
+ };
+ systemd.services.acme-sh-wildcard-yori-cc.environment = {
+ TRANSIP_Username = "yorickvp";
+ TRANSIP_Key_File = config.age.secrets.transip-key.path;
+ };
+
}
diff --git a/nixos/modules/acme-sh.nix b/nixos/modules/acme-sh.nix
new file mode 100644
index 0000000..2af49a2
--- /dev/null
+++ b/nixos/modules/acme-sh.nix
@@ -0,0 +1,166 @@
+# SPDX-FileCopyrightText: 2020 Serokell
+#
+# SPDX-License-Identifier: MPL-2.0
+
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.acme-sh;
+ # todo: upstream into serokell.nix
+ dnstype = lib.types.enum [ "dns_aws" "dns_dnsimple" "dns_transip" ];
+ submod = with lib; config: {
+ domains = mkOption {
+ type = types.coercedTo
+ (types.listOf types.str)
+ (f: lib.genAttrs f (x: config.dns)) (types.attrsOf dnstype);
+ default = { "${config.mainDomain}" = config.dns; };
+ };
+ mainDomain = mkOption {
+ type = types.str;
+ description = "domain to use as primary domain for the cert";
+ };
+ postRun = mkOption {
+ type = types.str;
+ default = "true";
+ };
+ keyFile = mkOption {
+ type = types.str;
+ default = "/dev/null";
+ };
+ user = mkOption {
+ type = types.str;
+ default = "root";
+ description = "User running the ACME client.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "root";
+ description = "Group running the ACME client.";
+ };
+
+ server = mkOption {
+ type = types.str;
+ default = "letsencrypt";
+ description = "Certificate Authority to use";
+ };
+ dns = mkOption {
+ type = dnstype;
+ };
+ renewInterval = mkOption {
+ type = types.str;
+ default = "weekly";
+ description = ''
+ Systemd calendar expression when to check for renewal. See
+ systemd.time
+ 7.
+ '';
+ };
+ production = mkOption {
+ type = types.bool;
+ default = true;
+ };
+ statePath = mkOption {
+ readOnly = true;
+ type = types.str;
+ };
+ keyPath = mkOption {
+ readOnly = true;
+ type = types.str;
+ };
+ certPath = mkOption {
+ readOnly = true;
+ type = types.str;
+ };
+ consulLock = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "vault";
+ };
+};
+in
+{
+ options.services.acme-sh = with lib; {
+ stateDir = lib.mkOption {
+ type = types.str;
+ default = "/var/lib/acme.sh";
+ };
+ certs = lib.mkOption {
+ type = types.attrsOf (types.submodule ({config, name, ...}: (with config; {
+ options = submod config;
+ config.statePath = "${cfg.stateDir}/${name}";
+ config.keyPath = "${statePath}/${mainDomain}/${mainDomain}.key";
+ config.certPath = "${statePath}/${mainDomain}/fullchain.cer";
+ })));
+ default = {};
+ };
+ };
+ config = {
+ systemd.services = lib.mapAttrs' (name: value: lib.nameValuePair "acme-sh-${name}" (with value; {
+ description = "Renew ACME Certificate for ${name}";
+ after =
+ [ "network.target" "network-online.target" ]
+ # wait for consul if we use it for locking
+ ++ lib.optionals (consulLock != null) [ "consul.service" ];
+ wants = [ "network-online.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ PermissionsStartOnly = true;
+ User = user;
+ Group = group;
+ PrivateTmp = true;
+ EnvironmentFile = keyFile;
+ SuccessExitStatus = "0 2";
+ };
+ path = with pkgs; [ acme-sh systemd util-linuxMinimal procps ];
+ preStart = ''
+ mkdir -p ${cfg.stateDir}
+ chown 'root:root' ${cfg.stateDir}
+ chmod 755 ${cfg.stateDir}
+ mkdir -p "${statePath}"
+ chown -R '${user}:${group}' "${statePath}"
+ chmod 750 "${statePath}"
+ rm -f "${statePath}/renewed"
+ '';
+ environment.LE_WORKING_DIR = statePath;
+ environment.SHELL = "${pkgs.bash}/bin/bash";
+ script = let
+ mapDomain = name: dns: ''-d "${name}" --dns ${dns}'';
+ primary = mapDomain mainDomain domains."${mainDomain}";
+ domainsStr = lib.concatStringsSep " " ([primary] ++ (lib.remove primary (lib.mapAttrsToList mapDomain domains)));
+ cmd = ''acme.sh --server ${server} --issue ${lib.optionalString (!production) "--test"} ${domainsStr} --reloadcmd "touch ${statePath}/renewed" --syslog 6 > /dev/null'';
+ in
+ if consulLock == null then ''
+ ${cmd}
+ rm -f "$LE_WORKING_DIR/account.conf"
+ '' else ''
+ # consul lock does not expose the exit code, because of platform compatiblity or something
+ # write it to the 'ecode' file, or exit 1 if it fails altogether
+ if ${config.services.consul.package}/bin/consul lock -verbose "${consulLock}" '${cmd}; echo $? > ${statePath}/ecode'; then
+ rm -f "$LE_WORKING_DIR/account.conf"
+ exit $(cat ${statePath}/ecode)
+ else
+ rm -f "$LE_WORKING_DIR/account.conf"
+ exit 1
+ fi
+ '';
+ postStart = ''
+ if [ -e "${statePath}/renewed" ]; then
+ ${postRun}
+ rm -f "${statePath}/renewed"
+ fi
+ '';
+ })) cfg.certs;
+ systemd.timers = lib.mapAttrs' (name: value: lib.nameValuePair "acme-sh-${name}" (with value; {
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = renewInterval;
+ Unit = "acme-sh-${name}.service";
+ Persistent = "yes";
+ AccuracySec = "5m";
+ RandomizedDelaySec = "1h";
+ };
+ })) cfg.certs;
+ };
+
+}
diff --git a/nixos/modules/nginx.nix b/nixos/modules/nginx.nix
index 6982e14..b8608cf 100644
--- a/nixos/modules/nginx.nix
+++ b/nixos/modules/nginx.nix
@@ -28,6 +28,7 @@ in {
if ! [[ -e /etc/nginx/dhparam.pem ]]; then
mkdir -p /etc/nginx/
${pkgs.openssl}/bin/openssl dhparam -out /etc/nginx/dhparam.pem 2048
+ chown nginx:nginx /etc/nginx/dhparam.pem
fi
'';
};
diff --git a/nixos/roles/default.nix b/nixos/roles/default.nix
index 96aa32b..b3eb7ff 100644
--- a/nixos/roles/default.nix
+++ b/nixos/roles/default.nix
@@ -6,6 +6,7 @@ let
in {
imports = [
inputs.agenix.nixosModule
+ ../modules/acme-sh.nix
../modules/tor-hidden-service.nix
../modules/nginx.nix
../modules/lumi-cache.nix
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
index 3728ca7..5272299 100644
Binary files a/secrets/secrets.nix and b/secrets/secrets.nix differ
diff --git a/secrets/transip-key.age b/secrets/transip-key.age
new file mode 100644
index 0000000..a3ef8e2
Binary files /dev/null and b/secrets/transip-key.age differ