Ansible安装及树莓派系统初始化示例
前言:
关键字:Raspberry Pi、Ansible、自动化运维;
Ansible是一款目前比较热门的自动化运维工具,其应用方向基本上是面向需要大批量的进行系统配置的运维人员,或经常性需要部署某一类服务的系统工程师。虽然说Ansible适合所有人使用,但博主依然认为Ansible不太适合个人使用,原因是,个人的服务配置,一般都是单一的一次性的安装配置过程,在这一情况下,从键盘敲入几条命令显然要比写一份Ansible的配置文件简单得多,而这个所谓的“简单”,甚至是建立在你已经会使用Ansible的基础上!如果再算上学习Ansible的时间成本,那么我认为,如果为了搭建某几个仅需要一次性部署的服务,而特别的去学习Ansible的使用与YAML文件编写,那博主会认为这是一个XX的选择~
尽管上面博主如此评价Ansible,但无论如何,这毕竟是一篇关于Ansible的文章,所以博主依然希望文章能对读者有点帮助,或者说给读者一些可以体验Ansible的东西,所以~博主顺便写了个对树莓派初始系统的进行优化的“PlayBook”[Ansible称这种用于配置远程主机系统环境的配置文件为“PlayBook”]。这一“PlayBook”包括了一系列的将在远程主机/本地主机上执行的任务,以帮助你对原有树莓派初始系统进行一些基本的优化/修改,这些优化/修改能帮助你提高系统的安全性、个性化你的树莓派、更适合中国区域使用[软件源设定]等。
关于本文所提供的“PlayBook”文件的使用说明,见下文~
另注:本文所提供的下载文件 --> Init_Rasp.tar.gz
一、硬件与软件环境
本教程在以下环境下搭建测试通过!由于Ansible的使用比较特别,所以本博文中使用了两台树莓派做演示,即一台树莓派用于安装Ansible作为服务器,另一台树莓派用于测试。当然,很多情况下,实际上读者手上只会有一台树莓派,所以本文也会介绍如何在单台树莓派上进行操作[见下文],但无论如何,要使用本文所提供的初始化PlayBook,Ansible服务是必需安装的~
-
主硬件 :Raspberry Pi 4B [服务器]
-
从硬件 :Raspberry Pi 3B [测试主机]
-
操作系统 :Raspbian Buster Lite
-
Ansible版本 :2.9.10
-
测试日期:2020年07月20日
二、配置及安装
假设以下为一个全新配置的树莓派操作系统,系统版本为“Raspbian Buster Lite”;在安装Ansible之前,我们需要一个非必要的操作,更换树莓派上默认使用的“Python”版本~在树莓派的官方操作系统上,“python”命令[实际上这是一个软链接文件]默认指向/使用“Python 2.x”,但PYTHON官方已于2020年1月1日停止了对“Python 2.x”的技术支持,所以,在安装依赖“python”的服务时,我们尽量使用“python 3.x”的版本,有关的命令如下,包括更换与还原操作;
1 2 3 4 5 6 7 |
sudo ln -snf python3 /usr/bin/python # 使用“python 3.x”版本; # 这实际上等同“sudo ln -snf /usr/bin/python3 /usr/bin/python”命令; sudo ln -snf python2 /usr/bin/python # 使用“python 2.x”版本; # 这实际上等同“sudo ln -snf /usr/bin/python2 /usr/bin/python”命令; |
在完成了默认使用的“python”版本设定后,继续进行Ansible服务的部署;博主严重建议使用“python 3.x”版本;Ansible服务的部署完全没难度,基本上有四种安装方法。1.直接从系统中的内置软件源安装[版本较旧];2.依据官方文档的安装方法从官方指定的软件源安装[版本较新并且稳定];3.从GITHUB下载TAR包/ZIP包进行安装[你可以选择任何已发布的版本];4.利用官方开源的源码安装[最新并不保证稳定性];以下为四种安装方法的介绍,读者可按需要选择使用~
1、直接从系统中的内置软件源安装;
1 2 3 4 5 6 7 8 |
sudo apt-get update -y sudo apt-get install ansible -y # 与平时安装工具软件的方法一样; # 版本较旧,写这篇博文时的版本为“2.7.7” sudo apt-cache show ansible sudo apt-cache madison ansible # 补充命令,你可以使用以上命令查询当前软件源中的Ansible版本情况; |
2、依据官方文档的安装方法从官方指定的软件源安装[推荐];
1 2 3 4 5 6 7 8 9 10 11 12 13 |
echo "deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 93C4A3FD7BB9C367 sudo apt update sudo apt install ansible -y # 还是与平时安装工具软件的方法一样,只不过使用了官方指定的软件源; # 而这一个官方软件源中,实际上只有Ansible这一个软件; # 版本较新,写这篇博文时的版本为“2.9.10”,建议使用本方法安装!! # 本处命令代码基本是官方原文[博主只在某个地方加了个"sudo"]; # 注意:这是在Debian系统上的安装方法; sudo apt-cache show ansible sudo apt-cache madison ansible # 补充命令,你可以使用以上命令查询当前软件源中的Ansible版本情况; |
3、从GITHUB下载TAR包/ZIP包进行安装[不建议];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
cd /opt # 博主只是习惯安装在这个目录,你可以选择任意目录; sudo wget https://github.com/ansible/ansible/archive/v2.9.10.tar.gz # 本处博主选择了“2.9.10”版本; # 你可以从官方项目中查看你希望使用的版本; # 官方地址:https://github.com/ansible/ansible sudo tar -zxf v2.9.10.tar.gz # 解压安装包;解压后为“ansible-2.9.10”目录; cd ansible-2.9.10 sudo python setup.py # 进入目录并安装;这个安装过程并不一定顺利,实际上很有可能缺失一些必要的依赖, # 造成无法安装成功,所以此方法博主并不推荐,也没测试; ansible --version # 测试一下是否安装成功; |
4、利用官方的源码进行安装[不建议];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
git clone https://github.com/ansible/ansible.git cd ./ansible source ./hacking/env-setup # 环境变量设定; # 这是官方代码,但实际上树莓派的官方系统上并没有默认安装"git"; # 所以这个方法还是忽略吧;另外注意“source ./hacking/env-setup”这一命令是 # 在使用的SHELL为“bash”时使用的,如果你使用的是其它的SHELL,那么请参照官 # 方说明!!! curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py --user # 安装"pip"工具,在系统中没有"pip"命令时需要的操作; pip install --user -r ./requirements.txt # 安装相关的依赖;这一部分博主也没测试; |
以上简单说明了四种安装方法,仅供参考,实际安装过程中还需要考虑系统上"Python"的版本问题;个人建议方法二,简单官方安全~但官方也不是万能的,比如说,在博主写这篇文章时,官方就没有提供基于ARMHF硬件架构的,依赖"Python 3.x"的ANSIBLE;
三、PlayBook概念结构[因手残而被自己删掉]
本处博主原本是想写一下Ansible服务的“PlayBook”文件的概念,以方便读者了解后面将要执行的“PlayBook”文件[Init_Rasp.yml],但无奈博主在写了约一个小时之后,不小心在浏览器上执行了页面后退操作,于是一个小时写的东西全没了,而博主也不想再重新写了……有关PlayBook的概念于结构,博主可能在下一篇博文再写吧,如果想了解此部分内容,见《暂不 存在》……
四、Ansible项目文件
在此之前,博主已经在“Raspberry Pi 4B”上安装/部署了Ansible软件/服务,之后,又为“Raspberry Pi 3B”烧录了一个全新的基于官方的操作系统[已启用SSH功能],现在,博主将要利用Ansible服务,对“Raspberry Pi 3B”的初始操作系统进行优化[更安全更适合中国区域使用];
在进行操作之前,先对本文所提供的下载文件[Init_Rasp.tar.gz]作一个简单的说明,目录结构;
1 2 3 4 5 6 7 8 9 |
$ tree Init_Rasp Init_Rasp ├── ansible.cfg # 指定Ansible服务要使用的配置文件; ├── file # 存放文件用,本处主要是存放了当要启用SSH公钥登录时, │ │ # 要使用到的公钥与私钥; │ ├── AuthorizedKeysFile.key # 私钥; │ └── AuthorizedKeysFile.pub # 公钥; ├── Init_Rasp.yml # 这就是PlayBook文件; └── inventory # 服务清单文件; |
“ansible.cfg”文件,可配置项非常多,但在使用上并不会在此定义太多的配置项,本文件中比较重要的就是定义服务器文件清单的位置;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# ========================================================================= [defaults] # 服务器清单文件的位置; inventory = ./inventory # 是否对远程主机的密钥进行检查,生产环境不建议设定为“False”; # 此设定与"~/.ssh/known_hosts"文件有关; host_key_checking = False # 当PLAYBOOK执行时提示输入登录密码; ask_pass = False # 启用或禁用"Cowsay"; nocows = 1 # ========================================================================= [privilege_escalation] [inventory] [paramiko_connection] [ssh_connection] [persistent_connection] [accelerate] [selinux] [colors] [diff] |
“inventory”文件[服务器清单],定义PlayBook中任务,将会在那些主机上执行;本处的IP需要按实际情况进行修改。另外,可配置项也是非常多,但一般不使用;本处只定义了一项,即树莓派“pi”用户所使用的默认密码“ansible_password=raspberry”;如果你有数台树莓派的操作系统需要同时进行相同的优化操作,可以往"[raspberry]"组中增加任意数量的主机;
1 2 3 4 5 6 |
[raspberry] 192.168.100.105 ansible_password=raspberry #192.168.100.xxx ansible_password=raspberry [local_rasp] 127.0.0.1 ansible_password=raspberry |
“Init_Rasp.yml”文件,这就是所谓的PlayBook了;这份文件中的内容比较多,包括了变量定义、任务以及一些各种各样的配置项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
--- - name: Raspberry Pi initialization for CN # 这就是概念“play”了 check_mode: no # 测试模式切换[如果只想测试并不是实际执行当中的任务,那么可设定为“yes”] hosts: raspberry # 指定了服务器清单中要应用PlayBook的主机组 remote_user: pi # SSH连接远程主机时所使用的用户名 port: 22 # SSH连接远程主机时的端口 become: yes # 是否在提权的情况下执行任务 become_method: sudo # 提权所使用的命令 become_user: root # 提权的目标用户 vars_prompt: # 交互式的变量定义 - name: New_hostsname # 设定主机名称 prompt: "The hostname you want to set. Default: FionaWong\n" default: FionaWong private: no - name: NewU_name # 设定一个新用户代替“pi”用户 prompt: "The new username you want to set. Default: Edward\n" default: Edward private: no # - name: NewU_uid # 设定新用户的UID # prompt: "Set the UID fot the new user. Default: 1001\n" # default: 1001 # private: no - name: NewU_passwd # 设定新用户的密码 prompt: "Set the password for the new user. Default: 12345678\n" default: 12345678 private: yes confirm: yes - name: Sshd_fun # 是否启用SSH公钥登录[默认不启用];注意:启用本功能同时会禁用传统的密码登录方式 prompt: | "Enable AuthorizedKeys login of ssh, and disable login SSH with password, modify the SSH port to '10086' and some security based configuration, input greater than 1 to enable [e.g.: 2]: ; Default[disable]: 1 " default: 1 private: no tasks: # 执行的任务列表 - name: Set use "python3" default # 设定默认使用PYTHON"3.x"版本 file: path: /usr/bin/python src: python3 state: link force: yes mode: '0755' owner: root group: root - name: Set timezone to Asia/Shanghai # 设定时区 timezone: name: Asia/Shanghai - name: Set repo to AliYun [/etc/apt/sources.list] # 设定国内软件源[阿里云] replace: path: /etc/apt/sources.list regexp: 'http://raspbian\.raspberrypi\.org' replace: 'https://mirrors.aliyun.com/raspbian' - name: Set repo to USTC [/etc/apt/sources.list.d/raspi.list] # 设定国内软件源[中科大] replace: path: /etc/apt/sources.list.d/raspi.list regexp: 'http://archive\.raspberrypi\.org' replace: 'https://mirrors.ustc.edu.cn/archive.raspberrypi.org' # - name: Install softwares # 软件安装[自行启用] # apt: # pkg: # - bc # - tree # - rsync # - vim # - aptitude # - unzip # - sysstat # - dos2unix # - python3-pip # update_cache: yes - name: Create a new user to replace "pi" # 创建新用户 user: name: "{{ NewU_name }}" # uid: "{{ NewU_uid | string }}" password: "{{ NewU_passwd | string | password_hash('sha512') }}" group: pi home: /home/{{ NewU_name }} shell: /bin/bash - name: Set the groups fot the new user # 设定新用户所属的用户组 raw: usermod -G $(groups pi | cut -c 6- | sed 's/ /,/g') {{ NewU_name }} - name: Copy ".bashrc" # 复制"pi"用户的"BASH"环境配置文件[较旧的系统镜像需要的操作] copy: src: /home/pi/.bashrc dest: /home/{{ NewU_name }}/.bashrc mode: '0644' owner: "{{ NewU_name }}" group: pi remote_src: yes - name: Set "/etc/sudoers" have write permission # 新用户SUDO命令免密码设定 file: path: /etc/sudoers mode: 'u+w' - name: Set without password when use "sudo" for new user # 新用户SUDO命令免密码设定 lineinfile: path: /etc/sudoers line: '{{ NewU_name }} ALL=(ALL) NOPASSWD: ALL' - name: Remove the write permission of "/etc/sudoers" # 新用户SUDO命令免密码设定 file: path: /etc/sudoers mode: 'u-w' - block: # SSH服务功能部分 - name: Copy "AuthorizedKeysFile.pub" to remote host # 指定SSH公钥认证功能所使用的公钥文件 copy: src: ./file/AuthorizedKeysFile.pub dest: /etc/ssh/AuthorizedKeysFile.pub mode: '0644' owner: root group: root - name: Configuge "/etc/ssh/sshd_config" # SSH服务配置[可依据实际需要修改配置内容] blockinfile: path: /etc/ssh/sshd_config block: | PubkeyAuthentication yes # SSH公钥认证功能开关 AuthorizedKeysFile ./AuthorizedKeysFile.pub # 公钥文件的位置 port 10086 # SSH服务的监听端口 Protocol 2 # 协议版本[不建议修改] LoginGraceTime 2m # 连续错误登录后多少可再次登录 MaxAuthTries 3 # 连续错误登录的最大次数 PasswordAuthentication no # 禁用SSH密码登录 #PermitRootLogin yes # 允许ROOT用户从SSH登录[删除"#"号启用] #PermitRootLogin without-password # 仅允许ROOT用户使用公钥认证的方式从SSH登录[删除"#"号启用] when: ( Sshd_fun | int ) != 1 - name: Lock "pi" # 锁定"pi"用户 user: name: pi password_lock: yes - name: New hostname # 设定主机名称 hostname: name: "{{ New_hostsname }}" - name: Fix "/etc/hosts" file # 主机域名解释设定 replace: path: /etc/hosts regexp: raspberrypi replace: "{{ New_hostsname }}" notify: - Reboot machine handlers: # 触发任务列表 - name: Reboot machine # 重启设备 reboot: reboot_timeout: 30 ignore_errors: yes |
注意:以上代码并没有任何错误,但严重不建议以复制粘贴的方式创建相关文件,建议下载本文所提供的文件然后进行修改;
原因:使用复制粘贴的方式复制配置至文件内,所有空格的前面将可能存在不可见的字符"M-BM-",造成文件不可用,这是字符编码的问题~若必需使用复制粘贴方式,请务必手动替换掉配置项中的所有空格!!!或使用以下命令过滤掉所有"M-BM-"字符~
1 2 3 4 |
sed 's/\xc2\xa0/ /g' [文件名称] # LINUX下可使用以下命令查看不可见字符的情况 cat -A [文件名称] |
五、PlayBook测试/使用说明[树莓派初始状态优化]
在进行使用之前,你需要:
1、将从本博文的下载到的"Init_Rasp.tar.gz"文件上传到Ansible服务器[本文设备为:"Raspberry Pi 4B"]内,并执行"tar -zxvf Init_Rasp.tar.gz"解压;
2、执行"cd Init_Rasp"进入对应目录;
3、执行"nano inventory"命令修改目录内的"inventory"文件中的远程主机的IP地址;本示例中"Raspberry Pi 3B"的IP地址为"192.168.100.105";
4、保证"Raspberry Pi 3B"已启用SSH功能,并能被ANSIBLE服务器连接[处于同一网段];
在完成以上操作后,执行以下命令开始对"Raspberry Pi 3B"进行初始状态优化:
1 2 |
ansible-playbook Init_Rasp.yml # 注意:此时你应该在"Init_Rasp"目录中; |
在执行完以上命令后,会要求你输入一些变量,直接回车使用默认值,依次为:
A、你希望使用的树莓派主机名称;默认值:FionaWong
B、你希望创建的新用户的名称[代替"pi"用户];默认值:Edward
C、设定新用户的密码;默认值:12345678
D、是否启用SSH的公钥登录方式[输入任意不等于"1"的值启用,例如"2"];默认值:1
在完成以上变量的自定义后,你将能从ANSIBLE服务器上看到各个任务的执行状态,其中:黄色-->表示执行成功并产生数据变更、绿色-->表示执行成功但未产生数据变更、浅蓝色-->表示跳过该任务[未执行]、红色-->表示任务执行失败;博文所提供的PlayBook[即"Init_Rasp.yml"]执行状态如下图所示:
本PlayBook执行完成后分两种状态:
I 、未使用SSH公钥认证功能:此情况下未对SSH服务进行配置修改,设备重启后,默认"pi"用户已被锁定[不可用状态],并且仅能以"新用户"以传统的SSH登录方式登录设备;
II、已使用SSH公钥认证功能:此情况下,由于SSH服务的配置已变更,所以登录方式已被改变;如使用本文所提供的PlayBook文件[Init_Rasp.yml],并未作修改,SSH的登录方式变更为-->禁用传统的密码登录方式、只能使用公钥谁的方式登录SSH服务、SSH服务端口变更为"10086"、"pi"用户被锁定[不可用状态]、仅能以新用户的身份以公钥认证的方式登录SSH服务;另外,若启用SSH公钥认证功能,在PlayBook执行重启设备的任务时,将会返回任务执行失败的信息,这是正常的,这是由于可登录用户、SSH登录方式、SSH服务端口均已变更所造成的。若重启任务最后返回“SSH服务拒绝连接”的信息,可以认为,SSH服务已按照PlayBook中的设定生效了,见下图:
六、将PlayBook应用至当前主机
以上操作均是基于服务器对远程主机进行的操作,以下介绍为单台设备的操作,即安装有ANSIBLE服务的树莓派设备,需要对设备自身的操作系统进行初始的系统优化,建议操作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
sudo ln -snf python3 /usr/bin/python # 使用"python 3.x"版本; sudo sed -i 's#http://raspbian.raspberrypi.org#https://mirrors.aliyun.com/raspbian#g' /etc/apt/sources.list sudo sed -i 's#http://archive.raspberrypi.org#https://mirrors.ustc.edu.cn/archive.raspberrypi.org#g' /etc/apt/sources.list.d/raspi.list # 国内软件源设定; echo "deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main" | sudo tee /etc/apt/sources.list.d/ansible.list sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 93C4A3FD7BB9C367 # ANSIBLE官方软件源设定及信息密钥设定 sudo apt update sudo apt-cache madison ansible # 更新软件索引并查询ANSIBLE软件的所有版本的信息; # 注意:ANSIBLE软件分为依赖"Python 2.x"与"Pytnon 3.x"; # 本示例中设定了"python"命令[软链接文件]指向了"Pytnon 3.x"版本, # 所以本处也应该安装对应"Pytnon 3.x"的ANSIBLE; sudo apt-get install ansible=2.7.7+dfsg-1 -y # 安装ANSILBE; # 在本示例中,对应"Pytnon 3.x"的ANSIBLE版本号为:2.7.7+dfsg-1 [这是树莓派默认软件仓库附带的] # 对应"Pytnon 2.x"的ANSIBLE版本号为:2.9.11-1ppa~trusty [这是ANSIBLE官方仓库提供的] # 令人有点意外的是,ANSIBLE官方并没有提供基于Armhf的,依赖"Pytnon 3.x"版本的ANSILBE最新版本; # 所以本处使用的较低的版本; # 上传本文中下载的""文件;本示例假设上传至"/home/pi"目录; cd /home/pi && tar -zxvf Init_Rasp.tar.gz # 解压; cd Init_Rasp && sed -i 's#hosts: raspberry#hosts: local_rasp#g' ./Init_Rasp.yml # 修改"Init_Rasp.yml"文件的操作主机组; ansible-playbook Init_Rasp.yml # 执行对应命令开始对本地系统进行初始优化操作; # 注意:启用SSH公钥认证功能会对原SSH登录方式进行变更, # 见下文说明; |
七、关于本文提供的PlayBook中的SSH说明
即关于变量"D"项的说明:由于很多初次接触树莓派的人并不一定了解SSH公钥登录的相关知识,所以本处解释一下本示例用到的,关于"/etc/ssh/sshd_config"文件中的配置项,并对对应的配置项的提供个人建议;
1、PubkeyAuthentication yes
# "yes"启用公钥登录功能;建议启用;
2、AuthorizedKeysFile /etc/ssh/AuthorizedKeysFile.pub
# 所使用的公钥文件位置,必须;建议生成个人所属的公私钥文件,见下;
3、port 10086
# SSH服务所使用的端口;建议使用其它非"22"端口;
4、Protocol 2
# SSH协议的版本;安全相关,建议使用;
5、LoginGraceTime 2m
# 连续登录失败后,需要等待多少时间后才可以再次登录;安全相关,建议使用;
6、MaxAuthTries 3
# 登录错误次数限制;安全相关,建议使用;
7、PasswordAuthentication no
# "no"时为禁止使用密码登录方式;已经使用了公钥方式时建议设定为"no";
8、PermitRootLogin yes
# "yes"时为允许root用户使用SSH方式登录;建议按实际需求设定;
# 建议"no"因为完全可以通过"sudo"或"su"命令代替本功能;
9、PermitRootLogin without-password
# "without-password"为root用户只允许公钥方式从SSH登录;
# 只在root用户允许SSH登录时本项才有存在意义;
# 建议,如果真的需要root用户以SSH方式登录,建议只允许其使用公钥方式登录;
关于本文所提供的下载文件中的"file"目录中的公私钥,原则上直接使用并没有什么问题,毕竟博主给的东西基本不会有人看,也就不会有什么人去使用了;但从安全的角度考虑,使用者应尽可能的替换掉该公私钥,生成替换文件的命令如下,仅供参考:
1 2 3 4 5 6 7 8 9 |
ssh-keygen -t ecdsa -b 521 -f AuthorizedKeysFile # 生成密钥对;输入本命令后将要求输入密码短语[就是这个密钥对的密码], # 无需设定,直接回车即可; # 如果有想了解这个“密码短语”的请自行搜索; mv AuthorizedKeysFile AuthorizedKeysFile.key # 改了一下私钥的文件名称;博主只是习惯将私钥用".key"后缀标识而已, # 实际上不改也没问题,私钥是登录时用的,该PlayBook并没有使用到该文件; # 但因使用了公钥,所以下载文件中也提供了对应的私钥; |
八、结
~END~
I reside in Egypt and people here need to know this valuable information, and I have been yours for a long time. The truth is your information is very useful. I hope you continue to publish at this wonderful level for the benefit to prevail.
2022-02-13 上午4:12