mirror of
https://github.com/nmasur/dotfiles
synced 2025-07-06 19:00:14 +00:00
continuing dev
This commit is contained in:
@ -1,60 +0,0 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
home-packages = config.home-manager.users.${config.user}.home.packages;
|
||||
in
|
||||
{
|
||||
|
||||
options.gaming.legendary.enable = lib.mkEnableOption "Legendary Epic Games launcher.";
|
||||
|
||||
config = lib.mkIf config.gaming.legendary.enable {
|
||||
environment.systemPackages = with pkgs; [
|
||||
legendary-gl
|
||||
wineWowPackages.stable # 32-bit and 64-bit wineWowPackages, see https://nixos.wiki/wiki/Wine
|
||||
heroic # GUI launcher
|
||||
];
|
||||
|
||||
home-manager.users.${config.user} = {
|
||||
|
||||
xdg.configFile."legendary/config.ini".text = ''
|
||||
[Legendary]
|
||||
; Disables the automatic update check
|
||||
disable_update_check = false
|
||||
; Disables the notice about an available update on exit
|
||||
disable_update_notice = true
|
||||
; Set install directory
|
||||
install_dir = ${config.homePath}/media/games
|
||||
; Make output quiet
|
||||
log_level = error
|
||||
'';
|
||||
|
||||
home.file =
|
||||
let
|
||||
ignorePatterns = ''
|
||||
.wine/
|
||||
drive_c/'';
|
||||
in
|
||||
{
|
||||
".rgignore".text = ignorePatterns;
|
||||
".fdignore".text = ignorePatterns;
|
||||
};
|
||||
|
||||
programs.fish.functions = lib.mkIf (builtins.elem pkgs.fzf home-packages) {
|
||||
epic-games = {
|
||||
body = ''
|
||||
set game (legendary list 2>/dev/null \
|
||||
| awk '/^ \* / { print $0; }' \
|
||||
| sed -e 's/ (.*)$//' -e 's/ \* //' \
|
||||
| fzf)
|
||||
and legendary launch "$game" &> /dev/null
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
options.gaming.lutris.enable = lib.mkEnableOption "Lutris game installer.";
|
||||
|
||||
config = lib.mkIf config.gaming.lutris.enable {
|
||||
environment.systemPackages = with pkgs; [
|
||||
lutris
|
||||
amdvlk # Vulkan drivers (probably already installed)
|
||||
wineWowPackages.stable # 32-bit and 64-bit wineWowPackages
|
||||
];
|
||||
};
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
options.gaming.ryujinx.enable = lib.mkEnableOption "Ryujinx Nintendo Switch application.";
|
||||
|
||||
config = lib.mkIf config.gaming.ryujinx.enable {
|
||||
environment.systemPackages = with pkgs; [ ryujinx ];
|
||||
|
||||
home-manager.users.${config.user}.xdg.desktopEntries.ryujinx = lib.mkIf pkgs.stdenv.isLinux {
|
||||
name = "Ryujinx";
|
||||
exec = "env DOTNET_EnableAlternateStackCheck=1 Ryujinx -r /home/${config.user}/media/games/ryujinx/ %f";
|
||||
};
|
||||
};
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
|
||||
rofi = config.home-manager.users.${config.user}.programs.rofi.finalPackage;
|
||||
in
|
||||
{
|
||||
|
||||
# Adapted from:
|
||||
# A rofi powered menu to execute brightness choices.
|
||||
|
||||
config.brightnessCommand = builtins.toString (
|
||||
pkgs.writeShellScript "brightness" ''
|
||||
|
||||
dimmer=""
|
||||
medium=""
|
||||
brighter=""
|
||||
|
||||
chosen=$(printf '%s;%s;%s\n' \
|
||||
"$dimmer" \
|
||||
"$medium" \
|
||||
"$brighter" \
|
||||
| ${rofi}/bin/rofi \
|
||||
-theme-str '@import "brightness.rasi"' \
|
||||
-hover-select \
|
||||
-me-select-entry ''' \
|
||||
-me-accept-entry MousePrimary \
|
||||
-dmenu \
|
||||
-sep ';' \
|
||||
-selected-row 1)
|
||||
|
||||
|
||||
case "$chosen" in
|
||||
"$dimmer")
|
||||
${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 25; ${pkgs.ddcutil}/bin/ddcutil --disable-dynamic-sleep --display 2 setvcp 10 25
|
||||
;;
|
||||
|
||||
"$medium")
|
||||
${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 75; ${pkgs.ddcutil}/bin/ddcutil --disable-dynamic-sleep --display 2 setvcp 10 75
|
||||
;;
|
||||
|
||||
"$brighter")
|
||||
${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 100; ${pkgs.ddcutil}/bin/ddcutil --disable-dynamic-sleep --display 2 setvcp 10 100
|
||||
;;
|
||||
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
|
||||
''
|
||||
);
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
|
||||
rofi = config.home-manager.users.${config.user}.programs.rofi.finalPackage;
|
||||
in
|
||||
{
|
||||
|
||||
# Adapted from:
|
||||
# https://gitlab.com/vahnrr/rofi-menus/-/blob/b1f0e8a676eda5552e27ef631b0d43e660b23b8e/scripts/rofi-power
|
||||
# A rofi powered menu to execute power related action.
|
||||
|
||||
config.powerCommand = builtins.toString (
|
||||
pkgs.writeShellScript "powermenu" ''
|
||||
power_off=''
|
||||
reboot=''
|
||||
lock=''
|
||||
suspend=''
|
||||
log_out=''
|
||||
|
||||
chosen=$(printf '%s;%s;%s;%s;%s\n' \
|
||||
"$power_off" \
|
||||
"$reboot" \
|
||||
"$lock" \
|
||||
"$suspend" \
|
||||
"$log_out" \
|
||||
| ${rofi}/bin/rofi \
|
||||
-theme-str '@import "power.rasi"' \
|
||||
-hover-select \
|
||||
-me-select-entry "" \
|
||||
-me-accept-entry MousePrimary \
|
||||
-dmenu \
|
||||
-sep ';' \
|
||||
-selected-row 2)
|
||||
|
||||
confirm () {
|
||||
${builtins.readFile ./rofi-prompt.sh}
|
||||
}
|
||||
|
||||
case "$chosen" in
|
||||
"$power_off")
|
||||
confirm 'Shutdown?' && doas shutdown now
|
||||
;;
|
||||
|
||||
"$reboot")
|
||||
confirm 'Reboot?' && doas reboot
|
||||
;;
|
||||
|
||||
"$lock")
|
||||
${pkgs.betterlockscreen}/bin/betterlockscreen --lock --display 1 --blur 0.5 --span
|
||||
;;
|
||||
|
||||
"$suspend")
|
||||
systemctl suspend
|
||||
;;
|
||||
|
||||
"$log_out")
|
||||
confirm 'Logout?' && i3-msg exit
|
||||
;;
|
||||
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
''
|
||||
);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Credit: https://gist.github.com/Nervengift/844a597104631c36513c
|
||||
|
||||
sink=$(
|
||||
ponymix -t sink list |
|
||||
awk '/^sink/ {s=$1" "$2;getline;gsub(/^ +/,"",$0);print s" "$0}' |
|
||||
rofi \
|
||||
-dmenu \
|
||||
-p 'pulseaudio sink:' \
|
||||
-width 100 \
|
||||
-hover-select \
|
||||
-me-select-entry '' \
|
||||
-me-accept-entry MousePrimary \
|
||||
-theme-str 'inputbar { enabled: false; }' |
|
||||
grep -Po '[0-9]+(?=:)'
|
||||
) &&
|
||||
ponymix set-default -d "$sink" &&
|
||||
for input in $(ponymix list -t sink-input | grep -Po '[0-9]+(?=:)'); do
|
||||
echo "$input -> $sink"
|
||||
ponymix -t sink-input -d "$input" move "$sink"
|
||||
done
|
@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Credit: https://gitlab.com/vahnrr/rofi-menus/-/blob/b1f0e8a676eda5552e27ef631b0d43e660b23b8e/scripts/rofi-prompt
|
||||
|
||||
# Rofi powered menu to prompt a message and get a yes/no answer.
|
||||
# Uses: rofi
|
||||
|
||||
yes='Confirm'
|
||||
no='Cancel'
|
||||
query='Are you sure?'
|
||||
|
||||
while [ $# -ne 0 ]; do
|
||||
case "$1" in
|
||||
-y | --yes)
|
||||
[ -n "$2" ] && yes="$2" || yes=''
|
||||
shift
|
||||
;;
|
||||
|
||||
-n | --no)
|
||||
[ -n "$2" ] && no="$2" || no=''
|
||||
shift
|
||||
;;
|
||||
|
||||
-q | --query)
|
||||
[ -n "$2" ] && query="$2"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
chosen=$(printf '%s;%s\n' "$yes" "$no" |
|
||||
rofi -theme-str '@import "prompt.rasi"' \
|
||||
-hover-select \
|
||||
-me-select-entry "" \
|
||||
-me-accept-entry MousePrimary \
|
||||
-p "$query" \
|
||||
-dmenu \
|
||||
-sep ';' \
|
||||
-a 0 \
|
||||
-u 1 \
|
||||
-selected-row 1)
|
||||
|
||||
case "$chosen" in
|
||||
"$yes") return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
@ -1,6 +0,0 @@
|
||||
@import "common.rasi"
|
||||
|
||||
#window {
|
||||
width: 605px;
|
||||
height: 230px;
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Allows to change the settings of every menu simply by editing this file
|
||||
* https://gitlab.com/vahnrr/rofi-menus/-/blob/b1f0e8a676eda5552e27ef631b0d43e660b23b8e/themes/shared/settings.rasi
|
||||
*/
|
||||
|
||||
* {
|
||||
/* General */
|
||||
font: "Hack Nerd Font 60";
|
||||
|
||||
/* option menus: i3-layout, music, power and screenshot
|
||||
*
|
||||
* Values bellow are 'no-padding' ones for a size 60 (@icon-font) font, played
|
||||
* around using this character: ■
|
||||
* We then add add 100 actual padding around the icons.
|
||||
* -12px 0px -19px -96px */
|
||||
option-element-padding: 1% 1% 1% 1%;
|
||||
option-5-window-padding: 4% 4%;
|
||||
option-5-listview-spacing: 15px;
|
||||
|
||||
prompt-text-font: "Hack Nerd Font 18";
|
||||
prompt-window-height: 300px;
|
||||
prompt-window-width: 627px;
|
||||
prompt-window-border: 2px;
|
||||
prompt-prompt-padding: 20px 30px;
|
||||
prompt-prompt-margin: 8px;
|
||||
prompt-listview-padding: 60px 114px 0px 114px;
|
||||
/* Values bellow are 'no-padding' ones for a size 18 (@prompt-text-font) font,
|
||||
* played around using this character: ■
|
||||
* We then add add 30 actual padding around the text.
|
||||
* -4px -1px -6px -28px */
|
||||
prompt-element-padding: 26px 29px 24px 2px;
|
||||
|
||||
vpn-textbox-prompt-colon-padding: @network-textbox-prompt-colon-padding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings used in every rofi option menu:
|
||||
*/
|
||||
#window {
|
||||
children: [ horibox ];
|
||||
}
|
||||
#horibox {
|
||||
children: [ listview ];
|
||||
}
|
||||
#listview {
|
||||
layout: horizontal;
|
||||
}
|
||||
element {
|
||||
padding: 40px 68px 43px 30px;
|
||||
}
|
||||
#window {
|
||||
padding: 20px;
|
||||
}
|
||||
#listview {
|
||||
spacing: 10px;
|
||||
lines: 5;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
#entry {
|
||||
placeholder: "Launch Program";
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
@import "common.rasi"
|
||||
|
||||
#window {
|
||||
width: 980px;
|
||||
height: 230px;
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* This theme is intended for a 2 items option menu with a headerbar.
|
||||
* https://gitlab.com/vahnrr/rofi-menus/-/blob/b1f0e8a676eda5552e27ef631b0d43e660b23b8e/themes/prompt.rasi
|
||||
*/
|
||||
@import "common.rasi"
|
||||
* {
|
||||
font: @prompt-text-font;
|
||||
}
|
||||
#window {
|
||||
height: @prompt-window-height;
|
||||
width: @prompt-window-width;
|
||||
children: [ inputbar, horibox ];
|
||||
border: @prompt-window-border;
|
||||
}
|
||||
#inputbar {
|
||||
enabled: false;
|
||||
}
|
||||
#prompt {
|
||||
padding: @prompt-prompt-padding;
|
||||
margin: @prompt-prompt-margin;
|
||||
}
|
||||
#listview {
|
||||
padding: @prompt-listview-padding;
|
||||
spacing: @option-5-listview-spacing;
|
||||
lines: 2;
|
||||
}
|
||||
#element {
|
||||
font: @prompt-text-font;
|
||||
padding: @prompt-element-padding;
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
# This module is necessary for hosts that are serving through Cloudflare.
|
||||
|
||||
# Cloudflare is a CDN service that is used to serve the domain names and
|
||||
# caching for my websites and services. Since Cloudflare acts as our proxy, we
|
||||
# must allow access over the Internet from Cloudflare's IP ranges.
|
||||
|
||||
# We also want to validate our HTTPS certificates from Caddy. We'll use Caddy's
|
||||
# DNS validation plugin to connect to Cloudflare and automatically create
|
||||
# validation DNS records for our generated certificates.
|
||||
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
pkgs-caddy,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
cloudflareIpRanges = [
|
||||
|
||||
# Cloudflare IPv4: https://www.cloudflare.com/ips-v4
|
||||
"173.245.48.0/20"
|
||||
"103.21.244.0/22"
|
||||
"103.22.200.0/22"
|
||||
"103.31.4.0/22"
|
||||
"141.101.64.0/18"
|
||||
"108.162.192.0/18"
|
||||
"190.93.240.0/20"
|
||||
"188.114.96.0/20"
|
||||
"197.234.240.0/22"
|
||||
"198.41.128.0/17"
|
||||
"162.158.0.0/15"
|
||||
"104.16.0.0/13"
|
||||
"104.24.0.0/14"
|
||||
"172.64.0.0/13"
|
||||
"131.0.72.0/22"
|
||||
|
||||
# Cloudflare IPv6: https://www.cloudflare.com/ips-v6
|
||||
"2400:cb00::/32"
|
||||
"2606:4700::/32"
|
||||
"2803:f800::/32"
|
||||
"2405:b500::/32"
|
||||
"2405:8100::/32"
|
||||
"2a06:98c0::/29"
|
||||
"2c0f:f248::/32"
|
||||
];
|
||||
in
|
||||
{
|
||||
|
||||
options.cloudflare.enable = lib.mkEnableOption "Use Cloudflare.";
|
||||
|
||||
options.cloudflare.noProxyDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "Domains to use for dyndns without CDN proxying.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
config = lib.mkIf config.cloudflare.enable {
|
||||
|
||||
# Forces Caddy to error if coming from a non-Cloudflare IP
|
||||
caddy.cidrAllowlist = cloudflareIpRanges;
|
||||
|
||||
# Tell Caddy to use Cloudflare DNS for ACME challenge validation
|
||||
services.caddy.package = pkgs-caddy.caddy.override {
|
||||
externalPlugins = [
|
||||
{
|
||||
name = "cloudflare";
|
||||
repo = "github.com/caddy-dns/cloudflare";
|
||||
version = "master";
|
||||
}
|
||||
];
|
||||
vendorHash = "sha256-C7JOGd4sXsRZL561oP84V2/pTg7szEgF4OFOw35yS1s=";
|
||||
};
|
||||
caddy.tlsPolicies = [
|
||||
{
|
||||
issuers = [
|
||||
{
|
||||
module = "acme";
|
||||
email = "acme@${config.mail.server}";
|
||||
account_key = "{env.ACME_ACCOUNT_KEY}";
|
||||
challenges = {
|
||||
dns = {
|
||||
provider = {
|
||||
name = "cloudflare";
|
||||
api_token = "{env.CLOUDFLARE_API_TOKEN}";
|
||||
};
|
||||
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
|
||||
config.secrets.letsencrypt-key.dest
|
||||
];
|
||||
|
||||
# Private key is used for LetsEncrypt
|
||||
secrets.letsencrypt-key = {
|
||||
source = ../../../private/letsencrypt-key.age;
|
||||
dest = "${config.secretsDirectory}/letsencrypt-key";
|
||||
owner = "caddy";
|
||||
group = "caddy";
|
||||
};
|
||||
|
||||
# API key must have access to modify Cloudflare DNS records
|
||||
secrets.cloudflare-api = {
|
||||
source = ../../../private/cloudflare-api.age;
|
||||
dest = "${config.secretsDirectory}/cloudflare-api";
|
||||
owner = "caddy";
|
||||
group = "caddy";
|
||||
};
|
||||
|
||||
# Wait for secret to exist
|
||||
systemd.services.caddy = {
|
||||
after = [
|
||||
"cloudflare-api-secret.service"
|
||||
"letsencrypt-key-secret.service"
|
||||
];
|
||||
requires = [
|
||||
"cloudflare-api-secret.service"
|
||||
"letsencrypt-key-secret.service"
|
||||
];
|
||||
};
|
||||
|
||||
# Allows Nextcloud to trust Cloudflare IPs
|
||||
services.nextcloud.settings.trusted_proxies = cloudflareIpRanges;
|
||||
|
||||
# Allows Transmission to trust Cloudflare IPs
|
||||
services.transmission.settings.rpc-whitelist = builtins.concatStringsSep "," (
|
||||
[ "127.0.0.1" ] ++ cloudflareIpRanges
|
||||
);
|
||||
|
||||
# Using dyn-dns instead of ddclient because I can't find a way to choose
|
||||
# between proxied and non-proxied records for Cloudflare using just
|
||||
# ddclient.
|
||||
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" ];
|
||||
};
|
||||
|
||||
};
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
# This is a tool for blocking IPs of anyone who attempts to scan all of my
|
||||
# ports.
|
||||
|
||||
# Currently has some issues that don't make this viable.
|
||||
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
# Taken from:
|
||||
# https://dataswamp.org/~solene/2022-09-29-iblock-implemented-in-nixos.html
|
||||
|
||||
# You will need to flush all rules when removing:
|
||||
# https://serverfault.com/questions/200635/best-way-to-clear-all-iptables-rules
|
||||
|
||||
let
|
||||
|
||||
portsToBlock = [
|
||||
25545
|
||||
25565
|
||||
25570
|
||||
];
|
||||
portsString = builtins.concatStringsSep "," (builtins.map builtins.toString portsToBlock);
|
||||
|
||||
# Block IPs for 20 days
|
||||
expire = 60 * 60 * 24 * 20;
|
||||
|
||||
rules = table: [
|
||||
"INPUT -i eth0 -p tcp -m multiport --dports ${portsString} -m state --state NEW -m recent --set"
|
||||
"INPUT -i eth0 -p tcp -m multiport --dports ${portsString} -m state --state NEW -m recent --update --seconds 10 --hitcount 1 -j SET --add-set ${table} src"
|
||||
"INPUT -i eth0 -p tcp -m set --match-set ${table} src -j nixos-fw-refuse"
|
||||
"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")
|
||||
);
|
||||
|
||||
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.";
|
||||
|
||||
config.networking.firewall = lib.mkIf config.honeypot.enable {
|
||||
|
||||
extraPackages = [ pkgs.ipset ];
|
||||
# allowedTCPPorts = portsToBlock;
|
||||
|
||||
# Restore ban list when starting up
|
||||
extraCommands = ''
|
||||
if test -f /var/lib/ipset.conf
|
||||
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 blocked6 hash:ip family inet6 ${
|
||||
if expire > 0 then "timeout ${toString expire}" else ""
|
||||
}
|
||||
fi
|
||||
${create-rules}
|
||||
'';
|
||||
|
||||
# Save list when shutting down
|
||||
extraStopCommands = ''
|
||||
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 ""
|
||||
}
|
||||
ipset save > /var/lib/ipset.conf
|
||||
${delete-rules}
|
||||
'';
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user