从云服务器迁移到家庭服务器,配置内网穿透映射站点到公网

使用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+(我们已经装好),官方支持裸金属部署:

  1. 下载最新版(去 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
  1. 先前台测试:

cd /opt/onedev
bin/server.sh console

浏览器访问 http://你的内网IP:6610 初始化向导,按提示走完(设置管理员账号、数据库等,我用了自带的 H2 先,后面可以换)。

  1. 做成 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 帮助下才解决。

完整安装步骤

  1. 下载最新 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
  1. 创建目录放配置文件:

mkdir -p ~/.halo2
cp application.yaml ~/.halo2/   # 从官网下载模板改数据库配置

修改 ~/.halo2/application.yaml 把数据库改成你的 MariaDB。

  1. 创建 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
  1. 启动并解决数据库问题(重点!):

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,但国内主流运营商给的基本都是阉割版,不具备公网能力(有些地区打客服电话能开,我这儿没试成)。

最后我选了免费内网穿透工具 chmlfrphttps://www.chmlfrp.net/),选了日本节点,穿透地址是 jp.5.frp.one:15200,速度很不错。

详细步骤

  1. 去官网注册账号并登录(建议实名,免费节点更多)。

  2. 创建隧道:选择 TCP/HTTP 模式,填本地服务端口(比如 Halo 8090、OneDev 6610 等),选日本节点,保存后获得 Token 和隧道 ID。

  3. 下载对应 ARM64 的 frpc 客户端(官网有下载链接)。

  4. 解压后编辑配置文件(frpc.toml 或 ini,根据版本)填入你的 Token 和隧道信息,或者用命令行参数启动。

  5. 运行客户端:

./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 啥时候也能在“玩具级自部署”这块儿支棱起来啊~

希望这篇分享对正在考虑自建家庭服务器的朋友有帮助!我们下次见~

(完)

评论