Compare commits
3 commits
087b583dd2
...
3771134e3a
Author | SHA1 | Date | |
---|---|---|---|
3771134e3a | |||
cc19e46df0 | |||
7118348263 |
14 changed files with 326 additions and 50 deletions
|
@ -174,17 +174,17 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1716948383,
|
"lastModified": 1718870667,
|
||||||
"narHash": "sha256-SzDKxseEcHR5KzPXLwsemyTR/kaM9whxeiJohbL04rs=",
|
"narHash": "sha256-jab3Kpc8O1z3qxwVsCMHL4+18n5Wy/HHKyu1fcsF7gs=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ad57eef4ef0659193044870c731987a6df5cf56b",
|
"rev": "9b10b8f00cb5494795e5f51b39210fed4d2b0748",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9b10b8f00cb5494795e5f51b39210fed4d2b0748",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
# Pin nixpkgs to a specific commit
|
# Pin nixpkgs to a specific commit
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/9b10b8f00cb5494795e5f51b39210fed4d2b0748";
|
||||||
nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-23.05";
|
nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-23.05";
|
||||||
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
tmux
|
tmux
|
||||||
ffmpeg
|
ffmpeg
|
||||||
tealdeer
|
tealdeer
|
||||||
neofetch
|
|
||||||
rclone
|
rclone
|
||||||
|
|
||||||
inetutils
|
inetutils
|
||||||
|
|
|
@ -40,6 +40,17 @@
|
||||||
gamescopeSession = { enable = true; };
|
gamescopeSession = { enable = true; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
programs.oidc-agent.enable = true;
|
||||||
|
programs.oidc-agent.providers = [
|
||||||
|
{ issuer = "https://home.xinyang.life:9201";
|
||||||
|
pubclient = {
|
||||||
|
client_id = "xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69";
|
||||||
|
client_secret = "UBntmLjC2yYCeHwsyj73Uwo9TAaecAetRwMw0xYcvNL9yRdLSUi0hUAHfvCHFeFh";
|
||||||
|
scope = "openid offline_access profile email";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
programs.vim.defaultEditor = true;
|
programs.vim.defaultEditor = true;
|
||||||
|
|
||||||
# Keep this even if enabled in home manager
|
# Keep this even if enabled in home manager
|
||||||
|
@ -97,7 +108,7 @@
|
||||||
|
|
||||||
# Enable CUPS to print documents.
|
# Enable CUPS to print documents.
|
||||||
services.printing.enable = true;
|
services.printing.enable = true;
|
||||||
services.printing.drivers = [ pkgs.hplip ];
|
# services.printing.drivers = [ pkgs.hplip ];
|
||||||
|
|
||||||
# Enable sound with pipewire.
|
# Enable sound with pipewire.
|
||||||
sound.enable = true;
|
sound.enable = true;
|
||||||
|
@ -145,6 +156,7 @@
|
||||||
# List packages installed in system profile. To search, run:
|
# List packages installed in system profile. To search, run:
|
||||||
# $ nix search wget
|
# $ nix search wget
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
|
oidc-agent
|
||||||
# Filesystem
|
# Filesystem
|
||||||
owncloud-client
|
owncloud-client
|
||||||
nfs-utils
|
nfs-utils
|
||||||
|
|
|
@ -80,7 +80,7 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
nix.settings = {
|
nix.settings = {
|
||||||
trusted-users = config.users.groups.wheel.members;
|
trusted-users = config.users.groups.wheel.members ++ [ "root" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.sing-box = let
|
services.sing-box = let
|
||||||
|
|
66
machines/dolomite/ec2-metadata-fetcher.sh
Normal file
66
machines/dolomite/ec2-metadata-fetcher.sh
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
metaDir=/etc/ec2-metadata
|
||||||
|
mkdir -m 0755 -p "$metaDir"
|
||||||
|
rm -f "$metaDir/*"
|
||||||
|
|
||||||
|
get_imds_token() {
|
||||||
|
# retry-delay of 1 selected to give the system a second to get going,
|
||||||
|
# but not add a lot to the bootup time
|
||||||
|
curl \
|
||||||
|
--silent \
|
||||||
|
--show-error \
|
||||||
|
--retry 3 \
|
||||||
|
--retry-delay 1 \
|
||||||
|
--fail \
|
||||||
|
-X PUT \
|
||||||
|
--connect-timeout 1 \
|
||||||
|
-H "X-aws-ec2-metadata-token-ttl-seconds: 600" \
|
||||||
|
http://169.254.169.254/latest/api/token
|
||||||
|
}
|
||||||
|
|
||||||
|
preflight_imds_token() {
|
||||||
|
# retry-delay of 1 selected to give the system a second to get going,
|
||||||
|
# but not add a lot to the bootup time
|
||||||
|
curl \
|
||||||
|
--silent \
|
||||||
|
--show-error \
|
||||||
|
--retry 3 \
|
||||||
|
--retry-delay 1 \
|
||||||
|
--fail \
|
||||||
|
--connect-timeout 1 \
|
||||||
|
-H "X-aws-ec2-metadata-token: $IMDS_TOKEN" \
|
||||||
|
-o /dev/null \
|
||||||
|
http://169.254.169.254/1.0/meta-data/instance-id
|
||||||
|
}
|
||||||
|
|
||||||
|
try=1
|
||||||
|
while [ $try -le 3 ]; do
|
||||||
|
echo "(attempt $try/3) getting an EC2 instance metadata service v2 token..."
|
||||||
|
IMDS_TOKEN=$(get_imds_token) && break
|
||||||
|
try=$((try + 1))
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "x$IMDS_TOKEN" == "x" ]; then
|
||||||
|
echo "failed to fetch an IMDS2v token."
|
||||||
|
fi
|
||||||
|
|
||||||
|
try=1
|
||||||
|
while [ $try -le 10 ]; do
|
||||||
|
echo "(attempt $try/10) validating the EC2 instance metadata service v2 token..."
|
||||||
|
preflight_imds_token && break
|
||||||
|
try=$((try + 1))
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "getting EC2 instance metadata..."
|
||||||
|
|
||||||
|
get_imds() {
|
||||||
|
# --fail to avoid populating missing files with 404 HTML response body
|
||||||
|
# || true to allow the script to continue even when encountering a 404
|
||||||
|
curl --silent --show-error --fail --header "X-aws-ec2-metadata-token: $IMDS_TOKEN" "$@" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
get_imds -o "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path
|
||||||
|
(umask 077 && get_imds -o "$metaDir/user-data" http://169.254.169.254/1.0/user-data)
|
||||||
|
get_imds -o "$metaDir/hostname" http://169.254.169.254/1.0/meta-data/hostname
|
||||||
|
get_imds -o "$metaDir/public-keys-0-openssh-key" http://169.254.169.254/1.0/meta-data/public-keys/0/openssh-key
|
|
@ -26,23 +26,19 @@ in
|
||||||
|
|
||||||
boot.growPartition = true;
|
boot.growPartition = true;
|
||||||
|
|
||||||
fileSystems."/" = mkIf (!cfg.zfs.enable) {
|
fileSystems."/" = {
|
||||||
device = "/dev/disk/by-label/nixos";
|
device = "/dev/disk/by-label/nixos";
|
||||||
fsType = "ext4";
|
fsType = "ext4";
|
||||||
autoResize = true;
|
autoResize = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/boot" = mkIf (cfg.efi || cfg.zfs.enable) {
|
fileSystems."/boot" = {
|
||||||
# The ZFS image uses a partition labeled ESP whether or not we're
|
# The ZFS image uses a partition labeled ESP whether or not we're
|
||||||
# booting with EFI.
|
# booting with EFI.
|
||||||
device = "/dev/disk/by-label/ESP";
|
device = "/dev/disk/by-label/ESP";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
};
|
};
|
||||||
|
|
||||||
services.zfs.expandOnBoot = mkIf cfg.zfs.enable "all";
|
|
||||||
|
|
||||||
boot.zfs.devNodes = mkIf cfg.zfs.enable "/dev/";
|
|
||||||
|
|
||||||
boot.extraModulePackages = [
|
boot.extraModulePackages = [
|
||||||
config.boot.kernelPackages.ena
|
config.boot.kernelPackages.ena
|
||||||
];
|
];
|
||||||
|
|
|
@ -17,7 +17,7 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
cxxPackages = {
|
cxxPackages = {
|
||||||
systemPackages = with pkgs; [ clang-tools ];
|
systemPackages = with pkgs; [ clang-tools cmake-format ];
|
||||||
extension = with inputs.nix-vscode-extensions.extensions.${pkgs.system}.vscode-marketplace; [
|
extension = with inputs.nix-vscode-extensions.extensions.${pkgs.system}.vscode-marketplace; [
|
||||||
llvm-vs-code-extensions.vscode-clangd
|
llvm-vs-code-extensions.vscode-clangd
|
||||||
(ms-vscode.cmake-tools.overrideAttrs (_: { sourceRoot = "extension"; }))
|
(ms-vscode.cmake-tools.overrideAttrs (_: { sourceRoot = "extension"; }))
|
||||||
|
|
|
@ -9,5 +9,6 @@
|
||||||
./kanidm-client.nix
|
./kanidm-client.nix
|
||||||
./ssh-tpm-agent.nix # FIXME: Waiting for upstream merge
|
./ssh-tpm-agent.nix # FIXME: Waiting for upstream merge
|
||||||
./forgejo-actions-runner.nix
|
./forgejo-actions-runner.nix
|
||||||
|
./oidc-agent.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
126
modules/nixos/inbounds.nix
Normal file
126
modules/nixos/inbounds.nix
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
{ config
|
||||||
|
, lib
|
||||||
|
, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.custom.sing-box-server;
|
||||||
|
|
||||||
|
secretFileType = lib.types.submodule {
|
||||||
|
_secret = lib.types.path;
|
||||||
|
};
|
||||||
|
singTls = {
|
||||||
|
enabled = true;
|
||||||
|
server_name = config.deployment.targetHost;
|
||||||
|
key_path = config.security.acme.certs.${config.deployment.targetHost}.directory + "/key.pem";
|
||||||
|
certificate_path = config.security.acme.certs.${config.deployment.targetHost}.directory + "/cert.pem";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
enable = lib.mkEnableOption "sing-box proxy server";
|
||||||
|
users = lib.types.listOf lib.types.submodule {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "proxy";
|
||||||
|
};
|
||||||
|
password = lib.mkOption {
|
||||||
|
type = secretFileType;
|
||||||
|
};
|
||||||
|
uuid = lib.mkOption {
|
||||||
|
type = secretFileType;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
wgOut = {
|
||||||
|
privKeyFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
};
|
||||||
|
pubkey = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
inbounds = {
|
||||||
|
trojan = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
tuic = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
ports = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.int;
|
||||||
|
default = lib.range 6311 6313;
|
||||||
|
};
|
||||||
|
directPorts = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.int;
|
||||||
|
default = [ 6314 ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.sing-box = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
dns = {
|
||||||
|
servers = [
|
||||||
|
{
|
||||||
|
address = "1.1.1.1";
|
||||||
|
detour = "wg-out";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
inbounds = [
|
||||||
|
# TODO: Trojan and tuic enable
|
||||||
|
{
|
||||||
|
tag = "trojan-in";
|
||||||
|
type = "trojan";
|
||||||
|
listen = "::";
|
||||||
|
listen_port = 8080;
|
||||||
|
users = map (u: removeAttrs u [ "uuid" ]) cfg.users;
|
||||||
|
tls = singTls;
|
||||||
|
}
|
||||||
|
] ++ lib.forEach (cfg.tuic.ports ++ cfg.tuic.directPorts) (port: {
|
||||||
|
tag = "tuic-in" + toString port;
|
||||||
|
type = "tuic";
|
||||||
|
listen = "::";
|
||||||
|
listen_port = port;
|
||||||
|
congestion_control = "bbr";
|
||||||
|
users = cfg.users;
|
||||||
|
tls = singTls;
|
||||||
|
});
|
||||||
|
outbounds = [
|
||||||
|
{
|
||||||
|
type = "wireguard";
|
||||||
|
tag = "wg-out";
|
||||||
|
private_key = cfg.wgOut.privKeyFile;
|
||||||
|
local_address = [
|
||||||
|
"172.16.0.2/32"
|
||||||
|
"2606:4700:110:82ed:a443:3c62:6cbc:b59b/128"
|
||||||
|
];
|
||||||
|
peers = [
|
||||||
|
{ public_key= cfg.wgOut.pubkey;
|
||||||
|
allowed_ips = [ "0.0.0.0/0" "::/0" ];
|
||||||
|
server = "162.159.192.1";
|
||||||
|
server_port = 500;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
{ type = "direct"; tag = "direct-out"; }
|
||||||
|
{ type = "dns"; tag = "dns-out"; }
|
||||||
|
];
|
||||||
|
route = {
|
||||||
|
rules = [
|
||||||
|
{ outbound = "dns-out"; protocol = "dns"; }
|
||||||
|
] ++ lib.forEach cfg.tuic.directPorts (port: {
|
||||||
|
inbound = "tuic-in" + toString port;
|
||||||
|
outbound = "direct-out";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
50
modules/nixos/oidc-agent.nix
Normal file
50
modules/nixos/oidc-agent.nix
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) mkIf mkEnableOption mkOption types;
|
||||||
|
|
||||||
|
cfg = config.programs.oidc-agent;
|
||||||
|
providerFormat = pkgs.formats.json {};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.programs.oidc-agent = {
|
||||||
|
enable = mkEnableOption "OpenID Connect Agent";
|
||||||
|
package = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
default = pkgs.oidc-agent;
|
||||||
|
description = ''
|
||||||
|
Which oidc-agent package to use
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
providers = mkOption {
|
||||||
|
type = providerFormat.type;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Configuration of providers which contains a json array of json objects
|
||||||
|
each describing an issuer, see https://indigo-dc.gitbook.io/oidc-agent/configuration/issuers
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.user.services.oidc-agent = {
|
||||||
|
unitConfig = {
|
||||||
|
Description = "OpenID Connect Agent";
|
||||||
|
Documentation = "man:oidc-agent(1)";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${cfg.package}/bin/oidc-agent -d --log-stderr -a %t/oidc-agent";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# environment.etc."oidc-agent/config".source = "${pkgs.oidc-agent}/etc/oidc-agent/config";
|
||||||
|
|
||||||
|
# environment.etc."oidc-agent/issuer.config.d".source =
|
||||||
|
# "${pkgs.oidc-agent}/etc/oidc-agent/issuer.config.d";
|
||||||
|
|
||||||
|
# environment.etc."oidc-agent/issuer.config".source =
|
||||||
|
# providerFormat.generate "oidc-agent-issuer.config" cfg.providers;
|
||||||
|
|
||||||
|
environment.extraInit = ''export OIDC_SOCK="$XDG_RUNTIME_DIR/oidc-agent"'';
|
||||||
|
};
|
||||||
|
}
|
|
@ -3,7 +3,8 @@
|
||||||
{
|
{
|
||||||
nixpkgs.overlays = [
|
nixpkgs.overlays = [
|
||||||
(self: super: {
|
(self: super: {
|
||||||
ssh-tpm-agent = pkgs.callPackage ./pkgs/ssh-tpm-agent.nix { };
|
oidc-agent = pkgs.callPackage ./pkgs/oidc-agent { };
|
||||||
|
python3 = super.python312;
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
58
overlays/pkgs/oidc-agent/default.nix
Normal file
58
overlays/pkgs/oidc-agent/default.nix
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
{ lib
|
||||||
|
, stdenv
|
||||||
|
, fetchFromGitHub
|
||||||
|
, curl
|
||||||
|
, webkitgtk
|
||||||
|
, libmicrohttpd
|
||||||
|
, libsecret
|
||||||
|
, qrencode
|
||||||
|
, libsodium
|
||||||
|
, pkg-config
|
||||||
|
, help2man
|
||||||
|
}:
|
||||||
|
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
pname = "oidc-agent";
|
||||||
|
version = "5.1.0";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "indigo-dc";
|
||||||
|
repo = "oidc-agent";
|
||||||
|
rev = "v${version}";
|
||||||
|
sha256 = "sha256-cOK/rZ/jnyALLuhDM3+qvwwe4Fjkv8diQBkw7NfVo0c="
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
pkg-config
|
||||||
|
help2man
|
||||||
|
];
|
||||||
|
nativeBuildInputs = [
|
||||||
|
curl
|
||||||
|
webkitgtk
|
||||||
|
libmicrohttpd
|
||||||
|
libsecret
|
||||||
|
qrencode
|
||||||
|
libsodium
|
||||||
|
];
|
||||||
|
enableParallelBuilding = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
make -j $NIX_BUILD_CORES PREFIX=$out BIN_PATH=$out LIB_PATH=$out/lib \
|
||||||
|
install_bin install_lib install_conf
|
||||||
|
'';
|
||||||
|
postFixup = ''
|
||||||
|
# Override with patched binary to be used by help2man
|
||||||
|
cp -r $out/bin/* bin
|
||||||
|
make install_man PREFIX=$out
|
||||||
|
'';
|
||||||
|
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "oidc-agent for managing OpenID Connect tokens on the command line";
|
||||||
|
homepage = "https://github.com/indigo-dc/oidc-agent";
|
||||||
|
maintainers = [ ];
|
||||||
|
license = licenses.mit;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
{ lib
|
|
||||||
, buildGo122Module
|
|
||||||
, fetchFromGitHub
|
|
||||||
, openssl
|
|
||||||
}:
|
|
||||||
|
|
||||||
buildGo122Module rec {
|
|
||||||
pname = "ssh-tpm-agent";
|
|
||||||
version = "0.3.1";
|
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
|
||||||
owner = "Foxboron";
|
|
||||||
repo = "ssh-tpm-agent";
|
|
||||||
rev = "v${version}";
|
|
||||||
hash = "sha256-8CGSiCOcns4cWkYWqibs6hAFRipYabKPCpkhxF4OE8w=";
|
|
||||||
};
|
|
||||||
|
|
||||||
proxyVendor = true;
|
|
||||||
|
|
||||||
vendorHash = "sha256-zUAIesBeuh1zlxXcjKSNmMawZGgUr9z3NzT0XKn/YCQ=";
|
|
||||||
|
|
||||||
buildInputs = [
|
|
||||||
openssl
|
|
||||||
];
|
|
||||||
|
|
||||||
meta = with lib; {
|
|
||||||
description = "SSH agent with support for TPM sealed keys for public key authentication";
|
|
||||||
homepage = "https://github.com/Foxboron/ssh-agent-tpm";
|
|
||||||
license = licenses.mit;
|
|
||||||
platforms = platforms.linux;
|
|
||||||
maintainers = with maintainers; [ sgo ];
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Reference in a new issue