add presets/wireguard.nix
This allows using `lndconnect` via a direct WireGuard connection.
This commit is contained in:
parent
05310fc02b
commit
5f1e747270
@ -85,7 +85,9 @@ NixOS modules ([src](modules/modules.nix))
|
||||
* [Lightning Loop](https://github.com/lightninglabs/loop)
|
||||
* [Lightning Pool](https://github.com/lightninglabs/pool)
|
||||
* [charge-lnd](https://github.com/accumulator/charge-lnd): policy-based channel fee manager
|
||||
* [lndconnect](https://github.com/LN-Zap/lndconnect): connect your wallet to lnd or clightning via a REST onion service
|
||||
* [lndconnect](https://github.com/LN-Zap/lndconnect): connect your wallet to lnd or
|
||||
clightning [via WireGuard](./docs/services.md#use-zeus-mobile-lightning-wallet-via-wireguard) or
|
||||
[Tor](./docs/services.md#use-zeus-mobile-lightning-wallet-via-tor)
|
||||
* [Ride The Lightning](https://github.com/Ride-The-Lightning/RTL): web interface for `lnd` and `clightning`
|
||||
* [spark-wallet](https://github.com/shesek/spark-wallet)
|
||||
* [electrs](https://github.com/romanz/electrs): Electrum server
|
||||
|
@ -45,4 +45,34 @@ with lib;
|
||||
nix-bitcoin.nodeinfo.enable = true;
|
||||
# test.container.enableWAN = true;
|
||||
};
|
||||
|
||||
wireguard-lndconnect-online = { config, pkgs, lib, ... }: {
|
||||
imports = [
|
||||
../modules/presets/wireguard.nix
|
||||
scenarios.regtestBase
|
||||
];
|
||||
|
||||
# 51820 (default wg port) + 1
|
||||
networking.wireguard.interfaces.wg-nb.listenPort = 51821;
|
||||
test.container.enableWAN = true;
|
||||
# test.container.exposeLocalhost = true;
|
||||
|
||||
services.clightning.extraConfig = "disable-dns";
|
||||
|
||||
services.lnd = {
|
||||
enable = true;
|
||||
lndconnect = {
|
||||
enable = true;
|
||||
onion = true;
|
||||
};
|
||||
};
|
||||
services.clightning-rest = {
|
||||
enable = true;
|
||||
lndconnect = {
|
||||
enable = true;
|
||||
onion = true;
|
||||
};
|
||||
};
|
||||
nix-bitcoin.nodeinfo.enable = true;
|
||||
};
|
||||
}
|
||||
|
64
dev/topics/lndconnect-and-wireguard.sh
Normal file
64
dev/topics/lndconnect-and-wireguard.sh
Normal file
@ -0,0 +1,64 @@
|
||||
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
# Test Tor and WireGuard connections on a mobile device
|
||||
|
||||
# 1. Run container
|
||||
run-tests.sh -s wireguard-lndconnect-online container
|
||||
|
||||
# 2. Test connecting via Tor
|
||||
# Print QR codes for lnd, clightning-rest connections via Tor
|
||||
c lndconnect
|
||||
c lndconnect-clightning
|
||||
# Add these to Zeus >= 0.7.1.
|
||||
# To explicitly check if the connection is successful, press the node logo in the top
|
||||
# left corner, and then "Node Info".
|
||||
|
||||
# Debug
|
||||
c lndconnect --url
|
||||
c lndconnect-clightning --url
|
||||
|
||||
# 3. Test connecting via WireGuard
|
||||
|
||||
# 3.1 Forward WireGuard port from the container host to the container
|
||||
iptables -t nat -A PREROUTING -p udp --dport 51821 -j DNAT --to-destination 10.225.255.2
|
||||
|
||||
# 3.2. Optional: When your container host has an external firewall,
|
||||
# forward the WireGuard port to the container host:
|
||||
# - Port: 51821
|
||||
# - Protocol: UDP
|
||||
# - Destination: IPv4 of the container host
|
||||
|
||||
# 3.2 Print QR code and setup wireguard on the mobile device
|
||||
c nix-bitcoin-wg-connect
|
||||
c nix-bitcoin-wg-connect --text
|
||||
|
||||
# Print QR codes for lnd, clightning-rest connections via WireGuard
|
||||
c lndconnect-wg
|
||||
c lndconnect-clightning-wg
|
||||
# Add these to Zeus >= 0.7.1.
|
||||
# To explicitly check if the connection is successful, press the node logo in the top
|
||||
# left corner, and then "Node Info".
|
||||
|
||||
# Debug
|
||||
c lndconnect-wg --url
|
||||
c lndconnect-clightning-wg --url
|
||||
|
||||
# 3.3.remove external firewall port forward, remove local port forward:
|
||||
iptables -t nat -D PREROUTING -p udp --dport 51821 -j DNAT --to-destination 10.225.255.2
|
||||
# Now exit the container shell
|
||||
|
||||
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
# Debug lndconnect
|
||||
|
||||
run-tests.sh -s wireguard-lndconnect-online container
|
||||
|
||||
c nodeinfo
|
||||
|
||||
c lndconnect --url
|
||||
c lndconnect-wg --url
|
||||
c lndconnect-clightning --url
|
||||
c lndconnect-clightning-wg --url
|
||||
|
||||
c lndconnect
|
||||
c lndconnect-wg
|
||||
c lndconnect-clightning
|
||||
c lndconnect-clightning-wg
|
@ -200,6 +200,97 @@ See: [Secrets dir](./configuration.md#secrets-dir)
|
||||
lndconnect --host myhost
|
||||
```
|
||||
|
||||
# Use Zeus (mobile lightning wallet) via WireGuard
|
||||
|
||||
Connecting Zeus directly to your node is much faster than using Tor, but a bit more complex to setup.
|
||||
|
||||
There are two ways to establish a secure, direct connection:
|
||||
|
||||
- Connecting via TLS. This requires installing your lightning app's
|
||||
TLS Certificate on your mobile device.
|
||||
|
||||
- Connecting via WireGuard. This approach is simpler and more versatile, and is
|
||||
described in this guide.
|
||||
|
||||
1. Install [Zeus](https://zeusln.app) (version ≥ 0.7.1) and
|
||||
[WireGuard](https://www.wireguard.com/install/) on your mobile device.
|
||||
|
||||
2. Add the following to your `configuration.nix`:
|
||||
```nix
|
||||
imports = [
|
||||
# Use this line when using the default deployment method
|
||||
<nix-bitcoin/modules/presets/wireguard.nix>
|
||||
|
||||
# Use this line when using Flakes
|
||||
(nix-bitcoin + /modules/presets/wireguard.nix)
|
||||
]
|
||||
|
||||
# For lnd
|
||||
services.lnd.lndconnect.enable = true;
|
||||
|
||||
# For clightning
|
||||
services.clightning-rest = {
|
||||
enable = true;
|
||||
lndconnect.enable = true;
|
||||
};
|
||||
```
|
||||
3. Deploy your configuration.
|
||||
|
||||
4. If your node is behind an external firewall or NAT, add the following port forwarding
|
||||
rule to the external device:
|
||||
- Port: 51820 (the default value of option `networking.wireguard.interfaces.wg-nb.listenPort`)
|
||||
- Protocol: UDP
|
||||
- Destination: IP of your node
|
||||
|
||||
5. Setup WireGuard on your mobile device.
|
||||
|
||||
Run the following command on your node (as user `operator`) to create a QR code
|
||||
for WireGuard:
|
||||
```bash
|
||||
nix-bitcoin-wg-connect
|
||||
|
||||
# For debugging: Show the WireGuard config as text
|
||||
nix-bitcoin-wg-connect --text
|
||||
```
|
||||
The above commands automatically detect your node's external IP.\
|
||||
To set a custom IP or hostname, run the following:
|
||||
```
|
||||
nix-bitcoin-wg-connect 93.184.216.34
|
||||
nix-bitcoin-wg-connect mynode.org
|
||||
```
|
||||
|
||||
Configure WireGuard:
|
||||
- Press the `+` button in the bottom right corner
|
||||
- Scan the QR code
|
||||
- Add the tunnel
|
||||
|
||||
6. Setup Zeus
|
||||
|
||||
Run the following command on your node (as user `operator`) to create a QR code for Zeus:
|
||||
|
||||
##### For lnd
|
||||
```
|
||||
lndconnect-wg
|
||||
```
|
||||
|
||||
##### For clightning
|
||||
```
|
||||
lndconnect-clightning-wg
|
||||
```
|
||||
|
||||
Configure Zeus:
|
||||
- Add a new node and scan the QR code
|
||||
- Click `Save node config`
|
||||
- On the certificate warning screen, click `I understand, save node config`.\
|
||||
Certificates are not needed when connecting via WireGuard.
|
||||
- Start sending and stacking sats privately
|
||||
|
||||
### Additional lndconnect features
|
||||
Create a plain text URL:
|
||||
```bash
|
||||
lndconnect-wg --url
|
||||
``````
|
||||
|
||||
# Connect to spark-wallet
|
||||
### Requirements
|
||||
* Android phone
|
||||
|
@ -58,7 +58,9 @@
|
||||
# Set this to create a clightning REST onion service.
|
||||
# This also adds binary `lndconnect-clightning` to the system environment.
|
||||
# This binary creates QR codes or URLs for connecting applications to clightning
|
||||
# via the REST onion service (see ../docs/services.md).
|
||||
# via the REST onion service.
|
||||
# You can also connect via WireGuard instead of Tor.
|
||||
# See ../docs/services.md for details.
|
||||
#
|
||||
# services.clightning-rest = {
|
||||
# enable = true;
|
||||
@ -84,7 +86,10 @@
|
||||
# Set this to create a lnd REST onion service.
|
||||
# This also adds binary `lndconnect` to the system environment.
|
||||
# This binary generates QR codes or URLs for connecting applications to lnd via the
|
||||
# REST onion service (see ../docs/services.md).
|
||||
# REST onion service.
|
||||
# You can also connect via WireGuard instead of Tor.
|
||||
# See ../docs/services.md for details.
|
||||
#
|
||||
# services.lnd.lndconnect = {
|
||||
# enable = true;
|
||||
# onion = true;
|
||||
|
214
modules/presets/wireguard.nix
Normal file
214
modules/presets/wireguard.nix
Normal file
@ -0,0 +1,214 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
# Create a WireGuard server with a single peer.
|
||||
# Private/public keys are created via the secrets system.
|
||||
# Add helper binaries `nix-bitcoin-wg-connect` and optionally `lndconnect-wg`, `lndconnect-clightning-wg`.
|
||||
|
||||
# See ../../docs/services.md ("Use Zeus (mobile lightning wallet) via WireGuard")
|
||||
# for usage instructions.
|
||||
|
||||
# This is a rather opinionated implementation that lacks the flexibility offered by
|
||||
# other nix-bitcoin modules, so ship this as a `preset`.
|
||||
# Some users will prefer to use `lndconnect` with their existing WireGuard or Tailscale setup.
|
||||
|
||||
with lib;
|
||||
let
|
||||
options.nix-bitcoin.wireguard = {
|
||||
subnet = mkOption {
|
||||
type = types.str;
|
||||
default = "10.10.0";
|
||||
description = mdDoc "The /24 subnet of the wireguard network.";
|
||||
};
|
||||
restrictPeer = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = mdDoc ''
|
||||
Prevent the peer from connecting to any addresses except for the WireGuard server address.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
cfg = config.nix-bitcoin.wireguard;
|
||||
wgSubnet = cfg.subnet;
|
||||
inherit (config.networking.wireguard.interfaces) wg-nb;
|
||||
inherit (config.services)
|
||||
lnd
|
||||
clightning-rest;
|
||||
|
||||
lndconnect = lnd.enable && lnd.lndconnect.enable;
|
||||
lndconnect-clightning = clightning-rest.enable && clightning-rest.lndconnect.enable;
|
||||
|
||||
serverAddress = "${wgSubnet}.1";
|
||||
peerAddress = "${wgSubnet}.2";
|
||||
|
||||
secretsDir = config.nix-bitcoin.secretsDir;
|
||||
|
||||
wgConnectUser = if config.nix-bitcoin.operator.enable
|
||||
then config.nix-bitcoin.operator.name
|
||||
else "root";
|
||||
|
||||
# A script that prints a QR code to connect a peer to the server.
|
||||
# The QR code encodes a wg-quick config that can be imported by the wireguard
|
||||
# mobile app.
|
||||
wgConnect = pkgs.writers.writeBashBin "nix-bitcoin-wg-connect" ''
|
||||
set -euo pipefail
|
||||
text=
|
||||
host=
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--text)
|
||||
text=1
|
||||
;;
|
||||
*)
|
||||
host=$arg
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! $host ]]; then
|
||||
# Use lndconnect to fetch the external ip.
|
||||
# This internally uses https://github.com/GlenDC/go-external-ip, which
|
||||
# queries a set of external ip providers.
|
||||
host=$(
|
||||
${getExe config.nix-bitcoin.pkgs.lndconnect} --url --nocert \
|
||||
--configfile=/dev/null --adminmacaroonpath=/dev/null \
|
||||
| sed -nE 's|.*?/(.*?):.*|\1|p'
|
||||
)
|
||||
fi
|
||||
|
||||
config="[Interface]
|
||||
PrivateKey = $(cat ${secretsDir}/wg-peer-private-key)
|
||||
Address = ${peerAddress}/24
|
||||
|
||||
[Peer]
|
||||
PublicKey = $(cat ${secretsDir}/wg-server-public-key)
|
||||
AllowedIPs = ${wgSubnet}.0/24
|
||||
Endpoint = $host:${toString wg-nb.listenPort}
|
||||
PersistentKeepalive = 25
|
||||
"
|
||||
|
||||
if [[ $text ]]; then
|
||||
echo "$config"
|
||||
else
|
||||
echo "$config" | ${getExe pkgs.qrencode} -t UTF8 -o -
|
||||
fi
|
||||
'';
|
||||
in {
|
||||
inherit options;
|
||||
|
||||
config = {
|
||||
assertions = [
|
||||
{
|
||||
# Don't support `netns-isolation` for now to keep things simple
|
||||
assertion = !(config.nix-bitcoin.netns-isolation.enable or false);
|
||||
message = "`nix-bitcoin.wireguard` is not compatible with `netns-isolation`.";
|
||||
}
|
||||
];
|
||||
|
||||
networking.wireguard.interfaces.wg-nb = {
|
||||
ips = [ "${serverAddress}/24" ];
|
||||
listenPort = mkDefault 51820;
|
||||
privateKeyFile = "${secretsDir}/wg-server-private-key";
|
||||
allowedIPsAsRoutes = false;
|
||||
peers = [
|
||||
{
|
||||
# To use the actual public key from the secrets file, use dummy pubkey
|
||||
# `peer0` and replace it via `getPubkeyFromFile` (see further below)
|
||||
# at peer service runtime.
|
||||
publicKey = "peer0";
|
||||
allowedIPs = [ "${peerAddress}/32" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services = {
|
||||
wireguard-wg-nb = rec {
|
||||
wants = [ "nix-bitcoin-secrets.target" ];
|
||||
after = wants;
|
||||
};
|
||||
|
||||
# HACK: Modify start/stop scripts of the peer setup service to read
|
||||
# the pubkey from a secrets file.
|
||||
wireguard-wg-nb-peer-peer0 = let
|
||||
getPubkeyFromFile = mkBefore ''
|
||||
if [[ ! -v inPatchedSrc ]]; then
|
||||
export inPatchedSrc=1
|
||||
publicKey=$(cat "${secretsDir}/wg-peer-public-key")
|
||||
<"''${BASH_SOURCE[0]}" sed "s|\bpeer0\b|$publicKey|g" | ${pkgs.bash}/bin/bash -s
|
||||
exit
|
||||
fi
|
||||
'';
|
||||
in {
|
||||
script = getPubkeyFromFile;
|
||||
postStop = getPubkeyFromFile;
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
wgConnect
|
||||
] ++ (optional lndconnect
|
||||
(pkgs.writers.writeBashBin "lndconnect-wg" ''
|
||||
exec lndconnect --host "${serverAddress}" --nocert "$@"
|
||||
'')
|
||||
) ++ (optional lndconnect-clightning
|
||||
(pkgs.writers.writeBashBin "lndconnect-clightning-wg" ''
|
||||
exec lndconnect-clightning --host "${serverAddress}" --nocert "$@"
|
||||
'')
|
||||
);
|
||||
|
||||
networking.firewall = let
|
||||
restrictPeerRule = "-s ${peerAddress} ! -d ${serverAddress} -j REJECT";
|
||||
in {
|
||||
allowedUDPPorts = [ wg-nb.listenPort ];
|
||||
|
||||
extraCommands =
|
||||
optionalString lndconnect ''
|
||||
iptables -w -A nixos-fw -p tcp -s ${wgSubnet}.0/24 --dport ${toString lnd.restPort} -j nixos-fw-accept
|
||||
''
|
||||
+ optionalString lndconnect-clightning ''
|
||||
iptables -w -A nixos-fw -p tcp -s ${wgSubnet}.0/24 --dport ${toString clightning-rest.port} -j nixos-fw-accept
|
||||
''
|
||||
+ optionalString cfg.restrictPeer ''
|
||||
iptables -w -A nixos-fw ${restrictPeerRule}
|
||||
iptables -w -A FORWARD ${restrictPeerRule}
|
||||
'';
|
||||
|
||||
extraStopCommands =
|
||||
# Rules added to chain `nixos-fw` are automatically removed when restarting
|
||||
# the NixOS firewall service.
|
||||
mkIf cfg.restrictPeer ''
|
||||
iptables -w -D FORWARD ${restrictPeerRule} || :
|
||||
'';
|
||||
};
|
||||
|
||||
# Listen on all addresses, including `serverAddress`.
|
||||
# This is safe because the listen ports are secured by the firewall.
|
||||
services.lnd.restAddress = mkIf lndconnect "0.0.0.0";
|
||||
# clightning-rest always listens on "0.0.0.0"
|
||||
|
||||
nix-bitcoin.secrets = {
|
||||
wg-server-private-key = {};
|
||||
wg-server-public-key = { user = wgConnectUser; group = "root"; };
|
||||
wg-peer-private-key = { user = wgConnectUser; group = "root"; };
|
||||
wg-peer-public-key = {};
|
||||
};
|
||||
|
||||
nix-bitcoin.generateSecretsCmds.wireguard = let
|
||||
wg = "${pkgs.wireguard-tools}/bin/wg";
|
||||
in ''
|
||||
makeWireguardKey() {
|
||||
local name=$1
|
||||
local priv=wg-$name-private-key
|
||||
local pub=wg-$name-public-key
|
||||
if [[ ! -e $priv ]]; then
|
||||
${wg} genkey > $priv
|
||||
fi
|
||||
if [[ $priv -nt $pub ]]; then
|
||||
${wg} pubkey < $priv > $pub
|
||||
fi
|
||||
}
|
||||
makeWireguardKey server
|
||||
makeWireguardKey peer
|
||||
'';
|
||||
};
|
||||
}
|
@ -274,6 +274,7 @@ buildable=(
|
||||
hardened
|
||||
clightning-replication
|
||||
lndPruned
|
||||
wireguard-lndconnect
|
||||
)
|
||||
buildable() { buildTests buildable "$@"; }
|
||||
|
||||
|
@ -405,6 +405,7 @@ in {
|
||||
in
|
||||
{
|
||||
clightning-replication = import ./clightning-replication.nix makeTestVM pkgs;
|
||||
wireguard-lndconnect = import ./wireguard-lndconnect.nix makeTestVM pkgs;
|
||||
} // mainTests;
|
||||
|
||||
tests = makeTests scenarios;
|
||||
|
103
test/wireguard-lndconnect.nix
Normal file
103
test/wireguard-lndconnect.nix
Normal file
@ -0,0 +1,103 @@
|
||||
# You can run this test via `run-tests.sh -s wireguard-lndconnect`
|
||||
|
||||
makeTestVM: pkgs:
|
||||
with pkgs.lib;
|
||||
|
||||
makeTestVM {
|
||||
name = "wireguard-lndconnect";
|
||||
|
||||
nodes = {
|
||||
server = {
|
||||
imports = [
|
||||
../modules/modules.nix
|
||||
../modules/presets/wireguard.nix
|
||||
];
|
||||
|
||||
nixpkgs.pkgs = pkgs;
|
||||
|
||||
nix-bitcoin.generateSecrets = true;
|
||||
nix-bitcoin.operator.enable = true;
|
||||
|
||||
services.clightning-rest = {
|
||||
enable = true;
|
||||
lndconnect.enable = true;
|
||||
};
|
||||
# TODO-EXTERNAL:
|
||||
# When WAN is disabled, DNS bootstrapping slows down service startup by ~15 s.
|
||||
services.clightning.extraConfig = "disable-dns";
|
||||
|
||||
services.lnd = {
|
||||
enable = true;
|
||||
lndconnect.enable = true;
|
||||
port = 9736;
|
||||
};
|
||||
};
|
||||
|
||||
client = {
|
||||
nixpkgs.pkgs = pkgs;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
wireguard-tools
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import base64
|
||||
import urllib.parse as Url
|
||||
from types import SimpleNamespace
|
||||
|
||||
def parse_lndconnect_url(url):
|
||||
u = Url.urlparse(url)
|
||||
queries = Url.parse_qs(u.query)
|
||||
macaroon = queries['macaroon'][0]
|
||||
is_clightning = url.startswith("c-lightning-rest")
|
||||
|
||||
return SimpleNamespace(
|
||||
host = u.hostname,
|
||||
port = u.port,
|
||||
macaroon_hex =
|
||||
macaroon if is_clightning else base64.urlsafe_b64decode(macaroon + '===').hex().upper()
|
||||
)
|
||||
|
||||
client.start()
|
||||
server.connect()
|
||||
|
||||
if not "is_interactive" in vars():
|
||||
|
||||
with subtest("connect client to server via WireGuard"):
|
||||
server.wait_for_unit("wireguard-wg-nb-peer-peer0.service")
|
||||
|
||||
# Get WireGuard config from server and save it to `/tmp/wireguard.conf` on the client
|
||||
wg_config = server.succeed("runuser -u operator -- nix-bitcoin-wg-connect server --text")
|
||||
# Encode to base64
|
||||
b64 = base64.b64encode(wg_config.encode('utf-8')).decode()
|
||||
client.succeed(f"install -m 400 <(echo -n {b64} | base64 -d) /tmp/wireguard.conf")
|
||||
|
||||
# Connect to server via WireGuard
|
||||
client.succeed("wg-quick up /tmp/wireguard.conf")
|
||||
|
||||
# Ping server from client
|
||||
print(client.succeed("ping -c 1 -W 0.5 10.10.0.1"))
|
||||
|
||||
with subtest("lndconnect-wg"):
|
||||
server.wait_for_unit("lnd.service")
|
||||
lndconnect_url = server.succeed("runuser -u operator -- lndconnect-wg --url")
|
||||
api = parse_lndconnect_url(lndconnect_url)
|
||||
# Make lnd REST API call
|
||||
client.succeed(
|
||||
f"curl -fsS --max-time 3 --insecure --header 'Grpc-Metadata-macaroon: {api.macaroon_hex}' "
|
||||
f"-X GET https://{api.host}:{api.port}/v1/getinfo"
|
||||
)
|
||||
|
||||
with subtest("lndconnect-clightning-wg"):
|
||||
server.wait_for_unit("clightning-rest.service")
|
||||
lndconnect_url = server.succeed("runuser -u operator -- lndconnect-clightning-wg --url")
|
||||
api = parse_lndconnect_url(lndconnect_url)
|
||||
# Make clightning-rest API call
|
||||
client.succeed(
|
||||
f"curl -fsS --max-time 3 --insecure --header 'macaroon: {api.macaroon_hex}' "
|
||||
f"--header 'encodingtype: hex' -X GET https://{api.host}:{api.port}/v1/getinfo"
|
||||
)
|
||||
'';
|
||||
}
|
Loading…
Reference in New Issue
Block a user