新增虚拟网络搭建资料
@@ -353,6 +353,9 @@ Windows虚拟机:
|
||||
- [显卡直通](./details/pve虚拟机设置及独显直通.md)
|
||||
- [USB直通](./details/pve下直通usb.md)
|
||||
|
||||
进阶优化:
|
||||
- [在pve中搭建一个静态ip的虚拟网络](./details/在pve中搭建一个静态ip的虚拟网络.md)
|
||||
|
||||
## 服务初始化配置
|
||||
在服务跑起来之后,他们大多数应用都需要一个初始化配置的过程,大部分配置比较简单,可以凭借页面的提示直接完成,同时也可以去参考各个应用的官方文档页面。在此我列出一些与Aquar环境相关的几个注意事项:
|
||||
|
||||
|
||||
BIN
_resources/05bd204a2dd0454c8d3790c935486444.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
_resources/0c748919c38fe3c5446b7e3cb8d034dd.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
_resources/10839948b8ac8fa617e466f4cb7382d7.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
_resources/18e1b608195e42eb791f54898cd8d1c5.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
_resources/1a527c735e865653cfe1bdc35c94b9a.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
_resources/3260f682bc4fee09cb8cfdc661202716.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
_resources/3c06dc7e877c591de13938a4552a0535.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
_resources/4425aa27a62309df0ccb74a34e76ebb9.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
_resources/4619e3460cb689358e231be3f8ff430d.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
_resources/5b18756987565a8d80bcb1a71351fc57.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
_resources/6b0e21840799c7e37f09b468a7800cc7.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
_resources/721f39b22ac13c67c67b76f1936110a5.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
_resources/a0a46d59b5394b388b60f6ec7f1371b5.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
_resources/be4913ff84d608c57a1811cdee535804.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
_resources/dc12c07f2b1c8090d25d1f2e648720dd.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
97
details/在pve中搭建一个静态ip的虚拟网络.md
Normal file
@@ -0,0 +1,97 @@
|
||||
## 概念及原理
|
||||
在最初的版本中aquar系统依靠开机时扫描局域网中其他节点的服务然后修改配置文件的方式来应对DHCP下的网络地址漂移,这在大部分情况下可以解决问题,但如果本地网络过于庞大,或者本地网络中有多个提供文件共享服务的节点,那么这种做法可能会出现问题,这种做法本身也不够优雅。所以后来探索出这种基于虚拟网络的服务互联方式。这个方案的原理是,在几个虚拟机节点间搭建一个独立与局域网的虚拟网络,虚拟网络中节点的ip地址是静态的,这样无论外部局域网如何变化,我们永远可以通过虚拟网络中的IP地址访问到那个节点,稳定而优雅。
|
||||
|
||||
以我自己的系统为例,按下文设置后pve宿主机的内部ip地址是192.168.172.1,TrueNAS的内部IP地址是:192.168.172.2,两个ubuntu:192.168.172.3/4,Windows:192.168.172.5。这样一来如果你要在ubuntu上面挂载TrueNAS的NFS服务,那直接配置192.168.172.2这个地址就可以了,类似的如果你要在Windows云电脑上访问Ubuntu中的Docker服务,直接访问192.168.172.3就可以。
|
||||
|
||||
要强调的是目前这个虚拟网络只存在于aquar系统这个虚拟化体系之内,你不能用自己的设备直连这个网络,这同时是对aquar内部的一种隔离。
|
||||
## 配置步骤
|
||||
1.在pve节点的network设置中新建一个网桥,命名为vmbr1,赋予vmbr1一个掩码为24位的ip地址192.168.172.2/24,其他都为空,设置完以后重启pve,让这个网桥启动
|
||||
|
||||

|
||||
|
||||
2.在windows虚拟机中配置一个新的网络设备,Bridge选择新建的vmbr1,然后关闭虚拟机再启动
|
||||
|
||||

|
||||
|
||||
在windows中点击右下角的网络链接,选择网络和Internet设置
|
||||
|
||||

|
||||
|
||||
点击更改适配器选项
|
||||
|
||||

|
||||
|
||||
会看到一个以太网2,右键属性
|
||||
|
||||

|
||||
|
||||
设置IPv4属性
|
||||
|
||||

|
||||
|
||||
选择“使用下面的IP地址”,然后将ip地址设置为192.168.172.5,子网掩码为255.255.255.0,默认网关空着不填,然后确定
|
||||
|
||||

|
||||
|
||||
然后在cmd命令行中ping 192.168.172.1,如果可以ping通,则代表静态虚拟网络配置成功了。
|
||||
|
||||
3.为truenas配置虚拟网络。
|
||||
|
||||
在truenas的硬件设置页中配置一个新的网络设,Bridge选择新建的vmbr1,然后关闭虚拟机再启动
|
||||
|
||||

|
||||
|
||||
在network->interfaces设置页面中找到刚添加的新网卡,一般为em1,点击edit按钮进入编辑页面,关闭dhcp,并给它配置一个静态ip为192.168.172.2,其他配置不用变,点击apply保存设置
|
||||
|
||||

|
||||
|
||||
在network->static routes页面中,点击右上的ADD按钮,添加一个静态路由规则,destination填写192.167.172.0/24,Gateway填写0.0.0.0。保存后将truenas系统关机,然后在开机。
|
||||
|
||||

|
||||
|
||||
这里TrueNAS有可能会出一个bug,就是设置完第二个网卡以后,第一个网卡的DHCP选项会取消掉,这时如果重启,会发现TrueNAS服务只出现在了虚拟网络中,如果出现了这种情况,那么进入network->interfaces,然后进入em0的设置,看看DHCP选项是否勾选,如果没有勾选,则勾选上,保存后再关机并开机。设置完以后开机控制台如果出现了类似这样的多个网址,就说明两张网卡都已经生效了。
|
||||
|
||||

|
||||
|
||||
4.为ubuntu配置虚拟网络
|
||||
|
||||
首先为ubuntu虚拟机添加一个新的网络设备,bridge选vmbr1,然后关闭虚拟机再启动。
|
||||
|
||||

|
||||
|
||||
进入命令行,`sudo -i`切换为管理员身份
|
||||
|
||||
使用ip addr show 查看网卡会看到一个没有ip地址的网卡设备,图上的是ens19。
|
||||
|
||||

|
||||
|
||||
~~执行下面两句命令,首先将ens19的ip设置为静态的192.168.172.3/24,然后给它添加一条路由规则,即所有192.168.172.0/24网段的报文都走ens19这个网络接口。~~
|
||||
|
||||
```
|
||||
netplan set ens19 addresses 192.168.172.3/24
|
||||
netplan set route ens19 to 192.168.172.0/24 via 0.0.0.0 metric 100
|
||||
```
|
||||
|
||||
使用`vim /etc/netplan/00-installer-config.yaml`编辑netplan的配置文件,配置为如下文的形式
|
||||
|
||||
```
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
ens18:
|
||||
dhcp4: true
|
||||
ens19:
|
||||
dhcp4: false
|
||||
addresses:
|
||||
- 192.168.172.3/24
|
||||
routes:
|
||||
- to: 192.168.172.0/24
|
||||
via: 0.0.0.0
|
||||
metric: 100
|
||||
nameservers:
|
||||
addresses: [8.8.8.8,114.114.114.114]
|
||||
```
|
||||
|
||||
执行完以后执行`netplan apply`重载网络配置,这时候因为机器主网卡是dhcp的,有可能ip会飘走,这时你的ssh命令行可能会卡住。这时候退出命令行工具,直接使用pve的console登录系统,使用`p addr show`命令查看网络设备的ip地址,如果前面的配置正确,那么就可以看到两张网卡都获得了自己的ip地址。
|
||||
|
||||

|
||||
@@ -112,25 +112,40 @@ services:
|
||||
depends_on:
|
||||
- "mariadb"
|
||||
restart: unless-stopped
|
||||
# jellyfin:
|
||||
# image: ghcr.io/linuxserver/jellyfin
|
||||
# container_name: jellyfin
|
||||
# environment:
|
||||
# - PUID=1000
|
||||
# - PGID=1000
|
||||
# - TZ="Asia/Shanghai"
|
||||
# # - UMASK_SET=<022> #optional
|
||||
# volumes:
|
||||
# - /opt/aquar/storages/apps/jellyfin/config:/config
|
||||
# - /opt/aquar/storages/apps/jellyfin/data/tvshows:/data/tvshows
|
||||
# - /opt/aquar/storages/aquarpool/movies:/data/movies
|
||||
# # - /opt/vc/lib:/opt/vc/lib #optional
|
||||
# ports:
|
||||
# - 8096:8096
|
||||
# - 8920:8920 #optional
|
||||
# - 7359:7359/udp #optional
|
||||
# - 1900:1900/udp #optional
|
||||
# restart: unless-stopped
|
||||
jellyfin:
|
||||
image: ghcr.io/linuxserver/jellyfin
|
||||
image: nyanmisaka/jellyfin
|
||||
container_name: jellyfin
|
||||
network_mode: host
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ="Asia/Shanghai"
|
||||
# - UMASK_SET=<022> #optional
|
||||
- TZ=Asia/Shanghai
|
||||
# - JELLYFIN_PublishedServerUrl="http://192.168.0.118:8096" #optional
|
||||
volumes:
|
||||
- /opt/aquar/storages/apps/jellyfin/config:/config
|
||||
- /opt/aquar/storages/apps/jellyfin/data/tvshows:/data/tvshows
|
||||
- /opt/aquar/storages/aquarpool/movies:/data/movies
|
||||
# - /opt/vc/lib:/opt/vc/lib #optional
|
||||
ports:
|
||||
- 8096:8096
|
||||
- 8920:8920 #optional
|
||||
- 7359:7359/udp #optional
|
||||
- 1900:1900/udp #optional
|
||||
- /opt/aquar/storages/apps/jellyfin/cache:/cache
|
||||
- /opt/aquar/storages/aquarpool/movies:/media
|
||||
restart: unless-stopped
|
||||
privileged: true
|
||||
devices:
|
||||
- /dev/dri:/dev/dri
|
||||
syncthing:
|
||||
image: ghcr.io/linuxserver/syncthing
|
||||
container_name: syncthing
|
||||
@@ -293,7 +308,7 @@ GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
|
||||
EOF
|
||||
cat > /etc/docker/daemon.json <<EOF
|
||||
{
|
||||
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
|
||||
"registry-mirrors": ["https://hub-mirror.c.163.com","https://docker.mirrors.ustc.edu.cn"]
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -320,4 +335,5 @@ systemctl enable aquar
|
||||
echo '********启动docker-compose********'
|
||||
cd /opt/aquar/src/docker-compose/
|
||||
docker-compose up -d
|
||||
mkdir -p /opt/aquar/storages/apps/filerun/html/system/data/temp
|
||||
# systemctl start aquar
|
||||
256
files/setup_pve.sh
Normal file
@@ -0,0 +1,256 @@
|
||||
echo '********开始修改pve源为国内源********'
|
||||
mv /etc/apt/sources.list.d/pve-enterprise.list /etc/apt/sources.list.d/pve-enterprise.list.bak
|
||||
if ! grep -q '##\[aquar config start\]##' /etc/apt/sources.list;
|
||||
then
|
||||
cp /etc/apt/sources.list /etc/apt/sources.list.bak
|
||||
cat > /etc/apt/sources.list <<EOF
|
||||
##[aquar config start]##
|
||||
deb https://mirrors.ustc.edu.cn/debian/ bullseye main contrib non-free
|
||||
deb https://mirrors.ustc.edu.cn/debian/ bullseye-updates main contrib non-free
|
||||
deb https://mirrors.ustc.edu.cn/debian/ bullseye-backports main contrib non-free
|
||||
deb https://mirrors.ustc.edu.cn/debian-security bullseye-security main contrib
|
||||
deb https://mirrors.ustc.edu.cn/proxmox/debian bullseye pve-no-subscription
|
||||
##[aquar config end]##
|
||||
EOF
|
||||
else
|
||||
echo '********探测到已配置成功,跳过/etc/fstab的配置********'
|
||||
fi
|
||||
apt update
|
||||
echo '*******配置vmbr1********'
|
||||
|
||||
sed -i '/iface vmbr0 inet static/s/static/dhcp/g' /etc/network/interfaces
|
||||
sed -i '/address [0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+/d' /etc/network/interfaces
|
||||
sed -i '/gateway [0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+/d' /etc/network/interfaces
|
||||
if ! grep -q '##\[aquar config start\]##' /etc/network/interfaces;
|
||||
then
|
||||
cp /etc/network/interfaces /etc/network/interfaces.bak
|
||||
cat >> /etc/network/interfaces <<EOF
|
||||
|
||||
##[aquar config start]##
|
||||
auto vmbr1
|
||||
iface vmbr1 inet static
|
||||
address 192.168.172.1/24
|
||||
bridge-ports none
|
||||
bridge-stp off
|
||||
bridge-fd 0
|
||||
##[aquar config end]##
|
||||
EOF
|
||||
else
|
||||
echo '********探测到已配置成功,跳过/etc/fstab的配置********'
|
||||
fi
|
||||
|
||||
echo '*******安装ipupdater.py脚本********'
|
||||
sed '/(^10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}.+$)|(^172\.1[6-9]\.[0-9]{1,3}\.[0-9]{1,3}.+$)|(^172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3}.+$)|(^172\.3[0-1]\.[0-9]{1,3}\.[0-9]{1,3}.+$)|(^192\.168\.[0-9]{1,3}\.[0-9]{1,3}.+$)/s/static/dhcp/g' /etc/hosts
|
||||
## 扫描hosts中的内容,取出带有私有地址的那一行,找到后面跟的host名称,赋值到变量,然后带入到下面的脚本中
|
||||
pve_host=$(grep -m1 "^:" /tmp/example.log)
|
||||
|
||||
cat > /root/ipupdater.py <<EOF
|
||||
#! /usr/bin/env python
|
||||
# vim: set fenc=utf8 ts=4 sw=4 et :
|
||||
# -----------/lib/systemd/system/ipupdater.service systemd配置---------------
|
||||
# [Unit]
|
||||
# Description=update ip config when system start
|
||||
# After=network-online.target
|
||||
#
|
||||
# [Service]
|
||||
# Type=simple
|
||||
# User=root
|
||||
# ExecStart=python3 /root/ipupdater.py
|
||||
#
|
||||
# [Install]
|
||||
# WantedBy=multi-user.target
|
||||
|
||||
|
||||
import socket
|
||||
import shutil
|
||||
import re
|
||||
|
||||
NTERFACE_PATH = '/etc/network/interfaces'
|
||||
HOSTS_PATH = '/etc/hosts'
|
||||
ISSUE_PATH = '/etc/issue'
|
||||
|
||||
def getRealNetInfo():
|
||||
# defaultGateWay, defaultInterface = ni.gateways()['default'][ni.AF_INET]
|
||||
# addressInfo = ni.ifaddresses(defaultInterface)[ni.AF_INET][0]
|
||||
# ip = addressInfo['addr']
|
||||
# maskBits = IPv4Network('0.0.0.0/'+addressInfo['netmask']).prefixlen
|
||||
# return ip, defaultGateWay, maskBits
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.settimeout(0)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(('10.254.254.254', 1))
|
||||
ip = s.getsockname()[0]
|
||||
except Exception:
|
||||
ip = '127.0.0.1'
|
||||
finally:
|
||||
s.close()
|
||||
return ip
|
||||
|
||||
def checkIfIpChanged(ip, defaultGateWay):
|
||||
shutil.copy(INTERFACE_PATH, INTERFACE_PATH + '.bak')
|
||||
targetFile = open(INTERFACE_PATH, "r")
|
||||
configText = targetFile.read()
|
||||
|
||||
addressMatch = re.search(' {8}address (.+)\n',configText)
|
||||
if addressMatch is None or len(addressMatch.groups()) == 0:
|
||||
print("pve静态IP配置文件%s中未找到IP地址信息,配置文件内容:%s" % (NTERFACE_PATH, configText) )
|
||||
return False
|
||||
configIp = addressMatch.groups()[0][:-3]
|
||||
|
||||
gatewayMatch = re.search(' {8}gateway (.+)\n',configText)
|
||||
if gatewayMatch is None or len(gatewayMatch.groups()) == 0:
|
||||
print("pve静态IP配置文件%s中未找到默认路由信息,配置文件内容:%s" % (NTERFACE_PATH, configText) )
|
||||
return False
|
||||
configGateway = gatewayMatch.groups()[0]
|
||||
if defaultGateWay == configGateway and ip == configIp:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def updateInterfaces(ip, gateway, maskBits):
|
||||
shutil.copy(INTERFACE_PATH, INTERFACE_PATH + '.bak')
|
||||
targetFile = open(INTERFACE_PATH, "r+")
|
||||
configText = targetFile.read()
|
||||
splitRes = re.split(" {8}address .+\n {8}gateway .+\n", configText)
|
||||
prepart = splitRes[0]
|
||||
postpart = splitRes[1]
|
||||
updateConfig = " address %s/%s\n gateway %s\n" % (ip, maskBits, gateway)
|
||||
print("interfaces updateConfig: %s" % updateConfig)
|
||||
newConifg = prepart + updateConfig+ postpart
|
||||
print("interfaces newConifg:%s" % newConifg)
|
||||
|
||||
targetFile.seek(0)
|
||||
targetFile.write(newConifg)
|
||||
targetFile.truncate()
|
||||
targetFile.close()
|
||||
|
||||
def updateHosts(ip):
|
||||
shutil.copy(HOSTS_PATH, HOSTS_PATH + '.bak')
|
||||
targetFile = open(HOSTS_PATH, "r+")
|
||||
configText = targetFile.read()
|
||||
splitRes = re.split("\n.+ pve\n", configText)
|
||||
print(splitRes)
|
||||
prepart = splitRes[0]
|
||||
postpart = splitRes[1]
|
||||
updateConfig = "\n%s pve\n" % ip
|
||||
print("----host updateConfig----\n %s" % updateConfig)
|
||||
newConifg = prepart + updateConfig+ postpart
|
||||
print("----host newConifg----\n%s" % newConifg)
|
||||
|
||||
targetFile.seek(0)
|
||||
targetFile.write(newConifg)
|
||||
targetFile.truncate()
|
||||
targetFile.close()
|
||||
|
||||
def updateIssue(ip):
|
||||
shutil.copy(ISSUE_PATH, ISSUE_PATH + '.bak')
|
||||
targetFile = open(ISSUE_PATH, "r+")
|
||||
configText = targetFile.read()
|
||||
splitRes = re.split(" https://.+:8006/\n", configText)
|
||||
prepart = splitRes[0]
|
||||
postpart = splitRes[1]
|
||||
updateConfig = " https://%s:8006/\n" % ip
|
||||
print("----issue updateConfig----\n%s" % updateConfig)
|
||||
newConifg = prepart + updateConfig+ postpart
|
||||
print("----issue newConifg----\n%s" % newConifg)
|
||||
|
||||
targetFile.seek(0)
|
||||
targetFile.write(newConifg)
|
||||
targetFile.truncate()
|
||||
targetFile.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print('ipupdater start.')
|
||||
ip = getRealNetInfo()
|
||||
print("ip:%s" % ip)
|
||||
updateHosts(ip)
|
||||
updateIssue(ip)
|
||||
EOF
|
||||
|
||||
cat > /lib/systemd/system/ipupdater.service <<EOF
|
||||
[Unit]
|
||||
Description=update ip config when system start
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=python3 /root/ipupdater.py
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable ipupdater.service
|
||||
|
||||
ls -l /dev/disk/by-id/
|
||||
# 获取到这个命令的磁盘信息
|
||||
|
||||
qm set 101 -scsi2 /dev/disk/by-id/ata-WDC_WD42PURU-78C4CY0_WD-WX42AC29T231
|
||||
qm set 101 -scsi3 /dev/disk/by-id/ata-WDC_WD42PURU-78C4CY0_WD-WXD2A13N6NCP
|
||||
|
||||
# 配置TrueNAS的存储池
|
||||
|
||||
|
||||
cp /etc/default/grub /etc/default/grub.bak
|
||||
sed -i '/GRUB_CMDLINE_LINUX_DEFAULT/s/quiet/quiet quiet intel_iommu=on video=efifb:off,vesafb:off i915.enable_guc=7/g' /etc/default/grub
|
||||
|
||||
cat > /etc/modules <<EOF
|
||||
vfio
|
||||
vfio_iommu_type1
|
||||
vfio_pci
|
||||
vfio_virqfd
|
||||
EOF
|
||||
|
||||
cat > /etc/modprobe.d/blacklist.conf <<EOF
|
||||
blacklist snd_hda_intel
|
||||
blacklist snd_hda_codec_hdmi
|
||||
blacklist i915
|
||||
EOF
|
||||
update-grub
|
||||
reboot
|
||||
|
||||
# 从配置中拿出8086:4680的部分
|
||||
$gpu_info=lspci -n -s 00:02
|
||||
cat > /etc/modprobe.d/vfio.conf <<EOF
|
||||
options vfio-pci ids=8086:4680
|
||||
EOF
|
||||
update-initramfs -u
|
||||
# 配置TrueNAS的网卡
|
||||
######################Ubuntu中执行##########################
|
||||
# 配置Ubuntu的网卡
|
||||
cp /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak
|
||||
|
||||
cat > /etc/netplan/00-installer-config.yaml <<EOF
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
ens18:
|
||||
dhcp4: true
|
||||
ens19:
|
||||
dhcp4: false
|
||||
addresses:
|
||||
- 192.168.172.3/24
|
||||
routes:
|
||||
- to: 192.168.172.0/24
|
||||
via: 0.0.0.0
|
||||
metric: 100
|
||||
nameservers:
|
||||
addresses: [8.8.8.8,114.114.114.114,192.168.3.1]
|
||||
EOF
|
||||
|
||||
apt install intel-gpu-tools
|
||||
apt install libmfx1 libmfx-tools
|
||||
apt install libva-dev libmfx-dev intel-media-va-driver-non-free
|
||||
|
||||
cat >> /etc/bash.bashrc <<EOF
|
||||
export LIBVA_DRIVER_NAME=iHD
|
||||
EOF
|
||||
|
||||
cat > /etc/modprobe.d/i915.conf <<EOF
|
||||
options i915 enable_guc=2
|
||||
undate-initramfs -u -k all
|
||||
EOF
|
||||