Add NNCP module
This commit is contained in:
parent
c869def65b
commit
732ae81533
77
flake.lock
77
flake.lock
|
@ -8,11 +8,11 @@
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1641536797,
|
"lastModified": 1642486955,
|
||||||
"narHash": "sha256-SlU2xiYPjN/a7NGosAI8LoHJPaFj+2gnwGLsdAh3wF4=",
|
"narHash": "sha256-PiPn+ClAr/294i3hGHRygGCPtDrDnqbyl7JRcYRLmZc=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "80a97954c9d39423e9bd273d84bb55770335565e",
|
"rev": "a69f7ac3008066e991a83367bbe89c11ccf239ab",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -88,11 +88,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1638219772,
|
"lastModified": 1641890987,
|
||||||
"narHash": "sha256-R396OImY09dKCRgbY0s54/cQxoFNushVtHyCVw+y7FU=",
|
"narHash": "sha256-CJOAOlk7mZlBVvUiv9q8SYkK0rjY0UmkWedMI0eajWE=",
|
||||||
"owner": "mhuesch",
|
"owner": "mhuesch",
|
||||||
"repo": "naersk",
|
"repo": "naersk",
|
||||||
"rev": "80649a7ebe7c0675699893b278ebed851d711a69",
|
"rev": "193e049d6e4c841faf800e302551d2e0a48eee88",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -103,11 +103,11 @@
|
||||||
},
|
},
|
||||||
"nixos-hardware": {
|
"nixos-hardware": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1640686209,
|
"lastModified": 1641965797,
|
||||||
"narHash": "sha256-6glXUlKRDhEhNuYx6r3fXU6KH2/Vq9mJZjB9oUpwrmc=",
|
"narHash": "sha256-AfxfIzAZbt9aAzpVBn0Bwhd/M4Wix7G91kEjm9H6FPo=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixos-hardware",
|
"repo": "nixos-hardware",
|
||||||
"rev": "46df95ca81e7e4cf3458cdb4b7d1714b5fce9da5",
|
"rev": "87a35a0d58f546dc23f37b4f6af575d0e4be6a7a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -118,11 +118,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1641528457,
|
"lastModified": 1642281915,
|
||||||
"narHash": "sha256-FyU9E63n1W7Ql4pMnhW2/rO9OftWZ37pLppn/c1aisY=",
|
"narHash": "sha256-jcMsXmmO1knyf99o242A+2cy1A0eKa9afly0cwBknPA=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ff377a78794d412a35245e05428c8f95fef3951f",
|
"rev": "d5dae6569ea9952f1ae4e727946d93a71c507821",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -134,11 +134,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs-master": {
|
"nixpkgs-master": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1639061474,
|
"lastModified": 1641927215,
|
||||||
"narHash": "sha256-iCrfAyfrQo7y1OFyp98lEcTFDGL6oJMduI1B8bwYZdg=",
|
"narHash": "sha256-96otEPy5y/4aeM6Sjl//Qr8v3+CdjTBNtnS4eKyoY0U=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "61727e0e57e73113aca16981ea00275b299fbf94",
|
"rev": "0fdbcb2355f0681908cc9fef0cc9138f938ae1a3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -165,27 +165,26 @@
|
||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1641528457,
|
"lastModified": 1642533784,
|
||||||
"narHash": "sha256-FyU9E63n1W7Ql4pMnhW2/rO9OftWZ37pLppn/c1aisY=",
|
"narHash": "sha256-RUgoUQe/p8hL2v8gjHQ3W9PNl+FioqOqrnQpI92PpM4=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ff377a78794d412a35245e05428c8f95fef3951f",
|
"rev": "95dfbf360c09df1556a312297a7b3605252a2227",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1641550923,
|
"lastModified": 1642160200,
|
||||||
"narHash": "sha256-vKd+7BWjZO6/p8kdP+szOfecJBw/zbWUWhNNoOx2PUU=",
|
"narHash": "sha256-5mb1nvh2vWlnKvyJzGNKsL7AQRFk+H5E/Xh83fTzYG4=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9bc01c54624b128a9533f912849cd7f2d2bab9f2",
|
"rev": "e5a50e8f2995ff359a170d52cc40adbcfdd92ba4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -195,11 +194,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1641578038,
|
"lastModified": 1642522226,
|
||||||
"narHash": "sha256-fE5tYnyxiAmgg4qJp7jcHxGcQIazhLY2AI89SInxOck=",
|
"narHash": "sha256-m/j9U8KYuwwxjwgRCjmEj8ejftvdMLJ+NGXh/L2I4FU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "d89eab1e42717622cfdd5f43f3b99e8680bdb637",
|
"rev": "610d4ea2750e064bf34b33fa38cb671edd893d3d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -243,11 +242,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs_6": {
|
"nixpkgs_6": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1639061333,
|
"lastModified": 1641924320,
|
||||||
"narHash": "sha256-rG04piqc/mCGM+6IU0o1JRlH+iqwOXbuuqA1Wtszexw=",
|
"narHash": "sha256-DuOpJqoMmQ3Yk4C64QQHFaByhbSIi872He6z5BXY1YM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "fe4ebb5a53789ecea5eddaf48589f69701c125c3",
|
"rev": "05f3de54d568dba52efaad966f0e09bfea796dcb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -260,11 +259,11 @@
|
||||||
"openwrt": {
|
"openwrt": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1638708860,
|
"lastModified": 1641673875,
|
||||||
"narHash": "sha256-6az+TFKk6A94YEFvrNRBuCnDfTG2y9HT9xivx5wknhU=",
|
"narHash": "sha256-Eb9tVxE9Pmob/xWZ6wRt95cTycPeyPkyyaaWaz6cWtA=",
|
||||||
"ref": "openwrt-21.02",
|
"ref": "openwrt-21.02",
|
||||||
"rev": "c67509efd7d0c43eb3f622f06c8a31aa28d22f6e",
|
"rev": "6ced8cad8edd2a04fc6bb914c333c8aac9a1c825",
|
||||||
"revCount": 50860,
|
"revCount": 50927,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.openwrt.org/openwrt/openwrt.git"
|
"url": "https://git.openwrt.org/openwrt/openwrt.git"
|
||||||
},
|
},
|
||||||
|
@ -296,11 +295,11 @@
|
||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1641511071,
|
"lastModified": 1642463850,
|
||||||
"narHash": "sha256-xxT0f2r+9n3gxnvvIhwDeYpqr6xG6F5FzGXjO33rBdk=",
|
"narHash": "sha256-kY5+G0F353wG4ob5cZLPAa+lnSmbC2Z7q9HcVqe8buk=",
|
||||||
"owner": "rust-analyzer",
|
"owner": "rust-analyzer",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "2fb6f5e46a5f53a78c2f750dd3c8e79dd418c4c9",
|
"rev": "477b654f4f4896dab766a398cf1f2955146088d5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -473,11 +472,11 @@
|
||||||
"openwrt": "openwrt"
|
"openwrt": "openwrt"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1640735504,
|
"lastModified": 1642474243,
|
||||||
"narHash": "sha256-O/SGZSA61fmkHkXi6lTyMt0IovCqxxwBw4xgs9td3q0=",
|
"narHash": "sha256-LZZUsdbpZvkjAL1+4G5aJKjnRCA+Kfa7hUoTtHEHIfs=",
|
||||||
"ref": "master",
|
"ref": "master",
|
||||||
"rev": "187a20f67e1230c8ca5f1d6147a2d729ad54d502",
|
"rev": "2789589c25900bc87316dd9f1e7f66cf1ee5e3ab",
|
||||||
"revCount": 1299,
|
"revCount": 1330,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.c3d2.de/zentralwerk/network.git"
|
"url": "https://gitea.c3d2.de/zentralwerk/network.git"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/release-21.11";
|
nixpkgs.url = "github:nixos/nixpkgs/release-21.11";
|
||||||
nixpkgs-mobilizon.url = "github:erictapen/nixpkgs/mobilizon";
|
nixpkgs-mobilizon.url = "github:erictapen/nixpkgs/mobilizon";
|
||||||
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs-unstable.url = "github:nixos/nixpkgs";
|
||||||
secrets.url = "git+ssh://gitea@gitea.c3d2.de/c3d2-admins/secrets.git";
|
secrets.url = "git+ssh://gitea@gitea.c3d2.de/c3d2-admins/secrets.git";
|
||||||
secrets.inputs.sops-nix.follows = "sops-nix";
|
secrets.inputs.sops-nix.follows = "sops-nix";
|
||||||
nixos-hardware.url = "github:nixos/nixos-hardware";
|
nixos-hardware.url = "github:nixos/nixos-hardware";
|
||||||
|
@ -509,6 +509,7 @@
|
||||||
c3d2.hosts = hostRegistry.hosts;
|
c3d2.hosts = hostRegistry.hosts;
|
||||||
c3d2.users = import ./users.nix;
|
c3d2.users = import ./users.nix;
|
||||||
};
|
};
|
||||||
|
nncp = ./modules/nncp.nix;
|
||||||
plume = {
|
plume = {
|
||||||
imports = [ ./modules/plume.nix ];
|
imports = [ ./modules/plume.nix ];
|
||||||
nixpkgs.overlays = [ fenix.overlay naersk.overlay ];
|
nixpkgs.overlays = [ fenix.overlay naersk.overlay ];
|
||||||
|
|
194
modules/nncp.nix
Normal file
194
modules/nncp.nix
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
nncpCfgFile = "/run/nncp.hjson";
|
||||||
|
programCfg = config.programs.nncp;
|
||||||
|
callerCfg = config.services.nncp.caller;
|
||||||
|
daemonCfg = config.services.nncp.daemon;
|
||||||
|
settingsFormat = pkgs.formats.json { };
|
||||||
|
jsonCfgFile = settingsFormat.generate "nncp.json" programCfg.settings;
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
programs.nncp = {
|
||||||
|
|
||||||
|
enable = mkEnableOption "NNCP (Node to Node copy) utilities";
|
||||||
|
|
||||||
|
secrets = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
example = [ "/run/keys/nncp.hjson" ];
|
||||||
|
description = ''
|
||||||
|
A list of paths to NNCP configuration files that should not be
|
||||||
|
in the Nix store. These files are layered on top of the values at
|
||||||
|
<xref linkend="opt-programs.nncp.settings"/>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
type = settingsFormat.type;
|
||||||
|
description = ''
|
||||||
|
NNCP configuration, see
|
||||||
|
<link xlink:href="http://www.nncpgo.org/Configuration.html"/>.
|
||||||
|
At runtime these settings will be overlayed by the contents of
|
||||||
|
<xref linkend="opt-programs.nncp.secrets"/> into the file
|
||||||
|
<literal>${nncpCfgFile}</literal>. Node keypairs go in
|
||||||
|
<literal>secrets</literal>, do not specify them in
|
||||||
|
<literal>settings</literal> as they will be leaked into
|
||||||
|
<literal>/nix/store</literal>!
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nncp = {
|
||||||
|
|
||||||
|
caller = {
|
||||||
|
enable = mkEnableOption ''
|
||||||
|
croned NNCP TCP daemon caller.
|
||||||
|
The daemon will take configuration from
|
||||||
|
<xref linkend="opt-programs.nncp.settings"/>
|
||||||
|
'';
|
||||||
|
extraArgs = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
description = "Extra command-line arguments to pass to caller.";
|
||||||
|
default = [ ];
|
||||||
|
example = [ "-autotoss" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
daemon = {
|
||||||
|
enable = mkEnableOption ''
|
||||||
|
NNCP TCP synronization daemon.
|
||||||
|
The daemon will take configuration from
|
||||||
|
<xref linkend="opt-programs.nncp.settings"/>
|
||||||
|
'';
|
||||||
|
|
||||||
|
socketActivation = {
|
||||||
|
enable = mkEnableOption ''
|
||||||
|
Whether to run nncp-daemon persistently or socket-activated.
|
||||||
|
'';
|
||||||
|
listenStreams = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
description = ''
|
||||||
|
TCP sockets to bind to.
|
||||||
|
See <xref linkend="opt-systemd.sockets._name_.listenStreams"/>.
|
||||||
|
'';
|
||||||
|
default = [ "5400" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
extraArgs = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
description = "Extra command-line arguments to pass to daemon.";
|
||||||
|
default = [ ];
|
||||||
|
example = [ "-autotoss" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf (programCfg.enable or callerCfg.enable or daemonCfg.enable) {
|
||||||
|
|
||||||
|
assertions = [{
|
||||||
|
assertion = with builtins;
|
||||||
|
let
|
||||||
|
callerCongfigured =
|
||||||
|
let neigh = config.programs.nncp.settings.neigh or { };
|
||||||
|
in lib.lists.any (x: hasAttr "calls" x && x.calls != [ ])
|
||||||
|
(attrValues neigh);
|
||||||
|
in !callerCfg.enable || callerCongfigured;
|
||||||
|
message = "NNCP caller enabled but call configuration is missing";
|
||||||
|
}];
|
||||||
|
|
||||||
|
programs.nncp.settings = {
|
||||||
|
spool = mkDefault "/var/spool/nncp";
|
||||||
|
log = mkDefault "/var/spool/nncp/log";
|
||||||
|
};
|
||||||
|
|
||||||
|
environment = mkIf programCfg.enable {
|
||||||
|
systemPackages = [ pkgs.nncp ];
|
||||||
|
etc."nncp.hjson".source = nncpCfgFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${programCfg.settings.spool} 0770 root uucp"
|
||||||
|
"f ${programCfg.settings.log} 0770 root uucp"
|
||||||
|
];
|
||||||
|
|
||||||
|
system.activationScripts.nncp = ''
|
||||||
|
nncpCfgDir=$(mktemp --directory nncp.XXXXXXXXXX)
|
||||||
|
for f in ${jsonCfgFile} ${toString config.programs.nncp.secrets}; do
|
||||||
|
tmpdir=$(mktemp --directory nncp.XXXXXXXXXX)
|
||||||
|
${pkgs.nncp}/bin/nncp-cfgdir -cfg $f -dump $tmpdir
|
||||||
|
${pkgs.findutils}/bin/find $tmpdir -size 1c -delete
|
||||||
|
cp -a $tmpdir/* $nncpCfgDir/
|
||||||
|
rm -rf $tmpdir
|
||||||
|
done
|
||||||
|
${pkgs.nncp}/bin/nncp-cfgdir -load $nncpCfgDir > ${nncpCfgFile}
|
||||||
|
rm -rf $nncpCfgDir
|
||||||
|
chgrp uucp ${nncpCfgFile}
|
||||||
|
'';
|
||||||
|
|
||||||
|
systemd.services."nncp-caller" = {
|
||||||
|
inherit (callerCfg) enable;
|
||||||
|
description = "Croned NNCP TCP daemon caller.";
|
||||||
|
documentation = [ "http://www.nncpgo.org/nncp_002dcaller.html" ];
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = ''
|
||||||
|
${pkgs.nncp}/bin/nncp-caller -noprogress -cfg "${nncpCfgFile}" ${
|
||||||
|
lib.strings.escapeShellArgs callerCfg.extraArgs
|
||||||
|
}'';
|
||||||
|
Group = "uucp";
|
||||||
|
UMask = "0002";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services."nncp-daemon" = mkIf daemonCfg.enable {
|
||||||
|
enable = !daemonCfg.socketActivation.enable;
|
||||||
|
description = "NNCP TCP syncronization daemon.";
|
||||||
|
documentation = [ "http://www.nncpgo.org/nncp_002ddaemon.html" ];
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = ''
|
||||||
|
${pkgs.nncp}/bin/nncp-daemon -noprogress -cfg "${nncpCfgFile}" ${
|
||||||
|
lib.strings.escapeShellArgs daemonCfg.extraArgs
|
||||||
|
}'';
|
||||||
|
Restart = "on-failure";
|
||||||
|
Group = "uucp";
|
||||||
|
UMask = "0002";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services."nncp-daemon@" = mkIf daemonCfg.socketActivation.enable {
|
||||||
|
description = "NNCP TCP syncronization daemon.";
|
||||||
|
documentation = [ "http://www.nncpgo.org/nncp_002ddaemon.html" ];
|
||||||
|
after = [ "network.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = ''
|
||||||
|
${pkgs.nncp}/bin/nncp-daemon -noprogress -ucspi -cfg "${nncpCfgFile}" ${
|
||||||
|
lib.strings.escapeShellArgs daemonCfg.extraArgs
|
||||||
|
}'';
|
||||||
|
Group = "uucp";
|
||||||
|
UMask = "0002";
|
||||||
|
StandardInput = "socket";
|
||||||
|
StandardOutput = "inherit";
|
||||||
|
StandardError = "journal";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.sockets.nncp-daemon = mkIf daemonCfg.socketActivation.enable {
|
||||||
|
inherit (daemonCfg.socketActivation) listenStreams;
|
||||||
|
description = "socket for NNCP TCP syncronization.";
|
||||||
|
conflicts = [ "nncp-daemon.service" ];
|
||||||
|
wantedBy = [ "sockets.target" ];
|
||||||
|
socketConfig.Accept = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user