Compare commits

...

6 Commits

Author SHA1 Message Date
Kopatz
1180c1b65f ip blocklist by clanker 2026-03-14 23:13:10 +01:00
Kopatz
d9246f76e6 rm gitolite 2026-03-14 21:52:30 +01:00
Kopatz
36183992a2 backup gitea 2026-03-14 21:28:04 +01:00
Kopatz
fb5651fc16 fix adam site storage 2026-03-14 21:06:04 +01:00
Kopatz
e609400d4e ssh config for git 2026-03-14 21:05:58 +01:00
Kopatz
c07acb5268 add gitea 2026-03-14 20:03:37 +01:00
8 changed files with 287 additions and 22 deletions

View 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 ];
}

View File

@@ -10,6 +10,7 @@
./ente.nix
./fileshelter.nix
./games
./gitea.nix
./github-runner.nix
./gitolite.nix
./kavita.nix

View 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";
};
};
}

View File

@@ -128,6 +128,7 @@ in
enable = true;
port = 9000;
globalConfig.scrape_interval = "1m";
retentionTime = "15d";
#stateDir = "../../${base}/prometheus";
exporters = {
node = {

View File

@@ -21,6 +21,38 @@ in
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 = {
enable = true;
# removed, create on initial setup now

View File

@@ -19,6 +19,8 @@
443
];
networking = {
hostname = "adam-site";
nameservers = [ "1.1.1.1" "1.0.0.1" ];
defaultGateway6 = {
address = "fe80::1";
interface = "enp1s0";

View File

@@ -20,6 +20,7 @@
../../modules/services/ddclient-cloudflare.nix
../../modules/services/grafana.nix
../../modules/services/coturn.nix
../../modules/services/blocklist.nix
./disk-config.nix
./mail.nix
(modulesPath + "/installer/scan/not-detected.nix")
@@ -58,7 +59,7 @@
backup =
let
kavita = "/data/kavita";
gitolite = "/var/lib/gitolite";
gitea = "/1tbssd/gitea";
mail = [
"/data/vmail"
"/var/lib/opendkim"
@@ -73,20 +74,18 @@
];
backupPathsSmall = [
"/home"
gitolite
]
++ syncthing
++ mail;
backupPathsMedium = [
"/home"
gitolite
]
++ syncthing
++ mail;
backupPathsFull = [
"/home"
kavita
gitolite
gitea
]
++ syncthingFull
++ mail;
@@ -104,7 +103,7 @@
};
services = {
acme.enable = true;
gitolite.enable = true;
gitea.enable = true;
github-runner.enable = true;
caldav.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;
services.xserver.videoDrivers = [ "vmware" ];
environment.systemPackages = with pkgs; [
@@ -174,25 +184,9 @@
1234 #kop-audio default 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.
#containers.privnetwork = {
# autoStart = true;
# privateNetwork = true;

View File

@@ -120,6 +120,7 @@
};
};
services.trilium-server.enable = false; # maybe consider in the future
services.nohang.enable = true;
services.logind.settings.Login = {