JoyLau's Blog

JoyLau 的技术学习与思考

背景

出差在外或者在家工作都需要连接公司网络,没有 VPN 怎么能行

OpenVPN 服务端部署

  1. 全局变量配置: OVPN_DATA=”/home/joylau/ovpn-data”
  2. mkdir ${OVPN_DATA} , cd ${OVPN_DATA}
  3. 这里我使用的是 tcp, udp 的好像没映射, 我用起来有问题,后来换的 tcp 方式, docker run -v ${OVPN_DATA}:/etc/openvpn --rm kylemanna/openvpn ovpn_genconfig -u tcp://公网 IP
  4. 初始化,这里的密码我们都设置为 123456, docker run -v ${OVPN_DATA}:/etc/openvpn --rm -it kylemanna/openvpn ovpn_initpki
  5. 创建用户 liufa , 不使用密码的话在最后面加上 nopass, 使用密码就键入密码,这里我们使用 123456, docker run -v ${OVPN_DATA}:/etc/openvpn --rm -it kylemanna/openvpn easyrsa build-client-full liufa
  6. 为用户 liufa 生成秘钥, docker run -v ${OVPN_DATA}:/etc/openvpn --rm kylemanna/openvpn ovpn_getclient liufa > ${OVPN_DATA}/liufa.ovpn
  7. 创建的文件中端口默认使用的是 1194, 而我用的是 6001,那我们还得修改下 liufa.ovpn 文件的端口
  8. 运行容器,这里我的宿主机端口为 6001, docker run --name openvpn -v ${OVPN_DATA}:/etc/openvpn -d -p 6001:1194 --privileged kylemanna/openvpn

OpenVPN 管理接口

服务端配置文件加入 management 0.0.0.0 5555

可以使用 telnet ip 5555 来使用管理接口, 输入 help 查看详细命令, 具体如下:

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
Commands:
auth-retry t : Auth failure retry mode (none,interact,nointeract).
bytecount n : Show bytes in/out, update every n secs (0=off).
echo [on|off] [N|all] : Like log, but only show messages in echo buffer.
exit|quit : Close management session.
forget-passwords : Forget passwords entered so far.
help : Print this message.
hold [on|off|release] : Set/show hold flag to on/off state, or
release current hold and start tunnel.
kill cn : Kill the client instance(s) having common name cn.
kill IP:port : Kill the client instance connecting from IP:port.
load-stats : Show global server load stats.
log [on|off] [N|all] : Turn on/off realtime log display
+ show last N lines or 'all' for entire history.
mute [n] : Set log mute level to n, or show level if n is absent.
needok type action : Enter confirmation for NEED-OK request of 'type',
where action = 'ok' or 'cancel'.
needstr type action : Enter confirmation for NEED-STR request of 'type',
where action is reply string.
net : (Windows only) Show network info and routing table.
password type p : Enter password p for a queried OpenVPN password.
remote type [host port] : Override remote directive, type=ACCEPT|MOD|SKIP.
proxy type [host port flags] : Enter dynamic proxy server info.
pid : Show process ID of the current OpenVPN process.
client-auth CID KID : Authenticate client-id/key-id CID/KID (MULTILINE)
client-auth-nt CID KID : Authenticate client-id/key-id CID/KID
client-deny CID KID R [CR] : Deny auth client-id/key-id CID/KID with log reason
text R and optional client reason text CR
client-kill CID [M] : Kill client instance CID with message M (def=RESTART)
env-filter [level] : Set env-var filter level
client-pf CID : Define packet filter for client CID (MULTILINE)
rsa-sig : Enter an RSA signature in response to >RSA_SIGN challenge
Enter signature base64 on subsequent lines followed by END
certificate : Enter a client certificate in response to >NEED-CERT challenge
Enter certificate base64 on subsequent lines followed by END
signal s : Send signal s to daemon,
s = SIGHUP|SIGTERM|SIGUSR1|SIGUSR2.
state [on|off] [N|all] : Like log, but show state history.
status [n] : Show current daemon status info using format #n.
test n : Produce n lines of output for testing/debugging.
username type u : Enter username u for a queried OpenVPN username.
verb [n] : Set log verbosity level to n, or show if n is absent.
version : Show current version number.
END


OpenVPN 客户端使用说明

Windows

  1. 安装 openVPN windows 客户端,地址:https://swupdate.openvpn.org/community/releases/openvpn-install-2.4.6-I602.exe , 该地址需要梯子
  2. 启动客户端,右键,选择 import file, 导入 ovpn 文件,文件请 联系管理员发给你
  3. 右键 connect,如果弹出框提示输入密码,输入默认密码 123456 ,等待连接成功即可

Linux

  1. 安装 openvpn:sudo yum install openvpn 或者 sudo apt install openvpn
  2. 找到 ovpn 文件所在目录: sudo openvpn --config ./liufa.ovpn, 看到成功信息时即连接成功
  3. --daemon 参数以守护进程运行
  4. 或者写个 service 文件以守护进程并且开机启动运行

GUI 客户端 [2020-04-29更新]

可以使用开源客户端工具: pritunl-client-electron
安装方法:
Ubuntu 16.04:

1
2
3
4
5
6
7
sudo tee /etc/apt/sources.list.d/pritunl.list << EOF
deb http://repo.pritunl.com/stable/apt xenial main
EOF

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com --recv 7568D9BB55FF9E5287D586017AE645C0CF8E292A
sudo apt-get update
sudo apt-get install pritunl-client-electron

注意 apt 安装需要科学上网来设置代理

或者从 GitHub 上下载软件包: https://github.com/pritunl/pritunl-client-electron/releases

MacOS

  1. 安装 Tunnelblick,地址:https://tunnelblick.net/
  2. 导入 ovpn文件
  3. 状态栏上点击连接VPN

路由设置

连接上 VPN 后,默认所有流量都走的 VPN,但事实上我们并不想这么做

Windows 上路由手动配置

  • ⽐如公司内网的网段为 192.168.10.0 网段,我们先删除 2 个 0.0.0.0 的路由: route delete 0.0.0.0
  • 然后添加 0.0.0.0 到本机的网段 route add 0.0.0.0 mask 255.255.255.0 本机内网网关
  • 再指定 10 网段走 VPN 通道 route add 192.168.10.0 mask 255.255.255.0 VPN 网关
  • 以上路由添加默认是临时的,重启失效,⽤久保存可加 -p 参数

OpenVPN 服务端配置

修改配置文件 openvpn.conf

1
2
### Route Configurations Below
route 192.168.254.0 255.255.255.0

下面添加路由即可, 客户端连接时会收到服务端推送的路由

OpenVPN 客户端设置

很多时候我们希望自己的客户端能够自定义路由,而且更该服务端的配置并不是一个相对较好的做法

找到我们的 ovpn 配置文件

到最后一行

redirect-gateway def1
即是我们全部流量走 VPN 的配置

route-nopull

客户端加入这个参数后,OpenVPN 连接后不会添加路由,也就是不会有任何网络请求走 OpenVPN

vpn_gateway

当客户端加入 route-nopull 后,所有出去的访问都不从 OpenVPN 出去,但可通过添加 vpn_gateway 参数使部分IP访问走 OpenVPN 出去

1
2
route 192.168.255.0 255.255.255.0 vpn_gateway
route 192.168.10.0 255.255.255.0 vpn_gateway

net_gateway

vpn_gateway 相反,他表示在默认出去的访问全部走 OpenVPN 时,强行指定部分 IP 访问不通过 OpenVPN 出去

1
2
max-routes 1000 # 表示可以添加路由的条数,默认只允许添加100条路由,如果少于100条路由可不加这个参数
route 172.121.0.0 255.255.0.0 net_gateway

客户端互相访问

  1. 配置 client-to-client
  2. 192.168.255.0 的路由要能够走VPN通道, 可以配置 redirect-gateway def1 或者 route-nopull route 192.168.255.0 255.255.255.0 vpn_gateway

OpenVPN 服务端配置静态 ip

  1. 配置文件配置 ifconfig-pool-persist /etc/openvpn/ipp.txt 0

ipp.txt 文件的格式

1
2
3
user1,192.168.255.10
user2,192.168.255.11
user3,192.168.255.12

经自己测试, 该方式配置静态 IP 没生效,实际得到的 IP 会大 2 位

  1. 配置文件配置 client-config-dir ccd

然后在 ccd 目录下以用户名为文件名命名,写入内容: ifconfig-push 192.168.255.10 255.255.255.0 来为单个用户配置 IP

OpenVPN 客户端证书续期

默认生成的证书有效期为 825 天,2 年多就过期了
过期后连接服务端会报错,连不上

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
# openvpn 客户端连接服务端报错
VERIFY ERROR: depth=0, error=certificate has expired: CN=26.64.10.114

# 客户端连接报警告
May 06 13:15:16 msmp-v5 openvpn[2357]: 2025-05-06 13:15:16 WARNING: Your certificate has expired!
May 06 13:15:16 msmp-v5 openvpn[2357]: 2025-05-06 13:15:16 TCP/UDP: Preserving recently used remote address: [AF_INET]26.64.10.114:1194
May 06 13:15:16 msmp-v5 openvpn[2357]: 2025-05-06 13:15:16 Attempting to establish TCP connection with [AF_INET]26.64.10.114:1194 [nonblock]
May 06 13:15:16 msmp-v5 openvpn[2357]: 2025-05-06 13:15:16 TCP connection established with [AF_INET]26.64.10.114:1194
May 06 13:15:16 msmp-v5 openvpn[2357]: 2025-05-06 13:15:16 TCP_CLIENT link local: (not bound)
May 06 13:15:16 msmp-v5 openvpn[2357]: 2025-05-06 13:15:16 TCP_CLIENT link remote: [AF_INET]26.64.10.114:1194
May 06 13:15:16 msmp-v5 openvpn[2357]: 2025-05-06 13:15:16 Connection reset, restarting [-1]
May 06 13:15:16 msmp-v5 openvpn[2357]: 2025-05-06 13:15:16 SIGUSR1[soft,connection-reset] received, process restarting


# 服务端报错
Tue May 6 05:50:16 2025 26.64.10.121:54270 TLS: Initial packet from [AF_INET]26.64.10.121:54270, sid=f947d193 380748a0
Tue May 6 05:50:16 2025 26.64.10.121:54270 CRL: loaded 1 CRLs from file /etc/openvpn/crl.pem
Tue May 6 05:50:16 2025 26.64.10.121:54270 VERIFY OK: depth=1, CN=Easy-RSA CA
Tue May 6 05:50:16 2025 26.64.10.121:54270 VERIFY ERROR: depth=0, error=certificate has expired: CN=省局-121
Tue May 6 05:50:16 2025 26.64.10.121:54270 OpenSSL: error:1417C086:SSL routines:tls_process_client_certificate:certificate verify failed
Tue May 6 05:50:16 2025 26.64.10.121:54270 TLS_ERROR: BIO read tls_read_plaintext error
Tue May 6 05:50:16 2025 26.64.10.121:54270 TLS Error: TLS object -> incoming plaintext read error
Tue May 6 05:50:16 2025 26.64.10.121:54270 TLS Error: TLS handshake failed
Tue May 6 05:50:16 2025 26.64.10.121:54270 Fatal TLS error (check_tls_errors_co), restarting
Tue May 6 05:50:16 2025 26.64.10.121:54270 SIGUSR1[soft,tls-error] received, client-instance restarting

需要手动续期, 这里我续期 10 年
由 825 天改为 10 年,需要修改

  1. /usr/share/easyrsa 的 vars 配置文件中 set_var EASYRSA_CERT_EXPIRE 3650
  2. 或者容器使用变量 EASYRSA_CERT_EXPIRE=3650

这里我选择的是第二种方式

具体涉及命令

撤销证书: ovpn_revokeclient $client_name (remove) 加上 remove 是删除证书
重新生成: easyrsa build-client-full $client_name nopass
重新导出配置文件:ovpn_getclient $client_name > /etc/openvpn/$client_name.ovpn

由于执行上面的命令需要输入 yes 和密码确认, 可以写个脚本自动完成(需要容器内安装 expect 工具完成自动交互的功能)
update_client.exp

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
#!/usr/bin/expect

# 从命令行参数获取客户端名称
if {[llength $argv] == 0} {
send_user "错误:请指定客户端名称\n"
send_user "用法:./revoke_client.exp <客户端名称>\n"
exit 1
}
set client_name [lindex $argv 0]
set password "123456"

spawn ovpn_revokeclient $client_name

# 等待 "yes/no" 确认提示
expect {
"*Continue with revocation:*" {
send "yes\r"
exp_continue
}
"*Enter pass phrase for /etc/openvpn/pki/private/ca.key:*" {
send "$password\r"
exp_continue
}
# 其他可能的错误处理
timeout {
send_user "操作超时\n"
exit 1
}
eof {
send_user "操作完成\n"
}
}

spawn easyrsa build-client-full $client_name nopass

expect {
"*Enter pass phrase for /etc/openvpn/pki/private/ca.key:*" {
send "$password\r"
exp_continue
}
# 其他可能的错误处理
timeout {
send_user "操作超时\n"
exit 1
}
eof {
send_user "操作完成\n"
}
}

exec ovpn_getclient $client_name > /etc/openvpn/$client_name.ovpn
exec sed -i "s/^redirect-gateway def1$/# redirect-gateway def1/" /etc/openvpn/$client_name.ovpn

send_user "客户端配置文件已生成:/etc/openvpn/$client_name.ovpn\n"

使用方法为 ./update_client.exp user1

另外,重新生成的证书需要更新到客户端上
这里有个命令快速部署到客户端服务器上
update_opvn_user.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/bash
set -e
set -u

NAME=$1
host=$2

if [[ ! $NAME ]]; then
echo "请输入名称"
exit 1
fi

if [[ ! $host ]]; then
echo "请输入主机"
exit 1
fi

# 重新签发10年的证书,需要容器内安装 expect 工具完成自动交互的功能
docker exec -it openvpn /etc/openvpn/update_client.exp $NAME
# 验证有效期
openssl x509 -in /home/data/common-service/openvpn/ovpn-data/$NAME.ovpn -noout -dates
# 拷贝到服务器上,重启服务
scp /home/data/common-service/openvpn/ovpn-data/$NAME.ovpn root@$host:/etc/openvpn/client/
ssh root@$host "systemctl restart openvpn"

使用方法 sh update_opvn_user.sh user1 192.168.255.21

  1. docker pull registry

  2. docker run -itd -v /data/registry:/var/lib/registry -p 5000:5000 –restart=always –privileged=true –name registry registry:latest
    参数说明
    -itd:在容器中打开一个伪终端进行交互操作,并在后台运行;
    -v:把宿主机的/data/registry目录绑定 到 容器/var/lib/registry目录(这个目录是registry容器中存放镜像文件的目录),来实现数据的持久化;
    -p:映射端口;访问宿主机的5000端口就访问到registry容器的服务了;
    –restart=always:这是重启的策略,假如这个容器异常退出会自动重启容器;
    –privileged=true 在CentOS7中的安全模块selinux把权限禁掉了,参数给容器加特权,不加上传镜像会报权限错误OSError: [Errno 13] Permission denied: ‘/tmp/registry/repositories/liibrary’)或者(Received unexpected HTTP status: 500 Internal Server Error)错误
    –name registry:创建容器命名为registry,你可以随便命名;
    registry:latest:这个是刚才pull下来的镜像;

  3. 测试是否成功: curl http://127.0.0.1:5000/v2/_catalog, 返回仓库的镜像列表

  4. 在中央仓库下载一个镜像: docker pull openjdk

  5. 更改这个镜像的标签: docker tag imageId domain:5000/openjdk 或者 docker tag imageName:tag domain:5000/openjdk

  6. 上传镜像到私服: docker push domain:5000/openjdk

报错: Get https://172.18.18.90:5000/v2/: http: server gave HTTP response to HTTPS client

解决: 需要https的方法才能上传,我们可以修改下daemon.json
vim /etc/docker/daemon.json
{
“insecure-registries”: [ “domain:5000”]
}

无网络搭建

  1. 在有网络的机器上 docker pull registry
  2. docker save registry > registry.tar 保存到个 tar 包
  3. 拷贝到服务器上, docker load -i registry.tar 导入镜像
  4. docker images 查看镜像
  5. 再继续上面的操作

docker 开启 tcp 端口

  • vim /usr/lib/systemd/system/docker.service

修改

1
ExecStart=/usr/bin/dockerd-current -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock \

重启即可,之后 idea 可输入 tcp://ip:2375 连接

允许跨域请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version: 0.1
log:
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
Access-Control-Allow-Headers: ['Origin,Accept,Content-Type,Authorization']
Access-Control-Allow-Origin: ['*']
Access-Control-Allow-Methods: ['GET,POST,PUT,DELETE']
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3

head 添加

1
2
3
Access-Control-Allow-Headers: ['Origin,Accept,Content-Type,Authorization']
Access-Control-Allow-Origin: ['*']
Access-Control-Allow-Methods: ['GET,POST,PUT,DELETE']

之后保存到本地,再挂载到容器的 /etc/docker/registry/config.yml 中

Harbor 搭建 Docker 私服

上述方式搭建的 docker 私服,属于比较简单使用的方法,只能在命令行上操作,很不方便,比如不能直接删除镜像,无法添加用户,设置私有仓库
Harbor 是一个图形化的私服管理界面,安装使用更易于操作

Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,通过添加一些企业必需的功能特性,例如安全、标识和管理等,扩展了开源Docker Distribution。作为一个企业级私有Registry服务器,Harbor提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。

  1. 下载离线包: https://github.com/goharbor/harbor/releases
  2. 解压
  3. 更改配置文件 docker-compose.yml 私服的仓库端口我们默认设置为 5000,但是 docker-compose.yml 文件中并没有配置,我们需要添加一个 ports 配置
1
2
3
4
5
registry:
networks:
- harbor
ports:
- 5000:5000
  1. Harbor 默认使用的是 80 端口,不想使用的话可切换其他端口, 配置在 docker-compose.yml 的最下方
1
2
3
4
5
6
proxy:
image: goharbor/nginx-photon:v1.7.0
ports:
- 9339:80
- 443:443
- 4443:4443

此处需要注意的是,如果更改了其他端口,则需要在 common/templates/registry/config.yml 文件中更改一个配置 realm 加上端口,否则登录会出现错误

1
2
3
4
5
6
auth:
token:
issuer: harbor-token-issuer
realm: $public_url:9339/service/token
rootcertbundle: /etc/registry/root.crt
service: harbor-registry
  1. 修改配置文件 harbor.cfg
1
2
3
hostname = 34.0.7.183 ## 改为 IP 或者 域名,不要写错 localhost 或者 127.0.0.1
ui_url_protocol = http ## http 方式
harbor_admin_password = Hardor12345 ## admin 账号的默认登录密码
  1. ./prepare 完成配置

  2. ./install.sh 开始安装

  3. 打开浏览器

  4. 创建一个项目 joylau 注意这个名称很重要,名称对不上的话,会造成 image push 不成功,还有就是若果这个项目的是公开的话,则所有人都可以 pull ,但是 push 的话是需要登录的,登录的用户名和密码在该项目的成员下.默认的 admin 用户就可以

  5. 登录,退出命令 docker login 34.0.7.183:5000 ; docker logout 34.0.7.183:5000

  6. 之后的操作都是日常操作了

Docker Registry 添加认证

生成用户名密码

1
docker run --rm --entrypoint htpasswd registry -Bbn username password > ./htpasswd

假设将生成的文件放到 /registry/pwd/htpasswd

挂载用户名密码文件

-v /registry/pwd:/auth -e “REGISTRY_AUTH=htpasswd” -e “REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm” -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd

至此,添加用户名密码完成,现在 pull 和 push 都需要登录

Docker login 密码存储

默认存储位置为: $HOME/.docker/config.json
很不安全, 使用 base64 解密即可看到用户名密码

将密码存储到钥匙串:

  1. 下载工具: https://github.com/docker/docker-credential-helpers/releases
  2. 将 docker-credential-osxkeychain 配置到 path 路径
  3. 配置 config.json
1
2
3
{
"credsStore": "osxkeychain"
}

说明

ownCloud 除了传统的部署方式,在如今 docker 大行其道的环境下,使用 docker 部署 ownCloud 才是最方便的

第一种 owncloud 镜像直接安装

直接部署 owncloud 镜像,该镜像地址: https://hub.docker.com/r/_/owncloud/

1
2
docker pull owncloud
docker run -d -p 80:80 owncloud

这种方式的需要你提前装好 MariaDb 数据库,在启动完成后打开页面会按照流程填写数据库的链接信息,之后就可以使用 ownCloud 了

第二种 分别安装

分别先后使用 docker 按照 redis,mariadb,ownCloud
安装 redis 的 mariadb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
docker volume create owncloud_redis

docker run -d \
--name redis \
-e REDIS_DATABASES=1 \
--volume owncloud_redis:/var/lib/redis \
webhippie/redis:latest

docker volume create owncloud_mysql
docker volume create owncloud_backup

docker run -d \
--name mariadb \
-e MARIADB_ROOT_PASSWORD=owncloud \
-e MARIADB_USERNAME=owncloud \
-e MARIADB_PASSWORD=owncloud \
-e MARIADB_DATABASE=owncloud \
--volume owncloud_mysql:/var/lib/mysql \
--volume owncloud_backup:/var/lib/backup \
webhippie/mariadb:latest

接着我们配置一些 ownCloud web 服务的环境变量,并在启动容器时使用这些变量

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
export OWNCLOUD_VERSION=10.0
export OWNCLOUD_DOMAIN=localhost
export ADMIN_USERNAME=admin
export ADMIN_PASSWORD=admin
export HTTP_PORT=80

docker volume create owncloud_files

docker run -d \
--name owncloud \
--link mariadb:db \
--link redis:redis \
-p ${HTTP_PORT}:8080 \
-e OWNCLOUD_DOMAIN=${OWNCLOUD_DOMAIN} \
-e OWNCLOUD_DB_TYPE=mysql \
-e OWNCLOUD_DB_NAME=owncloud \
-e OWNCLOUD_DB_USERNAME=owncloud \
-e OWNCLOUD_DB_PASSWORD=owncloud \
-e OWNCLOUD_DB_HOST=db \
-e OWNCLOUD_ADMIN_USERNAME=${ADMIN_USERNAME} \
-e OWNCLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD} \
-e OWNCLOUD_REDIS_ENABLED=true \
-e OWNCLOUD_REDIS_HOST=redis \
--volume owncloud_files:/mnt/data \
owncloud/server:${OWNCLOUD_VERSION}

之后稍等片刻,打开网页即可

第三种 docker-compose 部署

首先保证 docker-compose 的版本在 1.12.0+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建一个新的目录
mkdir owncloud-docker-server

cd owncloud-docker-server

# 下载 docker-compose.yml 文件
wget https://raw.githubusercontent.com/owncloud-docker/server/master/docker-compose.yml

# 配置环境变量文件
cat << EOF > .env
OWNCLOUD_VERSION=10.0
OWNCLOUD_DOMAIN=localhost
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin
HTTP_PORT=80
HTTPS_PORT=443
EOF

# 构建并启动容器
docker-compose up -d

当上面的流程都完成时,通过运行 docker-compose ps 检查所有容器是否已成功启动
还可以使用 docker-compose logs --follow owncloud 来查看日志
docker-compose stop 停止容器
docker-compose down 停止和删除容器

版本更新

  1. 进入 .yaml 或 .env 目录
  2. 将 ownCloud 设置维护模式, docker-compose exec server occ maintenance:mode --on
  3. 停止容器, docker-compose down
  4. 修改. env 文件的版本号,手动或者 sed -i 's/^OWNCLOUD_VERSION=.*$/OWNCLOUD_VERSION=<newVersion>/' /compose/*/.env
  5. 重新构建并启动, docker-compose up -d

指定挂载目录

  1. owncloud-server : /mnt/data

注意挂载本地目录时,要设置递归文件夹的可读权限 chmod -R 777 ./owncloud/*

配置说明
OWNCLOUD_VERSION: ownCloud 版本
OWNCLOUD_DOMAIN: ownCloud 可访问的域
ADMIN_USERNAME: 管理员用户名
ADMIN_PASSWORD: 管理员密码
HTTP_PORT: 使用的端口
HTTPS_PORT: SSL使用的端口

总结来说,推荐使用第三种方式来部署.

docker-compose 文件备份

docker-compose.yml:

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
version: '2.1'

volumes:
files:
driver: local
mysql:
driver: local
backup:
driver: local
redis:
driver: local

services:
owncloud:
image: owncloud/server:${OWNCLOUD_VERSION}
restart: always
container_name: owncloud-server
ports:
- ${HTTP_PORT}:8080
depends_on:
- db
- redis
environment:
- OWNCLOUD_DOMAIN=${OWNCLOUD_DOMAIN}
- OWNCLOUD_DB_TYPE=mysql
- OWNCLOUD_DB_NAME=owncloud
- OWNCLOUD_DB_USERNAME=owncloud
- OWNCLOUD_DB_PASSWORD=owncloud
- OWNCLOUD_DB_HOST=db
- OWNCLOUD_ADMIN_USERNAME=${ADMIN_USERNAME}
- OWNCLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD}
- OWNCLOUD_MYSQL_UTF8MB4=true
- OWNCLOUD_REDIS_ENABLED=true
- OWNCLOUD_REDIS_HOST=redis
healthcheck:
test: ["CMD", "/usr/bin/healthcheck"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- /home/liufa/owncloud-data:/mnt/data

db:
image: webhippie/mariadb:latest
restart: always
container_name: owncloud-mysql
environment:
- MARIADB_ROOT_PASSWORD=owncloud
- MARIADB_USERNAME=owncloud
- MARIADB_PASSWORD=owncloud
- MARIADB_DATABASE=owncloud
- MARIADB_MAX_ALLOWED_PACKET=128M
- MARIADB_INNODB_LOG_FILE_SIZE=64M
healthcheck:
test: ["CMD", "/usr/bin/healthcheck"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- /home/liufa/owncloud-mysql:/var/lib/mysql
- /home/liufa/owncloud-mysql-backup:/var/lib/backup

redis:
image: webhippie/redis:latest
container_name: owncloud-redis
restart: always
environment:
- REDIS_DATABASES=1
healthcheck:
test: ["CMD", "/usr/bin/healthcheck"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- /home/liufa/owncloud-redis:/var/lib/redis

.env:

1
2
3
4
5
6
OWNCLOUD_VERSION=10.0
OWNCLOUD_DOMAIN=0.0.0.0
ADMIN_USERNAME=admin
ADMIN_PASSWORD=
HTTP_PORT=1194
HTTPS_PORT=443

nginx 反向代理时的配置

注意配置 请求头 和 限制上传文件的大小

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
#listen [::]:80 default_server;
server_name cloud.joylau.cn;
location / {
# proxy_pass http://JoyCloud;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:1194;
client_max_body_size 10000m;
}
}

背景

我们经常能看到在各大电商网站搜索关键字的时候,底下下拉框会补全你要搜索的商品,或者类似的商品,有时候甚至连错别字也能纠正过来,其实ElasticSearch也能实现这样的功能

创建索引

首先,能够被自动补全的需要设置索引类型为”completion”,其次,还可以设置自动提示为中文分词

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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
{
"settings": {
"analysis": {
"analyzer": {
"ik": {
"tokenizer": "ik_max_word"
},
"ngram_analyzer": {
"tokenizer": "ngram_tokenizer"
}
},
"tokenizer": {
"ngram_tokenizer": {
"type": "ngram",
"min_gram": 1,
"max_gram": 30,
"token_chars": [
"letter",
"digit"
]
}
}
}
},
"mappings": {
"knowledge_info": {
"properties": {
"infoId": {
"type": "string"
},
"infoTitle": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word",
"fields": {
"suggest": {
"max_input_length": 30,
"preserve_position_increments": false,
"type": "completion",
"preserve_separators": false,
"analyzer": "ik_max_word"
},
"wordCloud": {
"type": "string",
"analyzer": "ik_smart",
"fielddata":"true"
}
}
},
"infoKeywords": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word",
"fields": {
"suggest": {
"max_input_length": 30,
"preserve_position_increments": false,
"type": "completion",
"preserve_separators": false,
"analyzer": "ik_max_word"
},
"wordCloud": {
"type": "string",
"analyzer": "ik_smart",
"fielddata":"true"
}
}
},
"infoSummary": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word",
"fields": {
"suggest": {
"max_input_length": 30,
"preserve_position_increments": false,
"type": "completion",
"preserve_separators": false,
"analyzer": "ik_max_word"
},
"wordCloud": {
"type": "string",
"analyzer": "ik_smart",
"fielddata":"true"
}
}
},
"infoContent": {
"type": "text",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word"
},
"propertyAuthor": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word"
},
"propertyIssueUnit": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word"
},
"propertyStandardCode": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word"
},
"propertyLiteratureCategory": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word"
},
"propertyLcCode": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word"
},
"propertyLiteratureCode": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word"
},
"data": {
"type": "text"
},
"attachment.content": {
"type": "text",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word"
},
"auditState": {
"type": "string"
},
"infoType": {
"type": "string"
},
"infoFileUrl": {
"type": "string"
},
"infoFileName": {
"type": "string",
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word",
"fields": {
"suggest": {
"max_input_length": 60,
"preserve_position_increments": false,
"type": "completion",
"preserve_separators": false,
"analyzer": "ik_max_word"
}
}
},
"createTime": {
"type": "string"
}
}
}
}
}

其中 elasticsearch 需要安装中文分词 ik 插件和附件处理插件 ingest-attachment

Java API 调用

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
/**
* 自动完成提示
* @param search search
* @return MessageBody
*/
public MessageBody autoCompleteKnowledgeInfo(KnowledgeSearch search) {
//设置搜索建议
CompletionSuggestionBuilder infoTitleSuggestion = new CompletionSuggestionBuilder("infoTitle.suggest")
.text(search.getQuery())
.size(6);
CompletionSuggestionBuilder infoKeywordsSuggestion = new CompletionSuggestionBuilder("infoKeywords.suggest")
.text(search.getQuery())
.size(6);
CompletionSuggestionBuilder infoSummarySuggestion = new CompletionSuggestionBuilder("infoSummary.suggest")
.text(search.getQuery())
.size(6);
CompletionSuggestionBuilder infoFileNameSuggestion = new CompletionSuggestionBuilder("infoFileName.suggest")
.text(search.getQuery())
.size(6);
SuggestBuilder suggestBuilder = new SuggestBuilder()
.addSuggestion("标题", infoTitleSuggestion)
.addSuggestion("关键字", infoKeywordsSuggestion)
.addSuggestion("摘要", infoSummarySuggestion)
.addSuggestion("附件",infoFileNameSuggestion);
SearchRequestBuilder searchRequest = client.prepareSearch(ES_KNOWLEDGE_INDEX)
.setFetchSource(false)
.suggest(suggestBuilder);
List<JSONObject> list = new ArrayList<>();

//查询结果
SearchResponse searchResponse = searchRequest.get();

/*没查到结果*/
if (searchResponse.getSuggest() == null) {
return MessageBody.success(list);
}
searchResponse.getSuggest().forEach(entries -> {
String name = entries.getName();
for (Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option> entry : entries) {
for (Suggest.Suggestion.Entry.Option option : entry.getOptions()) {
JSONObject object = new JSONObject();
object.put("name",name);
object.put("text",option.getText().string());
list.add(object);
}
}
});
return MessageBody.success(list);
}

代码摘取自项目中的部分, 另外前端还可以配合自动完成的插件,最终来实现效果.

背景

有时我们希望查询 固定条件下的全部数据
ES 默认的策略是返回10条数据
虽然可以 setSize()
但是默认上限是 10 万还是 100 万条数据,这不够优雅,一般不这么干

TransportClient 方法

1
2
3
4
5
6
7
8
9
10
TimeValue keepAlive = TimeValue.timeValueMinutes(30);
SearchRequestBuilder searchRequest = client.prepareSearch(ES_KNOWLEDGE_INDEX)
.setScroll(keepAlive)
.setSize(10000);
SearchResponse searchResponse = searchRequest.get();
do {
//处理的业务 saveIds(searchResponse);
searchResponse = client.prepareSearchScroll(searchResponse.getScrollId()).setScroll(keepAlive).execute()
.actionGet();
} while (searchResponse.getHits().getHits().length != 0);

背景

因项目需求,需要一个自动提示的功能,想到之前有 jquery 的 jQuery-Autocomplete 插件,于是就直接拿来用了,
直接在github 上找到了一个 starts 最多的项目 jQuery-Autocomplete
看了下插件的 API 可配置项很多,有一个 appendTo 配置,是我想要的,于是就决定使用这个差价

直接把 插件下载下来 放到项目中去,直接 $(…).autocomplete is not a function
……

项目中我写的只是其中的一个模块,页面的代码是纯 html 页面写的,然后通过 panel 引入 html 代码片段
很奇怪,为什么插件无法加载

于是就就把官方的demo跑了一下,没有问题

又怀疑是 jQuery 版本的问题,
官方的demo jQuery 版本是 1.8.2,项目使用的是1.11.1,
于是又在官方的 demo 下替换jQuery的版本
发现使用没有问题

又怀疑是插件的版本过高,于是再 GitHub 的 release 上找了个2014年发布的1.2.2的版本,这已经是能找到的最低版本了
发现还是不行

这就奇怪了,我之前也引入过其他的插件,正常使用都没有问题,偏偏使用这个有问题
于是想着插件的引入方式有问题,打开一看,jQuery插件的引入方式都是大同小异的
本人前端不擅长,也不知道怎么改…..

于是又在 GitHub上找了其他的插件,有的能用,但是没有我想要的功能….

一直这么来来回回的测试,已经晚上 10 点了…..
从吃完晚饭一直研究到现在还是没有解决
心里好气啊!!!!!
空调一关,直接回家了!!!!

解决

今天早上来又差了点资料,找到了个不太靠谱,但又想尝试了下的方法
TypeError: $(…).autocomplete is not a function

试一下吧,没想到真的可以

发一张对比图

query-Load-Plugins

背景

这一段时间 GitHub 在国内的访问又出问题,代码提交不上去,需要在 Git 上走代理了

Git 使用 ss 代理配置

  1. 需要全局 git 都走代理
1
2
git config --global http.proxy 'socks5://127.0.0.1:1080'
git config --global https.proxy 'socks5://127.0.0.1:1080'

取消

1
2
git config --global --unset http.proxy
git config --global --unset https.proxy

但是有时候我们并不需要所有的 git 仓库都走代理,可以去掉上述的命令中的 --global,然后到你需要走代理的那个 git 仓库下执行命令,或者添加配置:

  1. 单独配置 git 走代理
    在 .git => config 文件中加入配置
1
2
3
4
[https]
proxy = socks5://127.0.0.1:1080
[http]
proxy = socks5://127.0.0.1:1080

其实,也就是上述命令执行后添加的配置.配置后就可以愉快的 clone push 了.

Ubuntu 使用全局代理

Windows 和 MacOS 下的 ss 全局代理很方便,点击切换下就可以了,而 Ubuntu 下需要多点操作:

  1. 启动 shadowsocks-qt5,并连接上
  2. 生成 pac 文件,如果有现成的 pac 文件,直接进入第四步
  3. 生成 pac 文件

安装 pip

1
2
$ sudo pip install genpac
$ pip install -U genpac ## 安装或更新

创建 user-rules.txt 文件

1
2
3
mkdir vpnPAC
cd vpnPAC
touch user-rules.txt

生成 autoproxy.pac 文件

1
genpac --format=pac --pac-proxy="SOCKS5 127.0.0.1:1080" --output="autoproxy2.pac" --gfwlist-url="https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt" --user-rule-from="user-rules.txt"

github 上的 gfwlist.txt 文件可能读取不到,多试几次

  1. 配置使用

配置使用

背景

昨天和别人吃饭谈起了家里宽带的问题,办了多少兆的宽带,网速能有多少等云云,对方是个小白,和他说了半天,但是有些深层次的原理我也弄不清楚,后来我上网科普了一下,现在整理如下

什么是宽带速率?

宽带速率是指技术上所能达到的理论最高信息传送比特率,一般是上传和下载的速度,速率越高,上传和下载的越快。用户申请的宽带业务速率指技术上所能达到的最大理论速率值。但用户上网时还受到用户电脑软硬件的配置、所浏览网站的地址、终端网站带宽等情况的影响。因此,用户上网时的速率通常低于理论速率值。

理论上,2M,即2Mb/s,宽带理论速率是 256KB/S。实际速率大约为103–200KB/S。(其原因是受用户计算机性能、网络设备质量、资源使用情况、网络高峰期、网站服务能力、线路衰耗、信号衰减等多因素的影响而造成的)。4M,即4Mb/s宽带理论速率是 512KB/S 实际速率大约为200—440KB/S。

计算方法

在计算机科学中,bit是表示信息的最小单位,叫做二进制位;一般用0和1表示。Byte叫做字节,由8个位(8bit)组成一个字节(1Byte),用于表示计算机中的一个字符。bit与Byte之间可以进行换算,其换算关系为:1Byte=8bit(或简写为:1B=8b);在实际应用中一般用简称,即1bit简写为1b(注意是小写英文字母b),1Byte简写为1B(注意是大写英文字母B)。

在计算机网络或者是网络运营商中,一般,宽带速率的单位用bps(或b/s)表示;bps表示比特每秒即表示每秒钟传输多少位信息,是bit per second的缩写。在实际所说的1M带宽的意思是1Mbps(是兆比特每秒Mbps不是兆字节每秒MBps)。

换算公式:1B=8b 1B/s=8b/s(或1Bps=8bps)

规范提示:实际书写规范中B应表示Byte(字节),b应表示bit(比特),但在平时的实际书写中有的把bit和Byte都混写为b ,如把Mb/s和MB/s都混写为Mb/s,导致人们在实际计算中因单位的混淆而出错。

实例: 在我们实际上网应用中,下载软件时常常看到诸如下载速度显示为128KBps(KB/s),103KB/s等等宽带速率大小字样,因为ISP提供的线路带宽使用的单位是比特,而一般下载软件显示的是字节(1字节=8比特),所以要通过换算,才能得实际值。然而我们可以按照换算公式换算一下:

1Mb/s = 1024Kb/s = 128×8(Kb/s) = 128KB/s

即 1Mb/s = 128KB/s

宽带速率对照表

常见宽带 理论最高速率(Mbps) 理论最高速率(KB/S) 常见下载速率(供参考)
1M 1 Mbps 128 KB/S 77~128 KB/S
2M 2 Mbps 256 KB/S 154~256 KB/S
3M 3 Mbps 384 KB/S 231~384 KB/S
4M 4 Mbps 512 KB/S 307~512 KB/S
6M 6 Mbps 620 KB/S 462~620 KB/S
8M 8 Mbps 1024 KB/S 614~1024 KB/S
10M 10 Mbps 1280 KB/S 768~1280 KB/S
12M 12 Mbps 1536 KB/S 922~1536 KB/S
20M 20 Mbps 2560 KB/S 1536~2560 KB/S
30M 30 Mbps 3840 KB/S 2560~3840 KB/S
50M 50 Mbps 6400 KB/S 3840~6400 KB/S
100M 100 Mbps 12800 KB/S 7680~12800 KB/S
0%