从云服务器迁移到家庭服务器,配置内网穿透映射站点到公网
使用Grok AI进行过修改,不保证内容完全准确或适用于您的设备,请根据实际情况自行验证并调整。
嗨,大家好,我回来了!正如标题所说,我终于把站点从云服务器彻底迁移到了家庭服务器上。这次折腾下来,感觉收获满满,也踩了不少坑,特意写下来分享给大家,省得你们再走弯路。
首先我在淘宝上淘了个 Linux 小盒子,ARM 架构,4核4G 配置,支持加装 TF 卡和 USB 硬盘,板载 64GB eMMC,性价比很高,功耗也低,放在家里 24 小时跑着完全没压力。
面板我选了宝塔(aaPanel 国际版),没继续用之前一直用的 1Panel。主要是考虑到以后可能会在本地部署一些自己写的小工具,纯容器化有时候不太方便,宝塔的图形化管理更直观。
1. 安装 ARM 版宝塔 / aaPanel
重要提醒:
宝塔国内版目前官方没明确支持 ARM,千万别直接装,容易失败。
推荐用 宝塔国际版 aaPanel,脚本有时候会误判架构,如果报错可以手动装依赖或者直接切 Docker 版。
安装命令超级简单:
# 更新系统
sudo apt update && sudo apt upgrade -y
# 安装 aaPanel 国际版
wget -O install.sh http://www.aapanel.com/script/install-ubuntu_6.0_en.sh
sudo bash install.sh aapanel安装完后,浏览器输入你的盒子内网 IP:7800(默认端口)就能看到登录界面。第一次登录会让你设置管理员账号和密码,建议设置强密码。登录后就能用面板一键装 Nginx、MariaDB、PHP 等了,超级省心。
2. 安装 Docker
我的系统是 Armbian,官方推荐直接用系统源里的稳定版,最稳。
# 更新源
sudo apt update
# 安装 docker.io
sudo apt install -y docker.io
# 启动并开机自启
sudo systemctl start docker
sudo systemctl enable docker
# 验证安装
sudo docker run hello-world
# 让普通用户也能跑 docker(推荐)
sudo usermod -aG docker $USER
# 执行完后退出 SSH 重新登录生效装完 Docker 后,后面部署 Java 服务或者其他容器就方便多了。
3. 安装基础运行环境(JRE + MariaDB)
系统该装的都装好了,接下来开始装具体软件。先装 JRE,因为 OneDev 和 Halo 都是 Java 程序。
# 安装 OpenJDK 21(Halo 和 OneDev 推荐这个版本)
sudo apt install -y openjdk-17-jre-headless
# 验证
java -version应该看到 openjdk version "21.x" 的输出。
MariaDB 我是通过 apt 包管理器安装的(推荐),因为宝塔提供的 MariaDB 安装脚本是用于 RedHat 系的,直接用宝塔的安装脚本会报错:
sudo apt install -y mariadb-server
sudo mysql_secure_installation # 按提示设置 root 密码4. 安装 OneDev(DevOps 工具)
本来想继续用 Gitea,结果创建 systemd 服务老是失败,折腾半天放弃了,改用 OneDev,体验好多了。
OneDev 需要 Java 11+(我们已经装好),官方支持裸金属部署:
下载最新版(去 https://docs.onedev.io/ 找最新链接):
wget https://code.onedev.io/onedev/server/~site/onedev-latest.tar.gz
tar -xzf onedev-latest.tar.gz
sudo mv onedev /opt/onedev
sudo chown -R $USER:$USER /opt/onedev先前台测试:
cd /opt/onedev
bin/server.sh console浏览器访问 http://你的内网IP:6610 初始化向导,按提示走完(设置管理员账号、数据库等,我用了自带的 H2 先,后面可以换)。
做成 systemd 服务开机自启(超级推荐):
新建服务文件:
sudo vim /etc/systemd/system/onedev.service内容如下:
[Unit]
Description=OneDev Server
After=network.target
[Service]
Type=simple
User=你的用户名
WorkingDirectory=/opt/onedev
ExecStart=/opt/onedev/bin/server.sh start
ExecStop=/opt/onedev/bin/server.sh stop
Restart=always
[Install]
WantedBy=multi-user.target然后:
sudo systemctl daemon-reload
sudo systemctl enable --now onedev
sudo systemctl status onedev # 检查状态日志看 journalctl -u onedev -f 就行。
5. 安装 Halo 博客(jar 包部署)
Halo 迁移过程让我最头疼!我一开始没用 Docker,而是直接 jar 部署,结果连 MariaDB 连不上,折腾了好几天。最后在 DeepSeek 帮助下才解决。
完整安装步骤:
下载最新 Halo jar(去官网 https://halo.run 下载):
sudo mkdir -p /opt/halo
cd /opt/halo
wget https://dl.halo.run/release/halo-latest.jar -O halo.jar创建目录放配置文件:
mkdir -p ~/.halo2
cp application.yaml ~/.halo2/ # 从官网下载模板改数据库配置修改 ~/.halo2/application.yaml 把数据库改成你的 MariaDB。
创建 systemd 服务:
sudo vim /etc/systemd/system/halo.service内容:
[Unit]
Description=halo
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/java -jar /opt/halo/halo.jar --spring.config.additional-location=optional:file:/root/.halo2/
WorkingDirectory=/opt/halo
Restart=always
[Install]
WantedBy=multi-user.target启动并解决数据库问题(重点!):
sudo systemctl daemon-reload
sudo systemctl enable --now halo如果还是连不上 MariaDB(常见问题),就是 systemd 没读到配置文件。解决方案(我踩过的坑):
编辑服务文件,把 ExecStart 改成:
ExecStart=/usr/bin/java -jar /opt/halo/halo.jar --spring.config.additional-location=optional:file:/root/.halo2/然后:
sudo systemctl daemon-reload
sudo systemctl restart halo验证日志:
journalctl -u halo -n 100 | grep -i "r2dbc\|database"看到连上 MariaDB 就成功啦!强烈推荐大家用 Docker 部署 Halo,直接用编排里的 PostgreSQL,省得这些麻烦。
6. 安装 OpenList 网盘
OpenList 我没用 Zfile,因为它有官方一键安装脚本,自动识别 ARM64,装起来贼方便。
先配置加速(GitHub 镜像源,不然下载慢):
我用了 https://github.makkle.com/ 之类的代理。
一键安装:
curl -fsSL https://res.oplist.org/script/v4.sh > install-openlist-v4.sh && sudo bash install-openlist-v4.sh脚本会进入交互菜单,输入 1 安装,按提示走完。装好后会直接给你初始密码和访问地址(一般是 http://IP:端口)。
如果打不开,记得放行防火墙:
sudo ufw allow 端口号
sudo ufw reload挂载本地目录或者云盘按照官方文档操作就行,备份功能也超实用,换机器直接导入备份文件。
7. 配置内网穿透映射到公网
有了站点,最大的问题是没公网 IP。我先试了 IPv6,但国内主流运营商给的基本都是阉割版,不具备公网能力(有些地区打客服电话能开,我这儿没试成)。
最后我选了免费内网穿透工具 chmlfrp(https://www.chmlfrp.net/),选了日本节点,穿透地址是 jp.5.frp.one:15200,速度很不错。
详细步骤:
去官网注册账号并登录(建议实名,免费节点更多)。
创建隧道:选择 TCP/HTTP 模式,填本地服务端口(比如 Halo 8090、OneDev 6610 等),选日本节点,保存后获得 Token 和隧道 ID。
下载对应 ARM64 的 frpc 客户端(官网有下载链接)。
解压后编辑配置文件(frpc.toml 或 ini,根据版本)填入你的 Token 和隧道信息,或者用命令行参数启动。
运行客户端:
./frpc -s jp.5.frp.one:端口 -t 你的token # 具体参数看官网推荐也做成 systemd 服务,开机自动启动。
你现在访问的 www.exyone.me 是通过 Cloudflare Worker 反向代理的(感谢赛博义父 Cloudflare),代码如下,供大家参考(建议把 TARGET_URL 改成环境变量):
// ==================== 配置区域(建议使用环境变量) ====================
const DEFAULT_TARGET_URL = 'http://jp.5.frp.one:15200';
const HOP_BY_HOP_HEADERS = [
'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',
'te', 'trailers', 'transfer-encoding', 'upgrade'
];
const SENSITIVE_RESPONSE_HEADERS = [
'server', 'x-powered-by', 'x-aspnet-version'
];
export default {
async fetch(request, env, ctx) {
const TARGET_URL = env.TARGET_URL || DEFAULT_TARGET_URL;
try {
const url = new URL(request.url);
const targetUrl = TARGET_URL + url.pathname + url.search;
const proxyRequestInit = {
method: request.method,
headers: sanitizeRequestHeaders(request.headers),
body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : undefined,
};
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000);
proxyRequestInit.signal = controller.signal;
const proxyRequest = new Request(targetUrl, proxyRequestInit);
// 设置代理头
proxyRequest.headers.set('X-Forwarded-For', request.headers.get('cf-connecting-ip') || '');
proxyRequest.headers.set('X-Forwarded-Proto', url.protocol.replace(':', ''));
proxyRequest.headers.set('X-Forwarded-Host', url.host);
proxyRequest.headers.set('X-Real-IP', request.headers.get('cf-connecting-ip') || '');
const targetUrlObj = new URL(TARGET_URL);
proxyRequest.headers.set('Host', targetUrlObj.host);
let response = await fetch(proxyRequest);
clearTimeout(timeoutId);
response = new Response(response.body, response);
sanitizeResponseHeaders(response.headers);
// 处理重定向
if (response.status >= 300 && response.status < 400) {
const location = response.headers.get('location');
if (location) {
try {
const locationUrl = new URL(location, TARGET_URL);
const newLocation = url.protocol + '//' + url.host + locationUrl.pathname + locationUrl.search;
response.headers.set('location', newLocation);
} catch (e) {}
}
}
return response;
} catch (error) {
// 错误处理...
let status = 500;
let message = 'Internal Server Error';
if (error.name === 'AbortError') {
status = 504;
message = 'Upstream timeout';
}
return new Response(JSON.stringify({ error: message }), { status, headers: { 'Content-Type': 'application/json' } });
}
}
};
function sanitizeRequestHeaders(headers) {
const sanitized = new Headers(headers);
HOP_BY_HOP_HEADERS.forEach(h => sanitized.delete(h));
return sanitized;
}
function sanitizeResponseHeaders(headers) {
HOP_BY_HOP_HEADERS.forEach(h => headers.delete(h));
SENSITIVE_RESPONSE_HEADERS.forEach(h => headers.delete(h));
}部署到 Cloudflare Worker 后,域名解析到 Worker 就完事了。
整个迁移过程大概花了我一个周末,成本不到 200 块钱(盒子+硬盘),以后电费都比云服务器便宜多了。有什么问题欢迎评论区交流,我看到会尽量回复。
顺便说一句,也不得不吐槽一下咱们 ASP.NET(或者说 .NET 整体)的生态吧,主要还是牢牢扎根在企业/商业那一头,自建/个人用的开源软件生态真的是几乎一片空白啊哈哈。
想随便找个自己用着顺手的、自托管的工具(博客、网盘、DevOps、知识库啥的),基本全是 Go、Node、Python、Rust 或者 Java 的天下,.NET 的选择少得可怜,偶尔冒出来一两个也大多是老项目、维护不活跃,或者功能上总差口气。用着用着就忍不住感慨:朋友们,咱 .NET 啥时候也能在“玩具级自部署”这块儿支棱起来啊~
希望这篇分享对正在考虑自建家庭服务器的朋友有帮助!我们下次见~
(完)
评论