Compare commits
6 Commits
5a384c5043
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1180c1b65f | ||
|
|
d9246f76e6 | ||
|
|
36183992a2 | ||
|
|
fb5651fc16 | ||
|
|
e609400d4e | ||
|
|
c07acb5268 |
192
modules/services/blocklist.nix
Normal file
192
modules/services/blocklist.nix
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
# --- Blocklist sources ---
|
||||||
|
# Add or remove URLs here. Each must be a plain list of IPs or CIDRs (one per line).
|
||||||
|
# Comments (#) and blank lines are stripped automatically.
|
||||||
|
blocklistSources = [
|
||||||
|
# DShield top attacking subnets (SANS ISC) — tab-separated: IP / mask / CIDR prefix
|
||||||
|
"https://feeds.dshield.org/block.txt"
|
||||||
|
# blocklist.de — scanners, bots, brute-force, scrapers
|
||||||
|
"https://lists.blocklist.de/lists/all.txt"
|
||||||
|
# GreenSnow — bots and port scanners
|
||||||
|
"https://blocklist.greensnow.co/greensnow.txt"
|
||||||
|
# Spamhaus DROP — hijacked netblocks used by cybercrime operations
|
||||||
|
"https://www.spamhaus.org/drop/drop.txt"
|
||||||
|
# FireHOL level2 — 48-hour attack tracker, pre-merged CIDRs (good all-rounder)
|
||||||
|
"https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level2.netset"
|
||||||
|
];
|
||||||
|
|
||||||
|
# nftables table and set names — must match your existing nftables config
|
||||||
|
nftTable = "inet ip_drop";
|
||||||
|
nftSet = "blocked-ip4";
|
||||||
|
|
||||||
|
# How often to refresh (systemd calendar format)
|
||||||
|
refreshInterval = "*:0/4"; # every 4 hours
|
||||||
|
|
||||||
|
# --- The updater script ---
|
||||||
|
# Written as a pkgs.writeShellApplication so nix handles the PATH / dependencies.
|
||||||
|
updaterScript = pkgs.writeShellApplication {
|
||||||
|
name = "nft-blocklist-update";
|
||||||
|
|
||||||
|
runtimeInputs = with pkgs; [
|
||||||
|
curl
|
||||||
|
gawk
|
||||||
|
iproute2 # provides 'ip' for basic sanity; not strictly needed but good to have
|
||||||
|
nftables # provides 'nft'
|
||||||
|
coreutils
|
||||||
|
];
|
||||||
|
|
||||||
|
text = ''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
NFT_TABLE="${nftTable}"
|
||||||
|
NFT_SET="${nftSet}"
|
||||||
|
|
||||||
|
TMPFILE=$(mktemp /tmp/nft-blocklist-XXXXXX.txt)
|
||||||
|
trap 'rm -f "$TMPFILE"' EXIT
|
||||||
|
|
||||||
|
echo "[blocklist] Fetching sources..."
|
||||||
|
|
||||||
|
# --- Per-source fetch + normalise ---
|
||||||
|
# Each source may use a different format; we normalise to bare CIDRs.
|
||||||
|
fetch_and_normalise() {
|
||||||
|
local url="$1"
|
||||||
|
curl --silent --fail --max-time 30 --retry 3 "$url" | \
|
||||||
|
awk '
|
||||||
|
# Skip blank lines and comment lines
|
||||||
|
/^[[:space:]]*$/ { next }
|
||||||
|
/^[[:space:]]*#/ { next }
|
||||||
|
|
||||||
|
# DShield format: "IP<tab>netmask<tab>prefix ..." — convert to CIDR
|
||||||
|
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[[:space:]]+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[[:space:]]+[0-9]+/ {
|
||||||
|
print $1 "/" $3
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# Plain CIDR (e.g. 1.2.3.0/24) — pass through
|
||||||
|
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+/ {
|
||||||
|
print $1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# Plain host IP (no prefix) — treat as /32
|
||||||
|
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ {
|
||||||
|
print $1 "/32"
|
||||||
|
next
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fetch all sources into the temp file, skip on individual failure
|
||||||
|
${lib.concatMapStringsSep "\n" (url: ''
|
||||||
|
echo "[blocklist] Fetching: ${url}"
|
||||||
|
fetch_and_normalise "${url}" >> "$TMPFILE" || \
|
||||||
|
echo "[blocklist] WARNING: failed to fetch ${url}, skipping" >&2
|
||||||
|
'') blocklistSources}
|
||||||
|
|
||||||
|
# Deduplicate lines (nft auto-merge handles overlapping CIDRs at load time)
|
||||||
|
sort -u "$TMPFILE" -o "$TMPFILE"
|
||||||
|
|
||||||
|
TOTAL=$(wc -l < "$TMPFILE")
|
||||||
|
echo "[blocklist] Total entries after dedup: $TOTAL"
|
||||||
|
|
||||||
|
if [ "$TOTAL" -eq 0 ]; then
|
||||||
|
echo "[blocklist] ERROR: no entries fetched — aborting to avoid flushing the set" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the nft element string: "1.2.3.0/24, 5.6.7.8/32, ..."
|
||||||
|
ELEMENTS=$(paste -sd ',' "$TMPFILE")
|
||||||
|
|
||||||
|
echo "[blocklist] Loading into nft set $NFT_TABLE / $NFT_SET ..."
|
||||||
|
|
||||||
|
# Atomic-ish update: flush then reload inside a single nft transaction
|
||||||
|
nft -f - <<EOF
|
||||||
|
flush set $NFT_TABLE $NFT_SET
|
||||||
|
add element $NFT_TABLE $NFT_SET { $ELEMENTS }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "[blocklist] Done. $TOTAL entries active."
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# systemd one-shot service that runs the updater
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
systemd.services.nft-blocklist-update = {
|
||||||
|
description = "Update nftables IP blocklist (scanners / bots / scrapers)";
|
||||||
|
after = [
|
||||||
|
"network-online.target"
|
||||||
|
"nftables.service"
|
||||||
|
];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
# Also run once at boot (after nftables is up) so the set is populated immediately
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = lib.getExe updaterScript;
|
||||||
|
# Run as root — required to call nft
|
||||||
|
User = "root";
|
||||||
|
# Basic hardening
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ProtectHome = true;
|
||||||
|
# Allow writing only to /tmp (via PrivateTmp)
|
||||||
|
ReadWritePaths = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# systemd timer — fires on the configured interval
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
systemd.timers.nft-blocklist-update = {
|
||||||
|
description = "Periodically refresh nftables IP blocklist";
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = refreshInterval;
|
||||||
|
# Spread the start time by up to 5 min to avoid thundering-herd
|
||||||
|
RandomizedDelaySec = "5m";
|
||||||
|
Persistent = true; # catch up if the machine was off
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# nftables — make sure the table + set exist so the updater can populate them
|
||||||
|
# This merges with whatever other rules you already have.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
networking.nftables.tables.ip_drop = {
|
||||||
|
family = "inet";
|
||||||
|
content = ''
|
||||||
|
set blocked-ip4 {
|
||||||
|
typeof ip saddr
|
||||||
|
flags interval
|
||||||
|
auto-merge
|
||||||
|
# starts empty; nft-blocklist-update.service fills it at boot + every 4h
|
||||||
|
elements = { 45.144.212.240, 74.7.227.136 }
|
||||||
|
}
|
||||||
|
|
||||||
|
chain input {
|
||||||
|
type filter hook input priority -100; policy accept;
|
||||||
|
ip saddr @blocked-ip4 log prefix "nftables drop: " level info counter drop
|
||||||
|
}
|
||||||
|
|
||||||
|
chain forward {
|
||||||
|
type filter hook forward priority -100; policy accept;
|
||||||
|
ip saddr @blocked-ip4 drop
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Make the script available on PATH for manual runs: nft-blocklist-update
|
||||||
|
environment.systemPackages = [ updaterScript ];
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
./ente.nix
|
./ente.nix
|
||||||
./fileshelter.nix
|
./fileshelter.nix
|
||||||
./games
|
./games
|
||||||
|
./gitea.nix
|
||||||
./github-runner.nix
|
./github-runner.nix
|
||||||
./gitolite.nix
|
./gitolite.nix
|
||||||
./kavita.nix
|
./kavita.nix
|
||||||
|
|||||||
42
modules/services/gitea.nix
Normal file
42
modules/services/gitea.nix
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.custom.services.gitea;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.custom.services.gitea = {
|
||||||
|
enable = lib.mkEnableOption "Enables gitea";
|
||||||
|
fqdn = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "git.kopatz.dev";
|
||||||
|
description = "FQDN under which gitea is available";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# https://docs.gitea.com/next/administration/config-cheat-sheet
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
# Initial setup requires
|
||||||
|
services.gitea = {
|
||||||
|
enable = true;
|
||||||
|
stateDir = "/1tbssd/gitea";
|
||||||
|
settings = {
|
||||||
|
server.HTTP_PORT = 3001;
|
||||||
|
service.DISABLE_REGISTRATION = true;
|
||||||
|
server.DOMAIN = cfg.fqdn;
|
||||||
|
server.ROOT_URL = "https://${cfg.fqdn}";
|
||||||
|
#server.DISABLE_SSH = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services.nginx.virtualHosts."${cfg.fqdn}" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
quic = true;
|
||||||
|
http3 = true;
|
||||||
|
locations."/".proxyPass = "http://localhost:3001";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -128,6 +128,7 @@ in
|
|||||||
enable = true;
|
enable = true;
|
||||||
port = 9000;
|
port = 9000;
|
||||||
globalConfig.scrape_interval = "1m";
|
globalConfig.scrape_interval = "1m";
|
||||||
|
retentionTime = "15d";
|
||||||
#stateDir = "../../${base}/prometheus";
|
#stateDir = "../../${base}/prometheus";
|
||||||
exporters = {
|
exporters = {
|
||||||
node = {
|
node = {
|
||||||
|
|||||||
@@ -21,6 +21,38 @@ in
|
|||||||
file = ../../secrets/plausible-keybase.age;
|
file = ../../secrets/plausible-keybase.age;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.clickhouse = {
|
||||||
|
enable = true;
|
||||||
|
extraUsersConfig = ''
|
||||||
|
<clickhouse>
|
||||||
|
<profiles>
|
||||||
|
<default>
|
||||||
|
<log_queries>0</log_queries>
|
||||||
|
<log_query_threads>0</log_query_threads>
|
||||||
|
</default>
|
||||||
|
</profiles>
|
||||||
|
</clickhouse>
|
||||||
|
'';
|
||||||
|
extraServerConfig = ''
|
||||||
|
<clickhouse>
|
||||||
|
<logger>
|
||||||
|
<level>warning</level>
|
||||||
|
<console>true</console>
|
||||||
|
</logger>
|
||||||
|
<query_thread_log remove="remove"/>
|
||||||
|
<query_log remove="remove"/>
|
||||||
|
<text_log remove="remove"/>
|
||||||
|
<trace_log remove="remove"/>
|
||||||
|
<metric_log remove="remove"/>
|
||||||
|
<asynchronous_metric_log remove="remove"/>
|
||||||
|
|
||||||
|
<!-- Update: Required for newer versions of Clickhouse -->
|
||||||
|
<session_log remove="remove"/>
|
||||||
|
<part_log remove="remove"/>
|
||||||
|
</clickhouse>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
services.plausible = {
|
services.plausible = {
|
||||||
enable = true;
|
enable = true;
|
||||||
# removed, create on initial setup now
|
# removed, create on initial setup now
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
443
|
443
|
||||||
];
|
];
|
||||||
networking = {
|
networking = {
|
||||||
|
hostname = "adam-site";
|
||||||
|
nameservers = [ "1.1.1.1" "1.0.0.1" ];
|
||||||
defaultGateway6 = {
|
defaultGateway6 = {
|
||||||
address = "fe80::1";
|
address = "fe80::1";
|
||||||
interface = "enp1s0";
|
interface = "enp1s0";
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
../../modules/services/ddclient-cloudflare.nix
|
../../modules/services/ddclient-cloudflare.nix
|
||||||
../../modules/services/grafana.nix
|
../../modules/services/grafana.nix
|
||||||
../../modules/services/coturn.nix
|
../../modules/services/coturn.nix
|
||||||
|
../../modules/services/blocklist.nix
|
||||||
./disk-config.nix
|
./disk-config.nix
|
||||||
./mail.nix
|
./mail.nix
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
@@ -58,7 +59,7 @@
|
|||||||
backup =
|
backup =
|
||||||
let
|
let
|
||||||
kavita = "/data/kavita";
|
kavita = "/data/kavita";
|
||||||
gitolite = "/var/lib/gitolite";
|
gitea = "/1tbssd/gitea";
|
||||||
mail = [
|
mail = [
|
||||||
"/data/vmail"
|
"/data/vmail"
|
||||||
"/var/lib/opendkim"
|
"/var/lib/opendkim"
|
||||||
@@ -73,20 +74,18 @@
|
|||||||
];
|
];
|
||||||
backupPathsSmall = [
|
backupPathsSmall = [
|
||||||
"/home"
|
"/home"
|
||||||
gitolite
|
|
||||||
]
|
]
|
||||||
++ syncthing
|
++ syncthing
|
||||||
++ mail;
|
++ mail;
|
||||||
backupPathsMedium = [
|
backupPathsMedium = [
|
||||||
"/home"
|
"/home"
|
||||||
gitolite
|
|
||||||
]
|
]
|
||||||
++ syncthing
|
++ syncthing
|
||||||
++ mail;
|
++ mail;
|
||||||
backupPathsFull = [
|
backupPathsFull = [
|
||||||
"/home"
|
"/home"
|
||||||
kavita
|
kavita
|
||||||
gitolite
|
gitea
|
||||||
]
|
]
|
||||||
++ syncthingFull
|
++ syncthingFull
|
||||||
++ mail;
|
++ mail;
|
||||||
@@ -104,7 +103,7 @@
|
|||||||
};
|
};
|
||||||
services = {
|
services = {
|
||||||
acme.enable = true;
|
acme.enable = true;
|
||||||
gitolite.enable = true;
|
gitea.enable = true;
|
||||||
github-runner.enable = true;
|
github-runner.enable = true;
|
||||||
caldav.enable = true;
|
caldav.enable = true;
|
||||||
kop-monitor.enable = true;
|
kop-monitor.enable = true;
|
||||||
@@ -156,6 +155,17 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.openssh.extraConfig = ''
|
||||||
|
# Internal network: allow everyone
|
||||||
|
Match Address 192.168.2.0/24,192.168.0.0/24
|
||||||
|
AllowUsers *
|
||||||
|
|
||||||
|
# Everything else (internet): only git user
|
||||||
|
Match Address *,!192.168.2.0/24,!192.168.0.0/24
|
||||||
|
PermitRootLogin no
|
||||||
|
AllowUsers gitea
|
||||||
|
'';
|
||||||
|
|
||||||
virtualisation.vmware.guest.enable = true;
|
virtualisation.vmware.guest.enable = true;
|
||||||
services.xserver.videoDrivers = [ "vmware" ];
|
services.xserver.videoDrivers = [ "vmware" ];
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
@@ -174,25 +184,9 @@
|
|||||||
1234 #kop-audio default port
|
1234 #kop-audio default port
|
||||||
#9987 # teamspeak6 voice port
|
#9987 # teamspeak6 voice port
|
||||||
];
|
];
|
||||||
networking.nftables.tables.ip_drop = {
|
|
||||||
family = "inet";
|
|
||||||
content = ''
|
|
||||||
set blocked-ip4 {
|
|
||||||
typeof ip saddr
|
|
||||||
flags interval
|
|
||||||
auto-merge
|
|
||||||
elements = { 45.144.212.240 }
|
|
||||||
}
|
|
||||||
chain input {
|
|
||||||
# -100 priority to run before the default filter input chain (0)
|
|
||||||
type filter hook input priority -100; policy accept;
|
|
||||||
|
|
||||||
ip saddr @blocked-ip4 log prefix "nftables drop: " level info counter drop
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
networking.hostName = "server-vm"; # Define your hostname.
|
networking.hostName = "server-vm"; # Define your hostname.
|
||||||
|
|
||||||
|
|
||||||
#containers.privnetwork = {
|
#containers.privnetwork = {
|
||||||
# autoStart = true;
|
# autoStart = true;
|
||||||
# privateNetwork = true;
|
# privateNetwork = true;
|
||||||
|
|||||||
@@ -120,6 +120,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.trilium-server.enable = false; # maybe consider in the future
|
||||||
services.nohang.enable = true;
|
services.nohang.enable = true;
|
||||||
|
|
||||||
services.logind.settings.Login = {
|
services.logind.settings.Login = {
|
||||||
|
|||||||
Reference in New Issue
Block a user