介绍

在程序开发中经常面临一个语言多种版本的问题,这时候如果有一个自动切换版本的工具就方便了, 就比如 node的 nvm工具,而Debian 中的 update-alternatives 命令是系统上多版本软件管理的利器。它允许您轻松地在同一程序的不同版本之间切换。假设您在单个系统上安装了多个 Java 版本或 Python 版本,在 update-alternatives 的帮助下,可以将程序的任何版本设置为系统的默认版本。

为什么是 update-alternatives?

update-alternatives 是一个命令行工具,可设置、维护和管理不同版本软件的符号链接。这些符号链接指向系统上的实际文件,使得在不同版本之间切换变得很容易。当系统上安装了一个程序的多个版本,并且希望在它们之间轻松切换时,使用更新替代方案是有益的。例如,如果我安装了两个版本的 Python,就可以使用 update-alternatives 来切换系统使用的默认版本。

update-alternatives 使用

当我们在 Debian 上安装软件时,它通常会在 /usr/bin 目录。这些链接指向实际程序文件。

管理多版本的 Java

比如我已经在 /home/cong/language/java 安装了多个版本的Java:

1
2
3
4
5
6
drwxrwxr-x 8 cong cong      4096  4月 18 00:23 jdk-11.0.2
drwxrwxr-x 8 cong cong 4096 4月 18 00:23 jdk-17.0.2
drwxrwxr-x 8 cong cong 4096 4月 18 00:23 jdk1.8.0_441
drwxrwxr-x 8 cong cong 4096 4月 18 00:24 jdk-21.0.2
drwxrwxr-x 8 cong cong 4096 4月 18 00:24 jdk-22.0.2
drwxrwxr-x 8 cong cong 4096 4月 18 00:24 jdk-23.0.2

让我们创建链接 java 和 javac,并命名组为 java 和 javac:

1
2
3
sudo update-alternatives --install /usr/bin/java java /home/cong/language/java/jdk1.8.0_441/bin/java 1
sudo update-alternatives --install /usr/bin/javac javac /home/cong/language/java/jdk1.8.0_441/bin/javac 1

然后添加各个版本版本的java 和 javac:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
## java 11
sudo update-alternatives --install /usr/bin/java java /home/cong/language/java/jdk-11.0.2/bin/java 2
sudo update-alternatives --install /usr/bin/javac javac /home/cong/language/java/jdk-11.0.2/bin/javac 2

## java 17
sudo update-alternatives --install /usr/bin/java java /home/cong/language/java/jdk-17.0.2/bin/java 3
sudo update-alternatives --install /usr/bin/javac javac /home/cong/language/java/jdk-17.0.2/bin/javac 3

## java 21
sudo update-alternatives --install /usr/bin/java java /home/cong/language/java/jdk-21.0.2/bin/java 4
sudo update-alternatives --install /usr/bin/javac javac /home/cong/language/java/jdk-21.0.2/bin/javac 4

## java 22
sudo update-alternatives --install /usr/bin/java java /home/cong/language/java/jdk-22.0.2/bin/java 5
sudo update-alternatives --install /usr/bin/javac javac /home/cong/language/java/jdk-22.0.2/bin/javac 5

## java 23
sudo update-alternatives --install /usr/bin/java java /home/cong/language/java/jdk-23.0.2/bin/java 6
sudo update-alternatives --install /usr/bin/javac javac /home/cong/language/java/jdk-23.0.2/bin/javac 6

使用以下命令,在版本之间切换

1
2
sudo update-alternatives --config java
sudo update-alternatives --config javac

执行完命令后,可以看到以下输出:

1
2
3
4
5
6
7
8
9
10
11
12
有 6 个候选项可用于替换 java (提供 /usr/bin/java)。

选择 路径 优先级 状态
------------------------------------------------------------
* 0 /home/cong/language/java/jdk-23.0.2/bin/java 6 自动模式
1 /home/cong/language/java/jdk-11.0.2/bin/java 2 手动模式
2 /home/cong/language/java/jdk-17.0.2/bin/java 3 手动模式
3 /home/cong/language/java/jdk-21.0.2/bin/java 4 手动模式
4 /home/cong/language/java/jdk-22.0.2/bin/java 5 手动模式
5 /home/cong/language/java/jdk-23.0.2/bin/java 6 手动模式
6 /home/cong/language/java/jdk1.8.0_441/bin/java 1 手动模式

选择任意选择号 并按 Enter 使其成为系统上的默认 Java 版本。

安装 nvm

由于我不希望node 版本安装到home 目录,希望安装到 language/node 下,所以先设置了 NVM_DIR

1
export NVM_DIR=/home/cong/language/node && curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash 

安装完成后,需要重新启动终端或输入以下命令使 nvm 生效:

1
source ~/.bashrc

nvm 命令介绍和安装 node

查看nvm 版本

1
2
nvm -v
nvm --version

显示nvm 版本

查看 node 版本列表

1
2
3
4
5
nvm ls-remote --lts
# 显示已安装的版本列表
nvm list installed
# 显示可安装的版本列表
nvm list available

安装指定的版本

1
2
3
nvm install xx.xx
# 卸载指定的版本
nvm uninstall xx.xx

使用指定的版本

1
2
3
# 查看现在的node 版本
nvm current
nvm use xx.xx

安装 node 及初始化设置

1
2
3
4
5
6
7
8
nvm install 22.14.0
mkdir -p /home/cong/language/node/node_cache
mkdir -p /home/cong/language/node/node_global
npm config set cache /home/cong/language/node/node_cache/
npm config set prefix /home/cong/language/node/node_global/
mkdir -p /home/cong/language/node/pnpm
export PNPM_HOME=/home/cong/language/node/pnpm && curl -fsSL https://get.pnpm.io/install.sh | sh -

修改path

查看~/.bashrc,确保路径修改正确,如果不正确需要添加以下的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
export NVM_DIR="/home/cong/language/node"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
# node_global
export NODE_GLOBAL="/home/cong/language/node/node_global"
# node_global end
# pnpm
export PNPM_HOME="/home/cong/language/node/pnpm"
case ":$PATH:" in
*":$PNPM_HOME:"*) ;;
*) export PATH="$PNPM_HOME:$NODE_GLOBAL:$PATH" ;;
esac
# pnpm end

最近在折腾windows服务器远程部署,尝试自动部署的时候踩了一些坑,因此记录一下

远程停止和启动windows服务

稍微了解windows机器的人都知道使用SC命令可以启动和停止服务,但是对于一台远程的机器来说如何停止和启动远程机器的服务呢?我们需要使用WinRm。

WinRm————Windows 远程管理 (WinRM) 是 WS-Management 协议的 Microsoft 实现,该协议是标准简单对象访问协议 (基于 SOAP) 的防火墙友好协议,允许不同供应商的硬件和操作系统之间进行互操作。

官方文档

WS-Management协议规范为系统提供了一种跨 IT 基础结构访问和交换管理信息的常用方法。 WinRM 和 智能平台管理接口 (IPMI) 标准,以及 事件收集器服务 是称为 硬件管理的功能集的组件。
WinRM的默认端口是5985-http,5986-https, 使用的时候可能需要开通防火墙。

WinRm踩坑一:https 和http问题

WinRM是基于SOAP的远程控制协议,但是实际使用的时候,只用http的时候出现下面的错误:

1
2
Connecting to remote server xxx.xxx.xxx.xxx failed with the following error message : The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. You can get more information about that by running the following command: winrm help config. For more information, see the about_Remote_Troubleshooting Help topic.

从报错信息可以知道,如果不在同一个windows AD域下的两台机器,如果执行WinRM的远程调用必须使用HTTPS端口,
而不巧的是我的两台机器就是不同域的,所以必须使用HTTPS。

WinRm踩坑二:HTTPS证书问题

当尝试使用WinRM开启https服务的时候,发现windows机器没有安装自己的证书,那么就需要安装证书:
直接安装证书的powershell命令是New-SelfSignedCertificate,微软的文档地址是:

https://learn.microsoft.com/en-us/powershell/module/pki/new-selfsignedcertificate?view=winserver2012-ps

1
New-SelfSignedCertificate -DnsName x.x.x.x -CertStoreLocation cert:\LocalMachine\My

x.x.x.x表示证书的common name,用于做host认证的。

装完证书之后就是启动winRm的https服务了,powershell命令是:

1
winrm create winrm/config/Listener?Address=*+Transport=HTTPS '@{Hostname="x.x.x.x"; CertificateThumbprint="AAAAA14A4EEB259CC87C37942D82D8FF173807C4"}'

这里的AAAAA14A4EEB259CC87C37942D82D8FF173807C4是证书的指纹,在上面的创建证书命令执行之后会得到

windows服务器传输文件问题

当使用windows服务器的时候,肯定会需要传输文件到windows服务器的指定目录去,理所当然的以为只要有账号和密码就能传输文件到指定目录去,但是使用的时候有以下错误:

1
windows file copy System.ComponentModel.Win32Exception (0x80004005): The network name cannot be found

原因还是两台windows机器不在同一个AD域,不能直接copy文件过去,通过设置文件共享也不行,共享文件必须指定AD域的。所以直接使用windows Rdp端口使用命令传输文件是不太可行了。
传输文件用ssh是很方便的,所以:

windows 服务器安装OpenSSH服务

安装方法如下:

  1. 下载微软开源的openssh

  2. 下载最新的二进制版本: Releases · PowerShell/Win32-OpenSSH

  3. 解压到C:\Program Files并重命名为OpenSSH

  4. 启动Window PowerShell 并进入C:\Program Files\OpenSSH目录

  5. 输入命令安装sshd和ssh-agent服务

    1
    powershell -ExecutionPolicy Bypass -File install-sshd.ps1
  6. 打开防火墙,开启22端口 (也可使用控制面板中防火墙 高级 进行设置)

    1
    netsh advfirewall firewall add rule name=sshd dir=in action=allow protocol=TCP localport=22
  7. 设置开机启动

    1
    2
    3
    Set-Service sshd -StartupType Automatic

    Set-Service ssh-agent -StartupType Automatic
  8. 启动服务

    1
    2
    3
    Start-Service sshd

    Start-Service ssh-agent

安装完成之后,使用putty测试,传输文件果然可行了!
putty

Nginx也使用很多次,修改配置,调整参数等等。但是每次要使用的时候都是临时查,所以这次准备记录一下nginx的安装和配置调整过程,加深记忆和理解。

安装Nginx

这里我选择nginx 的源码安装方式,也就是下载源码手工编译安装。

1.安装依赖库

  • 安装gcc g++,nginx 源码进行编译,编译依赖 gcc 环境
  • 安装pcre pcre-devel #PCRE(Perl Compatible Regular Expressions) 是一个 Perl 库,包括 perl 兼容的正则表达式库。nginx 依赖 PCRE 库。
  • 安装zlib #zlib 适用于数据压缩的函式库,由 Jean-loup Gailly (负责 compression)和 Mark Adler (负责 decompression)开发。 nginx 依赖 zlib 库。
  • 安装OpenSSL #OpenSSL 是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。Nginx 也依赖 OpenSSL,需要在 Centos 安装此库。
1
2
3
4
yum install -y gcc-c++
yum install -y pcre pcre-devel
yum install -y zlib zlib-devel
yum install -y openssl openssl-devel

2.下载源码并解压

1
2
curl -O http://nginx.org/download/nginx-1.23.3.tar.gz
tar -zxvf nginx-1.23.3.tar.gz

curl

3.配置Nginx、编译、安装

nginx使用configure程序进行编译模块的配置,可以根据需求选择需要加载的模块,我这里是需要https模块,并且需要tcp转发所以需要https模块和stream模块等

1
2
3
4
5
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module  --with-stream --with-stream_ssl_module
make
make install
#创建软连接 随时可以找到nginx
ln -s /usr/local/nginx/sbin/nginx /usr/local/bin/

nginx-start
好了,到这里nginx 就安装完并且正常启动了。

nginx配置根据host header转发到不同地址

有时候nginx会有多个转发地址的需求,我们可以根据不同的请求参数来设定转发地址的路径,可以参考这篇文章:https://sites.psu.edu/jasonheffner/2015/06/19/nginx-use-different-backend-based-on-http-header/
我这里是需要配置两台http server,一个静态网页的:

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 8001;
server_name localhost;
location / {
root /data/sftp/my-sftp;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

这里相当于在8001端口开放了一个静态html网页的http server,然后实际http server转发的配置如下:

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
upstream remote{
server x.x.x.x:xx;
}
upstream local_another {
server 127.0.0.1:8001;
}
# map to different upstream backends based on header
map $http_host $pool {
default "remote";
xxx.com "local_another";
www.xxx.com "local_another";
}
server {
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
listen 443 ssl;
#ssl off;
ssl_certificate /root/ssl/server.pem;
ssl_certificate_key /root/ssl/server.key;
ssl_session_timeout 5m;
location / {
proxy_pass http://$pool;
#standard proxy settings
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

这里面最重要的配置是下面这个配置:

1
2
3
4
5
map $http_host $pool {
default "remote";
xxx.com "local_another";
www.xxx.com "local_another";
}

也就是根据http header host 来转发到不同的上游系统,默认转发到remote,如果host是xxx.com转发到local_another,也就是前面启动在8001端口的http server。
经过测试,这个配置也验证生效了, 果然nginx的配置还是很自由且简单的。

Centos已经用了很多了,有时候登录还是密码登录,仅以此文记录下修改服务器ssh证书登录和开启服务器sftp服务的过程,加深记忆。

服务器开启SSH证书登录和修改ssh端口

  1. 首先是使用命令生成一对RSA公钥和私钥,公钥上传到服务器,私钥本地登录使用。
1
ssh-keygen -t rsa -C "lc-server"

lc-server

  1. 将xxx.pub改名(或拷贝、添加到)~/.ssh/authorized_keys文件
1
cp lc-server.pub  .ssh/lc-server.pub

cp
3. 修改xxx.pub文件权限为0600

1
chmod 600 lc-server.pub

chmod
4. 修改/etc/ssh/sshd_config文件,然后重启sshd服务,如果是root用证书,则第一行是必须的,同时为了禁止密码登录服务器可以把第三个yes改为no,当然这需要使用证书登录成功之后修改。

1
systemctl restart sshd

auth
5. 私钥的格式问题:
如果是putty使用证书登陆,需要将私钥id_rsa文件格式转为ppk格式,使用putty提供的puttygen.exe,使用load加载本地私钥文件,然后使用save 保存为putty的ppk格式的私钥。
ppk
6. 使用putty登录:
login1
login2
login3

服务器开启SFTP服务

  1. 检查 openssh 版本–如果需要使用 ChrootDirectory 参数配置用户访问的默认路径,openssh 的版本不能低于 4.8
1
ssh -V
  1. 创建 sftp 用户
    Linux 系统内已存在的用户,都是可以直接使用 sftp 服务,创建用户是为了一些特殊的场景使用,做一些区分,将 my-sftp 用户的登录 shell 指定为 /sbin/nologin ,不让 my-sftp 用户可以通过 ssh 命令登录到服务器
1
useradd my-sftp -s /sbin/nologin

useradd
为 for-sftp 用户设置密码

1
echo passwd| passwd --stdin my-sftp

password
3. 修改ssh配置

1
vi /etc/ssh/sshd_config

internal-sftp
配置了 ChrootDirectory 时,sftp 对于 ChrootDirectory 目录的权限要求比较死
不允许 ChrootDirectory 的属组有写入权限,也就是最高只支持 750 和 755 两种权限
ChrootDirectory 的属主必须是 root 用户
不满足以上两种条件时,用 my-sftp 用户登录 sftp 会报错:
packet_write_wait: Connection to <ip地址> port 22: Broken pipe
Couldn’t read packet: Connection reset by peer
命令含义如下:

1
2
3
4
Subsystem sftp internal-sftp  #指定使用sftp服务使用系统自带的internal-sftp 
Match user sftpuser #匹配用户,如果要匹配多个组,多个组之间用逗号分割
ChrootDirectory /data/sftp #设定属于用户组sftp的用户访问的根文件夹如设置 /data/sftp 作为sftpuser 的sftp根目录
ForceCommand internal-sftp #指定sftp命令,强制执行内部sftp,并忽略任何 ~/.ssh/rc文件中的命令
  1. 重启ssh服务
1
systemctl restart sshd
  1. 创建 ChrootDirectory
    • -m 750 - 指定目录创建时的权限
    • -m 只会指定创建时的最后一级目录,不影响前面的父级目录
    • -p - 父级目录不存在时,创建父级目录
    • 要给 ChrootDirectory 配置一个 for-sftp 组的权限,否则 for-sftp 用户也没权限进入到自己有权限的路径下搞事情
1
2
3
4
mkdir /data/sftp -m 750 -p
chown root.my-sftp /data/sftp
mkdir /data/sftp/my-sftp
chown my-sftp.my-sftp /data/sftp/my-sftp

chroot

  1. 测试sftp

test

注意,如果上传的目录是暴露的nginx http 服务, 需要将nginx 加入到创建sftp用户组内才能正常访问

1
2
gpasswd -a nginx my-sftp

0%