initial commit

This commit is contained in:
Ash Walker
2024-09-24 14:48:05 -04:00
commit 28d9bb8317
5 changed files with 292 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
result
result-*

60
README.md Normal file
View File

@@ -0,0 +1,60 @@
# Nginx Virtual Host Defaults
A small NixOS module adding the option `services.nginx.virtualHostDefaults`, which is a submodule merged into every virtual host configuration, allowing you to set options common to every Nginx virtual host.
It also adds `services.nginx.virtualHosts.<name>.blockAgents`, which allows you to easily block a a set of user agent strings from accessing a vhost.
## Usage Examples
`flake.nix`:
```nix
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
nginx-vhost-defaults = {
url = "github:signalwalker/nix.nginx.vhost-defaults";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs @ {
self,
nixpkgs,
...
}: {
nixosConfigurations."example" = nixpkgs.lib.nixosSystem {
# ...
modules = [
inputs.nginx-vhost-defaults.nixosModules.default
./nixos-module.nix
# ...
];
};
};
```
`nixos-module.nix`:
```nix
{
config,
pkgs,
lib,
...
}: {
config = {
services.nginx = {
virtualHostDefaults = {
# force SSL on every virtual host
forceSSL = true;
# Block a set of user agents from accessing any virtual host.
blockAgents = {
enable = true;
# Use `lib.mkOptionDefault` if you want to preserve the default agent list, which includes all agents found in https://github.com/ai-robots-txt/ai-robots-txt
agents = lib.mkOptionDefault ["SemrushBot"];
# This is the default, which causes Nginx to drop the connection without any response.
method = "return 444";
};
};
};
};
}
```

122
flake.lock generated Normal file
View File

@@ -0,0 +1,122 @@
{
"nodes": {
"ai-robots-txt": {
"flake": false,
"locked": {
"lastModified": 1725844581,
"narHash": "sha256-3A1cuHtqrFeJKvdizAVQxYBOZRDs/MF02HNR0DICMsM=",
"owner": "ai-robots-txt",
"repo": "ai.robots.txt",
"rev": "6b8d7f5890d6bed722a95297996c054c210bd3b8",
"type": "github"
},
"original": {
"owner": "ai-robots-txt",
"repo": "ai.robots.txt",
"type": "github"
}
},
"alejandra": {
"inputs": {
"fenix": "fenix",
"flakeCompat": "flakeCompat",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1719514321,
"narHash": "sha256-ys1nJdZ8zB8JlpUbQmnj0hZalg03bEPgQdZN30DhETE=",
"owner": "kamadorueda",
"repo": "alejandra",
"rev": "d7552fef2ccf1bbf0d36b27f6fddb19073f205b7",
"type": "github"
},
"original": {
"owner": "kamadorueda",
"repo": "alejandra",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"alejandra",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1668234453,
"narHash": "sha256-FmuZThToBvRsqCauYJ3l8HJoGLAY5cMULeYEKIaGrRw=",
"owner": "nix-community",
"repo": "fenix",
"rev": "8f219f6b36e8d0d56afa7f67e6e3df63ef013cdb",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flakeCompat": {
"flake": false,
"locked": {
"lastModified": 1650374568,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1727089097,
"narHash": "sha256-ZMHMThPsthhUREwDebXw7GX45bJnBCVbfnH1g5iuSPc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "568bfef547c14ca438c56a0bece08b8bb2b71a9c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"ai-robots-txt": "ai-robots-txt",
"alejandra": "alejandra",
"nixpkgs": "nixpkgs"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1668182250,
"narHash": "sha256-PYGaOCiFvnJdVz+ZCaKF8geGdffXjJUNcMwaBHv0FT4=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "45ec315e01dc8dd1146dfeb65f0ef6e5c2efed78",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

25
flake.nix Normal file
View File

@@ -0,0 +1,25 @@
{
description = "A NixOS module for easily blocking user agents from Nginx.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
alejandra = {
url = "github:kamadorueda/alejandra";
inputs.nixpkgs.follows = "nixpkgs";
};
ai-robots-txt = {
url = "github:ai-robots-txt/ai.robots.txt";
flake = false;
};
};
outputs = inputs @ {
self,
nixpkgs,
...
}:
with builtins; let
std = nixpkgs.lib;
in {
formatter = std.mapAttrs (system: pkgs: pkgs.default) inputs.alejandra.packages;
nixosModules.default = import ./nixos-module.nix {inherit self;};
};
}

83
nixos-module.nix Normal file
View File

@@ -0,0 +1,83 @@
{self}: {
config,
pkgs,
lib,
...
}:
with builtins; let
std = pkgs.lib;
nginx = config.services.nginx;
mkRobotsTxt = robots: let
agents = std.concatStringsSep "\n" (map (agent: "User-agent: ${agent}") robots);
in
pkgs.writeText "robots.txt" ''
${agents}
Disallow: /
'';
regexEscapes = ["\"" "[" "]" "(" ")" "{" "}" "^" "$" "+" "*" "." "|" "?" "\\"];
defaultBlockList = attrNames (fromJSON (readFile "${self.inputs.ai-robots-txt}/robots.json"));
in {
options = with lib; {
services.nginx = {
virtualHostDefaults = mkOption {
type = types.deferredModuleWith {
staticModules = [];
};
description = "Default configuration merged into every virtual host.";
default = {};
};
virtualHosts = mkOption {
# type-merge the `virtualHosts` submodule so we can import `nginx.virtualHostDefaults` into every virtualHost
type = types.attrsOf (types.submoduleWith {
modules = [
nginx.virtualHostDefaults
({
config,
pkgs,
lib,
...
}: {
options = {
blockAgents = {
enable = mkEnableOption "blocking a set of user agents from accessing this virtual host.";
agents = mkOption {
type = types.listOf types.str;
description = "User agent strings to block from accessing this virtual host.";
default = defaultBlockList;
defaultText = "The user agent list from [github:ai-robots-txt/ai-robots-txt](https://github.com/ai-robots-txt/ai-robots-txt).";
example = ["Amazonbot" "AI2Bot" "Applebot"];
};
method = mkOption {
type = types.str;
description = "Method by which to block agents.";
default = "return 444";
defaultText = "`return 444`, dropping the connection.";
example = "return 307 https://ash-speed.hetzner.com/10GB.bin";
};
};
};
config = lib.mkMerge [
(lib.mkIf (config.blockAgents.enable && (length config.blockAgents.agents) > 0) {
locations."=/robots.txt" = {
alias = nginx.robotsTxt;
};
extraConfig = let
agentRules = pkgs.lib.concatStringsSep "|" (map (lib.strings.escape regexEscapes) config.blockAgents.agents);
in ''
if ($http_user_agent ~* "(${agentRules})") {
${config.blockAgents.method};
}
'';
})
];
})
];
});
};
};
};
disabledModules = [];
imports = [];
config = {};
meta = {};
}