前言
经常用各种云盘,但感觉还是不够好:
- 1.文件在别人服务器上终究不可靠,最近阿里云盘还曝出隐私文件泄露问题
- 2.这些个免费云盘又是限速又是要下载客户端才能用,很麻烦,这些个垃圾客户端一个都不想装
于是就想搞一个私有云盘,电脑端直接用网页访问。
正好之前买的国内的云服务器到期了,甲骨文云上免费的两台服务器国内访问又很慢,做不了什么东西,于是买了个低功耗迷你主机,直接把物理服务器放家里,牢牢掌握所有权,还能把一些小服务迁移过来。
研究了一下开源的一些云盘系统,要么功能很复杂,要么UI太难看或者没有安卓客户端可用,要么是英文文档不想看。
最后决定用国内的一个不开源的网盘系统“可道云”,界面看起来比较舒服,也有许多插件可以用(在线预览视频图片、图片编辑、在线文档、markdown编辑器等等很方便),可以外链分享,不需要下载客户端,网页可以直接访问,支持用户管理,而且可以用宝塔面板一键部署
云盘是部署好了,内网访问一点毛病没有,问题是公网到底应该怎么访问?
总的来说两种方式:
- 1.依赖中转服务器的,缺点:慢,数据经过别人手上
- 一些内网穿透服务商,花生壳之类的,一般速度比较慢,还有一个国内的chmlfrp
https://panel.chmlfrp.cn/
, 带宽可以给8M,算是很好了
- 或者各种frp服务,都需要一台云服务器进行转发,也有免费提供服务的,也是慢
- 也试过Cloudflare的Tunnel,直接建立隧道,而且默认开启SSL,还是相当不错的,但是在国内Cloudflare的速度实在是,一个字,超级慢!
- 2.P2P的模式,用公网IP通过端口映射连接内网设备,快的要命,缺点是麻烦,主要有以下一些问题:
- 运营商通常不会分配公网IP,很难申请
- 有公网IP也没用,光猫防火墙默认开启无法关闭,并且禁用ping,并且端口映射功能阉割没法用,必须把光猫改成桥接模式,改用路由器拨号
- 即使光猫能端口映射也没用,光猫的DHCP服务器非常垃圾,所以一般用路由器承担DHCP服务器,这样的话内网设备通常和光猫不在同一网段,即使光猫可以做端口转发,也需要路由器和光猫转发两次,有点复杂
- 用路由器拨号也没用,路由器本身也有防护墙,可能没法关闭
光猫改桥接用路由器拨号
这一步联系运营商解决,改桥接后至少解决了光猫防火墙无法关闭的问题,路由器能够设置端口转发了
路由器设置端口转发
我用的是小米路由器,自带端口转发功能,还有一个DMZ功能,可以把内网某个设备的所有端口映射到路由器公网IP的端口上,此时访问公网IP:端口等同于访问内网IP:端口,但是要注意:公网IP的80和443端口是被禁用的,用这个端口转发不会生效
高级设置-端口转发,这样会暴露所有端口,因此要在内网服务器上设置防火墙
此时在 常用设置-上网设置 查看路由器公网IP,在后面添加内网服务的端口号,即可访问内网服务,我这里ipv4和ipv6(需要路由器开启ipv6功能)都可以
设置DDNS
这样设置完成后就只剩下一个问题了,运营商给的公网IP是会变的,即使网络一直在用不中断,也会随机改变,公网ipv6地址不确定会不会变,目前没看到变化,但姑且认为他也是会变的,这时候就需要用到DDNS了,
DDNS(动态域名系统)是一种服务,它允许你将动态IP地址映射到一个固定的域名上。这样,即使你的IP地址变化,你也能通过域名来访问你的服务器或服务。DDNS服务对于家庭用户和小型企业尤其有用,因为他们通常没有静态IP地址,但可能需要远程访问家中或办公室的网络资源。
实现DDNS的方式也有很多,有很多DDNS服务商可以提供相关服务,我没有用这些服务,而是利用了Cloudflare的API,定时主动更新DNS记录,需要申请API令牌
代码如下,实现的功能是每隔10分钟自动读取一次设备的公网IP(我这里设备是没有公网ipv4地址的,由于开启了端口转发,因此只要获取路由器的公网IP即可,如果路由器没有公网IPv4地址,可以用IPv6地址)并调用API对DNS记录进行更新,我这里同时添加了ipv4和ipv6地址到pan.xinhaojin.top,客户端如果支持ipv6会优先使用ipv6访问
- while True:
- 读公网IP
- 通过API查二级域名对应的DNS记录值
- 如果没有记录,则添加一条
- 如果与当前获取的公网IP不一致,则进行更新
- sleep一段时间
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 161 162 163 164 165
| #!/bin/sh
logFile="/root/DdnsOnCloudFlare/ddns.log"
log() { local level=$1 local message=$2 local timestamp=$(date "+%Y-%m-%d %H:%M:%S") echo "[$timestamp] [$level] $message" >> "$logFile" }
zoneId='209367ebxxxxxxxx' recordName='pan.xinhaojin.top' apiKey='zRIxxxxxxx'
getIpv4Address() { wget -qO- https://ipinfo.io/ip }
getIpv6Address() { ip -6 addr show enp3s0 | grep 240e | awk '{print $2}' | cut -d'/' -f1 | head -n 1 }
listRecord() { local zoneId=$1 local recordName=$2 local apiKey=$3 local recordType=$4
local url="https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records?name=$recordName&type=$recordType" local result=$( curl -s -X GET "$url" \ -H "Content-Type:application/json" \ -H "Authorization: Bearer $apiKey" ) local resourceId=$(echo "$result" | grep -Po '(?<="id":")[^"]+') local currentValue=$(echo "$result" | grep -Po '(?<="content":")[^"]+')
local successStat=$(echo "$result" | grep -Po '(?<="success":)[^,]+') if [ $successStat != "true" ]; then return 1 fi
printf '%s\n%s' "$resourceId" "$currentValue" }
updateRecord() { local zoneId=$1 local recordName=$2 local apiKey=$3 local resourceId=$4 local type=$5 local value=$6
local result=$( curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records/$resourceId" \ -H "Authorization: Bearer $apiKey" \ -H "Content-Type: application/json" \ --data "{\"type\":\"$type\",\"name\":\"$recordName\",\"content\":\"$value\",\"ttl\":600,\"proxied\":false}" )
local successStat=$(echo "$result" | grep -Po '(?<="success":)[^,]+') [ "$successStat" = "true" ] return $? }
createRecord() { local zoneId=$1 local recordName=$2 local apiKey=$3 local type=$4 local value=$5
local result=$( curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records" \ -H "Authorization: Bearer $apiKey" \ -H "Content-Type: application/json" \ --data "{\"type\":\"$type\",\"name\":\"$recordName\",\"content\":\"$value\",\"ttl\":600,\"proxied\":false}" ) local successStat=$(echo "$result" | grep -Po '(?<="success":)[^,]+') if [ $successStat != "true" ]; then return 1 fi local recordId=$(echo "$result" | grep -Po '(?<="id":")[^"]+') echo "$recordId" }
while true; do currentIpv4=$(getIpv4Address) log "INFO" "获取到的外部 IPv4 地址为: $currentIpv4"
currentIpv6=$(getIpv6Address) log "INFO" "获取到的外部 IPv6 地址为: $currentIpv6"
currentStat=$(listRecord "$zoneId" "$recordName" "$apiKey" "A")
if [ $? -eq 0 ]; then resourceId=$(echo "$currentStat" | sed -n '1p') currentValue=$(echo "$currentStat" | sed -n '2p') if [ -z "$resourceId" ]; then log "INFO" "未找到 IPv4 记录,正在创建..." resourceId=$(createRecord "$zoneId" "$recordName" "$apiKey" "A" "$currentIpv4") if [ $? -eq 0 ]; then log "INFO" "IPv4 记录创建成功" else log "ERROR" "IPv4 记录创建失败" fi elif [ $currentValue != $currentIpv4 ]; then log "INFO" "当前 IPv4 记录为: $currentValue" if updateRecord "$zoneId" "$recordName" "$apiKey" "$resourceId" "A" "$currentIpv4"; then log "INFO" "IPv4 更新成功" else log "ERROR" "IPv4 更新失败" fi else log "INFO" "当前 IPv4 记录为: $currentValue" log "INFO" "当前 IPv4 记录与预更新 IPv4 相同,无需更新" fi else log "ERROR" "获取当前 IPv4 记录失败" fi
currentStat=$(listRecord "$zoneId" "$recordName" "$apiKey" "AAAA") if [ $? -eq 0 ]; then resourceId=$(echo "$currentStat" | sed -n '1p') currentValue=$(echo "$currentStat" | sed -n '2p') if [ -z "$resourceId" ]; then log "INFO" "未找到 IPv6 记录,正在创建..." resourceId=$(createRecord "$zoneId" "$recordName" "$apiKey" "AAAA" "$currentIpv6") if [ $? -eq 0 ]; then log "INFO" "IPv6 记录创建成功" else log "ERROR" "IPv6 记录创建失败" fi elif [ $currentValue != $currentIpv6 ]; then echo "当前 IPv6 记录为: $currentValue" if updateRecord "$zoneId" "$recordName" "$apiKey" "$resourceId" "AAAA" "$currentIpv6"; then log "INFO" "IPv6 更新成功" else log "ERROR" "IPv6 更新失败" fi else log "INFO" "当前 IPv6 记录为: $currentValue" log "INFO" "当前 IPv6 记录与预更新 IPv6 相同,无需更新" fi else log "ERROR" "获取当前 IPv6 记录失败" fi
sleep 600 done
|
添加一个服务到/etc/systemd/system/ddns.service
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| [Unit] Description=Update DNS Records Service After=network-online.target Wants=network-online.target
[Service] Type=simple WorkingDirectory=/root/DdnsOnCloudFlare/ ExecStart=/bin/bash /root/DdnsOnCloudFlare/autoUpdateDNS.sh Restart=on-failure RestartSec=5s
[Install] WantedBy=multi-user.target
|
1 2 3
| systemctl daemon-reload systemctl enable ddns systemctl start ddns
|
正常log
1 2 3 4 5 6
| [2024-10-16 20:58:16] [INFO] 获取到的外部 IPv4 地址为: 60.163.110.17 [2024-10-16 20:58:16] [INFO] 获取到的外部 IPv6 地址为: 240e:390:485b:ac40::e1a [2024-10-16 20:58:19] [INFO] 当前 IPv4 记录为: 60.163.110.17 [2024-10-16 20:58:19] [INFO] 当前 IPv4 记录与预更新 IPv4 相同,无需更新 [2024-10-16 20:58:36] [INFO] 未找到 IPv6 记录,正在创建... [2024-10-16 20:58:54] [INFO] IPv6 记录创建成功
|
nginx配置注意事项
由于公网访问pan.xinhaojin.top需要增加端口号88,不是很优雅,所以会想到通过nginx转发,如下配置
1 2 3 4 5 6 7 8
| server { listen 80; server_name pan.xinhaojin.top;
location / { return 301 http://pan.xinhaojin.top:88$request_uri; } }
|
但实际配置后在内网可省略端口号,但公网访问不可省略端口号,且可能导致添加端口号也无法访问,原因是运营商禁80和443端口
下图配置的时候我应用了nginx端口转发,同时启用了https,网站访问起来很优雅,但还是不建议添加这个nginx配置,还是多一步手动添加端口号吧
成果展示
上传速度可以达到40MB/s,哪个网盘有我快?(按理我上传没有有这么大带宽啊),反正5G网络测试下载里面的视频速度可以有10MB以上
下一步想看看能不能把夸克网盘挂载到这上面,这样就可以用这一个网页来管理我的私有云和常用的夸克云盘了