move all files to new nixfmt rfc

This commit is contained in:
Noah Masur
2024-04-20 09:42:06 -04:00
parent b23efc4d77
commit e43fc0f8db
159 changed files with 5309 additions and 4537 deletions

View File

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
@ -25,10 +30,12 @@ let
apiKey = config.secrets.sabnzbdApiKey.dest;
};
};
in
{
in {
options = { arrs.enable = lib.mkEnableOption "Arr services"; };
options = {
arrs.enable = lib.mkEnableOption "Arr services";
};
config = lib.mkIf config.arrs.enable {
@ -74,76 +81,95 @@ in {
# Group means that routes with the same name are mutually exclusive,
# so they are split between the appropriate services.
group = "download";
match = [{
host = [ config.hostnames.download ];
path = [ "/sonarr*" ];
}];
handle = [{
handler = "reverse_proxy";
# We're able to reference the url and port of the service dynamically
upstreams = [{ dial = arrConfig.sonarr.url; }];
}];
match = [
{
host = [ config.hostnames.download ];
path = [ "/sonarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
# We're able to reference the url and port of the service dynamically
upstreams = [ { dial = arrConfig.sonarr.url; } ];
}
];
}
{
group = "download";
match = [{
host = [ config.hostnames.download ];
path = [ "/radarr*" ];
}];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = arrConfig.radarr.url; }];
}];
match = [
{
host = [ config.hostnames.download ];
path = [ "/radarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = arrConfig.radarr.url; } ];
}
];
}
{
group = "download";
match = [{
host = [ config.hostnames.download ];
path = [ "/prowlarr*" ];
}];
handle = [{
handler = "reverse_proxy";
# Prowlarr doesn't offer a dynamic config, so we have to hardcode it
upstreams = [{ dial = "localhost:9696"; }];
}];
match = [
{
host = [ config.hostnames.download ];
path = [ "/prowlarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
# Prowlarr doesn't offer a dynamic config, so we have to hardcode it
upstreams = [ { dial = "localhost:9696"; } ];
}
];
}
{
group = "download";
match = [{
host = [ config.hostnames.download ];
path = [ "/bazarr*" ];
}];
handle = [{
handler = "reverse_proxy";
upstreams = [{
# Bazarr only dynamically sets the port, not the host
dial = "localhost:${
builtins.toString config.services.bazarr.listenPort
}";
}];
}];
match = [
{
host = [ config.hostnames.download ];
path = [ "/bazarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [
{
# Bazarr only dynamically sets the port, not the host
dial = "localhost:${builtins.toString config.services.bazarr.listenPort}";
}
];
}
];
}
{
group = "download";
match = [{
host = [ config.hostnames.download ];
path = [ "/sabnzbd*" ];
}];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = arrConfig.sabnzbd.url; }];
}];
match = [
{
host = [ config.hostnames.download ];
path = [ "/sabnzbd*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = arrConfig.sabnzbd.url; } ];
}
];
}
{
group = "download";
match = [{ host = [ config.hostnames.download ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{
dial =
"localhost:${builtins.toString config.services.jellyseerr.port}";
}];
}];
match = [ { host = [ config.hostnames.download ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.jellyseerr.port}"; } ];
}
];
}
];
@ -160,19 +186,17 @@ in {
serviceConfig = {
Type = "simple";
DynamicUser = true;
ExecStart = let
# Sabnzbd doesn't accept the URI path, unlike the others
url = if name != "sabnzbd" then
"http://${attrs.url}/${name}"
else
"http://${attrs.url}";
ExecStart =
let
# Sabnzbd doesn't accept the URI path, unlike the others
url = if name != "sabnzbd" then "http://${attrs.url}/${name}" else "http://${attrs.url}";
in
# Exportarr is trained to pull from the arr services
in ''
${pkgs.exportarr}/bin/exportarr ${name} \
--url ${url} \
--port ${attrs.exportarrPort}'';
EnvironmentFile =
lib.mkIf (builtins.hasAttr "apiKey" attrs) attrs.apiKey;
''
${pkgs.exportarr}/bin/exportarr ${name} \
--url ${url} \
--port ${attrs.exportarrPort}'';
EnvironmentFile = lib.mkIf (builtins.hasAttr "apiKey" attrs) attrs.apiKey;
Restart = "on-failure";
ProtectHome = true;
ProtectSystem = "strict";
@ -216,11 +240,14 @@ in {
};
# Prometheus scrape targets (expose Exportarr to Prometheus)
prometheus.scrapeTargets = map (key:
prometheus.scrapeTargets = map (
key:
"127.0.0.1:${
lib.attrsets.getAttrFromPath [ key "exportarrPort" ] arrConfig
}") (builtins.attrNames arrConfig);
lib.attrsets.getAttrFromPath [
key
"exportarrPort"
] arrConfig
}"
) (builtins.attrNames arrConfig);
};
}

View File

@ -1,7 +1,8 @@
# This is my setup for backing up SQlite databases and other systems to S3 or
# S3-equivalent services (like Backblaze B2).
{ config, lib, ... }: {
{ config, lib, ... }:
{
options = {
@ -22,7 +23,6 @@
default = null;
};
};
};
config = lib.mkIf (config.backup.s3.endpoint != null) {
@ -65,7 +65,5 @@
# timerConfig = { OnCalendar = "00:05:00"; };
# environmentFile = backup.s3File;
# };
};
}

View File

@ -9,7 +9,13 @@
# (compiled with an overlay) to insert a plugin for managing DNS validation
# with Cloudflare's DNS API.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
options = {
caddy = {
@ -40,51 +46,59 @@
# Force Caddy to 403 if not coming from allowlisted source
caddy.cidrAllowlist = [ "127.0.0.1/32" ];
caddy.routes = [{
match = [{ not = [{ remote_ip.ranges = config.caddy.cidrAllowlist; }]; }];
handle = [{
handler = "static_response";
status_code = "403";
}];
}];
caddy.routes = [
{
match = [ { not = [ { remote_ip.ranges = config.caddy.cidrAllowlist; } ]; } ];
handle = [
{
handler = "static_response";
status_code = "403";
}
];
}
];
services.caddy = {
adapter = "''"; # Required to enable JSON
configFile = pkgs.writeText "Caddyfile" (builtins.toJSON {
apps.http.servers.main = {
listen = [ ":443" ];
configFile = pkgs.writeText "Caddyfile" (
builtins.toJSON {
apps.http.servers.main = {
listen = [ ":443" ];
# These routes are pulled from the rest of this repo
routes = config.caddy.routes;
errors.routes = config.caddy.blocks;
# These routes are pulled from the rest of this repo
routes = config.caddy.routes;
errors.routes = config.caddy.blocks;
logs = { }; # Uncommenting collects access logs
};
apps.http.servers.metrics = { }; # Enables Prometheus metrics
apps.tls.automation.policies = config.caddy.tlsPolicies;
# Setup logging to file
logging.logs.main = {
encoder = { format = "console"; };
writer = {
output = "file";
filename = "${config.services.caddy.logDir}/caddy.log";
roll = true;
roll_size_mb = 1;
logs = { }; # Uncommenting collects access logs
};
level = "INFO";
};
});
apps.http.servers.metrics = { }; # Enables Prometheus metrics
apps.tls.automation.policies = config.caddy.tlsPolicies;
# Setup logging to file
logging.logs.main = {
encoder = {
format = "console";
};
writer = {
output = "file";
filename = "${config.services.caddy.logDir}/caddy.log";
roll = true;
roll_size_mb = 1;
};
level = "INFO";
};
}
);
};
# Allows Caddy to serve lower ports (443, 80)
systemd.services.caddy.serviceConfig.AmbientCapabilities =
"CAP_NET_BIND_SERVICE";
systemd.services.caddy.serviceConfig.AmbientCapabilities = "CAP_NET_BIND_SERVICE";
# Required for web traffic to reach this machine
networking.firewall.allowedTCPPorts = [ 80 443 ];
networking.firewall.allowedTCPPorts = [
80
443
];
# HTTP/3 QUIC uses UDP (not sure if being used)
networking.firewall.allowedUDPPorts = [ 443 ];
@ -92,7 +106,5 @@
# Caddy exposes Prometheus metrics with the admin API
# https://caddyserver.com/docs/api
prometheus.scrapeTargets = [ "127.0.0.1:2019" ];
};
}

View File

@ -4,13 +4,18 @@
# - Hostname defined with config.hostnames.books
# - File directory backed up to S3 on a cron schedule.
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
libraryPath = "/data/books";
in {
in
{
options = {
backups.calibre = lib.mkOption {
@ -33,20 +38,22 @@ in {
};
# Allow web traffic to Caddy
caddy.routes = [{
match = [{ host = [ config.hostnames.books ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{
dial = "localhost:${
builtins.toString config.services.calibre-web.listen.port
}";
}];
# This is required when calibre-web is behind a reverse proxy
# https://github.com/janeczku/calibre-web/issues/19
headers.request.add."X-Script-Name" = [ "/calibre-web" ];
}];
}];
caddy.routes = [
{
match = [ { host = [ config.hostnames.books ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [
{ dial = "localhost:${builtins.toString config.services.calibre-web.listen.port}"; }
];
# This is required when calibre-web is behind a reverse proxy
# https://github.com/janeczku/calibre-web/issues/19
headers.request.add."X-Script-Name" = [ "/calibre-web" ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.books ];
@ -80,7 +87,5 @@ in {
--endpoint-url=https://${config.backup.s3.endpoint}
'';
};
};
}

View File

@ -52,7 +52,9 @@
# Catch-all if no match (should never happen anyway)
default = "http_status:404";
# Match from ingress of any valid server name to SSH access
ingress = { "*.masu.rs" = "ssh://localhost:22"; };
ingress = {
"*.masu.rs" = "ssh://localhost:22";
};
};
};
};
@ -79,8 +81,7 @@
# if there is no existing AuthenticationMethods
AuthenticationMethods publickey
'';
services.openssh.settings.Macs =
[ "hmac-sha2-512" ]; # Fix for failure to find matching mac
services.openssh.settings.Macs = [ "hmac-sha2-512" ]; # Fix for failure to find matching mac
# Create credentials file for Cloudflare
secrets.cloudflared = {
@ -91,11 +92,8 @@
permissions = "0440";
};
systemd.services.cloudflared-secret = {
requiredBy =
[ "cloudflared-tunnel-${config.cloudflareTunnel.id}.service" ];
requiredBy = [ "cloudflared-tunnel-${config.cloudflareTunnel.id}.service" ];
before = [ "cloudflared-tunnel-${config.cloudflareTunnel.id}.service" ];
};
};
}

View File

@ -8,7 +8,12 @@
# DNS validation plugin to connect to Cloudflare and automatically create
# validation DNS records for our generated certificates.
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
@ -39,10 +44,9 @@ let
"2405:8100::/32"
"2a06:98c0::/29"
"2c0f:f248::/32"
];
in {
in
{
options.cloudflare.enable = lib.mkEnableOption "Use Cloudflare.";
@ -59,23 +63,26 @@ in {
# Tell Caddy to use Cloudflare DNS for ACME challenge validation
services.caddy.package = pkgs.caddy-cloudflare; # Patched overlay
caddy.tlsPolicies = [{
issuers = [{
module = "acme";
challenges = {
dns = {
provider = {
name = "cloudflare";
api_token = "{env.CLOUDFLARE_API_TOKEN}";
caddy.tlsPolicies = [
{
issuers = [
{
module = "acme";
challenges = {
dns = {
provider = {
name = "cloudflare";
api_token = "{env.CLOUDFLARE_API_TOKEN}";
};
resolvers = [ "1.1.1.1" ];
};
};
resolvers = [ "1.1.1.1" ];
};
};
}];
}];
}
];
}
];
# Allow Caddy to read Cloudflare API key for DNS validation
systemd.services.caddy.serviceConfig.EnvironmentFile =
config.secrets.cloudflare-api.dest;
systemd.services.caddy.serviceConfig.EnvironmentFile = config.secrets.cloudflare-api.dest;
# API key must have access to modify Cloudflare DNS records
secrets.cloudflare-api = {
@ -95,59 +102,58 @@ in {
services.nextcloud.settings.trusted_proxies = cloudflareIpRanges;
# Allows Transmission to trust Cloudflare IPs
services.transmission.settings.rpc-whitelist =
builtins.concatStringsSep "," ([ "127.0.0.1" ] ++ cloudflareIpRanges);
services.transmission.settings.rpc-whitelist = builtins.concatStringsSep "," (
[ "127.0.0.1" ] ++ cloudflareIpRanges
);
services.cloudflare-dyndns = lib.mkIf
((builtins.length config.services.cloudflare-dyndns.domains) > 0) {
enable = true;
proxied = true;
deleteMissing = true;
apiTokenFile = config.secrets.cloudflare-api.dest;
};
services.cloudflare-dyndns =
lib.mkIf ((builtins.length config.services.cloudflare-dyndns.domains) > 0)
{
enable = true;
proxied = true;
deleteMissing = true;
apiTokenFile = config.secrets.cloudflare-api.dest;
};
# Wait for secret to exist to start
systemd.services.cloudflare-dyndns =
lib.mkIf config.services.cloudflare-dyndns.enable {
after = [ "cloudflare-api-secret.service" ];
requires = [ "cloudflare-api-secret.service" ];
};
systemd.services.cloudflare-dyndns = lib.mkIf config.services.cloudflare-dyndns.enable {
after = [ "cloudflare-api-secret.service" ];
requires = [ "cloudflare-api-secret.service" ];
};
# Run a second copy of dyn-dns for non-proxied domains
# Adapted from: https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/services/networking/cloudflare-dyndns.nix
systemd.services.cloudflare-dyndns-noproxy =
lib.mkIf ((builtins.length config.cloudflare.noProxyDomains) > 0) {
description = "CloudFlare Dynamic DNS Client (no proxy)";
after = [ "network.target" "cloudflare-api-secret.service" ];
requires = [ "cloudflare-api-secret.service" ];
wantedBy = [ "multi-user.target" ];
startAt = "*:0/5";
lib.mkIf ((builtins.length config.cloudflare.noProxyDomains) > 0)
{
description = "CloudFlare Dynamic DNS Client (no proxy)";
after = [
"network.target"
"cloudflare-api-secret.service"
];
requires = [ "cloudflare-api-secret.service" ];
wantedBy = [ "multi-user.target" ];
startAt = "*:0/5";
environment = {
CLOUDFLARE_DOMAINS = toString config.cloudflare.noProxyDomains;
environment = {
CLOUDFLARE_DOMAINS = toString config.cloudflare.noProxyDomains;
};
serviceConfig = {
Type = "simple";
DynamicUser = true;
StateDirectory = "cloudflare-dyndns-noproxy";
EnvironmentFile = config.services.cloudflare-dyndns.apiTokenFile;
ExecStart =
let
args =
[ "--cache-file /var/lib/cloudflare-dyndns-noproxy/ip.cache" ]
++ (if config.services.cloudflare-dyndns.ipv4 then [ "-4" ] else [ "-no-4" ])
++ (if config.services.cloudflare-dyndns.ipv6 then [ "-6" ] else [ "-no-6" ])
++ lib.optional config.services.cloudflare-dyndns.deleteMissing "--delete-missing";
in
"${pkgs.cloudflare-dyndns}/bin/cloudflare-dyndns ${toString args}";
};
};
serviceConfig = {
Type = "simple";
DynamicUser = true;
StateDirectory = "cloudflare-dyndns-noproxy";
EnvironmentFile = config.services.cloudflare-dyndns.apiTokenFile;
ExecStart = let
args =
[ "--cache-file /var/lib/cloudflare-dyndns-noproxy/ip.cache" ]
++ (if config.services.cloudflare-dyndns.ipv4 then
[ "-4" ]
else
[ "-no-4" ]) ++ (if config.services.cloudflare-dyndns.ipv6 then
[ "-6" ]
else
[ "-no-6" ])
++ lib.optional config.services.cloudflare-dyndns.deleteMissing
"--delete-missing";
in "${pkgs.cloudflare-dyndns}/bin/cloudflare-dyndns ${toString args}";
};
};
};
}

View File

@ -1,7 +1,8 @@
# This file imports all the other files in this directory for use as modules in
# my config.
{ ... }: {
{ ... }:
{
imports = [
./arr.nix
@ -36,5 +37,4 @@
./victoriametrics.nix
./wireguard.nix
];
}

View File

@ -4,11 +4,15 @@
# the Runners don't necessarily need to be running Gitea. All we need is an API
# key for Gitea to connect to it and register ourselves as a Runner.
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
{
options.giteaRunner.enable =
lib.mkEnableOption "Enable Gitea Actions runner.";
options.giteaRunner.enable = lib.mkEnableOption "Enable Gitea Actions runner.";
config = lib.mkIf config.giteaRunner.enable {
@ -38,8 +42,7 @@
};
# Make sure the runner doesn't start until after Gitea
systemd.services."gitea-runner-${config.networking.hostName}".after =
[ "gitea.service" ];
systemd.services."gitea-runner-${config.networking.hostName}".after = [ "gitea.service" ];
# API key needed to connect to Gitea
secrets.giteaRunnerToken = {
@ -58,7 +61,5 @@
}.service"
];
};
};
}

View File

@ -1,8 +1,14 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let giteaPath = "/var/lib/gitea"; # Default service directory
in {
let
giteaPath = "/var/lib/gitea"; # Default service directory
in
{
config = lib.mkIf config.services.gitea.enable {
services.gitea = {
@ -54,27 +60,30 @@ in {
caddy.routes = [
# Prevent public access to Prometheus metrics.
{
match = [{
host = [ config.hostnames.git ];
path = [ "/metrics*" ];
}];
handle = [{
handler = "static_response";
status_code = "403";
}];
match = [
{
host = [ config.hostnames.git ];
path = [ "/metrics*" ];
}
];
handle = [
{
handler = "static_response";
status_code = "403";
}
];
}
# Allow access to primary server.
{
match = [{ host = [ config.hostnames.git ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{
dial = "localhost:${
builtins.toString
config.services.gitea.settings.server.HTTP_PORT
}";
}];
}];
match = [ { host = [ config.hostnames.git ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [
{ dial = "localhost:${builtins.toString config.services.gitea.settings.server.HTTP_PORT}"; }
];
}
];
}
];
@ -83,18 +92,14 @@ in {
# Scrape the metrics endpoint for Prometheus.
prometheus.scrapeTargets = [
"127.0.0.1:${
builtins.toString config.services.gitea.settings.server.HTTP_PORT
}"
"127.0.0.1:${builtins.toString config.services.gitea.settings.server.HTTP_PORT}"
];
## Backup config
# Open to groups, allowing for backups
systemd.services.gitea.serviceConfig.StateDirectoryMode =
lib.mkForce "0770";
systemd.tmpfiles.rules =
[ "f ${giteaPath}/data/gitea.db 0660 gitea gitea" ];
systemd.services.gitea.serviceConfig.StateDirectoryMode = lib.mkForce "0770";
systemd.tmpfiles.rules = [ "f ${giteaPath}/data/gitea.db 0660 gitea gitea" ];
# Allow litestream and gitea to share a sqlite database
users.users.litestream.extraGroups = [ "gitea" ];
@ -103,13 +108,12 @@ in {
# Backup sqlite database with litestream
services.litestream = {
settings = {
dbs = [{
path = "${giteaPath}/data/gitea.db";
replicas = [{
url =
"s3://${config.backup.s3.bucket}.${config.backup.s3.endpoint}/gitea";
}];
}];
dbs = [
{
path = "${giteaPath}/data/gitea.db";
replicas = [ { url = "s3://${config.backup.s3.bucket}.${config.backup.s3.endpoint}/gitea"; } ];
}
];
};
};
@ -129,24 +133,21 @@ in {
};
# Backup Gitea repos to object storage
systemd.services.gitea-backup =
lib.mkIf (config.backup.s3.endpoint != null) {
description = "Backup Gitea data";
environment.AWS_ACCESS_KEY_ID = config.backup.s3.accessKeyId;
serviceConfig = {
Type = "oneshot";
User = "gitea";
Group = "backup";
EnvironmentFile = config.secrets.backup.dest;
};
script = ''
${pkgs.awscli2}/bin/aws s3 sync --exclude */gitea.db* \
${giteaPath}/ \
s3://${config.backup.s3.bucket}/gitea-data/ \
--endpoint-url=https://${config.backup.s3.endpoint}
'';
systemd.services.gitea-backup = lib.mkIf (config.backup.s3.endpoint != null) {
description = "Backup Gitea data";
environment.AWS_ACCESS_KEY_ID = config.backup.s3.accessKeyId;
serviceConfig = {
Type = "oneshot";
User = "gitea";
Group = "backup";
EnvironmentFile = config.secrets.backup.dest;
};
script = ''
${pkgs.awscli2}/bin/aws s3 sync --exclude */gitea.db* \
${giteaPath}/ \
s3://${config.backup.s3.bucket}/gitea-data/ \
--endpoint-url=https://${config.backup.s3.endpoint}
'';
};
};
}

View File

@ -1,6 +1,12 @@
# GPG is an encryption tool. This isn't really in use for me at the moment.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
options.gpg.enable = lib.mkEnableOption "GnuPG encryption.";
@ -16,5 +22,4 @@
};
home = lib.mkIf config.gui.enable { packages = with pkgs; [ pinentry ]; };
};
}

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,12 @@
# Currently has some issues that don't make this viable.
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
# Taken from:
# https://dataswamp.org/~solene/2022-09-29-iblock-implemented-in-nixos.html
@ -13,9 +18,12 @@
let
portsToBlock = [ 25545 25565 25570 ];
portsString =
builtins.concatStringsSep "," (builtins.map builtins.toString portsToBlock);
portsToBlock = [
25545
25565
25570
];
portsString = builtins.concatStringsSep "," (builtins.map builtins.toString portsToBlock);
# Block IPs for 20 days
expire = 60 * 60 * 24 * 20;
@ -27,19 +35,17 @@ let
"INPUT -i eth0 -p udp -m set --match-set ${table} src -j nixos-fw-refuse"
];
create-rules = lib.concatStringsSep "\n"
(builtins.map (rule: "iptables -C " + rule + " || iptables -A " + rule)
(rules "blocked") ++ builtins.map
(rule: "ip6tables -C " + rule + " || ip6tables -A " + rule)
(rules "blocked6"));
create-rules = lib.concatStringsSep "\n" (
builtins.map (rule: "iptables -C " + rule + " || iptables -A " + rule) (rules "blocked")
++ builtins.map (rule: "ip6tables -C " + rule + " || ip6tables -A " + rule) (rules "blocked6")
);
delete-rules = lib.concatStringsSep "\n"
(builtins.map (rule: "iptables -C " + rule + " && iptables -D " + rule)
(rules "blocked") ++ builtins.map
(rule: "ip6tables -C " + rule + " && ip6tables -D " + rule)
(rules "blocked6"));
in {
delete-rules = lib.concatStringsSep "\n" (
builtins.map (rule: "iptables -C " + rule + " && iptables -D " + rule) (rules "blocked")
++ builtins.map (rule: "ip6tables -C " + rule + " && ip6tables -D " + rule) (rules "blocked6")
);
in
{
options.honeypot.enable = lib.mkEnableOption "Honeypot fail2ban system.";
@ -54,9 +60,7 @@ in {
then
ipset restore -! < /var/lib/ipset.conf
else
ipset -exist create blocked hash:ip ${
if expire > 0 then "timeout ${toString expire}" else ""
}
ipset -exist create blocked hash:ip ${if expire > 0 then "timeout ${toString expire}" else ""}
ipset -exist create blocked6 hash:ip family inet6 ${
if expire > 0 then "timeout ${toString expire}" else ""
}
@ -66,9 +70,7 @@ in {
# Save list when shutting down
extraStopCommands = ''
ipset -exist create blocked hash:ip ${
if expire > 0 then "timeout ${toString expire}" else ""
}
ipset -exist create blocked hash:ip ${if expire > 0 then "timeout ${toString expire}" else ""}
ipset -exist create blocked6 hash:ip family inet6 ${
if expire > 0 then "timeout ${toString expire}" else ""
}
@ -76,5 +78,4 @@ in {
${delete-rules}
'';
};
}

View File

@ -1,4 +1,5 @@
{ config, ... }: {
{ config, ... }:
{
# Wait for secret to be placed on the machine
systemd.services.wait-for-identity = {
@ -18,5 +19,4 @@
done
'';
};
}

View File

@ -3,7 +3,8 @@
# infinite retention separate from our other metrics, which can be nice for
# recording health information, for example.
{ config, lib, ... }: {
{ config, lib, ... }:
{
config = lib.mkIf config.services.influxdb2.enable {
@ -30,11 +31,10 @@
group = "influxdb2";
permissions = "0440";
};
systemd.services.influxdb2Password-secret =
lib.mkIf config.services.influxdb2.enable {
requiredBy = [ "influxdb2.service" ];
before = [ "influxdb2.service" ];
};
systemd.services.influxdb2Password-secret = lib.mkIf config.services.influxdb2.enable {
requiredBy = [ "influxdb2.service" ];
before = [ "influxdb2.service" ];
};
secrets.influxdb2Token = lib.mkIf config.services.influxdb2.enable {
source = ../../../private/influxdb2-token.age;
dest = "${config.secretsDirectory}/influxdb2-token";
@ -42,23 +42,24 @@
group = "influxdb2";
permissions = "0440";
};
systemd.services.influxdb2Token-secret =
lib.mkIf config.services.influxdb2.enable {
requiredBy = [ "influxdb2.service" ];
before = [ "influxdb2.service" ];
};
systemd.services.influxdb2Token-secret = lib.mkIf config.services.influxdb2.enable {
requiredBy = [ "influxdb2.service" ];
before = [ "influxdb2.service" ];
};
caddy.routes = lib.mkIf config.services.influxdb2.enable [{
match = [{ host = [ config.hostnames.influxdb ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:8086"; }];
}];
}];
caddy.routes = lib.mkIf config.services.influxdb2.enable [
{
match = [ { host = [ config.hostnames.influxdb ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:8086"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.influxdb ];
};
}

View File

@ -1,4 +1,5 @@
{ config, lib, ... }: {
{ config, lib, ... }:
{
config = lib.mkIf config.services.thelounge.enable {
@ -16,20 +17,19 @@
# sudo su - thelounge -s /bin/sh -c "thelounge add myuser"
# Allow web traffic to Caddy
caddy.routes = [{
match = [{ host = [ config.hostnames.irc ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{
dial =
"localhost:${builtins.toString config.services.thelounge.port}";
}];
}];
}];
caddy.routes = [
{
match = [ { host = [ config.hostnames.irc ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.thelounge.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.irc ];
};
}

View File

@ -1,32 +1,46 @@
# Jellyfin is a self-hosted video streaming service. This means I can play my
# server's videos from a webpage, mobile app, or TV client.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf config.services.jellyfin.enable {
services.jellyfin.group = "media";
users.users.jellyfin = { isSystemUser = true; };
users.users.jellyfin = {
isSystemUser = true;
};
caddy.routes = [
# Prevent public access to Prometheus metrics.
{
match = [{
host = [ config.hostnames.stream ];
path = [ "/metrics*" ];
}];
handle = [{
handler = "static_response";
status_code = "403";
}];
match = [
{
host = [ config.hostnames.stream ];
path = [ "/metrics*" ];
}
];
handle = [
{
handler = "static_response";
status_code = "403";
}
];
}
# Allow access to normal route.
{
match = [{ host = [ config.hostnames.stream ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:8096"; }];
}];
match = [ { host = [ config.hostnames.stream ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:8096"; } ];
}
];
}
];
@ -52,15 +66,15 @@
"VDPAU_DRIVER" = "radeonsi";
"LIBVA_DRIVER_NAME" = "radeonsi";
};
users.users.jellyfin.extraGroups =
[ "render" "video" ]; # Access to /dev/dri
users.users.jellyfin.extraGroups = [
"render"
"video"
]; # Access to /dev/dri
# Fix issue where Jellyfin-created directories don't allow access for media group
systemd.services.jellyfin.serviceConfig.UMask = lib.mkForce "0007";
# Requires MetricsEnable is true in /var/lib/jellyfin/config/system.xml
prometheus.scrapeTargets = [ "127.0.0.1:8096" ];
};
}

View File

@ -1,7 +1,13 @@
# Keybase is an encrypted communications tool with a synchronized encrypted
# filestore that can be mounted onto a machine's filesystem.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
options.keybase.enable = lib.mkEnableOption "Keybase.";
@ -19,16 +25,16 @@
systemd.user.services.kbfs.Service.PrivateTmp = lib.mkForce false;
home.packages = [ (lib.mkIf config.gui.enable pkgs.keybase-gui) ];
home.file = let
ignorePatterns = ''
keybase/
kbfs/'';
in {
".rgignore".text = ignorePatterns;
".fdignore".text = ignorePatterns;
};
home.file =
let
ignorePatterns = ''
keybase/
kbfs/'';
in
{
".rgignore".text = ignorePatterns;
".fdignore".text = ignorePatterns;
};
};
};
}

View File

@ -1,6 +1,12 @@
# Mullvad is a VPN service. This isn't currently in use for me at the moment.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
options.mullvad.enable = lib.mkEnableOption "Mullvad VPN.";
@ -8,7 +14,5 @@
services.mullvad-vpn.enable = true;
environment.systemPackages = [ pkgs.mullvad-vpn ];
};
}

View File

@ -1,7 +1,8 @@
# n8n is an automation integration tool for connecting data from services
# together with triggers.
{ config, lib, ... }: {
{ config, lib, ... }:
{
options = {
n8nServer = lib.mkOption {
@ -23,14 +24,16 @@
};
};
caddy.routes = [{
match = [{ host = [ config.n8nServer ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:5678"; }];
}];
}];
caddy.routes = [
{
match = [ { host = [ config.n8nServer ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:5678"; } ];
}
];
}
];
};
}

View File

@ -1,7 +1,8 @@
# Netdata is an out-of-the-box monitoring tool that exposes many different
# metrics. Not currently in use, in favor of VictoriaMetrics and Grafana.
{ config, lib, ... }: {
{ config, lib, ... }:
{
options.netdata.enable = lib.mkEnableOption "Netdata metrics.";
@ -11,9 +12,9 @@
enable = true;
# Disable local dashboard (unsecured)
config = { web.mode = "none"; };
config = {
web.mode = "none";
};
};
};
}

View File

@ -1,4 +1,10 @@
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf config.services.nextcloud.enable {
@ -46,107 +52,137 @@
users.users.caddy.extraGroups = [ "nextcloud" ];
# Point Caddy to Nginx
caddy.routes = [{
match = [{ host = [ config.hostnames.content ]; }];
handle = [{
handler = "subroute";
routes = [
# Sets variables and headers
caddy.routes = [
{
match = [ { host = [ config.hostnames.content ]; } ];
handle = [
{
handle = [
handler = "subroute";
routes = [
# Sets variables and headers
{
handler = "vars";
# Grab the webroot out of the written config
# The webroot is a symlinked combined Nextcloud directory
root =
config.services.nginx.virtualHosts.${config.services.nextcloud.hostName}.root;
handle = [
{
handler = "vars";
# Grab the webroot out of the written config
# The webroot is a symlinked combined Nextcloud directory
root = config.services.nginx.virtualHosts.${config.services.nextcloud.hostName}.root;
}
{
handler = "headers";
response.set.Strict-Transport-Security = [ "max-age=31536000;" ];
}
];
}
# Reroute carddav and caldav traffic
{
handler = "headers";
response.set.Strict-Transport-Security =
[ "max-age=31536000;" ];
match = [
{
path = [
"/.well-known/carddav"
"/.well-known/caldav"
];
}
];
handle = [
{
handler = "static_response";
headers = {
Location = [ "/remote.php/dav" ];
};
status_code = 301;
}
];
}
# Block traffic to sensitive files
{
match = [
{
path = [
"/.htaccess"
"/data/*"
"/config/*"
"/db_structure"
"/.xml"
"/README"
"/3rdparty/*"
"/lib/*"
"/templates/*"
"/occ"
"/console.php"
];
}
];
handle = [
{
handler = "static_response";
status_code = 404;
}
];
}
# Redirect index.php to the homepage
{
match = [
{
file = {
try_files = [ "{http.request.uri.path}/index.php" ];
};
not = [ { path = [ "*/" ]; } ];
}
];
handle = [
{
handler = "static_response";
headers = {
Location = [ "{http.request.orig_uri.path}/" ];
};
status_code = 308;
}
];
}
# Rewrite paths to be relative
{
match = [
{
file = {
split_path = [ ".php" ];
try_files = [
"{http.request.uri.path}"
"{http.request.uri.path}/index.php"
"index.php"
];
};
}
];
handle = [
{
handler = "rewrite";
uri = "{http.matchers.file.relative}";
}
];
}
# Send all PHP traffic to Nextcloud PHP service
{
match = [ { path = [ "*.php" ]; } ];
handle = [
{
handler = "reverse_proxy";
transport = {
protocol = "fastcgi";
split_path = [ ".php" ];
};
upstreams = [ { dial = "unix//run/phpfpm/nextcloud.sock"; } ];
}
];
}
# Finally, send the rest to the file server
{ handle = [ { handler = "file_server"; } ]; }
];
}
# Reroute carddav and caldav traffic
{
match =
[{ path = [ "/.well-known/carddav" "/.well-known/caldav" ]; }];
handle = [{
handler = "static_response";
headers = { Location = [ "/remote.php/dav" ]; };
status_code = 301;
}];
}
# Block traffic to sensitive files
{
match = [{
path = [
"/.htaccess"
"/data/*"
"/config/*"
"/db_structure"
"/.xml"
"/README"
"/3rdparty/*"
"/lib/*"
"/templates/*"
"/occ"
"/console.php"
];
}];
handle = [{
handler = "static_response";
status_code = 404;
}];
}
# Redirect index.php to the homepage
{
match = [{
file = { try_files = [ "{http.request.uri.path}/index.php" ]; };
not = [{ path = [ "*/" ]; }];
}];
handle = [{
handler = "static_response";
headers = { Location = [ "{http.request.orig_uri.path}/" ]; };
status_code = 308;
}];
}
# Rewrite paths to be relative
{
match = [{
file = {
split_path = [ ".php" ];
try_files = [
"{http.request.uri.path}"
"{http.request.uri.path}/index.php"
"index.php"
];
};
}];
handle = [{
handler = "rewrite";
uri = "{http.matchers.file.relative}";
}];
}
# Send all PHP traffic to Nextcloud PHP service
{
match = [{ path = [ "*.php" ]; }];
handle = [{
handler = "reverse_proxy";
transport = {
protocol = "fastcgi";
split_path = [ ".php" ];
};
upstreams = [{ dial = "unix//run/phpfpm/nextcloud.sock"; }];
}];
}
# Finally, send the rest to the file server
{ handle = [{ handler = "file_server"; }]; }
];
}];
terminal = true;
}];
terminal = true;
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.content ];
@ -168,8 +204,7 @@
users.users.${config.user}.extraGroups = [ "nextcloud" ];
# Open to groups, allowing for backups
systemd.services.phpfpm-nextcloud.serviceConfig.StateDirectoryMode =
lib.mkForce "0770";
systemd.services.phpfpm-nextcloud.serviceConfig.StateDirectoryMode = lib.mkForce "0770";
# Log metrics to prometheus
networking.hosts."127.0.0.1" = [ config.hostnames.content ];
@ -180,15 +215,11 @@
passwordFile = config.services.nextcloud.config.adminpassFile;
};
prometheus.scrapeTargets = [
"127.0.0.1:${
builtins.toString config.services.prometheus.exporters.nextcloud.port
}"
"127.0.0.1:${builtins.toString config.services.prometheus.exporters.nextcloud.port}"
];
# Allows nextcloud-exporter to read passwordFile
users.users.nextcloud-exporter.extraGroups =
lib.mkIf config.services.prometheus.exporters.nextcloud.enable
[ "nextcloud" ];
[ "nextcloud" ];
};
}

View File

@ -1,6 +1,7 @@
# Paperless-ngx is a document scanning and management solution.
{ config, lib, ... }: {
{ config, lib, ... }:
{
config = lib.mkIf config.services.paperless.enable {
@ -8,8 +9,7 @@
mediaDir = "/data/generic/paperless";
passwordFile = config.secrets.paperless.dest;
settings = {
PAPERLESS_OCR_USER_ARGS =
builtins.toJSON { invalidate_digital_signatures = true; };
PAPERLESS_OCR_USER_ARGS = builtins.toJSON { invalidate_digital_signatures = true; };
# Enable if changing the path name in Caddy
# PAPERLESS_FORCE_SCRIPT_NAME = "/paperless";
@ -18,23 +18,25 @@
};
# Allow Nextcloud and user to see files
users.users.nextcloud.extraGroups =
lib.mkIf config.services.nextcloud.enable [ "paperless" ];
users.users.nextcloud.extraGroups = lib.mkIf config.services.nextcloud.enable [ "paperless" ];
users.users.${config.user}.extraGroups = [ "paperless" ];
caddy.routes = [{
match = [{
host = [ config.hostnames.paperless ];
# path = [ "/paperless*" ]; # Change path name in Caddy
}];
handle = [{
handler = "reverse_proxy";
upstreams = [{
dial =
"localhost:${builtins.toString config.services.paperless.port}";
}];
}];
}];
caddy.routes = [
{
match = [
{
host = [ config.hostnames.paperless ];
# path = [ "/paperless*" ]; # Change path name in Caddy
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.paperless.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.paperless ];
@ -53,11 +55,7 @@
# Fix paperless shared permissions
systemd.services.paperless-web.serviceConfig.UMask = lib.mkForce "0026";
systemd.services.paperless-scheduler.serviceConfig.UMask =
lib.mkForce "0026";
systemd.services.paperless-task-queue.serviceConfig.UMask =
lib.mkForce "0026";
systemd.services.paperless-scheduler.serviceConfig.UMask = lib.mkForce "0026";
systemd.services.paperless-task-queue.serviceConfig.UMask = lib.mkForce "0026";
};
}

View File

@ -1,4 +1,10 @@
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
services.postgresql = {
package = pkgs.postgresql_15;
@ -12,19 +18,19 @@
root root postgres
admin ${config.user} admin
'';
ensureUsers = [{
name = "admin";
ensureClauses = {
createdb = true;
createrole = true;
login = true;
};
}];
ensureUsers = [
{
name = "admin";
ensureClauses = {
createdb = true;
createrole = true;
login = true;
};
}
];
};
home-manager.users.${config.user}.home.packages =
lib.mkIf config.services.postgresql.enable [
pkgs.pgcli # Postgres client with autocomplete
];
home-manager.users.${config.user}.home.packages = lib.mkIf config.services.postgresql.enable [
pkgs.pgcli # Postgres client with autocomplete
];
}

View File

@ -4,7 +4,13 @@
# Instead of running traditional Prometheus, I generally run VictoriaMetrics as
# a more efficient drop-in replacement.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
options.prometheus = {
exporters.enable = lib.mkEnableOption "Enable Prometheus exporters";
@ -15,105 +21,99 @@
};
};
config = let
config =
let
# If hosting Grafana, host local Prometheus and listen for inbound jobs. If
# not hosting Grafana, send remote Prometheus writes to primary host.
isServer = config.services.grafana.enable;
# If hosting Grafana, host local Prometheus and listen for inbound jobs. If
# not hosting Grafana, send remote Prometheus writes to primary host.
isServer = config.services.grafana.enable;
in
{
in {
# Turn on exporters if any Prometheus scraper is running
prometheus.exporters.enable = builtins.any (x: x) [
config.services.prometheus.enable
config.services.victoriametrics.enable
config.services.vmagent.enable
];
prometheus.scrapeTargets = [
"127.0.0.1:${
builtins.toString config.services.prometheus.exporters.node.port
}"
"127.0.0.1:${
builtins.toString config.services.prometheus.exporters.systemd.port
}"
"127.0.0.1:${
builtins.toString config.services.prometheus.exporters.process.port
}"
];
services.prometheus = {
exporters.node.enable = config.prometheus.exporters.enable;
exporters.node.enabledCollectors = [ ];
exporters.node.disabledCollectors = [ "cpufreq" ];
exporters.systemd.enable = config.prometheus.exporters.enable;
exporters.process.enable = config.prometheus.exporters.enable;
exporters.process.settings.process_names = [
# Remove nix store path from process name
{
name = "{{.Matches.Wrapped}} {{ .Matches.Args }}";
cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ];
}
# Turn on exporters if any Prometheus scraper is running
prometheus.exporters.enable = builtins.any (x: x) [
config.services.prometheus.enable
config.services.victoriametrics.enable
config.services.vmagent.enable
];
extraFlags = lib.mkIf isServer [ "--web.enable-remote-write-receiver" ];
scrapeConfigs = [{
job_name = config.networking.hostName;
static_configs = [{ targets = config.scrapeTargets; }];
}];
webExternalUrl =
lib.mkIf isServer "https://${config.hostnames.prometheus}";
# Web config file: https://prometheus.io/docs/prometheus/latest/configuration/https/
webConfigFile = lib.mkIf isServer
((pkgs.formats.yaml { }).generate "webconfig.yml" {
basic_auth_users = {
# Generate password: htpasswd -nBC 10 "" | tr -d ':\n'
# Encrypt and place in private/prometheus.age
"prometheus" =
"$2y$10$r7FWHLHTGPAY312PdhkPEuvb05aGn9Nk1IO7qtUUUjmaDl35l6sLa";
};
});
remoteWrite = lib.mkIf (!isServer) [{
name = config.networking.hostName;
url = "https://${config.hostnames.prometheus}/api/v1/write";
basic_auth = {
# Uses password hashed with bcrypt above
username = "prometheus";
password_file = config.secrets.prometheus.dest;
};
}];
};
# Create credentials file for remote Prometheus push
secrets.prometheus =
lib.mkIf (config.services.prometheus.enable && !isServer) {
prometheus.scrapeTargets = [
"127.0.0.1:${builtins.toString config.services.prometheus.exporters.node.port}"
"127.0.0.1:${builtins.toString config.services.prometheus.exporters.systemd.port}"
"127.0.0.1:${builtins.toString config.services.prometheus.exporters.process.port}"
];
services.prometheus = {
exporters.node.enable = config.prometheus.exporters.enable;
exporters.node.enabledCollectors = [ ];
exporters.node.disabledCollectors = [ "cpufreq" ];
exporters.systemd.enable = config.prometheus.exporters.enable;
exporters.process.enable = config.prometheus.exporters.enable;
exporters.process.settings.process_names = [
# Remove nix store path from process name
{
name = "{{.Matches.Wrapped}} {{ .Matches.Args }}";
cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ];
}
];
extraFlags = lib.mkIf isServer [ "--web.enable-remote-write-receiver" ];
scrapeConfigs = [
{
job_name = config.networking.hostName;
static_configs = [ { targets = config.scrapeTargets; } ];
}
];
webExternalUrl = lib.mkIf isServer "https://${config.hostnames.prometheus}";
# Web config file: https://prometheus.io/docs/prometheus/latest/configuration/https/
webConfigFile = lib.mkIf isServer (
(pkgs.formats.yaml { }).generate "webconfig.yml" {
basic_auth_users = {
# Generate password: htpasswd -nBC 10 "" | tr -d ':\n'
# Encrypt and place in private/prometheus.age
"prometheus" = "$2y$10$r7FWHLHTGPAY312PdhkPEuvb05aGn9Nk1IO7qtUUUjmaDl35l6sLa";
};
}
);
remoteWrite = lib.mkIf (!isServer) [
{
name = config.networking.hostName;
url = "https://${config.hostnames.prometheus}/api/v1/write";
basic_auth = {
# Uses password hashed with bcrypt above
username = "prometheus";
password_file = config.secrets.prometheus.dest;
};
}
];
};
# Create credentials file for remote Prometheus push
secrets.prometheus = lib.mkIf (config.services.prometheus.enable && !isServer) {
source = ../../../private/prometheus.age;
dest = "${config.secretsDirectory}/prometheus";
owner = "prometheus";
group = "prometheus";
permissions = "0440";
};
systemd.services.prometheus-secret =
lib.mkIf (config.services.prometheus.enable && !isServer) {
systemd.services.prometheus-secret = lib.mkIf (config.services.prometheus.enable && !isServer) {
requiredBy = [ "prometheus.service" ];
before = [ "prometheus.service" ];
};
caddy.routes = lib.mkIf (config.services.prometheus.enable && isServer) [{
match = [{ host = [ config.hostnames.prometheus ]; }];
handle = [{
handler = "reverse_proxy";
upstreams =
[{ dial = "localhost:${config.services.prometheus.port}"; }];
}];
}];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains =
if (config.services.prometheus.enable && isServer) then
[ config.hostnames.prometheus ]
else
[ ];
};
caddy.routes = lib.mkIf (config.services.prometheus.enable && isServer) [
{
match = [ { host = [ config.hostnames.prometheus ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${config.services.prometheus.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains =
if (config.services.prometheus.enable && isServer) then [ config.hostnames.prometheus ] else [ ];
};
}

View File

@ -1,6 +1,7 @@
# Samba is a Windows-compatible file-sharing service.
{ config, lib, ... }: {
{ config, lib, ... }:
{
config = {
@ -21,15 +22,12 @@
networking.firewall.allowedUDPPorts = [ 3702 ];
# Allow client browsing Samba and virtual filesystem shares
services.gvfs =
lib.mkIf (config.gui.enable && config.nautilus.enable) { enable = true; };
services.gvfs = lib.mkIf (config.gui.enable && config.nautilus.enable) { enable = true; };
# # Permissions required to mount Samba with GVFS, if not using desktop environment
# environment.systemPackages = lib.mkIf (config.gui.enable
# && config.nautilus.enable
# && config.services.xserver.windowManager.i3.enable)
# [ pkgs.lxqt.lxqt-policykit ];
};
}

View File

@ -3,7 +3,13 @@
# In my case, I pre-encrypt my secrets and commit them to git.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
options = {
@ -14,42 +20,43 @@
};
secrets = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
source = lib.mkOption {
type = lib.types.path;
description = "Path to encrypted secret.";
type = lib.types.attrsOf (
lib.types.submodule {
options = {
source = lib.mkOption {
type = lib.types.path;
description = "Path to encrypted secret.";
};
dest = lib.mkOption {
type = lib.types.str;
description = "Resulting path for decrypted secret.";
};
owner = lib.mkOption {
default = "root";
type = lib.types.str;
description = "User to own the secret.";
};
group = lib.mkOption {
default = "root";
type = lib.types.str;
description = "Group to own the secret.";
};
permissions = lib.mkOption {
default = "0400";
type = lib.types.str;
description = "Permissions expressed as octal.";
};
prefix = lib.mkOption {
default = "";
type = lib.types.str;
description = "Prefix for secret value (for environment files).";
};
};
dest = lib.mkOption {
type = lib.types.str;
description = "Resulting path for decrypted secret.";
};
owner = lib.mkOption {
default = "root";
type = lib.types.str;
description = "User to own the secret.";
};
group = lib.mkOption {
default = "root";
type = lib.types.str;
description = "Group to own the secret.";
};
permissions = lib.mkOption {
default = "0400";
type = lib.types.str;
description = "Permissions expressed as octal.";
};
prefix = lib.mkOption {
default = "";
type = lib.types.str;
description = "Prefix for secret value (for environment files).";
};
};
});
}
);
description = "Set of secrets to decrypt to disk.";
default = { };
};
};
config = lib.mkIf pkgs.stdenv.isLinux {
@ -80,7 +87,6 @@
chown '${attrs.owner}':'${attrs.group}' '${attrs.dest}'
chmod '${attrs.permissions}' '${attrs.dest}'
'';
};
}) config.secrets;
@ -92,7 +98,5 @@
# group = "my-app";
# permissions = "0440";
# };
};
}

View File

@ -1,6 +1,12 @@
# SSHD service for allowing SSH access to my machines.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
options = {
publicKeys = lib.mkOption {
@ -27,8 +33,9 @@
};
};
users.users.${config.user}.openssh.authorizedKeys.keys =
lib.mkIf (config.publicKeys != null) config.publicKeys;
users.users.${config.user}.openssh.authorizedKeys.keys = lib.mkIf (
config.publicKeys != null
) config.publicKeys;
# Implement a simple fail2ban service for sshd
services.sshguard.enable = true;
@ -39,5 +46,4 @@
# - Will disable until fixed
environment.enableAllTerminfo = pkgs.stdenv.isLinux && pkgs.stdenv.isx86_64;
};
}

View File

@ -1,85 +1,103 @@
# Transmission is a bittorrent client, which can run in the background for
# automated downloads with a web GUI.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
config = let
namespace = config.networking.wireguard.interfaces.wg0.interfaceNamespace;
vpnIp = lib.strings.removeSuffix "/32"
(builtins.head config.networking.wireguard.interfaces.wg0.ips);
in lib.mkIf config.services.transmission.enable {
config =
let
namespace = config.networking.wireguard.interfaces.wg0.interfaceNamespace;
vpnIp = lib.strings.removeSuffix "/32" (
builtins.head config.networking.wireguard.interfaces.wg0.ips
);
in
lib.mkIf config.services.transmission.enable {
# Setup transmission
services.transmission = {
settings = {
port-forwarding-enabled = false;
rpc-authentication-required = true;
rpc-port = 9091;
rpc-bind-address = "0.0.0.0";
rpc-username = config.user;
# This is a salted hash of the real password
# https://github.com/tomwijnroks/transmission-pwgen
rpc-password = "{c4c5145f6e18bcd3c7429214a832440a45285ce26jDOBGVW";
rpc-host-whitelist = config.hostnames.transmission;
rpc-host-whitelist-enabled = true;
rpc-whitelist = lib.mkDefault "127.0.0.1"; # Overwritten by Cloudflare
rpc-whitelist-enabled = true;
# Setup transmission
services.transmission = {
settings = {
port-forwarding-enabled = false;
rpc-authentication-required = true;
rpc-port = 9091;
rpc-bind-address = "0.0.0.0";
rpc-username = config.user;
# This is a salted hash of the real password
# https://github.com/tomwijnroks/transmission-pwgen
rpc-password = "{c4c5145f6e18bcd3c7429214a832440a45285ce26jDOBGVW";
rpc-host-whitelist = config.hostnames.transmission;
rpc-host-whitelist-enabled = true;
rpc-whitelist = lib.mkDefault "127.0.0.1"; # Overwritten by Cloudflare
rpc-whitelist-enabled = true;
};
};
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.transmission ];
# Bind transmission to wireguard namespace
systemd.services.transmission = lib.mkIf config.wireguard.enable {
bindsTo = [ "netns@${namespace}.service" ];
requires = [
"network-online.target"
"transmission-secret.service"
];
after = [
"wireguard-wg0.service"
"transmission-secret.service"
];
unitConfig.JoinsNamespaceOf = "netns@${namespace}.service";
serviceConfig.NetworkNamespacePath = "/var/run/netns/${namespace}";
};
# Create reverse proxy for web UI
caddy.routes =
let
# Set if the download domain is the same as the Transmission domain
useDownloadDomain = config.hostnames.download == config.hostnames.transmission;
in
lib.mkAfter [
{
group = if useDownloadDomain then "download" else "transmission";
match = [
{
host = [ config.hostnames.transmission ];
path = if useDownloadDomain then [ "/transmission*" ] else null;
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [
{ dial = "localhost:${builtins.toString config.services.transmission.settings.rpc-port}"; }
];
}
];
}
];
# Caddy and Transmission both try to set rmem_max for larger UDP packets.
# We will choose Transmission's recommendation (4 MB).
boot.kernel.sysctl."net.core.rmem_max" = 4194304;
# Allow inbound connections to reach namespace
systemd.services.transmission-web-netns = lib.mkIf config.wireguard.enable {
description = "Forward to transmission in wireguard namespace";
requires = [ "transmission.service" ];
after = [ "transmission.service" ];
serviceConfig = {
Restart = "on-failure";
TimeoutStopSec = 300;
};
wantedBy = [ "multi-user.target" ];
script = ''
${pkgs.iproute2}/bin/ip netns exec ${namespace} ${pkgs.iproute2}/bin/ip link set dev lo up
${pkgs.socat}/bin/socat tcp-listen:9091,fork,reuseaddr exec:'${pkgs.iproute2}/bin/ip netns exec ${namespace} ${pkgs.socat}/bin/socat STDIO "tcp-connect:${vpnIp}:9091"',nofork
'';
};
};
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.transmission ];
# Bind transmission to wireguard namespace
systemd.services.transmission = lib.mkIf config.wireguard.enable {
bindsTo = [ "netns@${namespace}.service" ];
requires = [ "network-online.target" "transmission-secret.service" ];
after = [ "wireguard-wg0.service" "transmission-secret.service" ];
unitConfig.JoinsNamespaceOf = "netns@${namespace}.service";
serviceConfig.NetworkNamespacePath = "/var/run/netns/${namespace}";
};
# Create reverse proxy for web UI
caddy.routes = let
# Set if the download domain is the same as the Transmission domain
useDownloadDomain = config.hostnames.download
== config.hostnames.transmission;
in lib.mkAfter [{
group = if useDownloadDomain then "download" else "transmission";
match = [{
host = [ config.hostnames.transmission ];
path = if useDownloadDomain then [ "/transmission*" ] else null;
}];
handle = [{
handler = "reverse_proxy";
upstreams = [{
dial = "localhost:${
builtins.toString config.services.transmission.settings.rpc-port
}";
}];
}];
}];
# Caddy and Transmission both try to set rmem_max for larger UDP packets.
# We will choose Transmission's recommendation (4 MB).
boot.kernel.sysctl."net.core.rmem_max" = 4194304;
# Allow inbound connections to reach namespace
systemd.services.transmission-web-netns = lib.mkIf config.wireguard.enable {
description = "Forward to transmission in wireguard namespace";
requires = [ "transmission.service" ];
after = [ "transmission.service" ];
serviceConfig = {
Restart = "on-failure";
TimeoutStopSec = 300;
};
wantedBy = [ "multi-user.target" ];
script = ''
${pkgs.iproute2}/bin/ip netns exec ${namespace} ${pkgs.iproute2}/bin/ip link set dev lo up
${pkgs.socat}/bin/socat tcp-listen:9091,fork,reuseaddr exec:'${pkgs.iproute2}/bin/ip netns exec ${namespace} ${pkgs.socat}/bin/socat STDIO "tcp-connect:${vpnIp}:9091"',nofork
'';
};
};
}

View File

@ -2,11 +2,17 @@
# service, which allows for self-hosting the synchronization of a Bitwarden
# password manager client.
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let vaultwardenPath = "/var/lib/bitwarden_rs"; # Default service directory
in {
let
vaultwardenPath = "/var/lib/bitwarden_rs"; # Default service directory
in
{
config = lib.mkIf config.services.vaultwarden.enable {
services.vaultwarden = {
@ -39,18 +45,20 @@ in {
networking.firewall.allowedTCPPorts = [ 3012 ];
caddy.routes = [{
match = [{ host = [ config.hostnames.secrets ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{
dial = "localhost:${
builtins.toString config.services.vaultwarden.config.ROCKET_PORT
}";
}];
headers.request.add."X-Real-IP" = [ "{http.request.remote.host}" ];
}];
}];
caddy.routes = [
{
match = [ { host = [ config.hostnames.secrets ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [
{ dial = "localhost:${builtins.toString config.services.vaultwarden.config.ROCKET_PORT}"; }
];
headers.request.add."X-Real-IP" = [ "{http.request.remote.host}" ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.secrets ];
@ -58,8 +66,7 @@ in {
## Backup config
# Open to groups, allowing for backups
systemd.services.vaultwarden.serviceConfig.StateDirectoryMode =
lib.mkForce "0770";
systemd.services.vaultwarden.serviceConfig.StateDirectoryMode = lib.mkForce "0770";
systemd.tmpfiles.rules = [
"f ${vaultwardenPath}/db.sqlite3 0660 vaultwarden vaultwarden"
"f ${vaultwardenPath}/db.sqlite3-shm 0660 vaultwarden vaultwarden"
@ -73,13 +80,14 @@ in {
# Backup sqlite database with litestream
services.litestream = {
settings = {
dbs = [{
path = "${vaultwardenPath}/db.sqlite3";
replicas = [{
url =
"s3://${config.backup.s3.bucket}.${config.backup.s3.endpoint}/vaultwarden";
}];
}];
dbs = [
{
path = "${vaultwardenPath}/db.sqlite3";
replicas = [
{ url = "s3://${config.backup.s3.bucket}.${config.backup.s3.endpoint}/vaultwarden"; }
];
}
];
};
};
@ -117,7 +125,5 @@ in {
--exclude ".db.sqlite3*"
'';
};
};
}

View File

@ -1,37 +1,44 @@
# VictoriaMetrics is a more efficient drop-in replacement for Prometheus and
# InfluxDB (timeseries databases built for monitoring system metrics).
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
username = "prometheus";
prometheusConfig = (pkgs.formats.yaml { }).generate "prometheus.yml" {
scrape_configs = [{
job_name = config.networking.hostName;
stream_parse = true;
static_configs = [{ targets = config.prometheus.scrapeTargets; }];
}];
scrape_configs = [
{
job_name = config.networking.hostName;
stream_parse = true;
static_configs = [ { targets = config.prometheus.scrapeTargets; } ];
}
];
};
authConfig = (pkgs.formats.yaml { }).generate "auth.yml" {
users = [{
username = username;
password = "%{PASSWORD}";
url_prefix =
"http://localhost${config.services.victoriametrics.listenAddress}";
}];
users = [
{
username = username;
password = "%{PASSWORD}";
url_prefix = "http://localhost${config.services.victoriametrics.listenAddress}";
}
];
};
authPort = "8427";
in {
in
{
config = {
services.victoriametrics.extraOptions =
[ "-promscrape.config=${prometheusConfig}" ];
services.victoriametrics.extraOptions = [ "-promscrape.config=${prometheusConfig}" ];
systemd.services.vmauth = lib.mkIf config.services.victoriametrics.enable {
description = "VictoriaMetrics basic auth proxy";
@ -55,39 +62,38 @@ in {
dest = "${config.secretsDirectory}/vmauth";
prefix = "PASSWORD=";
};
systemd.services.vmauth-secret =
lib.mkIf config.services.victoriametrics.enable {
requiredBy = [ "vmauth.service" ];
before = [ "vmauth.service" ];
};
systemd.services.vmauth-secret = lib.mkIf config.services.victoriametrics.enable {
requiredBy = [ "vmauth.service" ];
before = [ "vmauth.service" ];
};
caddy.routes = lib.mkIf config.services.victoriametrics.enable [{
match = [{ host = [ config.hostnames.prometheus ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:${authPort}"; }];
}];
}];
caddy.routes = lib.mkIf config.services.victoriametrics.enable [
{
match = [ { host = [ config.hostnames.prometheus ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${authPort}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains =
if config.services.victoriametrics.enable then
[ config.hostnames.prometheus ]
else
[ ];
if config.services.victoriametrics.enable then [ config.hostnames.prometheus ] else [ ];
# VMAgent
services.vmagent.prometheusConfig = prometheusConfig; # Overwritten below
systemd.services.vmagent.serviceConfig =
lib.mkIf config.services.vmagent.enable {
ExecStart = lib.mkForce ''
${pkgs.victoriametrics}/bin/vmagent \
-promscrape.config=${prometheusConfig} \
-remoteWrite.url="https://${config.hostnames.prometheus}/api/v1/write" \
-remoteWrite.basicAuth.username=${username} \
-remoteWrite.basicAuth.passwordFile=${config.secrets.vmagent.dest}'';
};
systemd.services.vmagent.serviceConfig = lib.mkIf config.services.vmagent.enable {
ExecStart = lib.mkForce ''
${pkgs.victoriametrics}/bin/vmagent \
-promscrape.config=${prometheusConfig} \
-remoteWrite.url="https://${config.hostnames.prometheus}/api/v1/write" \
-remoteWrite.basicAuth.username=${username} \
-remoteWrite.basicAuth.passwordFile=${config.secrets.vmagent.dest}'';
};
secrets.vmagent = lib.mkIf config.services.vmagent.enable {
source = ../../../private/prometheus.age;
@ -99,7 +105,5 @@ in {
requiredBy = [ "vmagent.service" ];
before = [ "vmagent.service" ];
};
};
}

View File

@ -1,7 +1,13 @@
# Wireguard is a VPN protocol that can be setup to create a mesh network
# between machines on different LANs. This is currently not in use in my setup.
{ config, pkgs, lib, ... }: {
{
config,
pkgs,
lib,
...
}:
{
options.wireguard.enable = lib.mkEnableOption "Wireguard VPN setup.";
@ -21,7 +27,6 @@
# Move to network namespace for isolating programs
interfaceNamespace = "wg";
};
};
};
@ -45,7 +50,5 @@
source = ../../../private/wireguard.age;
dest = "${config.secretsDirectory}/wireguard";
};
};
}