2025-05-13 05:39:15 +00:00

381 lines
42 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html><html lang="zh-CN" data-theme="dark"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>在 NAS 上部署自己的 Gitea 服务,无需公网服务器 | 時痕</title><meta name="author" content="Linloir"><meta name="copyright" content="Linloir"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#0d0d0d"><meta name="description" content="方案速览简单来说,方案包含了以下几个主要部分: 公网访问使用光猫桥接路由器拨号,通过路由器同时获取 IPv4 大内网和 IPv6 公网 &#x2F;64 地址 DNS 解析通过 MacMini 上部署的 ddns-go 实现 v6 &#x2F; v4 双栈访问采用了 Cloudflare 中针对 DNS 解析记录的 Proxied 能力 80 &#x2F; 443 端口封禁通过在 MacMini">
<meta property="og:type" content="article">
<meta property="og:title" content="在 NAS 上部署自己的 Gitea 服务,无需公网服务器">
<meta property="og:url" content="https://blog.linloir.cn/2024/10/13/host-git-at-home/">
<meta property="og:site_name" content="時痕">
<meta property="og:description" content="方案速览简单来说,方案包含了以下几个主要部分: 公网访问使用光猫桥接路由器拨号,通过路由器同时获取 IPv4 大内网和 IPv6 公网 &#x2F;64 地址 DNS 解析通过 MacMini 上部署的 ddns-go 实现 v6 &#x2F; v4 双栈访问采用了 Cloudflare 中针对 DNS 解析记录的 Proxied 能力 80 &#x2F; 443 端口封禁通过在 MacMini">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://blog.linloir.cn/img/cover.jpg">
<meta property="article:published_time" content="2024-10-13T02:52:30.000Z">
<meta property="article:modified_time" content="2025-05-13T05:38:52.369Z">
<meta property="article:author" content="Linloir">
<meta property="article:tag" content="瞎捣鼓">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://blog.linloir.cn/img/cover.jpg"><link rel="shortcut icon" href="/img/avatar.png"><link rel="canonical" href="https://blog.linloir.cn/2024/10/13/host-git-at-home/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox/fancybox.min.css" media="print" onload="this.media='all'"><script>
(() => {
const saveToLocal = {
set: (key, value, ttl) => {
if (!ttl) return
const expiry = Date.now() + ttl * 86400000
localStorage.setItem(key, JSON.stringify({ value, expiry }))
},
get: key => {
const itemStr = localStorage.getItem(key)
if (!itemStr) return undefined
const { value, expiry } = JSON.parse(itemStr)
if (Date.now() > expiry) {
localStorage.removeItem(key)
return undefined
}
return value
}
}
window.btf = {
saveToLocal,
getScript: (url, attr = {}) => new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.async = true
Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
script.onload = script.onreadystatechange = () => {
if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
}
script.onerror = reject
document.head.appendChild(script)
}),
getCSS: (url, id) => new Promise((resolve, reject) => {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = url
if (id) link.id = id
link.onload = link.onreadystatechange = () => {
if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
}
link.onerror = reject
document.head.appendChild(link)
}),
addGlobalFn: (key, fn, name = false, parent = window) => {
if (!true && key.startsWith('pjax')) return
const globalFn = parent.globalFn || {}
globalFn[key] = globalFn[key] || {}
if (name && globalFn[key][name]) return
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
parent.globalFn = globalFn
}
}
const activateDarkMode = () => {
document.documentElement.setAttribute('data-theme', 'dark')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', 'undefined')
}
}
const activateLightMode = () => {
document.documentElement.setAttribute('data-theme', 'light')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', 'undefined')
}
}
btf.activateDarkMode = activateDarkMode
btf.activateLightMode = activateLightMode
const theme = saveToLocal.get('theme')
const mediaQueryDark = window.matchMedia('(prefers-color-scheme: dark)')
const mediaQueryLight = window.matchMedia('(prefers-color-scheme: light)')
if (theme === undefined) {
if (mediaQueryLight.matches) activateLightMode()
else if (mediaQueryDark.matches) activateDarkMode()
else {
const hour = new Date().getHours()
const isNight = hour <= 6 || hour >= 18
isNight ? activateDarkMode() : activateLightMode()
}
mediaQueryDark.addEventListener('change', () => {
if (saveToLocal.get('theme') === undefined) {
e.matches ? activateDarkMode() : activateLightMode()
}
})
} else {
theme === 'light' ? activateLightMode() : activateDarkMode()
}
const asideStatus = saveToLocal.get('aside-status')
if (asideStatus !== undefined) {
document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
}
const detectApple = () => {
if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
document.documentElement.classList.add('apple')
}
}
detectApple()
})()
</script><script>const GLOBAL_CONFIG = {
root: '/',
algolia: undefined,
localSearch: undefined,
translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"简"},
noticeOutdate: undefined,
highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false,"highlightFullpage":false,"highlightMacStyle":false},
copy: {
success: '复制成功',
error: '复制失败',
noSupport: '浏览器不支持'
},
relativeDate: {
homepage: false,
post: false
},
runtime: '',
dateSuffix: {
just: '刚刚',
min: '分钟前',
hour: '小时前',
day: '天前',
month: '个月前'
},
copyright: undefined,
lightbox: 'fancybox',
Snackbar: {"chs_to_cht":"已切换为繁体中文","cht_to_chs":"已切换为简体中文","day_to_night":"已切换为深色模式","night_to_day":"已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"top-center"},
infinitegrid: {
js: 'https://cdn.jsdelivr.net/npm/@egjs/infinitegrid/dist/infinitegrid.min.js',
buttonText: '加载更多'
},
isPhotoFigcaption: false,
islazyload: false,
isAnchor: true,
percent: {
toc: false,
rightside: false,
},
autoDarkmode: true
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
title: '在 NAS 上部署自己的 Gitea 服务,无需公网服务器',
isPost: true,
isHome: false,
isHighlightShrink: false,
isToc: true,
postUpdate: '2025-05-13 13:38:52'
}</script><meta name="generator" content="Hexo 7.3.0"></head><body><script>window.paceOptions = {
restartOnPushState: false
}
btf.addGlobalFn('pjaxSend', () => {
Pace.restart()
}, 'pace_restart')
</script><link rel="stylesheet" href="/css/minimal.css"/><script src="https://cdn.jsdelivr.net/npm/pace-js/pace.min.js"></script><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="/img/avatar.png" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">22</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">20</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">2</div></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-th"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url(/img/cover.jpg);"><nav id="nav"><span id="blog-info"><a class="nav-site-title" href="/"><span class="site-name">時痕</span></a><a class="nav-page-title" href="/"><span class="site-name">在 NAS 上部署自己的 Gitea 服务,无需公网服务器</span></a></span><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-th"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div></div><div id="toggle-menu"><span class="site-page"><i class="fas fa-bars fa-fw"></i></span></div></div></nav><div id="post-info"><h1 class="post-title">在 NAS 上部署自己的 Gitea 服务,无需公网服务器</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2024-10-13T02:52:30.000Z" title="发表于 2024-10-13 10:52:30">2024-10-13</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2025-05-13T05:38:52.369Z" title="更新于 2025-05-13 13:38:52">2025-05-13</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E6%8A%80%E6%9C%AF/">技术</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">总字数:</span><span class="word-count">3.2k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>10分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title=""><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">浏览量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h2 id="方案速览"><a href="#方案速览" class="headerlink" title="方案速览"></a>方案速览</h2><p>简单来说,方案包含了以下几个主要部分:</p>
<ol>
<li>公网访问使用光猫桥接路由器拨号,通过路由器同时获取 IPv4 大内网和 IPv6 公网 &#x2F;64 地址</li>
<li>DNS 解析通过 MacMini 上部署的 <a target="_blank" rel="noopener" href="https://github.com/jeessy2/ddns-go">ddns-go</a> 实现</li>
<li>v6 &#x2F; v4 双栈访问采用了 <a target="_blank" rel="noopener" href="https://cloudflare.com/">Cloudflare</a> 中针对 DNS 解析记录的 Proxied 能力</li>
<li>80 &#x2F; 443 端口封禁通过在 MacMini 上开放 <a target="_blank" rel="noopener" href="https://developers.cloudflare.com/fundamentals/reference/network-ports/#network-ports-compatible-with-cloudflares-proxy">Cloudflare Proxy 支持的端口</a> 并在 Cloudflare 控制台指定回源端口解决</li>
<li>Gitea 仓库直接在 Nas 上通过 Docker Compose 部署,通过 MacMini 上 <a target="_blank" rel="noopener" href="https://github.com/caddyserver/caddy">Caddy</a> 反向代理 http&#x2F;https 流量</li>
<li>TLS 采用 <a target="_blank" rel="noopener" href="https://github.com/caddy-dns/cloudflare">caddy-dns</a> 插件,通过 Cloudflare token 实现 dns 验证,并由 Caddy 自动部署证书</li>
</ol>
<hr>
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>大四毕业上班之后,便不再和父母一起住了,自然而然地就想改善一下家里的网络环境,也正好实践一下刚考完研还热乎的计算机网络知识,而不至于像之前重启一下路由器马上就能听到 “怎么没网了” 这样的投诉。</p>
<p>做的第一件事就是选一个宽带运营商。由于我自己两个号码都是中国联通的,一张是米粉卡 5r &#x2F; mo 月租 + 1r &#x2F; GiB (3r 不限量) 日租流量包的套餐,另一张曾经也是米粉卡,后来手贱改成了 19r &#x2F; mo 月租 + 3GiB 流量 + 1r &#x2F; GiB 日租流量包的大王卡套餐,一直都觉得第二张卡套餐太亏,于是想趁着宽带的机会把这张卡和宽带绑在一起共用一个套餐 (事实证明其实没什么用,因为现在宽带都改成最低消费模式了,也就是只要满足卡的每月最低消费就能有宽带,如果没有达到最低消费就直接扣中间的差额。所以其实这么看还是米粉卡最划算,相当于每月不限量流量才 95r宽带 139r 低消只用再补 40r所以有便宜套餐千万不要随便改啊),就选了联通 139r &#x2F; mo 的 1000M 宽带的套餐。</p>
<p>办套餐的时候最关心的就是:</p>
<ol>
<li>有没有 IPv4 公网地址</li>
<li>有没有 IPv6 公网地址</li>
<li>上行带宽有多少</li>
<li>下行实际带宽能有多少</li>
</ol>
<p>在办的时候专门确认了前三点,确认是不会有公网 IPv4 但是有公网 IPv6上行带宽差不多 150M想了想感觉也够了遂同意签合同上门安装便也有了后文。</p>
<h2 id="光猫桥接和路由器拨号"><a href="#光猫桥接和路由器拨号" class="headerlink" title="光猫桥接和路由器拨号"></a>光猫桥接和路由器拨号</h2><p>这个 139r 的联通套餐实际上是他们的 FTTR 解决方案,会带一个主路由和一个副路由,中间用隐形光纤连接,主路由给的是 2.5G 网口版本,这点好评,不过官方的配网方案感觉应该是 AC + AP 模式,光猫必须作为网关,然后路由器估计也得用 AP 模式不然感觉应该路由器连接的设备和官方的 AP 连接的设备就不在一个子网了。</p>
<p>当即就觉得这样不行,还是得该桥接,代价就是官方的 AP 就浪费了 (虽然我感觉好像是不是如果把 AP 连上后面交换机的光口应该也能用?不知道官方的 AP 里面是什么逻辑),但反正也是不要钱的东西,浪费就浪费了,于是初步规划了一下家里的网络拓扑如下:</p>
<p><img src="/img/host-git-at-home/home_network_topology.png" alt="network_topology"></p>
<h3 id="啥是光猫桥接"><a href="#啥是光猫桥接" class="headerlink" title="啥是光猫桥接"></a>啥是光猫桥接</h3><p>以我浅薄的计算机网络知识理解一下光猫桥接:</p>
<p>在默认情况下,光猫作为家里的网关路由,光猫出口端连接的是运营商网络,具有运营商分配的 IP光猫另一端连接家庭网络的其他设备运行在 192.168.1.0&#x2F;24 子网 A (随便命一个名字方便区分) 下,并且光猫使用固定 IP 192.168.1.1 接入这个子网。</p>
<p>之后,如果路由器选择路由模式,则路由器与光猫连接的一端接入子网 A 中,并具有光猫分配的 IP此时所有设备和路由器连接并以路由器作为它们的网关此时这些设备和路由器向内的端口同处一个子网 B 中,一般也是 192.168.1.0&#x2F;24 这个网段,路由器以 IP 192.168.1.1 接入这个子网。</p>
<p>相反,如果路由器选择 AP 模式,则路由器可以理解为一个无线交换机,其不分割子网,所有设备和光猫内侧端口处于同一个子网 A 中,此时使用 IP 192.168.1.1 则可以访问到光猫管理页面。</p>
<p>如果将光猫桥接,则可以理解为,光猫以一种子网设备的形式连接到运营商和家里真实网关设备之间的子网上,也就是可以想象成如下的网络拓扑结构:</p>
<p><img src="/img/host-git-at-home/bridge_topology.png" alt="bridge_topology"></p>
<p>也就是,光猫自己充当这个交换机的角色,并以一个固定 IP 192.168.1.1 接入到广播域 A 中。路由器和入户网线都与光猫充当的交换机连接,路由器实际与运营商建立拨号连接并获得运营商提供的 IP充当广播域 B 的网关设备。</p>
<p>为什么要强调光猫以固定 IP 接入广播域 A 中呢,因为从拓扑图中也可以看到,一旦光猫开启了桥接模式,并且所有的设备都与路由器连接,在广播域 B 中的设备将无法访问到广播域 A 中的光猫,想要访问光猫只有在路由器中建立路由表转发 192.168.1.1 的包到广播域 A 中或是直接将设备接入广播域 A 中才能通过 192.168.1.1 访问到光猫。</p>
<h3 id="超管密码"><a href="#超管密码" class="headerlink" title="超管密码"></a>超管密码</h3><p>其实改光猫桥接很简单,网上针对不同的光猫型号和运营商都有很丰富的教程了,反倒是超管密码的获取才是最难的一步,这就要考验和安装人员拉扯的话术了。</p>
<p>还记得当时和安装小哥说能不能改成桥接,小哥说现在公司规定他们是不可以这么操作的,也就是说以前明着要求小哥改桥接的路子就不太行得通了。后来想了一下,问小哥之后要是网络出了什么问题是不是要找他上门帮我弄,小哥说是的,我又问了价格,说这种不收钱的,我就顺水推舟说要不给我个超管的密码,我自己也会配网络,如果有什么问题我自己弄一下就好了也不麻烦他上门一趟,还没有费用拿,小哥很高兴就给我写了个小纸条留了密码,还嘱咐说这个密码是动态的,过几个月就失效了,要配得快点,失效了可以再问他要。</p>
<p>果然沟通还是要拿捏住让双方都获利还不明显违规的点哈哈,给我密码对他来说节省了跑一趟的时间和精力,减少了投诉率,对我来说又能够达成目的,还不违背运营商禁止小哥配桥接的要求,问就是我自己淘宝买的超管密码。</p>
<h3 id="拨号上网和-IPv6"><a href="#拨号上网和-IPv6" class="headerlink" title="拨号上网和 IPv6"></a>拨号上网和 IPv6</h3><p>光猫桥接搞定以后,就是把路由器连接上光猫然后配置拨号连接了。</p>
<p>拨号的账号可以在光猫的页面看到,密码则可以问运营商人工客服,一般默认就是账号的后 6 位IPv6 可以复用 IPv4 线路进行拨号,如下图所示:</p>
<p><img src="/img/host-git-at-home/v6config_1.png" alt="ipv6_auth"></p>
<p>由于需要允许内网设备与公网建立连接,需要关闭 IPv6 防火墙功能其他厂商是否需要关闭取决于厂商如何定义它们的防火墙。TP-Link 对 IPv6 防火墙的描述为“IPv6 防火墙能有效将外网和内网进行隔离,避免家庭局域网完全暴露给外网,保障用户的网络安全。”</p>
<p>同时地址获取协议和前缀授权就都选择自动DNS 服务器我选择了 Cloudflare 的 1.1.1.1 中的配置。</p>
<p><img src="/img/host-git-at-home/v6config_2.png" alt="ipv6_config"></p>
<p>针对局域网内的设备,可以根据运营商分配的数量灵活选择 SLAAC 和 DHCPv6。由于抠门的深圳联通只给了 &#x2F;64 地址,相当于只有直接连接到路由器的设备可以使用 SLAAC 分配地址,下级设备无法继续使用 SLAAC 分配,索性我就直接关闭了 SLAAC 能力改用 DHCPv6。</p>
<p><img src="/img/host-git-at-home/v6config_3.png" alt="ipv6_config_2"></p>
<h2 id="DDNS-动态域名解析与-Cloudflare-Proxy"><a href="#DDNS-动态域名解析与-Cloudflare-Proxy" class="headerlink" title="DDNS 动态域名解析与 Cloudflare Proxy"></a>DDNS 动态域名解析与 Cloudflare Proxy</h2><p>路由有了 IPv6 之后,接上机柜里的 MacMini就可以使用 IPv6 访问到这台机器了,先拿个 python http 服务试试看通不通:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/some_dir</span><br><span class="line">python -m http.server 23333</span><br></pre></td></tr></table></figure>
<p>IPv6 地址可以使用 <code>ipconfig</code> (Windows) 或是 <code>ip addr</code> (Linux&#x2F;Mac) 命令查看到,一般是查看 <code>无线局域网适配器 WLAN</code> 或是 <code>以太网适配器</code> (Windows) 或是 <code>eth0</code> (Linux)<code>en0</code> (Mac) 下面的 <code>IPv6</code> 或是 <code>inet6</code> 标识的地址。还会有很多标有 <code>临时</code> 或是 <code>deprecated</code> 的地址,那些是系统为了防止网站跟踪 IP 地址,轮替生成的用于主动建立连接时使用的地址,这些地址有效期较短并且时常变化,不适用于监听传入的连接。</p>
<p><img src="/img/host-git-at-home/inet6_addr.png" alt="inet6_addr"></p>
<p>浏览器直接 <code>http://[这里填 IPv6 地址]:端口号</code> 即可测试能不能正常访问了,不放心可以流量访问确定链路是通的。</p>
<p>接下来就该把这个地址解析成域名了。因为 IPv6 始终不是静态的,因此需要动态地轮询设备的 IPv6 地址然后更新 DNS 服务商上的解析内容。</p>
<h3 id="ddns-go-安装"><a href="#ddns-go-安装" class="headerlink" title="ddns-go 安装"></a>ddns-go 安装</h3><p>参考 <a target="_blank" rel="noopener" href="https://github.com/jeessy2/ddns-go?tab=readme-ov-file#%E7%B3%BB%E7%BB%9F%E4%B8%AD%E4%BD%BF%E7%94%A8">ddns-go</a> 文档即可快速在系统中安装 ddns-go。没有选择 Docker 主要是看到可以使用 brew 安装,甚至感觉比 docker 还简单。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">brew install ddns-go</span><br><span class="line">ddns-go -s install -l 23333</span><br></pre></td></tr></table></figure>
<p>这样就启动了控制面板在 23333 端口的 ddns-go 服务,配置文件默认保存在 ~&#x2F;.ddns_go_config.yaml 中,这个我个人懒得改,想改看文档可以 <code>-c</code> 指定路径</p>
<p>然后就是 <code>http://192.168.X.X:XXXXX</code> 登陆进去面板配置了,这里需要注意的是需要局域网登录进去配置,如果是非局域网地址会拒绝连接。</p>
<p>Webhook 通知我选择了 <a target="_blank" rel="noopener" href="https://sct.ftqq.com/">Server 酱</a>,每天免费 5 条消息也够用Webhook 格式配置如下:</p>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://sctapi.ftqq.com/&lt;my_token&gt;.send?title=检测到 DDNS 设备 IP 变化&amp;desp=MacMini の IP 已变更为 #&#123;ipv6Addr&#125;, 域名更新 #&#123;ipv6Result&#125;</span><br></pre></td></tr></table></figure>
<p>搞完以后就可以浏览器进 <code>域名:端口号</code> 看看服务生没生效啦,在这一步测试的时候,记得在 Cloudflare 中关闭 Proxied 模式,后面会具体讲到原因。</p>
<h3 id="Cloudflare-Proxied-代理"><a href="#Cloudflare-Proxied-代理" class="headerlink" title="Cloudflare Proxied 代理"></a>Cloudflare Proxied 代理</h3><p>这个时候已经可以在 IPv6 的网络下通过 <code>域名:端口号</code> 访问到服务了,但是这时候还有两个令人头疼的问题:</p>
<ol>
<li>很多时候网络环境是仅 IPv4 的,例如各种酒店、公用 WiFi、公司内网等等仅 IPv6 的网络访问覆盖率还是太低,感觉实用性不强</li>
<li>网址后面带着端口号,怎么看都很丑,有些不能指定端口号的地方就会收受到阻碍</li>
</ol>
<p>所以在完成了 DDNS 之后,就可以着手使用 Cloudflare 提供的回源能力了。</p>
<p>Cloudflare Proxy本质上就是Cloudflare 将域名解析到它的代理服务器,然后转发你对源服务器的请求。这样做有几个好处:</p>
<ol>
<li>隐藏了服务器的实际 IP 地址,用户只知道 Cloudflare 代理服务器的 IP 地址,对于家里部署的服务而言更安全</li>
<li>Cloudflare Proxy 支持用户使用 IPv4 连接,同时使用 IPv6 与源服务器通信,也就是说可以充当 v4 转 v6 的中间件</li>
<li>回源的时候支持指定具体的端口号,这样就可以在网址里不再输入端口号了</li>
</ol>
<p>开启 Proxy 十分简单,直接在 DNS 记录里打开 Proxied 开关即可。</p>
<p>不过,由于 Cloudflare 只支持有限的端口转发,并且只支持基于 http&#x2F;https 协议的流量转发,因此适用面相对不那么广,但是家用部署网站服务还是绰绰有余了。具体允许的端口号见 <a target="_blank" rel="noopener" href="https://developers.cloudflare.com/fundamentals/reference/network-ports/#network-ports-compatible-with-cloudflares-proxy">Cloudflare 文档</a></p>
<p>经过测试,中国联通封了 804432096 端口,回源我采用的 2095 端口,这个在 <code>Cloudflare 控制台 - 规则 - Origin Rules</code> 可以创建回源规则针对特定域名指定。</p>
<h2 id="Gitea-安装与反向代理"><a href="#Gitea-安装与反向代理" class="headerlink" title="Gitea 安装与反向代理"></a>Gitea 安装与反向代理</h2><p>待后续翔实~</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta"><i class="fas fa-circle-user fa-fw"></i>文章作者: </span><span class="post-copyright-info"><a href="https://blog.linloir.cn">Linloir</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta"><i class="fas fa-square-arrow-up-right fa-fw"></i>文章链接: </span><span class="post-copyright-info"><a href="https://blog.linloir.cn/2024/10/13/host-git-at-home/">https://blog.linloir.cn/2024/10/13/host-git-at-home/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta"><i class="fas fa-circle-exclamation fa-fw"></i>版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外,均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来源 <a href="https://blog.linloir.cn" target="_blank">時痕</a></span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/%E7%9E%8E%E6%8D%A3%E9%BC%93/">瞎捣鼓</a></div><div class="post-share"><div class="social-share" data-image="/img/cover.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><a class="prev-post pull-left" href="/2024/10/13/micro-posts/" title="短文 - 关于短博文的碎碎念"><img class="cover" src="/img/cover.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">短文 - 关于短博文的碎碎念</div></div></a><a class="next-post pull-right" href="/2024/10/12/blog-from-scratch/" title="基于 IPv6 公网地址、NAS 和 MacMini 的私有部署博客方案"><img class="cover" src="/img/cover.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">基于 IPv6 公网地址、NAS 和 MacMini 的私有部署博客方案</div></div></a></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><a href="/2024/10/12/blog-from-scratch/" title="基于 IPv6 公网地址、NAS 和 MacMini 的私有部署博客方案"><img class="cover" src="/img/cover.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2024-10-12</div><div class="title">基于 IPv6 公网地址、NAS 和 MacMini 的私有部署博客方案</div></div></a></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info is-center"><div class="avatar-img"><img src="/img/avatar.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info-name">Linloir</div><div class="author-info-description">我、技术、生活与值得分享的一切</div><div class="site-data"><a href="/archives/"><div class="headline">文章</div><div class="length-num">22</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">20</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">2</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons"><a class="social-icon" href="https://github.com/Linloir" target="_blank" title="GitHub"><i class="fab fa-github"></i></a><a class="social-icon" href="mailto:jonathanzhang.st@gmail.com" target="_blank" title="Email"><i class="fas fa-envelope"></i></a></div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%96%B9%E6%A1%88%E9%80%9F%E8%A7%88"><span class="toc-number">1.</span> <span class="toc-text">方案速览</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%83%8C%E6%99%AF"><span class="toc-number">2.</span> <span class="toc-text">背景</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%85%89%E7%8C%AB%E6%A1%A5%E6%8E%A5%E5%92%8C%E8%B7%AF%E7%94%B1%E5%99%A8%E6%8B%A8%E5%8F%B7"><span class="toc-number">3.</span> <span class="toc-text">光猫桥接和路由器拨号</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%95%A5%E6%98%AF%E5%85%89%E7%8C%AB%E6%A1%A5%E6%8E%A5"><span class="toc-number">3.1.</span> <span class="toc-text">啥是光猫桥接</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%B6%85%E7%AE%A1%E5%AF%86%E7%A0%81"><span class="toc-number">3.2.</span> <span class="toc-text">超管密码</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%8B%A8%E5%8F%B7%E4%B8%8A%E7%BD%91%E5%92%8C-IPv6"><span class="toc-number">3.3.</span> <span class="toc-text">拨号上网和 IPv6</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#DDNS-%E5%8A%A8%E6%80%81%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90%E4%B8%8E-Cloudflare-Proxy"><span class="toc-number">4.</span> <span class="toc-text">DDNS 动态域名解析与 Cloudflare Proxy</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#ddns-go-%E5%AE%89%E8%A3%85"><span class="toc-number">4.1.</span> <span class="toc-text">ddns-go 安装</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Cloudflare-Proxied-%E4%BB%A3%E7%90%86"><span class="toc-number">4.2.</span> <span class="toc-text">Cloudflare Proxied 代理</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Gitea-%E5%AE%89%E8%A3%85%E4%B8%8E%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86"><span class="toc-number">5.</span> <span class="toc-text">Gitea 安装与反向代理</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2025/05/12/swift-state-binding/" title="Swift 学习笔记 - 从 Property Wrapper 视角探索 State 与 Binding 如何工作">Swift 学习笔记 - 从 Property Wrapper 视角探索 State 与 Binding 如何工作</a><time datetime="2025-05-12T15:30:52.000Z" title="发表于 2025-05-12 23:30:52">2025-05-12</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2025/03/17/coroutine-llm-qa/" title="Coroutine 相关疑惑大模型问答记录">Coroutine 相关疑惑大模型问答记录</a><time datetime="2025-03-17T13:09:21.000Z" title="发表于 2025-03-17 21:09:21">2025-03-17</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2024/12/31/summary-2023-2024/" title="年终总结 - 2023 至 2024">年终总结 - 2023 至 2024</a><time datetime="2024-12-31T17:15:49.000Z" title="发表于 2025-01-01 01:15:49">2025-01-01</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2024/11/22/debug-windows-socket-drain/" title="问题定位回顾 - Windows 上发起 tcp 连接时提示 Only one usage of each socket address (protocol/network address/port) is normally permitted">问题定位回顾 - Windows 上发起 tcp 连接时提示 Only one usage of each socket address (protocol/network address/port) is normally permitted</a><time datetime="2024-11-22T03:52:37.000Z" title="发表于 2024-11-22 11:52:37">2024-11-22</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/2024/11/19/listary-quick-clone-command/" title="Listary 命令分享 - 快捷 clone 仓库并使用 VSCode 打开">Listary 命令分享 - 快捷 clone 仓库并使用 VSCode 打开</a><time datetime="2024-11-19T06:08:32.000Z" title="发表于 2024-11-19 14:08:32">2024-11-19</time></div></div></div></div></div></div></main><footer id="footer" style="background: transparent;"><div id="footer-wrap"><div class="copyright">&copy;2022 - 2025 By Linloir</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div><div class="footer_custom_text">Wirtten with Love ❤</div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换"></button><button id="darkmode" type="button" title="日间和夜间模式切换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/instant.page/instantpage.min.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script>(() => {
const panguFn = () => {
if (typeof pangu === 'object') pangu.autoSpacingPage()
else {
btf.getScript('https://cdn.jsdelivr.net/npm/pangu/dist/browser/pangu.min.js')
.then(() => {
pangu.autoSpacingPage()
})
}
}
const panguInit = () => {
if (true){
GLOBAL_CONFIG_SITE.isPost && panguFn()
} else {
panguFn()
}
}
btf.addGlobalFn('pjaxComplete', panguInit, 'pangu')
document.addEventListener('DOMContentLoaded', panguInit)
})()</script><div class="js-pjax"><script>(async () => {
const showKatex = () => {
document.querySelectorAll('#article-container .katex').forEach(el => el.classList.add('katex-show'))
}
if (!window.katex_js_css) {
window.katex_js_css = true
await btf.getCSS('https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css')
if (false) {
await btf.getScript('https://cdn.jsdelivr.net/npm/katex/dist/contrib/copy-tex.min.js')
}
}
showKatex()
})()</script><script>(() => {
const runMermaid = ele => {
window.loadMermaid = true
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'default'
ele.forEach((item, index) => {
const mermaidSrc = item.firstElementChild
const mermaidThemeConfig = `%%{init:{ 'theme':'${theme}'}}%%\n`
const mermaidID = `mermaid-${index}`
const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent
const renderFn = mermaid.render(mermaidID, mermaidDefinition)
const renderMermaid = svg => {
mermaidSrc.insertAdjacentHTML('afterend', svg)
}
// mermaid v9 and v10 compatibility
typeof renderFn === 'string' ? renderMermaid(renderFn) : renderFn.then(({ svg }) => renderMermaid(svg))
})
}
const codeToMermaid = () => {
const codeMermaidEle = document.querySelectorAll('pre > code.mermaid')
if (codeMermaidEle.length === 0) return
codeMermaidEle.forEach(ele => {
const preEle = document.createElement('pre')
preEle.className = 'mermaid-src'
preEle.hidden = true
preEle.textContent = ele.textContent
const newEle = document.createElement('div')
newEle.className = 'mermaid-wrap'
newEle.appendChild(preEle)
ele.parentNode.replaceWith(newEle)
})
}
const loadMermaid = () => {
if (true) codeToMermaid()
const $mermaid = document.querySelectorAll('#article-container .mermaid-wrap')
if ($mermaid.length === 0) return
const runMermaidFn = () => runMermaid($mermaid)
btf.addGlobalFn('themeChange', runMermaidFn, 'mermaid')
window.loadMermaid ? runMermaidFn() : btf.getScript('https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js').then(runMermaidFn)
}
btf.addGlobalFn('encrypt', loadMermaid, 'mermaid')
window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid)
})()</script></div><script id="canvas_nest" defer="defer" color="165,165,165" opacity="0.8" zIndex="-1" count="99" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/canvas-nest.min.js"></script><script src="https://cdn.jsdelivr.net/npm/pjax/pjax.min.js"></script><script>(() => {
const pjaxSelectors = ["head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"]
window.pjax = new Pjax({
elements: 'a:not([target="_blank"])',
selectors: pjaxSelectors,
cacheBust: false,
analytics: false,
scrollRestoration: false
})
const triggerPjaxFn = (val) => {
if (!val) return
Object.values(val).forEach(fn => fn())
}
document.addEventListener('pjax:send', () => {
// removeEventListener
btf.removeGlobalFnEvent('pjaxSendOnce')
btf.removeGlobalFnEvent('themeChange')
// reset readmode
const $bodyClassList = document.body.classList
if ($bodyClassList.contains('read-mode')) $bodyClassList.remove('read-mode')
triggerPjaxFn(window.globalFn.pjaxSend)
})
document.addEventListener('pjax:complete', () => {
btf.removeGlobalFnEvent('pjaxCompleteOnce')
document.querySelectorAll('script[data-pjax]').forEach(item => {
const newScript = document.createElement('script')
const content = item.text || item.textContent || item.innerHTML || ""
Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
newScript.appendChild(document.createTextNode(content))
item.parentNode.replaceChild(newScript, item)
})
triggerPjaxFn(window.globalFn.pjaxComplete)
})
document.addEventListener('pjax:error', e => {
if (e.request.status === 404) {
pjax.loadUrl('/404')
}
})
})()</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>