
Private server setup with DDNS or gofrp
I’ve been using VPS for a few years and it is really convenient in the perspective of deployment and public accessibility. But the problem for me is that my VPS is with minimal cpu/memory/storage/bandwidth configuration and from time to time I need to migrate my existing websites/services to a new server.
What if I have a private server setup in my house? I would have almost unlimited storage for my projects and I won’t bother to do the service migration.
As we all know, ISPs don’t provide public static ip for individual usage in my territory. So the problem becomes how to translate a domain name to a dynamic ip.
Basically there’re two solutions. First is setup tunnels between a public server and your private server. Second is to use DDNS combined with NAT mirroring.
For first solution, we can use ngrok
as most of us already know. And fortunately I found another one – gofrp
which is written in go
and is said to be more robust. The problem for this solution is that you still need a public VPS to run a server side agent to forward http traffic to your private server for every data packet. This is not what I want or to say I want to avoid actually.
For the second solution, as I have my domain managed by Ali Cloud, so I’ll use Ali Cloud’s DNS service to implement DDNS, for free.
Here comes the python code:
# -*- coding: UTF-8 -*-
import json
import sys
import os
from datetime import datetime
import requests
from aliyunsdkalidns.request.v20150109 import DescribeDomainRecordsRequest, UpdateDomainRecordRequest, \
AddDomainRecordRequest
from aliyunsdkcore import client
# 尝试打开配置文件
try:
config_r = open(sys.path[0] + '/config.json', 'r')
except Exception as e:
print('An error occurred, open config file fail! Error MSG: ')
print(e)
print('Script exit!')
sys.exit(1)
else:
config_content = config_r.read()
# 尝试格式化配置文件
try:
config_json = json.loads(config_content)
except Exception as e:
print('Load json fail, please recheck config file! Error MSG: ')
print(e)
print('Script exit!')
sys.exit(1)
else:
pass
# 获取域名信息
# 输出格式:[RecordId, Value]
def get_record_info(ali_ctl, domain, sub_domain):
request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest()
request.set_DomainName(domain)
request.set_accept_format('json')
try:
result = ali_ctl.do_action_with_exception(request)
except Exception as f:
return 'An error occurred! Error MSG: ' + str(f)
else:
result = result.decode()
result_dict = json.JSONDecoder().decode(result)
result_list = result_dict['DomainRecords']['Record']
result = []
for i in result_list:
if sub_domain == i['RR']:
result.append(i['RecordId'])
result.append(i['Value'])
break
return result
# 更新子域名信息
def update_dns(ali_ctl, sub_domain, dns_value, ttl, dns_record_id, ip_ver):
request = UpdateDomainRecordRequest.UpdateDomainRecordRequest()
request.set_RR(sub_domain)
request.set_Value(dns_value)
request.set_RecordId(dns_record_id)
request.set_TTL(ttl)
request.set_accept_format('json')
if ip_ver == 4:
request.set_Type('A')
else:
request.set_Type('AAAA')
result = ali_ctl.do_action_with_exception(request)
return result
# 新增子域名解析
def add_dns(ali_ctl, dns_value, domain, sub_domain, ttl, ip_ver):
request = AddDomainRecordRequest.AddDomainRecordRequest()
request.set_DomainName(domain)
request.set_RR(sub_domain)
request.set_Value(dns_value)
request.set_TTL(ttl)
if ip_ver == 4:
request.set_Type('A')
else:
request.set_Type('AAAA')
result = ali_ctl.do_action_with_exception(request)
return result
# 写入日志
def write_to_file(dns_value, dns_output):
time_now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
write = open(sys.path[0] + '/aliyun_ddns.log', 'a')
write.write(time_now + ' ' + str(dns_value) + ' ' + dns_output + '\n')
write.close()
# 获取IP地址,支持v4与v6
def my_ip(i_ip_ver):
if i_ip_ver == 4:
get_ip_value = requests.get('http://home.kiwikiwi.fun', timeout=30).content.decode().strip('\n')
else:
get_ip_value = requests.get('http://home.kiwikiwi.fun', timeout=30).content.decode().strip('\n')
return get_ip_value
def my_ip_method_1():
get_ip_method = os.popen('curl -s ip.cn')
get_ip_responses = get_ip_method.readlines()[0]
get_ip_pattern = re.compile(r'\d+\.\d+\.\d+\.\d+')
get_ip_value = get_ip_pattern.findall(get_ip_responses)[0]
return get_ip_value
def my_ip_method_2():
get_ip_method = os.popen('curl -s http://ip-api.com/json')
get_ip_responses = get_ip_method.readlines()[0]
get_ip_responses = eval(str(get_ip_responses))
get_ip_value = get_ip_responses['query']
return get_ip_value
def my_ip_method_3():
result_dict = requests.get('http://ifconfig.co/json').json()
# result_dict = json.JSONDecoder().decode(get_ip_method)
print(result_dict)
get_ip_value = result_dict['ip']
return get_ip_value
def run_main():
for v in config_json.values():
rc_access_key_id = v['access_key_id']
rc_access_key_secret = v['access_Key_secret']
rc_domain = v['domain']
rc_sub_domain = v['sub_domain']
rc_ttl = v['ttl']
ip_ver = v['ip_ver']
# current_ip = my_ip(ip_ver)
current_ip = my_ip_method_3()
clt = client.AcsClient(rc_access_key_id, rc_access_key_secret, 'cn-hangzhou')
result_list = get_record_info(clt, rc_domain, rc_sub_domain)
if len(result_list) == 0:
aliyun_output = add_dns(clt, current_ip, rc_domain, rc_sub_domain, rc_ttl, ip_ver).decode()
write_to_file(current_ip, aliyun_output)
else:
result_record_id = result_list[0]
old_ip = result_list[1]
if old_ip == current_ip:
print('The specified value of parameter Value is the same as old')
else:
aliyun_output = update_dns(clt, rc_sub_domain, current_ip, rc_ttl, result_record_id, ip_ver).decode()
write_to_file(current_ip, aliyun_output)
# 运行
if __name__ == '__main__':
run_main()
And the config.json file:
{
"0": {
"access_key_id": "xxxxxx",
"access_Key_secret": "xxxxxxxxx",
"domain": "kiwikiwi.fun",
"sub_domain": "home",
"ttl": "600",
"ip_ver": 4
},
"1": {
"access_key_id": "xxxxxxxxx",
"access_Key_secret": "xxxxxxxxxxxxxx",
"domain": "kiwikiwi.fun",
"sub_domain": "home",
"ttl": "600",
"ip_ver": 6
}
}
To run the python script, you have to install python3
and also the following python packages:
pip list
Package Version
------------------------- ---------
aliyun-python-sdk-alidns 2.6.18
aliyun-python-sdk-core 2.13.26
aliyun-python-sdk-core-v3 2.13.11
requests 2.24.0
And we need to run the python script as cron job to compare the current ip with Ali Cloud’s DNS record from time to time. Maybe 10 minutes is a suitable time period for this. Create an entry in crontab as below.
*/10 * * * * root /usr/bin/python3 /usr/local/shell/aliyun_ddns.py > /dev/null 1>/dev/null
After setting up DDNS with Ali Cloud, I have to login my CPE(Optical modem) provided by ISP to setup NAT mapping.
If we want to add an nginx
reverse proxy for dynamic domain resolving, in order to match DDNS functionality, we may do the nginx
configration below on nginx
free version.
server {
...
resolver 114.114.114.114 valid=5s;
set $upstream "http://ddns.domain.com:4430";
proxy_pass $upstream;
...
}