本文同步发布于 xLog
本文同步发布于 xLog
随着服务器越开越多,各个服务器上运行的容器变得越来越杂,收集日志也变成了一件令喵烦恼的事情。平时习惯使用 docker 以方便管理,并且因为玩不明白 k8s 所以没有构建集群,收集日志就成了一件比较尴尬的事。使用 docker 或 docker-compose 自带的 log 指令来查询的话,不但要连上原服务器,进入服务目录,还要输入一串长长的指令,成功之后还要在一大串输出中寻找需要的部分,一不小心刚刚找到的内容又被新的日志刷下去了,实在有些不够优雅。于是就试着寻找一个能够快速方便地收集运行日志,并进行按需查询和整理的方案,正好前段时间在研究 Grafana 和 Loki ,加上确实找到了官方开发的 docker 日志插件,那就准备再水一篇文章记录一下用到的配置文件和指令,方便有需要的友友们也能快速解决类似的问题。
使用时请根据您的使用情况针对性调整相关配置文件,避免一些样例配置导致的干扰。
Loki 可以理解为实际收集日志的服务器, Grafana 可以理解为从服务器里查询数据的前端,两者相互独立又相互关联,可以在同一局域网环境内部署,也可以分开之后使用公网进行连接。因为这边使用的是纯收集日志的特化场景,所以就直接使用一个 docker-compose
关联启动了。
并且由于这个服务器是直接单给日志收集使用的,无需与其他服务分享 443 端口,所以也直接将 Caddy 的配置写到了一起。
docker-compose.yml
文件如下:
1 | version: '3.4' |
暴露出来的唯一端口是 Caddy 的 443 端口,并在 CDN 处开启强制 HTTPS ,以确保不会有发向 80 端口的请求。
将配置文件统一归类放置在 config 目录下,以方便管理。
grafana 的配置文件 grafana.ini
如下:
1 | [database] |
loki 的配置文件 loki.yml
如下:
1 | auth_enabled: false |
因为不知道 alertmanager_url
应该设置成什么,并且作为收集日志的服务暂时不考虑告警的需求,暂时就保留了样例值不变。
需要注意的是 auth_enabled
这个配置项,这个字段的意义是通过设置不同的区域请求头来实现多个组织共享同个 loki 服务,当开启后需要在 HTTP 调用中使用 X-Scope-OrgID
来区分发出请求的组织,在提交数据和拉取数据时都需要带上。而此处因为是单方使用,并不涉及分享数据相关的问题,所以关闭了这个选项。
loki 本身不带 HTTP 授权检验,因而推荐将授权请求头写在前端反向代理程序的配置中。例如此处放在 Caddy 配置文件中,仅要求对来自公网流量的请求进行授权检验,当 Grafana 使用内网连接时无需校验。
这里使用了 CDN 提供商的源服务器证书,无需让 Caddy 申请,所以配置文件如下:
1 | example.com { |
其中授权部分使用的是 BasicAuth ,即用户名 + 密码的方式;密码并非明文存储,而是由 caddy 的 hash-password
功能预先计算得出,以尽可能提升安全性。
1 | /srv # caddy hash-password |
在配置完成后,还需要将对应的 SSL 证书放到指定位置,以避免因没有证书文件导致的报错。
当全部配置完成后,应该就可以使用 docker-compose up -d
命令启动啦。
容器中的 Loki 不是以 root 用户启动的,所以 Docker 设置的 root 权限会导致 Loki 存取被拒绝,您需要手动执行形如以下的命令,来将 Loki 的数据目录权限开放给容器使用:
1 | chown -R 10001:10001 ./data/loki/ |
修改完成后重启 Loki 容器即可。
进入部署好的 Grafana ,使用默认账号密码 admin
登录,修改密码之后,就可以开始添加数据源了。
如果根据上述的 docker-compose 配置方案,则只需选择 Loki ,在 HTTP 的 URL 处输入 http://loki:3100
,即可保存并测试了。此时 Loki 因为没有任何日志,会报出一个查不到 label 的错误提示,不用担心,等到之后出现了日志就可以了。
因为使用的是 docker 环境,所以具体的指令此处就不再赘述了,直接从安装 loki 日志插件开始。
此处参照的是 Docker Driver Client 给出的教程,请注意相关的内容可能会更新。
为能将数据发送给 loki ,需要一个插件来将 Docker 的日志转化为带有详细标签信息的数据,并使用 Loki 兼容的接口发送给上面部署的服务器。使用这条指令可以安装这个插件:
1 | docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions |
安装完成后可以通过 docker plugin ls
查询 docker 安装的插件来进行检查。
如果后续有升级需要,可以参照原链接中给出的指令进行升级:
1 | docker plugin disable loki --force |
然后就可以对插件进行具体的配置了。因为我需要收集的是宿主机上所有的 docker 日志,并且无需区分不同的组织,所以配置 /etc/docker/daemon.json
文件,写入以下内容:
1 | { |
配置完成后,需要重启 docker 进程,以使更改生效。
需要注意的是,此时并不会对所有的容器都实时生效,已经创建的容器的日志系统已经被固定,只有对新创建的容器才会有效。如果您非常急需日志收集的话,您可以尝试使用 docker-compose up -d --force-recreate
来重建所有的容器。
当配置完成,且新创建的容器开始工作并产生日志后,应该就能在 Grafana 中查询到。因为只是单方使用,并不涉及详细可视化的需求,所以没有配置 Dashboard ,而是直接进入 Explore 功能进行交互式查询。
当日志出现后,即可通过 label 对来源进行筛选。这个插件会提供 host
compose_project
compose_service
container_name
source
filename
六个筛选标签,其中:
host
指的是发出日志的宿主机主机名, compose_project
compose_service
或是 container_name
可用于定位容器, source
表示日志来源是一般输出 stdout
还是错误日志 stderr
, filename
指的是日志的原始文件(通常情况下并不重要),利用这些标签配合 Grafana 的时间控制,就能快速查询某一具体容器的运行日志。再也没有日志分散而繁多的烦恼了!
]]>本文同步发布于 xLog
工作方面,说实话我感觉有点恍惚。在最意气奋发的时候遇到了 RSS3 这样一个充满少年气的公司,我甚至会怀疑自己在做什么爽文段子里的黄粱美梦。伴随着团队一步步从小变大,从默默无闻,到波澜起伏,在 Web3 这样一个鱼龙混杂的大江湖上打下属于我们自己的一方小小天地。有的人来了,有的人又离开了;有的人有过一两面之缘,有时乃至相谈甚欢,却还是因种种原因成为了匆匆的过客;也有人最终愿意沉淀下来,与我们一起向着理想中的美好出谋划策。自然也会看到外来者的质疑,但秉承着一份有则改之,无则加勉的心态,我们修正着遇到的每一个发现的不足,一步一个脚印地踏出我们的成长。
2022 是充满故事的一年:政治的暴虐与战争的残酷挫伤了世界经济发展的信心,而经济上的不景气带来的是漫长的熊市,大小公司的相继破产倒闭,或是有的卷钱跑路。这一年我们的发行的币价似乎没什么起色,也因此招来一些鼠目交易奴的唾骂与造谣污蔑,或是在社区群里公开羞辱;但我们依然能看到那闪烁着星星点点的光芒,来自社区开发者的倾情贡献,来自业界同行与前辈们的鼓励,支持者我们不断向前。我们坚持过来了,我们搭建起了一条注重社交属性的以太坊兼容链 Crossbell ,开发出了一系列好用又好玩的周边生态产品,创造出了也许是第一个真正完整意义上的 Web3 聚合搜索引擎,创造出了太多我们想说,但还是想等到时机成熟时厚积薄发的成就。
我们举办了第一届内部黑客松,各个团队都开发出了具有创造性或是趣味感的作品,其中也有不少成为了如今我们的日常: WLB bot 每天晚些时候的温馨提示;发源自 xCharacter 的生态项目;或是成为了年度数据总结的背景音乐,伴随着复古流行的视效设计为 Web3 的点滴生活呈递出一份值得交付的答卷。
基础教育上,在完成基本的课业学习,只剩下一门毕业设计课程后,我申请大四下半期在家完成课题。感恩导师的帮助与鼓励,时间过得很快,课题也完成得很成功。虽然最终并没能成功达到优秀评定的标准,但仍然能以一份不错的成绩为我的大学四年的学习生活画上一个圆满的句号。告别了昔日里或是喜气洋洋或是嬉笑怒骂的课程群,清理了一些或许再也不会相见的形式好友,我打开了一扇新的窗户,报名了在线的音乐课程班,试着探寻与声音相关的奥秘。
基础班的课程安排在暑假进行,一进课堂,老师平易近人的态度与新颖的授课方式就让我大呼物超所值。音乐学习的过程是艰难的,是枯燥的,但正是这些死记硬背也要想办法往脑子里塞进去的知识,最终搭建起了一首首让人能过耳不忘的美妙乐章。从古典乐理的简单涉猎,到现代的电子音乐的初步尝试,再到逐渐能对深藏不露贝斯有所感触,短短两个月不到的时间里我对音乐有了完全不一样的认知。年末寒假的到来也伴随着进阶班课程的开始,这又是一次对自己发起的挑战,一场战胜懒惰、战胜逃避、战胜放弃的战斗,一场永远不会结束、但永远都走在进步路上的人生苦旅。
自从棉签和封条对灵魂渴望的自由下达诛杀令开始,身处的城市里弥漫起消毒水味的硝烟,炮弹不断发出全员下楼核酸的尖锐呼啸,我们竟成为了自己的软肋。虽然作为一个比较爱好居家生活的阿宅,因为平时也不怎么出门所以其实影响不太大,但当不存在的平台上不可说的新闻一次次对眼球造成冲击的时候,我还是深深的感受到了现实世界的扭曲。有的人拿了臭钱睁着眼睛说瞎话,有的人逼着早已被折磨沙哑的喉咙也要吐出最后一个字。口罩和良民证,竟成了新经济时代人形生物出门的必需品。即使是魔幻现实主义的小说,恐怕都不敢描述火灾时被封锁在门里活活化为焦炭时的绝望;即使是反乌托邦作品,恐怕也不会安排救护车被隔离栏挡在门外导致伤员早早咽气的情节。仿佛生活在一场荒诞的大戏里,看到的一切是那么的真,又是那么的假,真得四月之声中每一声撕心裂肺的哭喊都痛彻心扉,假到连网易新闻的一个客观中立的年终视频都容不下。
然后这看似牢不可破的可悲厚障壁,却在一夜间原地倒转。摆烂式的放开外加舆论管控的风向引导,将中饱私囊导致药厂和医院无以为继的责任全部甩锅在敢于举起白纸说出内心想法的人头上。对参与者的抓捕一刻都没有停歇,丰县的铁链囚禁起了更多无辜的少女,人权律师的嘴上被缝了一刀又一刀鲜血淋漓的缝合线。有效的 mRNA 疫苗迟迟拒绝放行,涌入京城的布洛芬不见得有任何半点发放给其他有需要的地区。岁月静好的人到处炫耀自己家药万两,负隅前行的人背着家里的老人一趟趟奔赴火葬场。
所幸的是,家里的老人并没有被此次的病毒爆发感染,意味着即将到来的新年也不至于凄凄惨惨戚戚。但依然听闻了许多亲友的亲友没能熬过这个冬天的噩耗,深感不幸的同时,也只能祈祷,愿逝者安息,愿生者健康,愿所有人都能寻得慰藉。
对大部分用户来说,今年是风云剧变的一年。杜洛夫夺走那些珍藏多年用户名去链上公开售卖,让 Telegram 一夜从最好的聊天软件摇身一变成叔叔所热爱的生活。马斯克收购推特后的三把火烧走了工程师团队,三板斧赶跑了一众用户,三重奏吓坏了一堆广告商,驱使大量推特用户向 Fediverse 转移(注: Fediverse 网络不是 mastodon )。但很可惜,正如历史上的每一次赛博移民潮一样,伴随着时间的流逝,这些新来用户浪潮淘出来的愿意留下来的金最终还是少数。
前几天 elk 正式宣布开源,今天 Misskey 发布 13.0.0 ,这些值得铭记的时刻无一不为我们的社交生活带来了更多值得铭记的美好点滴。也希望这些觉得 mastodon 使用起来不舒服的用户,可以试着看看更多的平替产品,找找更适合自己使用习惯的方式,寻找到最喜欢一方小天地。
今年 6 月喵窝迎来了她的第 2 个生日。自从启动代码被第一次敲下的这两年半里,她见证了大大小小伙伴实例的兴起与消匿,也陪伴着大家度过了一个个辗转难眠的夜晚与意难平的时光,分享着独属于这片安静的小天地的快乐。大约是一个月前,喵窝迁移到了一台独立服务器上,更强劲的性能与更充足的空间,希望能帮助她支持起接纳更多寻找陪伴的伙伴。
作为自己的项目,今年最具有挑战里程碑意义的是写了 NyaTrace ,了结了一个困扰我多年的夙愿,从头开始了解了 ICMP 追踪的工作原理,学习了 Windows 下 C++ 对底层套接字的封装,学习了双栈函数分别的调用与对应的处理方案,复习了 Qt 图形界面的使用,并对 QML 有了一点初步的了解。
今年还有一个印象比较深的就是写了一个为 Misskey 服务的邀请管理系统,终于能解决邀请码的分发与管理困难了。也借着这个机会熟悉了一下 Misskey 的授权回调接口,过段时间应该会写一个 OAuth2 转换兼容服务器,来彻底地解决其他服务没法使用 SSO 登录的问题。
拜封控政策所赐,今年基本没出门,唯一值得记一笔的是十一假期期间和家里人一起去度假房住了几天,稍微远离了城市的喧嚣,感受自然的抚摸,体会安静的美好。春节假期中也许也会再去休息休息,感受一下久违的气息。
今年好像只认真地玩了 Stray 游戏,意识到了猫比人好。
我不知道应该如何为今年作结。一年太长,四季太短。今年还发生了太多太多事,我甚至已经忘了是从何时开始的了。我只知道,我还在思考,我还在学习,我还在工作,我还在为了也许会更好的明天,继续努力。
Never drown the flame of hope.
]]>本文同步发布于 xLog
这只是一篇开发碎碎念,里面没什么实质性的技术内容,并且 NyaTrace 项目也已经大改并实现了很多预期的目标所以文章内容可能会有些过时;如果您想要的是代码或是可执行程式的话,请移步 nyatrace.app 。
作为买到 GeoIP2 后的第三个项目(前两个分别是喵窝的登录位置标注和 NyaSpeed 的真实位置显示),这次我希望能完成一个长久未了的心愿:写一个可视化的、附带 IP 详细信息的路由追踪程序。
您可能听说过 17monipdb.exe 这个工具,或者它的后继者 Best Trace ,它曾经是我用于路由追踪工作的不二选择。但随着它的开发者 IPIP.NET 逐渐转向生态封闭的商业化(所有产品都是咨询定价的企业模式),出于一种本能的排斥心理,我开始寻找替代的解决方案。
后来有一款新的工具 WorstTrace 兴起(估计是为了对标 Best Trace 吧),但其使用 Electron 封装导致体积庞大,虽然 UI 更为现代化,却依然并不被我认为是一种好的解决思路。
加上上述的这两种工具都是闭源产品,代码安全审计无从谈起,也因此在很长的一段时间里,我实际上是依赖系统自带的 tracert
和 HE BGP Toolkit 与 Censys Search 配合使用的。
但这毕竟不是长久之计,一来需要人工手动操作,不适合快速判断链路情况;二来较为依赖与 HE 和 Censys 的连接情况,在一些特殊场合下并不能得到需要的数据,因而也就萌生了依赖本地运行环境执行路由追踪的任务。前段时间挤出一些资金采购了 MaxMind 的 GeoIP2 City 和 ISP 一个月的订阅,就想着能不能利用好这两个数据库,填一个心心念念了那么久的坑。
开发工作的第一步就是分析需求,所以我拆分出了三个模块:
第一步上来就撞了南墙。搜索 route trace open source
,第一个跳出来的 Open Visual Traceroute ,是一个使用 Java 开发的工具。可能是对 Java 有偏见,我总是认为其开发的软件既臃肿又高度依赖环境,一想到为了实现路由追踪这么个小玩具的功能就需要所有用户装一个巨大的硬盘吞噬者,只感觉悲从心中来。后来又用中文搜索 开源 路由追踪
,搜索到了 NextTrace ,但当我满心欢喜想要运行,却发现它并不支持 Windows 的时候,心又凉了一截。
期间搜索到了 golang版的traceroute实现 ,看到了它提到了 golang.org/x/net/ipv4
这个包,但发现其并不支持 Windows 功能时,有想过依照它的 TraceRoute 样例 封装一个 Docker 镜像,来实现 Windows 套 Docker 的 Linux 实现思路的时候,因为太过复杂,又被我摇头否决了。
我忘了是什么时候搜索到 TraceRoute的实现(Windows下 C/C++ 基于原始套接字) 这篇 Blog 的了,只记得终于看到一篇能明白我在想什么的文章,就差当场感动到大哭了。言归正传,这篇 blog 讲述的正是我需要的路由追踪功能的底层套接字实现(即不依赖任何外部组件,完全依赖底层的系统交互),所以在仔细地研读了它提到的实现方式描述之后,我决定先把代码拿下来测试下看看。
结果么,只能用又喜又悲来形容。喜的是这个程序它能运行,和其他日常时候找到牛头不对马嘴的报错地狱代码相比完全不是一类;悲的是它的结果并不尽如人意,除了最后一跳能获得 IP 之外其他所有的报文都显示超时。
我开了 WireShark 抓包,却发现有很多明明是有返回的 Time-to-live exceeded
数据包,但套接字的 recvFrom 就是获取不到。
于是我以为是传入的参数问题,找了半天,无果;又以为是 Windows 11 改动了底层套接字的配置方案(参考的这篇文章是 2020 年的),就到处去找有没有相关的资料的时候,得到的结果只能用完完全全的一无所获来形容。一筹莫展之际,我打算换一种语言试试。
我想到了 python ,想着大不了打一个大一点的环境包,也不是不能用。恰好 python 上有一个操作库支持路由追踪,那就是 Scapy 。不过很可惜的是,我找了各种文档各种 blog ,似乎人们总是很喜欢把官方那语焉不详的文档拿出来用中文翻译一遍,再贴上一些看似运行结果都一样的没有上下文的代码残肢,但对于怎么好好使用这个路由追踪功能来完成一件事,实在没有搜罗到什么有价值的信息,就又只好作罢。
之后搜索到了 nodejs-traceroute 这个库,发现它用了一个巧妙的技巧来实现路由追踪,即调用系统本身自带的 tracert 功能,接收其返回值来用于构建结果。当时的我基本已经处于在无效的信息海洋中翻滚的状态,也没想那么多,就只希望能尽快完成这个任务了。但具体为什么没有选择这个实现方案,则是和后文要提到的图形界面有关,等会再去读吧~
总之,当第二天我迷迷糊糊随搜乱翻的时候,看到了 rust 实现 tracert 里对于 Windows 用户的提示:
You may need to set up firewall rules that allow
ICMP Time-to-live Exceeded
andICMP Destination (Port) Unreachable
packets to be received.netsh
example
1
2 netsh advfirewall firewall add rule name="All ICMP v4" dir=in action=allow protocol=icmpv4:any,any
netsh advfirewall firewall add rule name="All ICMP v6" dir=in action=allow protocol=icmpv6:any,any
我当时完全没有想到防火墙竟然会拦截这些入站的请求包,而 WireShark 之所以能捕捉到可能是因为使用了 WinCap 进一步降低了层级,所以才能捕获到网卡上的纯数据报文。本着试一试的心态,我执行了上述的代码(需要使用管理员权限),结果完全可以用惊喜来形容:
至于 Windows 自带的 tracert 为什么能绕过这个限制,我需要研究研究文章末尾提到的 WinMTR 再说。 因为它调用的是系统提供的动态链接库接口来实现的,而不是手动构建请求报文。 NyaTrace 已经更新了它的路由追踪算法,现在可以不需要加防火墙规则啦 ♥
基础功能实验成功之后自然就进入到了下一个模块:图形界面。由于最先实验成功的是基于 NodeJS 的包,所以我就想基于它来试试。因为嫌弃 Electron 的资源占用不尽如人意(为了跑个路由追踪容易吗我!),我选择了 nodegui ,并且想试一试它的 React 封装 React NodeGui 。不过当我兴冲冲初始化样例项目,却发现编译器提示错误的时候,算了算了就还是老实点去看基础的用法吧~
于是就用回了最原版的 nodegui 用法,发现它其实调用的是 Qt 引擎库,所以和 Qt 的操作有点相似之处;经过一段时间的折腾,成功拼凑出了一个基础功能还算完整的窗口界面:
运行成功!趁热打铁,写完追踪与内容填充逻辑,点击运行,输入地址,按下开始按钮——
友谊的小船说翻就翻。
意识到这条路可能走不通之后,我又返回去研究其他的解决方案,直到后来解决了防火墙导致的包超时问题之后,还是选择了 C++ 作为开发主要使用的语言。
这时候就会进入下一个议题: C++ 的 GUI 库那么多,选择哪一个更好?
学生时代我没少写过 C++ ,也因此稍微接触过 MFC 、 MSVC 与 Qt 这三大经典图形界面库。虽然本项目的开发主要目标是 Windows 平台,但很有可能在未来的某个时间我会将所有的开发环境迁移到其他的平台,例如 Linux 或是 macOS 上。因而为了能确保未来的兼容性,还是选择了 Qt 作为图形库。并且 Qt 可以手捏 UI ,这对于我这种想要偷懒的开发者而言算得上是非常友好了。
但 Qt 本身并不友好,因为它是一款价格极其高昂的商业解决方案(只有企业版和专业版两种付费租赁方案,专业版只比企业版便宜了 8% 这摆明了就是卖企业版 395 USD 每月
啊),免费使用的社区版本只有基础的功能和资源,并且受到其开源许可证的限制。不过对我来说实现功能更重要,也不需要担心开源问题(这个项目本来就是要开源的,我写的东西基本上都开源),所以并不存在这些纠结。
担心 Qt 6 拿开源社区当试验场的行为可能会导致一些意想不到的问题,我使用的是 5 LTS 版本。
事实上这个决策很英明,因为 Qt 6 还没完成 QtLocation 和 QtPositioning 等地图相关组件的迁移工作,所以如果当初选的是 Qt 6 的话,现在的地图功能就加不上去了。
简单拼凑了一下 UI ,然后迭代了几版,截至发稿的时候长这样:
走的依旧是极简风格,把涉及到的功能组件放上去就是了。以后可能还会加一个地图功能,不过现在就先这样吧。
LOGO 使用的是 Nucleo 图标库里面,选择了一个 world-marker
图标,将图钉的颜色从红色改成了我们标志性的蓝色 62b6e7
做出来的,没什么技巧。
在开发的时候我遇到一个问题:路由追踪是一个连续且阻塞的过程,如果把追踪的流程函数放在主线程里,通过按下按钮启动,那么在直到结果出现之前,渲染主线程会一直保持阻塞状态,导致程序交互卡顿,且系统会提示程序未响应,无法完成拖动窗口等操作。
Qt 针对这种情况,设计了 QThread 类以方便地管理后台线程的任务,只需设计一个继承 QThread 的类,将会导致阻塞的操作放入 run() 函数中,通过在主线程调用 start() 函数就能启动。
需要注意的是子线程不可以调用 UI 执行变更操作,需要通过 signals 信号槽将处理的结果 emit 给主线程,让主线程来执行 UI 的变更。
Qt 默认的界面排布模式会让窗口放大缩小时其中的组件无法跟着变化,因而变得非常丑。
我在设置排布模式为栅格模式( Grid )之后它自己就解决了缩放问题,就很舒适。
MaxMind 的其他语言( nodejs , go 等)的客户端 SDK 封装得都很不错,我也以为 C++ 上的客户端会很方便好用,但我忽略了 C++ 并不存在的包管理系统的问题。
官网给出的样例代码里的示例为 C# ,使用 NuGet 进行包管理;但 C / C++ 并不能以同样简单的方法使用,所以很尴尬地只能去找其他的操作方案。
有趣的是,其实官方是有开发 C 操作的客户端的,罗列在 GeoIP2 and GeoLite2 Database Documentation 的 Official API Clients 段中,为 libmaxminddb ,但它看起来似乎需要构建安装,并且似乎并不对 Windows 平台非常友好的样子。
因而还是求助于万能的搜索引擎,但依旧没有什么收获,得到的信息看起来似乎都只是在 Linux 上的构建安装与开发操作,这让我感到很无奈。
其实这时候已经比较疲惫了,有点想放弃,但本着死马当活马医的摆烂心态,直接无脑将项目仓库里的代码文件和头文件加到 NyaTrace 项目中。可能是因为开发者本来就是作为多平台兼容的方式开发的,直接这么使用不但没有报错,而且还省去了编译动态链接库再连接并打包的繁琐流程,这不禁让我大为振奋,甚至好像有点忘记了此时的时间早已是深夜。
但还没高兴透,新的问题出现了:我应该如何调用其中的操作函数?查询了一些中文资料,其不外乎都是把匹配到的 IP 地址所有的信息可视化打印到标准输出,而这严格来讲并不符合我的需求,所以又还是求助于官方文档。
好在官方文档相对较为详细地描述了如何读取数据的调用操作,即先获取完整的 Map Object ,再通过层级 K-V 去选择其中需要的键。
先按照文档和各种资料提示的 dump 用法,取出来所有的数据:
数据很长,这里就只罗列一点点。
按照其中的键层级顺序,使用 MMDB_get_value
函数读取,最后需要填一个 NULL (不太明白为什么,但不填就取不出来):
我取到了需要的字段。很快我又发现了新的问题,即这些字符串本身并没有使用 \0
作为结尾,导致引用头拉出来的字符串超长,包含了很多无效的数据。
我选择求助于上面那个能正确打印的 MMDB_dump_entry_data_list
函数——阅读其中的代码发现,它使用了 data_size
来规定字段的长度,在提取数据时新建一块空间,并将完整的字符串复制过去,填充尾0后返回。
本着同样的思路,我调用了包含这个操作的头文件,却发现由于 C++ 下对于指针类型的定义比 C 严格,原本正常执行的函数此时出现了类型不匹配的报错。并且更糟糕的是, Windows 上似乎并没有实现这个字符串处理函数(也可能是我没看到)。没有别的办法,那就复制过来,对指针执行一次强制类型转换,作为一个独立的工具函数存在。
此时的代码已经变得一团糟,但好在各个模块各自负责的部分没有出现什么冲突,功能还都算正常,所以也就草草混杂在一起打包提交了。后续又执行了一些优化处理,将 IP 读取的调用封装成一个 IPDB 类,在追踪线程启动的时候同时构建这个类,以便在执行过程中执行对象级别的调用,可以方便后续可能的操作升级或是接口分离等等。
到这时, NyaTrace 的基础功能已经基本整理完成了,因此也就有了这篇贴文:
这一块就是标准流程了:
build-项目名-构建环境-Release
命名的工作环境,进入其中的 release 子目录,将构建的 .exe 文件拿出来放到一个空目录中windeployqt 可执行文件名.exe
指令,让命令行将需要的动态链接库等文件复制过来(或是生成出来)。蛮多东西的,本来小小一个程序一下子多了一堆运行环境(但依然比 Electron 和 Java 轻就是了)。需要注意的是因为我们在使用的时候需要用到 GeoIP2 作为查询依赖 ,所以最好在发布的时候就创建一个名为 mmdb 的空目录,方便指引用户放置数据库进去使用( MaxMind 的用户协议是不允许在软件打包时带上他们任何的数据库产品的,并且考虑到数据库的时效性,让用户自行下载最新的更好)。
因为它使用的是异步并发发包的思路,而不是这里实现的同步顺序发包,因而很快能出现对应的结果,超时部分最多也只会触发一轮。
因为它不但使用同步顺序发包,而且每一跳都会发三个包,并且出于某些不知名的原因它即使是三个成功包也会等上几秒;加上有些没法回应的中继就会连续三次都是请求超时,一跳就要吃掉 3 * 3 = 9 秒,所以自然就会显得很慢了。
不过重复发包也有一个好处,就是有些时候中继并不是完全不回包的,而如果刚好能在当它愿意发回包的时候成功接收到了,就能获得它的 IP 地址了。
开发完成后,偶然间找到了 WinMTR (Redux) 这个项目,应该可以作为进一步开发路由追踪的核心功能可以使用的参考。
虽然古老,但是好用, Windows 强大的兼容性确实可以(溜
以及它似乎可以无视防火墙的规则,这就更值得好好深入研究一下了!
如果本地有原始代码,那尚且还有一救,利用其他的平台重新开始;如果备份已然丢失,那么将宣告的是代码的完全湮灭。前有 youtube-dl 被封导致的群情激愤(虽然已经恢复),后有 ncmdump 被一纸 DMCA 摧毁带来的生态进一步封闭、矛盾进一步激化,中心化的服务,面临着的不光是各地法律的约束,更有各种敌对势力可能借助平台运营行为带来的打击。本着数据无价、自建为王的原则,在经过多方比对 Bitbucket 、 Gitea 与 GitLab 等平台的相关优缺点后,我决定自建一个 GitLab 实例,成为自己最信任的代码托管平台。
Bitbucket 自建的方案称为 Bitbucket Server ,但已经于 2021 年 2 月 2 日停止销售,并将于 2024 年 2 月 2 日停止支持,因而直接不纳入考虑范围。其他的一些开源平台,则因为有些 UI 过于古早,存在审美和逻辑交互上的障碍,所以不予考虑。最终的目光聚集在了 GitLab 和 Gitea 上,并基于多方的资料收集、比较,确定了 GitLab CE (Community Edition) 作为搭建的基础。
选择 GitLab 并不是因为 Gitea 不完美,正相反:使用 go 编写的 Gitea 更轻量、更快速、也更符合“搭建一个代码托管平台”最初的愿景;但正是因为它的轻量,导致它并不是一个一应俱全的产品,更多的需要依靠其他产品的结合:比如 TeamCity 提供 CI/CD 流水线,比如手搓容器镜像管理平台,比如暂时不知道能不能配置的外部存储和 Git LFS 存储支持等等。
虽然作为一名 JetBrains 全家桶用户对 TeamCity 这个方便实用的 CI/CD 平台有种天然的好感,但由于它是专有软件,免费使用的版本有严格的限制(3 个 Agent , 100 个构建配置),因而并不是一个足够打动人的产品。而没有 GitLFS 和外部存储的支持,也意味着并不能存储可能出现的大文件,也许未必会使用到——但当有所需要的时候,这些种种为了轻便而舍弃的功能会成为一块块前期埋下的绊脚石。而容器镜像的管理,一个直观的界面能带来的效率提升是再多的命令行调用都无法带来的。 Docker 官方没有提供管理面板,而一些第三方的管理面板也大多古老陈旧,所以也就并没有太多的选择余地。 Docker Hub 也存在 Hub 的问题,并且由于它是一款商业产品,免费版的体验,不能说所有人都会很糟糕吧——只能说是见仁见智了。
对应的,使用 Ruby on Rails 编写的经典代码托管平台 GitLab 则是一系列服务应有尽有,代价是更高的资源消耗—— Ruby 本身内存管理就受到诟病,而 Rails 使用 Ruby 的方式更是让内存成倍上涨,随手一搜「gitlab memory」就会有大量关于 GitLab 内存处理与优化相关的资料。
但这并不能阻挡 GitLab 成为一个优秀的产品。如同那句经典的自嘲:“贵不是商品的错,而是我穷的错”类似的道(歪)理,只要经过精心调整配置、在准备充足资源的环境下,相信 GitLab 能发挥出完美的水平,成为一位忠实可靠的 DevOps 伴侣。
之所以选择 CE 而不是 EE (Enterprise Edition) 进行搭建,是因为 GitLab EE 为专有软件,而基于 MIT 协议开源的 GitLab CE 已经足够满足我的需求,因而可以算是一种开源爱好者的精神追求吧——总觉得大差不差情况下,开放的比闭源的会更好一点。
「极狐」是 GitLab 中国大陆和港澳地区的特供版,所以在国际开发环境的内容部署上,还是不要沾染这些地区特供的版本了吧。
根据 GitLab installation minimum requirements ,对于 GitLab 服务器需要的资源,以承载 500 名用户为例,官方的推荐是 4 核处理器、 4GB 内存与 至少 20GB 硬盘存储空间( GitLab Omnibus 本体 + PostgreSQL 数据库 + 一些其他的存储需要)。
对于 GitLab Runner (CI/CD 执行时使用的容器) 所需的服务器资源,由于不同的项目有不同的消耗,所以并没有一个统一的规格。作为参考, GitLab 提供的 SaaS 服务上使用的 Runner 规格为 1 核 CPU 与 3.75GB 内存。
由于 Runner 的运行具有突发性和不确定性,在生产环境中请一定避免将 Runner 与 GitLab 安装在同一环境下。
我为 GitLab 本体准备的是一台 6 核 AMD EPYC 7282 处理器, 16GB 内存, 600GB SSD (500+GB 可用)的 VPS 。为 GitLab Runner 准备的是一台 4 核处理器, 8 GB 内存的 VPS 。
为了进一步优化站点的使用体验并提升站点安全性,我为站点启用了 CloudFlare 提供的 CDN 服务。
其中, CloudFlare 有一款名为 Spectrum 的产品可以提供针对 SSH 的保护,但仅在 Pro 及以上的方案中提供。因而我为即将用于代码部署的域名订购了 CloudFlare Pro 。但有一点需要注意的是, CloudFlare Spectrum 与一般的域名代理功能是有冲突的,它们不能运行在同一个具体域名下;因而对于 SSH 连接需要使用一个子域名,此处我选择的是 ssh
。
为进一步保护源站安全、避免绕过 CloudFlare 直接对源站进行访问的流量经过,我在 CloudFlare 上配置 SSL/TLS 加密模式为 完整(严格)
,并开启了 Authenticated Origin Pull
功能。其中使用到的服务端证书可以在 Zone-Level — Cloudflare certificate 获得,在文后的附件部分也会附上该证书文件。我还为源站服务器设置了防火墙规则,具体的内容在后面的部分会有涉及。
另外,为了避免防火墙阻挡 Runner 与 GitLab 的通信,需要在 防火墙 -> 防火墙规则 中为 GitLab Runner 设置允许绕行的策略。我的设置方案为 UserAgent 包含 gitlab-runner
字样的请求 允许 通过。
为了尽可能简化部署过程、减少系统不同导致的部署细节差异,我使用了 root 权限下的 Debian 11 作为主操作系统,使用 GitLab 官方提供的 CE 版镜像,配合 Docker + Docker Compose 方案进行部署。
首先安装 docker 。使用以下的指令需要您已经准备好 curl 命令行。如果没有,您可以先安装它。
1 | bash <(curl -L -s https://get.docker.com) |
安装完成后,下载使用二进制预先构建的 docker-compose 可执行文件
1 | curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose |
我参照的是官方的 Install GitLab using Docker Compose 提供的参考配置,并结合 swarm mode 的配置进行了一定的修改,以方便使用,并能进一步优化后续可能出现的升级、更新等操作时的流程。我使用了外置配置文件并映射进入容器的方案,这样当有配置更新时,直接在容器内执行 gitlab-ctl reconfigure
即可。
gitlab 的 docker 镜像使用的是 Omnibus 搭建方案,已经集成 PostgreSQL 与 Redis 数据库(尽管版本比较老旧,如 GitLab 14.7.3 的内置 PostgreSQL 为 12.7 ),因而无需像传统的原子应用程序那样手动配置外置的数据库。而在正式的企业生产环境中,更为推荐的做法是使用 kubernetes 部署 GitLab 的各个组件,并单独设定外接的 PostgreSQL 与 Redis 数据库。
最终获得的 docker-compose.yml 文件如下:
1 | version: '3.6' |
需要注意的除了 gitlab-ce
外,由于需要使用的是 HTTPS 协议,因而此处没有暴露容器的 80 端口;并且将固定使用的 SSL 证书单独存放到一个只读的目录下。
GitLab 官方文档中推荐的共享内存大小为 256MB ,在此我设置为了 4GB ,以便更好地利用系统资源,优化使用体验。
毋庸置疑,这是所有步骤中最为关键的一步,也因此我花了一整个周末的时间来仔细研读每一块配置内容的含义。此处的配置文件,也将以脱敏后分块的方式呈现,以方便您更好的阅读和理解。完整的配置文件将会附在文章末尾,您可以直接以此为模板进行修改。
此处先附上官方配置样例的链接: gitlab.rb.template
此处标注了 GitLab 运行实例的外部主域名,和 SSH 使用的主机名(在项目内提示 SSH 连接时使用)。由于 CloudFlare Spectrum 与直接 HTTPS 代理并不兼容,因此只能无奈让 SSH 使用 ssh
子域名进行连接。
1 | ## Main URL |
我使用了亮蓝色作为默认主题色。
1 | ## Default theme |
附上具体的颜色列表:
ID | 英文原名 | 中文名 |
---|---|---|
1 | Indigo | 靛蓝色 |
2 | Dark | 深色 |
3 | Light | 亮色 |
4 | Blue | 蓝色 |
5 | Green | 绿色 |
6 | Light Indigo | 亮靛蓝色 |
7 | Light Blue | 亮蓝色 |
8 | Light Green | 亮绿色 |
9 | Red | 红色 |
10 | Light Red | 亮红色 |
由于 Gravatar 在大陆的访问速度不佳,因此我们需要使用反向代理,以便让用户能正确加载头像。当然,此处的设置也可为兼容的其他服务,如 Libavatar ,或是自建的 邮箱 - 头像 映射服务等。
如果您没有反向代理或是其他服务的需要,请不要添加这些行,使用默认的 Gravatar 即可。
1 | ## Gravatar |
我使用的是自建的 mailcow: dockerized 邮局服务,因而使用的是以下的配置方案。 GitLab 官方根据不同的主流邮局系统给出了对应的配置选项,可以参考官方文档中的 SMTP settings 部分。
1 | ## SMTP settings |
GitLab 使用接收邮箱来方便用户进行一些流程,例如 使用邮件回复 issue 、使用邮件开启 issue 、使用邮件开启新的合并请求、通过邮件提供帮助 等。
此处我设置的是使用 mailcow 的 IMAP 协议进行邮件处理的方案,您也可以根据官方文档中的 Incoming email 部分配置参考来设置您的邮件服务器。
1 | ## Incoming settings |
使用外部存储能有效控制在 GitLab 服务器上的文件大小,并能提升其运行的稳定性,在升级和迁移时都会变得更加方便。
我使用的是 Wasabi 提供的服务,不同的服务商的配置可能会有所不同,请根据您的服务商的相关配置方案进行配置。
注意这些模块的存储细节可以在后续单独的字段中被单独配置,此处只是提供了一个整合管理的方案。
1 | ## S3 Storage settings |
Wasabi 虽然能提供全网最便宜的 S3 兼容存储解决方案 ($7/1TB/1月) ,但它有最小计费起始,并在文件删除后仍会持续计费 3 个月,所以见仁见智吧。
这个功能可以让管理员暂借用户身份,从管理的角度讲保持开启也许能更方便,但从安全的角度来讲还是关闭比较好。
1 | ## Impersonation settings |
备份是最重要的,所以这一块在全部设置完后可以使用 gitlab-backup create
指令创建一个备份文件进行调试测试。我使用了与之前同样的 S3 存储方案,因而设置如下:
1 | ## Backup Settings |
请注意 "SKIP"
的内容,默认设置中会跳过对数据库和代码仓库的备份,但在本文所述的配置环境中这样做可能会有安全隐患;而 terraform_state 和 packages 已经存储到外部存储中,所以不需要备份,因而进行了如上文的修改。
完整的备份列表可以参见 Excluding specific directories from the backup
Matomo (原名 Piwik )是用于统计站点访问量信息的一个开源可自建的 Google Analytics 替代方案,而 Sentry 是一个用于收集错误信息的开源可自建项目。如果您有对应的需求,您可以在这里配置 Matomo 和 Sentry 。
1 | ## Matomo |
Docker 模式下部署的 GitLab 容器镜像仓库使用外部存储(S3)是一定会失败、无法工作的,具体的解决方案还有待进一步的研究调试。
GitLab Omnibus 部署方案集成了容器镜像仓库的功能,可以通过类似如下的配置来启用它:
1 | ## Container Registry settings |
GitLab Omnibus 部署方案会使用一个 nginx 作为整体的反向代理整合,以保证统一管理所有的流量出入,并提供了一系列较为精细的配置选项,因而没必要再在前端追加一个 nginx 作为反向代理,我是直接让容器直接监听在 443 端口上的。
因而需要使用系列的参数来配置 nginx 。不同部分的需求已经分散在不同的模块中,此处仅指出一些与 CloudFlare 最紧密关联的配置:
1 | ## Built-in NGINX settings |
由于前面加了一层 CloudFlare 的 CDN 作为反向代理,因而此处关闭了使用 Let’s Encrypt 获取证书的功能,而是使用了 CloudFlare 签发的供源站使用的证书;并且开启了 Verify Client 功能,以避免绕过 CloudFlare 的请求(比如端口扫描)成功访问。后面还有一节是配置防火墙规则的。
开启 ssl_verify_client 会导致容器本地健康请求失败变成 unhealthy ,这是正常现象,可以通过公开的健康状态请求端口(可以在 管理中心 -> 监控 -> 运行状况检查 (/admin/health_check) 看到)获取最真实的信息。
GitLab Pages 是一个静态页面提供服务,但是在我们现在这样的配置下第三方的域名如果不经由 CloudFlare 估计不是太好访问,因为它不知道服务器的真实地址因而无法解析,而且会被防火墙规则拦截请求。但依旧可以提供这项服务的配置,方便加入了代理的域名经由的请求(我还没试过):
1 | ## GitLab Pages |
全称 Kubernetes Agent Server ,这是用于将一个 k8s 集群连接到 GitLab 进行 CI/CD 的服务。虽然不太明白,但还是将其配置好:
1 | ## GitLab Kubernetes Agent Server |
Mattermost 是一个开源可自建的在线聊天服务,主要用作 Slack 与 Microsoft Teams 的开源替代品。 GitLab Omnibus 集成了 Mattermost 的服务端,并支持让 Mattermost 通过 OAuth2 授权来获取用户的信息,实现 SSO ( Single Sign On ,单点登录 )的功能。
Mattermost 需要的应用 ID 和 私钥 并不会像 Grafana 那样在 GitLab 初始化的过程中被自动创建,因而需要在初始化完成后去 GitLab 管理员控制台手动设置,再在加入配置文件后重新生成 GitLab 配置。估计是一个 bug 。
并且 Mattermost 需要访问 user API 端点,因而需要授权 read_user 权限,而不是像官方文档描述的那样不需要授予任何权限(其实是因为不授予任何权限会报错不让创建应用)。
1 | ## GitLab Mattermost |
这个功能让团队可以不用其他任何工具,通过邮件直接与外接进行沟通交流。可以这样配置:
1 | ## Service Desk |
GitLab 默认会定期将实例的数据上报给官方的开发团队,以便于数据分析和用户体验优化。但是考虑到这种数据上报的行为可能会影响到开发者代码的隐秘性与安全性,所以在此处关闭了这个功能。
如果您没有关闭这个功能,您可以在 管理中心 -> 设置 -> 指标与分析 (/admin/application_settings/metrics_and_profiling) 中的 使用情况统计 展开栏下选择 “启用注册功能” ,以免费享受一些稍微进阶的功能。
1 | ## Disable Service Ping |
默认情况下, 由于监控的数据采集点为容器,默认的配置参数并非宿主机因而没有意义,因而 docker 运行环境时会默认关闭节点数据采集功能(参见 commit/891444 ),因而导致失去对节点状态的感知, grafana 中也一片空白。但可以通过指定 node_exporter 监听路径的方式来实现节点监控,其中映射所需的路径已经在 docker-compose.yml
中完成了只读模式的映射,以下是启动该功能并指定正确的 node-exporter 运行参数相关的配置:
1 | ## Enable node_exporter |
根据配置文件中的描述,将所需的 SSL 证书放置到了 ./ssl
目录下。
如果您使用和我一样的配置方案策略,那么放置完证书后的目录结构应该像这样:
1 | . |
到此, GitLab 应该已基本配置完成,您可以使用 docker-compose pull
拉取最新的镜像,并使用 docker-compose up -d
启动它。
GitLab 由于体系庞大、组件繁多,且初次启动会进行大量初始化操作,因而启动起来比较缓慢;当启动完成后,您即可进入系统进行体验。
默认的账号为 root
,默认的密码由初始化时生成,您可以通过 docker-compose exec gitlab grep 'Password:' /etc/gitlab/initial_root_password
命令获得。请在 24 小时内获得您的密码,出于安全因素考虑,它将在启动 24 小时后被删除。
到此 GitLab 主体的安装与配置部分基本接近尾声,我们使用防火墙为服务器的安全进行进一步的加固处理。我的习惯是使用 iptables (尽管它已经被 nftables 取代)。
我使用的这个服务器上默认没有安装 ipset 软件包,所以需要安装一份。
1 | apt install ipset -y |
然后是创建 IP 集规则,以便用于管理来自 CloudFlare 的 IP 。
1 | ipset create cfv4 hash:net |
其中第一条指令创建了一个名为 cfv4
,类型为 hash:net
的 IP 集。名称可以随意,类型推荐为 hash:net
,因为 CloudFlare 提供的 IP 信息是以 CIDR 格式列出的。
第二条指令从 CloudFlare 提供的 IPv4 地址列表中提取出 IP 地址,并将其添加到上一步创建的规则集中。
第三条指令将规则集保存到文件中。因为 ipset 是一个运行在内存中的记录列表,默认情况下会在开机时自动丢失,因而需要手动执行一次保存动作,以便在重新启动系统时能够手动执行恢复。
对 IPv6 也是一样的处理流程,只是需要进行一些显式的指定:
1 | ipset create cfv6 hash:net family inet6 |
如果您使用的是直接部署的方案,您可以使用以下的代码:
1 | iptables -I INPUT -p tcp -m multiport --dports ssh,http,https -m set ! --match-set cfv4 src -j DROP |
1 | ip6tables -I INPUT -p tcp -m multiport --dports ssh,http,https -m set ! --match-set cfv6 src -j DROP |
如果您像我一样使用的是 Docker 进行部署的方式,请注意 Docker 容器暴露到宿主机的流量是不经由 INPUT 表的。 Docker 自己会管理一张 DOCKER 表,而对于我们用户所需要的配置,需要手动在 DOCKER-USER 表中指定具体流量的通信与拦截规则:
1 | iptables -I DOCKER-USER -i ext_if -p tcp -m multiport --dports ssh,http,https -m set ! --match-set cfv4 src -j DROP |
ext_if
的意思是外部网卡( external interface ),例如您的外部网卡是 eth0
,那么您需要将这个值替换为 eth0
,否则防火墙规则不会生效。
如果不加 -i
选项,那么防火墙规则会对所有网卡生效,在此处的表现为容器的出站连接( HTTPS 、 SSH )也会失败,导致外部存储失效等一系列疑难杂症(表现状况为连接超时)。
Docker 默认不带有 IPv6 支持,因而此处略过了对 IPv6 规则集的配置。如果您有相关的需要,您可以使用类似的命令自行修改。
iptables 同样默认不带有持久化,因而需要显式执行备份命令,以便在重新启动系统时能够手动执行恢复。
1 | iptables-save > ip4tables.txt # 可以使用 iptables-restore ip4tables.txt 来恢复 |
这一个部分其实我也不是太明白,主要记录一下我的方案:使用 k3s 部署本地 k8s 环境,使用 helm 管理镜像,让 GitLab Runner 在 k8s 环境中运行。
这里的指令因为调试了好几天已经混乱了,可能有误,仅供参考。
k3s 和 helm 的环境可以这样安装:
1 | bash <(curl -L -s https://get.docker.com) # 安装 docker |
官方文档可以参见 GitLab Runner Helm Chart ,主要的配置文件 values.yaml
可以从 values.yaml 获取。
为了避免我比较时候有什么疏漏,我会将完整的配置文件作为附件附在文章末尾(当然也有可能就是错的)。需要修改的部分有:
gitlabUrl
部分,请取消注释并设置为您的 GitLab URL 。runnerRegistrationToken
部分,请取消注释并设置为您的 GitLab Runner 的注册令牌 (可以从 GitLab 的 管理中心 -> 概览 -> Runner -> 注册一个实例 runner (右上角) 获取)unregisterRunners
部分可以取消注释,这一个选项的意思是当一个 runner 结束时,会自动从 GitLab 中注销,这样就不会出现 GitLab 中有多个过时的失效 runner 的情况。concurrent
部分,根据您的实际服务器配置调整最大并发运行的任务数量。sentryDsn
部分,如果您需要为 Runner 启用 Sentry ,请取消注释并设置为您的 Sentry DSN 。create
部分需要设置为 true ,否则会出现 Runner CI/CD 容器没有权限导致运行失败的问题。rules
后面的方括号(使用下面的数组规则)privileged
部分,请取消注释并设置为 true 。但出于安全性考虑,建议这里保持 false ,并使用上述标注中提到的文档中介绍的使用 Kaniko 进行优化权限构建的方案(参见 Best practices for building containers without privileged mode )。然后这样启动 Runner :
1 | helm repo add gitlab https://charts.gitlab.io # add gitlab charts |
如果一切正常,那么您将可以在 GitLab 的控制台中看到一个 Runner 实例。如果不正常,那应该是我记错了什么。
到此,所有的基础配置与安装工作已经完成。
产品 | 规格 | 功能 | 价格(按年付计算) |
---|---|---|---|
域名 | - | 服务域名解析 | $37.05 |
CloudFlare | Pro | CDN 与 SSH 保护 | $240.00 |
GitLab | CE | 代码托管 | $0.00 |
服务器 | 6C16G | GitLab 服务器 | $143.88 |
服务器 | 4C8G | Runner 服务器 | $83.88 |
Wasabi | 按 1TB 计算 | S3 静态资源存储服务 | $84.00 |
年预计运行总开销为 $588.81 ,约合人民币 ¥3740.36 元(汇率按照截稿时的 6.3524 计算)。
当然,这是一个出于生产环境稳定性考虑的比较大规模的部署模式,需要相对高昂的各项服务费用;如果仅是使用小服务器 + 直接连接的方案,那么在只有服务器费用的情况下,将年开销控制在千元以内是完全没有问题的。而如果是个人纯代码托管、无其他需要的方案,使用 Gitea 甚至能将成本控制在百元以内。
1 | -----BEGIN CERTIFICATE----- |
1 | ######################################### |
1 | ## GitLab Runner Image |
eihei.net
经历了一次建站以来最严重的宕机事故,最长的业务宕机时间超过 2 小时。虽然目前站点已经恢复,但由于其部署的服务器存在物理上的局限,当等到资金资源足够的时候,我们将考虑把所有的相关服务全部撤出目前的这台垃圾服务器,从而避免再出现像今天这种算是负面机缘巧合导致的意外宕机事件。关于本次的宕机,我们将原因归结于以下几点:
以下的部分,将根据时间顺序,依次梳理本次事件的全部过程。
事实上这并不是第一次出现意外离线的情况了,在 4 月中旬开始,站点就会每隔几天不定期出现宕机或是反应缓慢的情况,有时会被状态监测系统捕获,有时则只是用户主观感受,并未出现严重的连接超时情况。
根据监测系统的记录,近段时间站点的平均反应时间接近了 1.5s 。
而我们的主实例站 nya.one
的平均反应时间则只有 300ms 左右。
虽然我们觉得可能是和地理位置有关,但同样位于欧洲的静态文件存储桶,则只有 125ms 左右的平均响应时间;也曾怀疑是否和 Mastodon 的高消耗占用有关,但根据 o3o.ca 的响应时间来看,最大也不过只有 400+ ms。即使是考虑 OVH 的网络,但也有不少实例(包含 nya.one
)承载于 OVH 网络之上,但从未听说有什么奇奇怪怪的情况发生。并且作为国际级的大型服务器提供商,OVH的网络也完全不会成为瓶颈。
考虑到这台服务器的处理器是 AMD Opteron 6128 ,一款 11 年前发布的极致典藏古董处理器,我们觉得主要的原因,应该还是由于服务器性能严重不足引起卡顿,进而导致愈发严重的丢包直至失去响应的情况发生。
再加上硬盘阵列曾出现过爆炸导致服务器失联、重启锁盘的情况,我们也怀疑有很大可能,是因为这些破烂硬盘出现了难以挽救的糟糕问题。
直到今天,我们觉得这样的情况应当要进行介入处理了。 Mastodon 即将发布 v3.4.0 正式版,我们也应当为这个大版本更新做好对应的准备工作。所以当卡顿再次出现的时候,我们在控制台上输入了重启的指令。而这,则是一切噩梦的开始。
输入 reboot
后,意料之中的 SSH 连接中断并没有很快出现。虽然大概有些怀疑,但这时的我们并没有意识到问题的严重性,还以为只是网络丢包导致断开连接的数据包没有及时返回。但几分钟后,当尝试开启一个新的连接时却发现根本无法连接,提示连接超时,不得已,登录到服务商提供的 VNC 上试图检查,发现事情隐隐约约有些不对劲之处:
服务器正在运行关机例程
包含但不仅限于结束容器服务、回收系统资源、保存随机数种子等等一系列平时难以见到的底层操作。而这反应速度,我甚至一度怀疑我是把我开发板上的系统连接上虚拟机了。
惊讶之余,更多的是懊恼为何没能及时意识到此事件的发生——早该在服务器失去响应的时候就料到它关机失效的。现在说什么都晚了,一时也没想到什么方便的通知方式(并且也来不及了),所以就一边罗列着待办事项,一边顺带等待服务器的重启完成。
经过十多分钟的艰难等待, VNC 的屏幕变黑了。
本以为至少开机的负载不大,希望能够稍微顺畅一点,但可能好巧不巧的正赶上其他用户疯狂挤占 CPU 的最后一丝性能,连试图加载出可以引导的选项,看上去都是那样的艰难苦困。甚至不知为何,它加载到了空无一物的虚拟光驱,之后便是光标闪烁的无尽空虚。茫然无措的我们,只能寄希望于界面上唯一的 “Send CtrlAltDelete” 字样,试图用跨越世界的力量,唤起虚拟机最后的一丝良知——很可惜,它的灵魂早已被支配,留给我们的,只剩下一副双目无光的空荡躯壳。
那就从头开始,再试一次——一边这样想,我们去控制台向它发送了软重启命令。但失去响应的它,对于我们的呐喊毫不理会,依旧我行我素地一味闪烁着失去梦想的光标。
那就只能这样了!强压下心中的焦急与五味杂陈,我们向它发送了硬重启命令。
很可惜,毫无波澜。
然而,不知是冥冥之中产生了意念的指引,还是一次太阳风暴的偶然乍现,正当我们手足无措之时,不知为何系统突然开始尝试从硬盘引导系统。惊喜之余我们也关闭了工单页面的诉苦,掏出整理的笔记,开始准备下一步的恢复工作。
使用容器部署服务,对于无论是安全性还是可维护性、亦或是备份与迁移的方便程度而言,都不失为一种相当优异的方案。唯一可能的困惑,恐怕就只剩下多一层包裹是否会带来性能损失的担忧了。 Docker 很强大,但 2010 年可没有 Docker 。启动容器环境本身,对于现代的处理器来说完全不成问题;但对于过去的处理器而言,似乎成为了一个不小的挑战。诚然,这块 CPU 已经是 AMD64 指令集支持的型号了(我想这就是为什么这台服务器还没被淘汰的原因吧),但在发展日新月异的计算机面前,一个 decade (十年) 的代价实在是过于沉重,沉重到当年的辉煌,如今在轻轻的负载下即可成为一滩只会发烫的废铁。在 htop 窗口的记录中,仅仅是为了启动 Docker 的底层环境, CPU 的占用率就长期保持在了 100% 。
于是非常有幸地,我们观摩到了 Docker 底层启动的详细过程,包括平时不会注意的网络层驱动的初始化、容器附属环境的启动、系统相关事件的注入等等等等。同时随之而来的,也有大量设置 restart 为 always 的服务的跟随启动。至少在这突如其来的黑暗之中,我们看到了一丝希望的光芒。
由于服务启动实在过于缓慢,有大量容器因为依赖没有完成启动而启动失败,从而导致不断的尝试或是崩溃,进一步加剧了服务器的卡顿。平时一句简简单单的 docker-compose up -d
,如今竟成了压倒服务器的最后一行指令,绿色的 done 犹如施舍一般不情愿地出现,更多的仅仅只是卡在了暗红的 ERROR 便不再活动。于是只能一遍一遍地启动,哪怕每次只启动一项服务也好啊——这样想着,这样做着,这样重复着,直到不再有错误返回,终于能长舒一口气:至少,它们愿意动一动了。
世界上最遥远的距离,莫过于满怀着虔诚敲下了一行指令,那边便再无回音。只能一次又一次地刷新页面,试图在 502、521 等出现或是空白页面的错误提示中,寻找到那一丝有效加载的希望。一边是监测平台辅助我们进行监测管理,另一方面是我们手动的请求,怀揣着愧疚与虔诚,期待着希望与转机,F5 的起起落落与路由器上信号灯的闪烁见证着我们的决意。
netdata 的加载需要时间,而加载出界面之后还要获取数据,更需要时间;而当我们发现甚至连 netdata 都已经到了无法采集到所有数据的情况之后,好不容易悬起的一丝希望,又只能被狠狠摔在地上;
portainer 的加载需要时间,而加载出界面之后,等待进入 localhost 进行管理,则更是需要一大段一大段用于通信的时间;而当在无数次 504 Gateway Timeout 的错误被毅力克服后,我们得到的,却只有些许 stopped 、些许 starting 、些许 unhealthy 和终于能让我们稍微安心一些的些许 healthy 。
在漫长的等待过程中,我们因为到了饭点,去简单吃了个饭。
而当走在路上时, Slack 上传来的上线消息提示,对于我们而言,不光是这几个小时的成果认可,更像是一种温馨的宽慰吧。
到 5:18 ,所有的服务启动完成,我们准备开始检查是否出现数据的损失。
虽然,由于服务器还是非常不稳定,因而在突然的某个时刻,用于监视资源状况的连接也被断开了。
预想着顺带就做一下 Matrix 部分的升级工作,于是就把顺带着升级了一下。当然,这速度确实也是不能用的,因为实在是太卡顿了——上面的某两张截图,其实就是现在才截的。即使是这么多小时之后,它的性能照样是那么拉跨,一点都没有恢复。
我们退出了一个大型的中转实例,不知道会不会是因为那边带来的负载过高,导致服务器没法正常响应。
服务器还是那么烂,基本可以说是没法使用的地步了。说句难听的,我开发板上跑的嵌入式 RISC-V 的嵌入式 Linux,或是路由器里跑的自编译 OpenWrt 都没这么卡。但由于是测试性的社区,并且目前没有资金用于升级更新更强大的服务器,所以就先继续这样使用着吧。
有预料的,当 Mastodon 升级 3.4.0 版本的时候,又将会是一大场腥风血雨。
]]>大概在准备接下来的两个大项目,一个是 AinPanel 作为自研机场面板测试(其实是水数据库课程设计用的,顺带自建玩玩),另一个是 RSS3Go-Hub 用 Go 来实现 RSS3-Hub ,顺带试着学习一下 Go 的写法之类的。
这两个项目也都是希望能长期维护下去的,毕竟因为确实真的想做才去做的(而不是纯粹的学术垃圾),写好之后自己也会用。像自研机场面板这种就还是怎么喜欢怎么写吧,因为是针对 Xray 特化的面板,后端会用 XrayR 魔改,前端也希望能尽可能多地提供方便的配置这样。
用 Node 确实还是略显拉跨,这一版实现功能、交完作业之后就差不多归档封存了,2.0 版用 Go 重写后端。现在也是按照前后端分离的思路在做的,所以迁移起来应该问题也不大(吧)。
之后打算试着用 Go 为 Hitokoto-CN 做一个后端,可能会连接数据库,周边的功能慢慢追加上去这样。想做的项目还有很多呢。
加油!(ง •_•)ง
]]>安装 curl (以 Debian 系的 apt 为例)
1 | apt update |
安装 Docker 服务
1 | bash <(curl -L -s https://get.docker.com) |
安装 docker-composer
1 | curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose |
我由于是使用 root 账户进行创建,站点名为 eihei ,所以将目录分别建立在 /root/eihei/mastodon 和 /root/eihei/matrix 下。可以先仅部署这些目录,后续的随着服务的部署再依次创建。
我最终的文件结构树是这样的:
1 | ├── autoscripts |
清楚的目录结构对于无论是搭建还是运维,都能提供不小的帮助。
部分目录需要特殊的权限才能运行(例如 elasticsearch 在默认权限下会无限重启,这边偷懒直接使用了 777 权限这种),会在之后的配置过程中会一一阐明。
由于要使用 Mastodon 作为核心,我们的搭建工作先从 Mastodon 开始。
在搭建 Mastodon 之前,我为数据库指定了额外的环境变量以取代默认的用户和密码。因而新建了一个 .env.pgsql
文件,并在其中输入以下内容(请注意替换成您需要的内容):
1 | POSTGRES_PASSWORD=<Databse_Password> |
在 docker-compose.yml 的 db 段中引入它:
1 | db: |
这样当初次启动数据库时,就能自动创建指定的数据库,并赋予指定的用户名以通过指定密码访问的权限了。
由于服务器的运行资源比较充足,这一次的构建,我为 Mastodon 启用了 elasticsearch (以下简称 es )的支持;并根据官方的提示文档,为 es 进行了中文语境下的优化;并修改了 mastodon 中关于 es 支持的分词代码,构建了站点专用的镜像。
请注意, elasticsearch 在默认目录权限下会因无法读写而无限重启(估计 es 容器里的用户并不是 root),这边偷懒,直接使用如下的指令,赋予了 elasticsearch 目录以 777 权限:
1 | chmod 777 ./elasticsearch |
之后即可避免 es 无限重启。
先安装 es 的插件。安装插件时,我们需要给 es 以外网权限,因而在 docker-compose.yml 的 es 段 networks 中增加一项 external_network ,安装完成后注释掉,重新 docker-compose up -d 自动重构容器即可。
参考代码如下:
1 | # 在 /root/eihei/mastodon 下执行 |
其中,安装 elasticsearch-analysis-ik 时会有一个允许该插件访问分词器的确认,输入 y 。两个插件都安装完成后,可以重启容器。
如果安装过程中出现版本不匹配的报错,请检查参考代码中 6.8.10
是否为您的 es 版本号(截至截稿时, mastodon 官方的 docker-compose.yml 中使用的是 es 6.8.10 ,请注意版本匹配)
之后是修改 mastodon 中 es 分词器的相关内容,让它调用新安装的中文分词器,而非默认的空格分词。可以参照官方文档,也可以参照 我们站的改动 。
请不要从默认的 main branch 拉取代码!
main branch 虽然是默认分枝,但其实这个严格来讲应该算是 dev branch,拉取这个分枝的未完成代码很可能会导致您的站点陷入不健康运行状态 (unhealthy) ,并可能因此导致一些奇奇妙妙的问题。为了安全,建议您从 master 拉取代码进行修改。另外,您也可以从形如 stable-* 的稳定备份分枝拉取对应版本的代码,但是在截稿的时候,最新的 3.3 发布版似乎没有完成版本迭代,因而这个中版本暂时还没有稳定的分枝。
其他的内容基本可以参照 Mastodon搭建小记 ,只是这次配置完成后现在已经可以直接初始化数据库并创建管理员用户了。在正式启动之前,记得手动写出环境配置文件,并添加 es 相关的兼容项。
1 | +ES_ENABLED=true |
另外,关于 wasabi s3 存储的配置也需要额外修改一下,S3_ENDPOINT
推荐改成带有区域的完整端点路径, S3_REGION
也需要调整为实际内容存在的区域。
一切就绪后,使用 docker-compose up -d
来启动服务。没有意外的话,应该在几分钟后即可成功进入站点首页了。
Mastodon 有自带的安全心跳检测机制,通过向检测使用的端口发送数据来获取健康状态,因而可以在 docker ps
时看到健康运行 (healthy) 或是不健康 (unhealthy) 的情况。请注意如果出现 unhealthy ,您应当注意日志问题及相关的报错信息,不健康的站点运行环境可能会导致难以预料的结果。
附上一份我们的 Mastodon 配置文件,希望能为您的配置提供帮助。
1 | # Generated with mastodon:setup on 2021-03-16 15:14:43 UTC |
当 Mastodon 建立完成后,我们还需要构建反向代理文件,以让服务正式可以运行。此处使用了官方的 nginx 反向代理进行配置,此处顺带附上文件,请记得根据您的实际情况进行调整:
1 | map $http_upgrade $connection_upgrade { |
为了能建立 Mastodon 和 Matrix 的账号通用,经过许多天的文档研究后,我放弃了使用 LDAP 创建身份系统及连接认证的方案(因为主流平台的界面实在是太丑了),经过多方资料查询后发现 Synapse 已经原生支持 OAuth 2.0 身份认证系统,且 Mastodon 也有对应的 OAuth 2.0 应用创建及管理机制,并且具有使用 API 来获取用户信息的接口,于是瞬间就不犹豫了,直接开始配置!
先去 站点的 首选项 - 开发 (https://您的Mastodon站点实例/settings/applications) 中 创建新应用 。应用名称 设置为方便辨识的即可,应用的 权限范围 只需要勾选 read:accounts
(请取消掉红色的 read
write
follow
前面的钩子),应用的 跳转URL 设置为 https://您的Synapse站点实例/_synapse/client/oidc/callback 。例如本站设置为 https://eihei.net/_synapse/client/oidc/callback ,用于授权访问的请求回调。
创建完成后会回到应用列表,它应该会出现在应用列表中。单击它进入应用的详细信息,记录下页面顶部的 应用 ID
和 应用密钥
两条记录,这是用来连接 OAuth 2.0 认证系统的关键,记得千万不要泄露。
这一步可以说是整个流程中最复杂的内容了。由于相关的技术资料并不充足,很多相关的项目还都处于初期开发状态;且由于 Matrix 概念本身相对较新,了解的用户(尤其是中文的用户)并不多,初期的道路也就都只能靠自己来摸打滚爬。但好在参与开发的大佬并不在少数,因而即使资料缺失,相关的技术却都能被投入开发,因而只需要仔细收集文档中的细节,并辅以相关代码的阅读,多少也能搓出一些实用产品的雏形。例如,Synapse 本身已经支持了 OAuth 2.0 方式对接身份认证系统(此处以 Mastodon 为例),只需要收集到对应 API 的端点信息,即可构建出足以用于完成认证操作的完整工作流。
本来我们是打算使用 matrix-docker-ansible-deploy 来构建服务器系统的,但是这个部署工具有一个缺陷:作为附加传入参数的 SSO 认证模板中使用了一些变量,但是这些变量会被 ansible 认为是用于部署时候的参数配置,因而会导致过不去预检查。所以就只能使用最原始却也是最安全的方式——直接部署了。
但是文档一样还是甚少,而且总是遇到一些稀奇古怪的情况,诸如配置文件注释把内容分割开了报错、数据库初始化的参数报错、数据库初始化时候尝试访问报错、容器回调地址控制出错等等等甚至完全可以说是无厘头的内容。不过总也有办法——搜索引擎。经过将近一整个星期的折磨,它总算是能工作上了,可喜可贺可喜可贺呀。
所以长话短说,先送上我们实例的配置文件(docker-compose.yml),之后会分每一个模块,分别介绍对应配置的原因,和该模块独有的配置文件。
1 | version: "3.4" |
先别慌,这里的内容其实可以分成三大块:Synapse本体 + Matrix 媒体代理 + 周边组件。(本次我们没有部署 ma1sd 身份认证服务器,因为我们觉得支持 3PID 的 Synapse 本身就已经足够强大了;而且我们的用户全都是使用 SSO 作为认证方式的,只要 Mastodon 端数据没有丢失,那么用户的信息就是安全的。秉承着这样的思想,我们仅仅是为鸡肋的媒体管理选择了一个新的模块以便对接 S3 存储(我们用的是 wasabi ),其他的内容全部交给 Synapse 管理。
题外话:为什么没有使用 dendrite
虽然 dendrite 是使用 go 编写的下一代 Matrix 服务器,性能更加强劲(配置也更加复杂,还有提供分布式部署的方案),但因为 dendrite 尚处于初步开发阶段,目前的功能覆盖率严重不足,并且兼容性也还不佳;大型项目的迭代需要一个缓慢的过程,按照 Synapse 现在的开发完成度来看,可能两三年内都不会被 dendrite 取代。
Synapse 本体承担着存储几乎所有数据的职责。为了最大化其运行性能,我们使用了 redis 作为缓存数据库,启用了 PostgreSQL 为其提供内容存储及数据库支持,而非默认的 SQLite 数据库(在高负载下的表现比较糟糕)。为了保证运行安全性,我们使用 docker 为 synapse 单独设置了一个隔离内网 synapse_network ,除了 synapse 本身的端口通过 nginx 的反向代理部署在外(监听127.0.0.1:10801端口),其他的包括 redis 和 PostgreSQL 全部都仅运行在内部网络上。因而我们的 Synapse 配置下有三个目录: data 、 db 和 redis 。其中 data 用于存放 Synapse 运行相关的数据文件,例如配置内容、或是用于 Matrix 协议通信的密钥等; db 目录中存放的是 PostgreSQL 的数据, redis 中存放的是 redis 的数据。因而在 docker-compose.yml 中,Synapse 占据三项:
1 | services: |
关于 Synapse 的配置,首先按照 Docker Hub 上的官方镜像里的说明 生成一个:
1 | # 记得调整您站点对应的目录位置和域名! |
能看到这样的输出:
1 | Creating log config /data/eihei.net.log.config |
这样就能在 ./synapse/data 目录下看到三个文件:
其中 homeserver.yaml 是 Synapse 的主配置文件, .log.config 结尾的是 Synapse 的日志配置文件, .signing.key 结尾的是 Synapse 用于 Matrix 协议的签名密钥。 eihei.net 是运行 docker run 指令时您输入的站点名,如果您输入的是 example.com ,那么生成的文件就变成了 example.com.log.config 和 example.com.signing.key 。
着重需要关注的,还是 homeserver.yaml 配置文件。
配置文件中的内容非常详细,基本一边读一边就能配置起来了。如果认为有必要的话,您可以尝试完成阅读。但由于考虑到快速部署及可用性迁移的需求,此处还是附上本站的配置文件,并逐一讲解其中每一项的内容:
1 | # Configuration file for Synapse. |
@username:example.com
中的这一串 example.com ,而并不一定是您 Synapse 服务器的域名——您完全可以将服务器架设在 matrix.example.com 上,然后通过之后会提及到的 well-known 设置来引导服务器/客户端来指向您需要用作服务的域名和端口。/
结尾,以保证截断域名,避免恶意站点的攻击;/
结尾,以保证截断域名。X-Forwarded-For
头来标记被反向代理之前的IP。psycopg2
以表示连接 PostgreSQL 数据库,这不是在开玩笑。db
。http
指屏蔽所有的 HTTP 开头的链接, https 不受影响。配置完成后,请记得删除配置文件中的注释行和空行,尽可能把整个配置文件拼在一起,以避免出现设置内容被阻断、进而造成服务器报错,或是设置失效的问题。
由于 Synapse 对于数据库有一定的初始化参数要求(编码设置为 UTF-8, LC_COLLATE 和 LC_CTYPE 设置为 C),因而我们加入了一串初始化参数(从 StackOverflow 还是 Reddit 的一个相关讨论里隐约找到的方案),也就是这个环境变量:
1 | # Add to PostgreSQL `environment` part |
其他的内容倒都没什么问题,按照规范化的流程进行即可。
只要启动了就可以,记得不要监听到外网去,免得被人利用了。
matrix-media-repo (以下简称 media-repo) 这个项目是用来解决 Synapse 本身媒体代理能力拉跨问题的,所以直接按照配置文件书写就好。此处附上官方的文档链接。
同样,先附上 docker-compose.yml 段中的相关代码,再解释为什么这样配置:
1 | # External media API for S3 storage |
这个 media-repo 直接通过 external_network ,在本地就能连接 synapse 对用户请求进行鉴权。
着重提一下 media-repo 的配置。官方的文档写得全面,但是默认的配置文件却很简短,基本要翻遍了文档才能整理出一份合适的内容。老规矩,贴上配置文件,我们一行一行来解读。
1 | repo: |
@username:example.com
那一串中的 example.com ,用于请求 host 验证,请注意并不是您的 Synapse 或是该 media-repo 的地址。写错的话处理媒体文件时会直接拒绝。基本一遍下来,配置完成即可。
比 Synapse 的好配置,没有稀奇古怪的问题,按照样例修改一下相关的参数就可以。注意一下只需要加入 媒体代理 的网络就好。
更方便了,直接用就可以,完全没有问题。同样注意一下只需要加入 媒体代理 的网络就好。
这些东西因为全是纯前端的,所以直接启动容器就好。容器只需要外部网络,无需内部的访问方案。当然, element-web 需要稍微多配置一下; synapse-admin 官方版本并不支持 SSO 登录也不支持中文,所以我使用的是我魔改的一个版本。
是 Element 的在线版本客户端,可以视为是 Matrix 协议客户端的一个强大实现,配置一下站点默认配置文件就好:
1 | { |
看着情况改就可以; zh-hans
是简体中文,如果有需要,可以改成其他语言。
额外需要关注的是映射到 Docker 容器中的文件名,要是您用于部署该前端客户端的 URL 为 element.example.com ,那么您的配置文件应当映射至容器中的 config.element.example.com.json 文件。同样您可以发现,无论您有多少个客户端,其实只需要这样单独一个实例 + 不同的配置文件名即可解决。
直接部署就好,这个是纯前端应用,无需任何后端连接。
需要额外注意的是,默认情况下第一个注册的人也不是 Synapse 服务器的管理员,所以需要手动执行以下的语句(记得将对应的变量都替换掉):
1 | docker exec -it <Your_Synapse_DB_Container> psql -U <Your_DB_User> -d <Your_DB_Name> -c "UPDATE users SET admin = 1 WHERE name = '@<Your_UserName>:<Your_SiteAddr>';" |
当看到 UPDATE 1
的返回时,说明记录被成功更新,指定的用户已经成为管理员了。
以上内容全部搭建完成后,我们需要为 Synapse 配置反向代理。参考 官方的配置文件 ,结合先前 Mastodon 的配置内容,我使用了这样的全站配置方案:
1 | map $http_upgrade $connection_upgrade { |
请注意两块 well-known 的内容,/.well-known/matrix/server
中指定的是供其他 Matrix 服务器进行互联通信使用的地址,而 location /.well-known/matrix/client
中标注的则是客户端连接到服务器时查询的地址信息,以便于指导客户端的行为。
由于 Matrix 互联及通信使用的是 HTTPS 协议,所以我们可以使用这样的方案同时兼顾 CDN 和服务。
另外,被注释掉的 ## Matrix Synapse API
段是供 Synapse-Admin 使用的 Synapse Admin API 路由,如果您有对应的需要,您可以删除掉这些内容的注释。
出于安全起见,您当然也可以将 API 放置在其他的路由下。但是请注意,由于 media-repo 对于这些路由没有授权,所以请在复制配置时将 media-repo 下的配置段中的 $host
改成您指定的站点域名;而对于 Synapse Admin API 路由,请务必保留原始域名,以避免 SSO 授权登录时可能出现的找不到会话问题。
而关于 element-web 和 synapse-admin 的反向代理部署,其实很方便,此处就直接丢配置文件了。
1 | upstream matrix_synapse_admin_backend { |
1 | upstream matrix_element_backend { |
如果我没有疏漏什么的话,设置完对应的端口,服务应该就能全都正常启动了。
GeoIP2 是 MaxMind 公司推出的知名 IP 定位数据库,有提供免费的 Lite 版本可供下载使用(需要注册),数据库每周更新。
请注意如果您使用了免费下载的 GeoLite2 数据库,您需要遵循 Creative Commons 4.0 的授权使用原则,在您使用到了该数据库的项目/页面上标注 MaxMind 的宣传(可在下载页面看到),自觉遵守使用协议从我做起。
nginx的版本比较重要,低于1.9.11
版本的nginx无法使用动态模块,低于1.11.5
版本的nginx编译动态模块时不支持--with-compat
兼容参数,所以请保证您的nginx版本不低于1.11.5
。
比较推荐的做法是使用稳定的主要版本,可以在nginx的官方网站上找到对应Linux系统的包管理软件安装教程。
本文中使用的下载方式是 wget ,您也可以选择使用其他的下载方案。
一个工作目录有助于保持环境的干净,防止其他项目的干扰,并且在不再需要时移除也比较方便。
假设我们是 root 用户,登入VPS后没有做什么其他的工作,那么我们当前的目录是 /root/
;
我们创建一个名为 nginx
的工作目录,并进入,那么我们现在的目录应当就是 /root/nginx/
。
可以稍微留一个心眼,之后编译构建的时候,我使用到的是绝对路径。
这是一个用于 C 语言读取 MaxMind 数据库的资源包,编译插件时需要使用。这里只介绍最简单的安装方式,如有出现任何问题您可以查询原始页面的说明,或是在评论区回复,一起定位出错点。
从Release下载libmaxminddb-*.tar.gz
的文件,例如我使用的是libmaxminddb-1.5.2.tar.gz
1 | wget https://github.com/maxmind/libmaxminddb/releases/download/1.5.2/libmaxminddb-1.5.2.tar.gz |
解压文件,进入目录
1 | tar zxvf libmaxminddb-1.5.2.tar.gz |
编译安装
1 | ./configure |
退出目录
1 | cd ../ |
nginx 的编译需要这三大模块的参与,缺少会导致在配置阶段就报错,因而建议分别下载并解压。
请注意 nginx 使用的是 openssl 1 的头文件,似乎还不支持 openssl 3 ,请还是选择 1.1.1j 版本吧。
名称 | 官网地址 | 下载地址 | 截稿时的最新软件包 |
---|---|---|---|
pcre | pcre | pcre-dl | pcre-8.44 |
zlib | zlib | zlib-dl | zlib-1.2.11 |
openssl | openssl | openssl-dl | openssl-1.1.1j |
同样,下载+解压缩:
1 | wget https://ftp.pcre.org/pub/pcre/pcre-8.44.tar.gz |
请根据您的 nginx 版本,选择对应版本的源码进行下载,跨越版本会导致插件无法正常工作,可能会需要您重新编译;当然如果您的 nginx 版本太过拉跨的话,不如就趁此机会顺带升级一下吧 (雾)
例如我使用的是 nginx/1.19.7
,您可以使用 nginx -v 指令查询您的 nginx 版本。
名称 | 官网地址 | 下载地址 | 截稿时的最新软件包 |
---|---|---|---|
nginx | nginx | nginx-dl | nginx-1.19.7 |
下载完成,解压备用
1 | wget https://nginx.org/download/nginx-1.19.7.tar.gz |
这个模块就是用来让 nginx 读取 MaxMind GeoIP2 数据库的模块。
1 | git clone https://github.com/leev/ngx_http_geoip2_module.git |
如果没有安装 git ,也可以下载 Release 包使用:
1 | wget https://github.com/leev/ngx_http_geoip2_module/archive/3.3.tar.gz |
编译使用的应该是 gcc 编译器,并且会使用到 make 相关的工具,我使用的系统中已经提供了这些工具,因而没有出现报错;如果您的系统在编译的过程中出现报错,那么可能是您缺少对应的工具,您可以自行搜索。
1 | cd nginx-1.19.7/ |
1 | ./configure \ |
请注意,如果使用的是不是 git clone
的方式获取 ngx_http_geoip2_module ,您可能需要修改目录的位置为 ngx_http_geoip2_module-3.3
(以3.3版本为例)
ngx_http_geoip2_module 里 ReadMe.md 的方法是错误的,不附带 --with-compat
参数会让构建出来的模块无法被使用。
而如果您安装的 nginx 默认参数不带有 --with-compat
(可以使用 nginx -V | grep with-compat
查询) ,那么您可能需要完成重新编译了。(非常感谢 @ADD-SP 的提醒!)
构建完成后,您应当可以看见如下的输出:
1 | Configuration summary |
构建时候单纯构建模块就足够了,完整构建浪费资源和时间。
1 | make modules |
构建完成后您可以看见类似如下的输出:
1 | objs/addon/ngx_http_geoip2_module/ngx_http_geoip2_module.o \ |
到此,模块就已经编译完成,可以准备动态加载了。
构建完成的模块在 objs/
目录下,为了避免后续清理工作目录的操作可能会导致误删,此处建议将这些模块移动到一个比较安全的地方,例如 /etc/nginx
中。为了和静态模块 modules
区分,我们新建一个 dynamic-modules
目录。
1 | mkdir /etc/nginx/dynamic-modules |
然后就可以快乐地迁移模块啦。
1 | mv objs/*.so /etc/nginx/dynamic-modules/ |
出于管理上的方便考虑,我们可以在 /etc/nginx
下新建一个模块配置文件 (如 modules.conf
),专门用于模块的加载管理,其中写入我们的模块加载语句:
1 | # Maxmind GeoIP2 |
并在 nginx.conf
中加载这个配置文件:
1 | user nginx; |
这样就能完成模块的加载啦。
光有模块加载还不足够,还需要让模块中的数据能为我们所用。而方法其实也很简单,以国家码为例,在 http / stream 块中按照如下的方式配置即可:
1 | geoip2 /path/to/your/GeoLite2-Country.mmdb { |
$geoip2_country_code
会被设置成 ISO 3166 规定的国家码。
详细的使用规则可以参见 模块的使用样例 ,此处不再赘述。
获得了请求地址的国家地区码后,就可以进行匹配了。例如对于加速站设置一条优化,将所有非中国的访客全部禁止访问:
1 | ## Country Restrict |
以避免其他请求消耗加速站的流量吧。毕竟在现代世界,使用 CloudFlare 获得的体验比直连好上不少呀。
]]>Waline - 一款从 Valine 衍生的带后端评论系统。可以将 Waline 等价成 With backend Valine.
—— Waline 官网的介绍
Waline同时具有Valine的轻量和后端管理的方便两大特性,并且提供了简单方便的Serverless部署方案,遵循轻量化、集约化设计思路的指导,可以方便地部署在各种FaaS平台上,包括著名的Vercel、腾讯云CloudBase以及使用Docker独立部署的方案,提供LeanCloud、MongoDB、MySQL、PostgreSQL和SQLite的数据库支持,支持邮件通知、Telegram通知、QQ通知及微信通知接口的接入,提供大量可自定义参数接口。强大的扩展组合能力让Waline成为了集几乎所有优点于一身的评论系统组合,只要配置适当,相信能成为静态站点的一大优秀评论服务合作伙伴。
本次我使用了Vercel作为部署平台,连接MongoDB提供的免费512MB数据库平台作为评论存储系统,通知系统接入了Telegram和邮件。经过两天的测试,发现除了慢之外似乎没有什么问题。经过一般的测试,仅仅是在全站有60条评论的情况下,不少操作(包括更新文章阅读量统计、获取评论数、获取评论内容)几乎都需要花费1500~3000ms的运行时间。当然,其中包含了不少连接数据库操作的延迟,并且本主题有非阻塞的评论加载模式,所以只要没有开访问量统计,使用懒加载的体验应该是还能过得去的…
而本以为将 Disqus 的评论数据迁移去Waline数据库会很麻烦,事实上却发现官方已经提供了迁移使用的工具,只需要选择 从 Disqus
迁移至 Waline MySQL/PostgreSQL/SQLite
存储服务,将从 Disqus 导出(可以在 //YOUR_SITE.disqus.com/admin/discussions/export/ 导出,好像有些不太全)的评论压缩包,解压出XML内容粘贴在数据框内,单击转换
即可下载转换完成的csv文件(后缀名居然是txt,坏耶)。使用 MongoDB Compass 连接到我们的 MongoDB 实例,批量导入csv文件就能将评论都恢复回去。只是由于Disqus的隐私设置,头像、邮箱等等无法导出的账户相关信息都已经丢失了。
然后就迁移过来了,预计会花上一段时间用于相关的调整与改进云云,升级了也不能坏了安全性和体验呀。过两天估计会能整理出完整的教程,有遇到什么问题随时来找我就可以。
Waline 配置时的鉴权方式只有Referer域名,频率限制方式只有IP请求频率,虽然可以连接Akismet反垃圾系统,也可以设置人工评论审查,但防止CC攻击/防止被作为邮件轰炸工具之类的安全性也许还有待进一步的提升吧。
首先从 Disqus 请求导出旧的评论数据。 Disqus 会开始整理数据并归档成一个压缩包,并当数据准备完成时通知我们。
当数据准备完成时,我们会收到一封附带有下载地址的邮件,邮件里给出的下载地址就是评论数据的压缩包链接,直接点击即可开始下载。
压缩包是 gzip 格式压缩的,我是用 7zip 就能轻松提取出里面的同名(少了 .gz )文件。文件格式是 XML ,文件编码是 UTF-8 。使用文本编辑器打开,复制所有的内容备用。
打开迁移使用的工具,选择从 Disqus
迁移至 Waline MySQL/PostgreSQL/SQLite
存储服务,将刚才复制的数据粘贴到输入框内,单击转换
按钮,我们会得到一个output.txt文件用于保存。
保存得到的文件,这个文件是使用 UTF-8 编码的 CSV (Comma-Separated Values,逗号分隔值) 文件,可以直接喂给 MongoDB Compass 用于导入数据。为了方便识别,我把后缀名 .txt 改成了 .csv 。
题外话:如果您有安装 Excel ,此时您会发现它的图标变成了 Excel 的文档,这是因为 Excel 也支持 csv 的编辑处理。但很可惜的是导出的文档没有 BOM 头,而 Excel 没有读到 BOM 头的时候就会用默认编码(简体中文系统中使用的是GB系列)进行处理,因而打开的时候将会呈现一大片的乱码。但这并不影响我们数据的导入—— MongoDB Compass 这种现代软件使用的是 UTF-8 默认编码,只要导出的文件的内容没有被动过,那么不会出现编码导致的问题。
新建集群
最好是选择美国西部的服务器节点。 Vercel 个人账号的后端服务器在华盛顿,因而距离美国西部的服务器最近,延迟最小。我选择了咕咕噜云的台湾机房,导致每次请求的速度那可是相当之缓慢啊(猫咪摊手.jpg
我使用了 MongoDB Atlas 提供的免费额度,可以参照官方文档,创建完账号后新建一个 Cluster , Cloud Provider & Region 建议选择带有★标识的集群(我用的是 Google Cloud 的平台), Cluster Tier 选择 M0 Sandbox
免费级实例, Cluster Name 按照要求填写(之后无法更改),创建完成后即可在控制台看到它:
(请注意初始创建的 Cluster 是几乎没有数据的,我是写入了一些数据之后截的图)
授权连接方式
单击 Connect
进入连接方式提示界面。由于我们是新建的项目,在进行连接之前,系统会提示我们先配置可连接的IP地址域与管理用户账户。
根据 Vercel 的文档中对于部署 IP 访问的说明,我们很无奈地授权所有 IP 段访问。选择第三项(没记错的话),保存。
主数据库用户倒是可以随意配置,请记得记录对应的账户名与密码(忘了也不怕,可以重新生成的),在导入数据时会使用到。而出于安全因素的考虑,建议新建一个专用于 Vercel 连接集群的用户,稍后的步骤中也会有提及。
以上两点配置完成后,就可以准备进行连接了。
记录集群
选择第2项 Connect your application
,进入应用连接指导界面。由于waline目前的写法问题,我们无法使用新的连接方式,因而需要选择 Node.js
和 2.2.12 or later
选项,获得一长串的连接信息。
例如我的链接:
1 | mongodb://<username>:<password>@cluster-0-shard-00-00.dzwkk.mongodb.net:27017,cluster-0-shard-00-01.dzwkk.mongodb.net:27017,cluster-0-shard-00-02.dzwkk.mongodb.net:27017/<dbname>?ssl=true&replicaSet=atlas-sgse0y-shard-0&authSource=admin&retryWrites=true&w=majority |
我们需要的信息有:
ssl=true
连接启用SSLreplicaSet=atlas-sgse0y-shard-0
集群信息为 atlas-sgse0y-shard-0authSource=admin
认证源为 admin根据 Waline 文档中 MongoDB 相关的配置,整理成环境变量的格式:
1 | # 环境变量名 = 值 |
(可选)创建专用账户
为了提升系统的安全性,个人习惯创建一个专用的账户用来管理相关的数据。
返回 MongoDB Atlas 控制台,进入 Security 下的 Database Access 模块,新建一个用户:
根据提示配置:
Done
以确认;记录下此步骤中的用户名、密码和数据库名,在配置 Waline 后端时需要提供。
提示:我初始化连接时是没有数据库的,因而使用的是最高权限的管理员(也就是在 Connect 可用之前,和授权 IP 一起分配的那个管理员账户);因而我不知道这样限制访问权限的管理员是否会导致无法创建数据库等问题,因而更推荐您先授权最高权限管理员账户,等初始化完成后再使用权限受限的管理员身份作为长期部署解决方案。
创建 Vercel 账号,如果有就直接登录,此步骤不再赘述。
参照 Waline 官方文档中 Vercel 部署 相关的部分进行部署。
我开启的功能有:
因而设置的环境变量有:
环境变量 | 备注 |
---|---|
SITE_NAME | 站点名 |
SITE_URL | 站点地址 |
SECURE_DOMAINS | 授权域名 |
DISABLE_USERAGENT | 是否隐藏评论者 UA |
AKISMET_KEY | Akismet 的反垃圾 Key |
AUTHOR_EMAIL | 博主邮箱(区分是否通知) |
SMTP_SERVICE | 支持的邮件发送服务提供商 |
SMTP_USER | SMTP 认证账户名 |
SMTP_PASS | SMTP 认证密码 |
SENDER_NAME | 发件用户名 |
SENDER_EMAIL | 发件邮箱 |
TG_BOT_TOKEN | Telegram Bot API Token |
TG_CHAT_ID | Telegram Chat ID |
GITHUB_ID | Github OAuth Client ID |
GITHUB_SECRET | Github OAuth Secret |
MongoDB 相关的参数 | 请参见上文 |
有必要的话,可以考虑重新生成后端的部署。
方法: 进入 项目 - Deployments,找到最新的部署,右边的三个点,选择 Redeploy 即可重新构建部署。
需要注意的是,虽然旧的项目不会占用计费资源,但出于对于云服务提供商的尊重,建议还是及时删除不使用的项目。
输入您部署后端的站点地址,您应当能看到一个评论区示例页面:
切换至 //your-project.vercel.app/ui ,您应当能加载出登录界面:
单击右下角的 用户注册
,输入您的信息,注册成功且没有出现问题,并且您应当可以登录管理系统了,那么您的后端应当是已经配置完成了。
第一个注册的用户会自然成为管理员。
连接数据库
我使用了 MongoDB Compass
作为连接工具。(主要是觉得有 GUI 方便操作)
回到 MongoDB Atlas 控制台,单击 Connect
进入连接方式提示界面,选择第3项,进入 MongoDB Compass 的连接指导界面。没有 MongoDB Compass 的话,就下载安装一个吧。
复制连接串,粘贴到 Compass 的连接框内,调整用户名和密码为您在上一步中设置的值,单击 Connect
即可建立与 Cluster 的连接。
导入数据
在导入之前,最好是能先去发送一条测试用的评论,以方便生成对应的数据表结构。
然后进入 站点数据库 - Comment 集,应当能看到那些已经提交上去的 测试 评论了:
单击 绿色的 ADD DATA
(添加数据),选择 Import File
(从文件导入)
选择需要导入的文件,记得修改右下角的文件后缀名筛选,默认是 JSON ,我们需要手动改成 CSV :
Compass 会自动识别出 CSV 文件,并且展示 10 条样例数据方便检查是否正确:
确认无误后,单击 IMPORT
即可将数据全部导入至数据库。
请注意,由于Disqus备份与相关隐私的问题,数据会有部分的丢失:例如评论者头像、评论者身份等等。
而由于MongoDB使用的是_id.$oid
索引,不是SQL的id
字段,因而导入之后需要根据ID调整各条评论的pid和rid关系,以避免因为找不到父评论内容而无法显示的情况发生。
至此应该就全部完成啦。别忘了测试一下是否工作正常哦。
]]>使用无配置misskey构建的镜像在部署时 可能 会出现包含但不仅限于 无法订阅中继 的问题(未经严格测试,还有待考证),因而如果您使用官方镜像/无配置构建的镜像时出现了类似的异常工作情况,您可以考虑配置完成后先手动构建一份本地镜像,再进行对应的部署操作。
推荐的补充阅读:对文章「使用 Docker 最小化部署 Misskey」的补充 (by ADD-SP)
文章提及了一些配置中的坑,也许可以方便您避免一些可能出现的问题。
首先就是关于Misskey的部署需求。由于我们使用Docker来部署,那么唯一需要在意的就是相关的配置文件。(如果是站点迁移,则还需要db、redis、files三个文件夹,之后会解释为什么)因而我们可以直接忽略程式本体内容,仅处理相关的配置文件:
进入目标目录(例如~/nyaone/misskey),参照以下样例新建docker-compose.yml
文件,请注意仔细检查每一行配置,保证与您的站点配置一致:
1 | # Misskey minimal deploy config |
新建配置文件夹 (随便叫什么名都可以,请注意和docker-compose.yml中的两处配置项匹配)
1 | mkdir config |
进入配置文件夹,分别参照example.yml和docker_example.env新建default.yml
和docker.env
文件,写入各项配置;此处再附上相关的注意事项:
default.yml
这个文件的编辑工作基本与非 Docker 环境的版本相同。
但请注意, Postgresql 和 Redis 的 主机名(hostname) 配置不应该是 localhost ,它们被设置在 docker-compose.yml 文件中。
以下是默认的主机名:
服务 | 主机名 |
---|---|
Postgresql | db |
Redis | redis |
文章确实有些年久失修了
现在部署的时候最好把 Misskey 配置文件的 signToActivityPubGet: true
这一行前面的注释去掉(即启用这个功能),部分 mastodon 实例开启了安全模式,因而如果不开启这个选项对 GET 请求也进行签名的话可能会导致连接障害。
docker.env
在这个文件中配置 Postgresql 。
至少需要如下这些配置:
名称 | 描述 |
---|---|
POSTGRES_PASSWORD | 数据库密码 |
POSTGRES_USER | 数据库用户名 |
POSTGRES_DB | 数据库名 |
初始化数据库
1 | docker-compose run --rm web yarn run init |
启动容器
1 | docker-compose up -d |
注册后设置管理员
1 | docker-compose run --rm web node built/tools/mark-admin @您的用户名 |
更新
由于Misskey官方有配置CI,所以等新版本发布后过几分钟就可以直接使用官方镜像进行更新。而更新起来也非常方便,只需要进入当前路径,运行以下指令即可:
1 | docker-compose pull |
请不要使用docker-compose stop
来停止容器,因为这么做需要额外的时间等待容器停止;docker-compose up -d
直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。
删除旧数据
我曾经用这个方法释放了10+G的旧数据,天知道我经历了些什么(猫咪摊手
1 | docker system prune |
要求确认时输入y并回车确认即可。
进阶操作
例如我们可以编写一个脚本,配合crontab每天定时尝试拉取最新数据并更新;如果已经是最新版本了的话,那就无事发生
1 | docker-compose -f /root/misskey/docker-compose.yml pull |
本实例使用的是 Debian GNU/Linux 9.13 (stretch) ,如果您使用的包管理软件并不相同,或是您的系统版本并不完全一致,则可能会有不一样的表现。
WireGuard 的网卡名未必必须为 wg0 的格式,您可以自行修改命名。只要记得同时调整相关的防火墙规则即可。客户端与服务器的网卡名也未必必须一致,您只要能确认分辨即可。
SaveConfig = true
这一行。已经被坑害过好多次了。2021/02/20 修正一个说法问题:
WireGuard 是一个节点间相互对等的 P2P 的协议,没有客户端与服务器的区分,所有的节点之间通过彼此配置的 Peer
来建立连接。
由于没有中心,因而没法 DHCP ,所有的 IP 均为手动静态分配,如果出现路由异常的情况,您可以尝试分配给每个 IPv4 均为 /32 的地址(即完全锁定的无子网地址),我现在就是通过这种方式建立起无中心的点对点网络的。
WireGuard®是一个简单,快速且安全的,使用了最先进加密技术的 VPN (注:Virtual Private Network, 虚拟私有网络,不是翻墙使用的代理工具,概念不能混淆)。使用了短小精悍的源码脚本的它,致力于成为比其他 VPN 协议,例如 OpenVPN 或是 IPSec ,更加精简与快速。 WireGuard 目前仍处于开发阶段,但就连它的还未优化的状态,甚至都已经比主流的 OpenVPN 协议更加快速了。
WireGuard 会设置标准的网络接口(例如wg0或是wg1,用户可以自定义命名),所以它的表现更像是常见的 eth0 接口。这使它能通过一些标准工具,例如ifconfig
或是ip
提供了可能。目前,WireGuard已经能在所有的平台上使用。
配置WireGuard就如同配置SSH一样简单。一个连接通过服务器与客户端间交换公钥来确定,只有当一个客户端在它对应的服务器配置文件中包含了它的公钥时,它才会被允许进行连接。一份 WireGuard 服务器的配置文件会以类似如下的样式来呈现:
1 | [Interface] |
在这份教程中,您将会学到:
请避免在关键的应用中使用WireGuard。这个项目目前还在进行严格性测试,并很有可能会在未来收到频繁的大升级。
请注意,如果您以root
权限运行,那么是不需要执行sudo
操作的。并且 apt-get install 操作会被逐渐淘汰,因而建议您直接使用 apt install 进行方便简洁的管理。
这份教程需要配合使用了GRUB 2
内核的系统使用。默认情况下新开出来的机器应当都是启用了。但如果您运行的是一个旧的版本,您需要检查您运行的是哪个内核。如果可以的话,您也许需要升级内核,并在管理面板处设置使用GRUB 2
引导启动。
将 WireGuard 的仓库加入到您的 apt 源列表中。apt 会自动更新包缓存。
1 | echo "deb http://deb.debian.org/debian/ unstable main" > /etc/apt/sources.list.d/unstable-wireguard.list |
更新您的包,并且安装 WireGuard 和 WireGuard 相关的工具。 DKMS (Dynamic Kernel Module Support,动态内核模组支持)会构建 WireGuard 内核模组。
1 | apt update |
成功后,您将看到类似这样的输出:
1
2
3
4
5
6
7
8
9
10wireguard.ko:
Running module version sanity check.
- Original module
- No original module exists within this kernel
- Installation
- Installing to /lib/modules/4.19.0-13-amd64/updates/dkms/
depmod....
DKMS: install completed.
切换至 /etc/wireguard
目录,并为 WireGuard 服务器生成一对公私钥:
1 | sudo umask 077 |
这将会同时保存公钥与私钥。它们可以通过 cat privatekey
和 cat publickey
来分别查看。
创建文件 /etc/wireguard/wg0.conf
并且添加如下的内容。您需要在PrivateKey
配置项处输入您服务器的私钥,并在Address
配置项处输入它的私有地址。您可以参阅配置样例下方的解释表来获取更多的细节。
1 | [Interface] |
10.0.0.1/24
,192.168.1.1/24
,或是192.168.2.1/24
。这和您的私有IP地址应当不重复。特别的,请检查您的网卡与已经分配的IP地址,该子域不应当与任何子域重复。我们推荐使用iptables一套带走,但出于对于原文的尊重,我们将ufw的相关操作翻译并放置于此。
安装UFW
1 | sudo apt-get install ufw |
添加SSH连接和WireGuard的VPN端口(请注意根据您的具体情况进行调整)
1 | sudo ufw allow 22/tcp |
验证您的设置
1 | sudo ufw status verbose |
安装 ipset
,为 iptables 提供一个方便的批量地址管理工具
1 | apt install ipset |
新建一个 ip 集合,添加会用到的客户端的公网 IP
1 | ipset create wgclients hash:ip |
编写 iptables 防火墙规则,仅允许可信客户端连接,丢弃所有非可信的数据包
1 | iptables -A INPUT -m set --match-set wgclients src -p udp --dport 51820 -j ACCEPT |
保存防火墙规则
1 | iptables-save |
启动 WireGuard
1 | sudo wg-quick up wg0 |
wg-quick
是wg
中许多常用功能的封装。您可以使用wg-quick down wg0
来关闭 wg0 接口。
设置 WireGuard 服务为开机自启
1 | sudo systemctl enable wg-quick@wg0 |
用下列两条指令来检查 VPN 隧道是否已经正确运行
1 | sudo wg show |
您应该会看到类似的输出:
1 | user@debian:/# wg show |
您可能需要安装 net-tools 来运行ifconfig
。如有必要的话,您可以使用sudo apt-get install net-tools
。
或者我们推荐使用更强大的 ip
指令,后文会有补充提及。出于对原文的尊重,此处是 ifconfig 指令的翻译。
1 | sudo ifconfig wg0 |
您的输出应当看上去像这样:
1 | user@debian:/# ifconfig wg0 |
1 | ip addr show wg0 |
您的输出应当看上去像这样:
1 | user@debian:~# ip addr show wg0 |
设置 WireGuard 客户端的过程与设置服务端的过程非常相似。当使用 Debian 作为您的客户端操作系统时,客户端与服务端间唯一的区别在于配置文件。在这个模块中,您将会了解到如何在 Debian 9 上配置一个 WireGuard 客户端。
您可以参阅 WireGuard 文档 以获取在其他平台上的安装指引。
参阅本教程中的 安装 WireGuard
段来完成安装工作。 WireGuard 的客户端与服务端是对等的。
当您完成安装后,您可以参阅 配置 WireGuard 服务端
段来完成客户端的配置工作。只需要将样例的配置文件题欢成如下的样例即可。
1 | [Interface] |
客户端与服务端的区别在于配置文件 wg0.conf
,该文件包含了本网卡自己的IP地址,并且不包含 ListenPort
,PostUP
,PostDown
,或是 SaveConfig
段的内容。
在您的 WireGuard 客户端上 配置防火墙规则
。
启动 WireGuard 服务
。
在服务器和客户端上使用 sudo wg-quick down wg0
来停止接口。
编辑客户端上的 wg0.conf
文件,添加服务器的公钥、公网 IP 地址、端口和该段分配的 IP CIDR 地址块(修正了原文中不够优雅的的写法)
1 | [Peer] |
编辑服务器上的 wg0.conf
文件,添加客户端的公钥和该段分配的 IP CIDR 地址块
1 | [Peer] |
在服务器和客户端上分别启动 WireGuard 服务
1 | sudo wg-quick up wg0 |
您也可以使用命令行将节点加入至服务器。由于配置文件中开启了 SaveConfig 选项,这些信息会被自动加入到配置文件中。
在服务器运行以下指令,使用预计分配给客户端的 IP 地址来替换样例中的内容:
1 | sudo wg set wg0 peer <Client Public Key> allowed-ips 10.0.0.2/32,fd86:ea04:1116::/64 |
验证连接。以下的指令在客户端和服务器上均可运行。
1 | sudo wg |
无论您使用哪种方式添加节点信息,当您运行 sudo wg
指令时, Peer 部分都应当会出现在输出中。
1 | user@debian:/# sudo wg |
当服务进程重启时,这个节点会被自动添加到 wg0.conf
中。如果您希望立刻将这信息添加到配置文件,您可以运行以下指令:
1 | sudo wg-quick save wg0 |
额外的客户端可以被使用同样的过程来加入。
回到客户端并使用 ping 来向服务器发起请求:
1 | ping 10.0.0.1 |
当您成功建立连接并从客户端访问到服务端时,您可以运行如下的命令:
1 | sudo wg |
wg
命令输出的最后两行应当看起来像这样:
1 | latest handshake: 1 minute, 17 seconds ago |
这证明了您已经建立了一条服务器与客户端的私密链接。如果您无法成功从客户端 ping 到服务端(前提是您的服务端允许 ping ),您将不会看到这些行。您也可以从服务端 ping 客户端,来证明这条连接在两边都能工作。
这篇教程中使用的步骤可以拓展,以便构建网络的拓补结构。如同上文提及的, WireGuard 是一种仍在发展中的技术。如果您使用 WireGuard, 您应当注意官方文档和计划清单,以便为关键的升级和新的特性做好准备。
WireGuard
是Jason A. Donenfeld的注册商标。
原文链接:Set Up WireGuard VPN on Debian | Linode,翻译时有补充修改内容,修复了一些写法上的错误。
RTNETLINK answers: Permission denied
请检查您的内核是否关闭了 IPv6 的支持:
1 | sysctl -a | grep disable_ipv6 |
如果看到有 = 1 的项,说明是存在禁用了 IPv6 的情况。
修改 /etc/sysctl.conf
文件,将所有 disable_ipv6
后 = 1 的全都改成 0 。
RTNETLINK answers: Operation not supported
您的系统可能缺失必要的 Kernel Headers ,您可以使用apt补充安装:
1 | apt install linux-headers-$(uname -r) |
对于版本 ≥1.19.4 的 nginx ,后文会提到一种更为简便的保护方案。
首先是配置nginx的默认证书。由于nginx的策略是没有被后续 server_name 捕获的请求都会被送往 default 文件中默认的 server_name _;
,没有的话就用配置文件中读取的第一个站点,因而80端口的流量确实已经是默认去了 Welcome to nginx! ,但对于HTTPS的443端口,第一个监听的站点就会被用来作为默认的承载站,进而导致源站的证书授权名泄露、IP面临被关联泄露的风险。为了同样保护起HTTPS协议的默认源站,我们可以使用openssl为自己签发一张不包含敏感信息的证书:
1 | 生成密钥 |
生成的 server.cer 即为SSL证书文件, server.key 则为SSL的私钥供nginx使用。修改/etc/nginx/sites-enabled/default
中的对应行:
1 | # ... |
但光是这样还不够,为了避免遭受中间人攻击,我们需要保证我们到CloudFlare的服务器的连接不被任何家伙干扰。
可以开启经过身份验证的源服务器拉取选项。
参照CloudFlare官方的配置教程,下载 CloudFlare的CA公钥证书 ,并配置到nginx中您站点配置文件中的对应段:
1 | server { |
经ADD-SP大佬的提醒,还有一种更简便的方案可以实现SSL证书的保护:
在 nginx-1.19.4 或更高版本中,新增了配置
ssl_reject_handshake
,只要在未指定server_name的 server 块(即默认块)里设置为 on,就可以直接在握手阶段阻断,证书也不会泄露,且不会影响指定了server_name并正确配置了证书的块。只需要把下列 server 块写到默认配置(80端口的默认请求可以直接拒绝了):
1
2
3
4
5
6
7
8
9 server {
listen 80;
deny all;
}
server {
listen 443 ssl;
+ ssl_reject_handshake on;
}可以参照 官方文档 获取更多信息与详细样例。
这样就能从应用层保护我们的源站安全。
然后是网关层,通过iptables和ipset,将80/443端口的访问权限制为仅有CloudFlare的IP可以访问,以便于进一步地阻碍攻击者的行为:
1 | # For IPv4 |
为了避免前后端的请求数据在公网明文传输,我们搭建了一个 WireGuard VPN 将两个节点加入对等的网络环境。由于是在 Debian 9 上搭建,您可以参照本站的 在 Debian 9 上安装 WireGuard VPN 这篇文章,我们成功完成了内网的组建。值得一提的是,文章中的wg0
并不是限定的,只要保证该conf文件的文件名和文件中涉及到的防火墙规则网卡名一致即可,WireGuard会自动读取配置目录下的配置文件,并且在启动时根据对应的文件自动初始化网卡。
我们新增了一些防火墙规则,将不是从我们WireGuard可信客户端中上传的数据包全部丢弃:
1 | ipset create wgclients hash:ip |
测试一下ping,应该已经成功组建成对应的私有网络了。
为了方便Misskey的流量也走保护机的公网IP流出,我们为Misskey配置了HTTP代理。
在default.yml文件中取消proxy: http://127.0.0.1:3128
行前的注释号#
,将后面的地址改为我们的http代理地址。
例如根据本站使用的WireGuard配置,设置为http://192.168.8.1:3128
。
之后编辑保护机的squid配置,取消对应分配的 局域网IP地址行 和 访问请求控制行 前的注释号#
,便于让使用WireGuard的后端站可以访问到squid:
1 | # 对应于您在WireGuard中设置的子网段 |
由于保护机使用的是多IP多网卡设置,因而需要手动指定squid的主机名和出口地址,以避免squid卡死在启动阶段一直报警告:
1 | # 您的域名 |
完成之后启动squid、重启Misskey即可。
]]>首先是docker的启动脚本,由于方便后续更新与维护,我使用了 docker compose 进行配置。
1 | version: "3" |
之后就是最重要的内容:设置nginx反向代理。
由于原来是走Cloudflare的,而Cloudflare有限制上传请求的文件最大大小(免费版为100MB),过大的文件会直接报 413 错误拒绝上传,所以只剩下直连上传这一条路了。但同时也需要修改nginx中相关的超时、限制选项,以避免在网关层出现莫名的限制导致错误。
附上一份整理完成的nginx配置,其中关闭了上传文件的体积限制,并将超时时间均设置为了3600秒,以避免因为文件过大、上传缓慢导致的连接关闭。
1 | upstream minio { |
然后一切就绪,docker-compose up -d
启动minio,nginx -s reload
重载nginx的配置即可。minio基本兼容AWS S3的API,因而可以直接用于替代。
顺带整理路上发现了一个价格实惠的存储服务商Wasabi(山葵),当之后数据量累积到一定的时候,就选择这些专业的存储机构吧。
]]>但总觉得每天手动检查新图既费时又费力,有没有什么方便一些的方法来推送这些喜欢的图片,让每一张图都有机会展现它的光彩呢?
联想到一种常用的资讯订阅方式:RSS,我正好在RSSHub的文档中翻到了对于pixiv关注画师的新图的订阅接口。
但光是RSS订阅并不方便,还需要一个单独的阅读器,不如直接将这些资讯推送至通讯工具上吧。
之前,我使用的基于IFTTT的解决方案,简单地将RSSHub生成的内容推送至Telegram频道。但随着IFTTT的使用策略更改,非付费用户只能创建三个小程序(Applet)之后,我也就放弃了这个方案。毕竟是开发者,被商业平台束缚了手脚可不好,不如自己来写一个功能类似的处理工具,不但能锻炼代码能力,还能拥有更高的可自定义能力,岂不是更棒。
于是,就去查询各种API接口,整理资料。思路很简单:将数据通过RSS采集,整理之后通过Bot的API接口发送至Telegram频道。
又因为我习惯使用Node.JS进行开发,所以这一次也一样,使用Node.JS作为开发与部署平台。
首先是RSS的采集工作。为了避免费时费力的手动分析XML文件,这里直接使用了一个现成的组件:rss-parser
。这个组件能获取RSS资讯,并转化成一个Object方便后续的处理工作。
npm i rss-parser --save
自不用说,使用完成之后的传参调用方式也是非常简洁明快:
1 | rss.parseURL(item.url, (err, feed) => { |
也可以使用官方样例中的async与await异步函数处理方式,但是我比较懒,所以也就直接这样传递参数了吧。
测试时可以将获取的feed信息输出,以RSShub生成的数据为例,我们不难发现其实内部是一个对象数组:
1 | { |
因而可以直接使用相关的字段进行内容处理。
由于RSSHub设计的初衷是为了方便阅读,因而生成的内容格式还是以方便阅读的格式,并使用了pixiv.cat的图片传递接口为主要组成部分的。不过好在获取的内容中包含了我们需要的所有信息,因而可以使用一个正则表达式来提取。
不难发现,对于那些单张图片的内容,RSSHub将其处理成形如https://pixiv.cat/*ArtworkID*.jpg
的链接;pixiv的ArtworkID目前也仅为数字,因而可以使用\d
来匹配;而对于那些多图的内容而言,ArtworkID后的-PicID
也是我们关注的关键。因而,我们可以使用这样的正则表达式来处理:
1 | const picIdReg = /https:\/\/pixiv\.cat\/(\d+)-?(\d+)?\.(jpg|png|gif)/gi; |
可以配合这条正则表达式,直接生成内容数组:
1 | const artworks = [...item.content.matchAll(picIdReg)]; |
对于单图的输入,我们能得到形如这样的输出:
1 | > Array ["https://pixiv.cat/*ArtworkID*.jpg", "*ArtworkID*", undefined, "jpg"] |
对于多图的输入,我们能得到形如这样的输出:
1 | > Array ["https://pixiv.cat/*ArtworkID*-1.jpg", "*ArtworkID*", "1", "jpg"] |
考虑到Telegram对于图片大小的限制,选择使用预览图发送、使用完整图下载的方案。
通过查看pixiv前端页面的源码,不难发现预览图的地址是由前缀、发布时间(UTC+9)、作品ID和一些固定组合搭配而成。发布时间可以由RSS item中的isoDate来手动计算获取,我们可以写一个整合表达式来完成预览图地址的装配工作:
1 | const pubTime = new Date(item.isoDate); |
pic表示一个Array(如样例中的一行);为了整合处理单图的情况,针对pic[2]
进行了额外的处理与判定:pixiv的预览图以0开始编码,单图也会保留此编码内容。
因而最终我们能组合出所需要的预览图片地址。至于下载地址,交给pixiv.cat去处理即可。
之前IFTTT时候使用的是直接推送订阅内容、让Telegram自动获取的方式,因而导致了大图片(≥5MB)无法有效获取、图片缓存请求限制等的问题,因而此次我们加入请求缓冲队列和预览图片,尝试解决以上的问题。
本来打算使用Telegraf作为推送框架,但是想了想发现好像也就一个请求信息,所以就直接使用了got进行POST请求的发送。根据Telegram的Bot API文档,可以整理出如下的请求样式:
1 | const apiBaseUrl = `https://api.telegram.org/bot${confData.bot.token}`; |
直接调用了Telegram的sendPhoto发送图片接口来发送请求数据,其中加入了消息下的内联小键盘inline_keyboard
,为提供图片的原始链接按钮和直接下载按钮,以方便用户的操作。如有其他的好想法,也可制作成相关的功能组件。Bot API Token可以向@BotFather申请,Chat ID可以使用@频道名
,而不必纠结于一长串的频道ID。
为了避免每次启动会将所有的图片加入队列等待发送,同时考虑到这个项目提供的所有数据都是按照时间进行排序的,因而维护一个timestamp,初始化为当前时间,每次将待发送的图片时间中记录最晚值,之后查询时仅发送更新的内容并更新时间戳标记即可。
为了进一步为用户提供方便,依旧加入了祖传的js-yaml组件来读取yml配置文件。并综合以上内容,最终整合的版本,已经发布到了Github上的PhanDream仓库中;您可以使用诸如pm2等方式进行运行:
1 | pm2 start bot.js --name phandream |
当一切准备完成后,过一会就能发现频道中出现新的图片啦~
]]>Misskey支持AWS S3、GCS等知名静态存储提供商的API接口,但由于这些家的国内连接体验通常都十分拉跨,因而还是选择自建的服务吧。本次我使用的是一台对于三网都提供了直接连接优化的存储型伺服器,相信能为国内的用户提供一个更为优质的使用环境。
我选择了开源软件MinIO作为解决方案,它支持AWS S3的存储API,可以与Misskey方便地建立连接,且其在docker环境下的部署也比较方便。
由于是使用docker部署,因而各种基本操作我想自然也不必多言,按照标准流程即可。
为了保证服务后期的可迁移可维护性,我选择了使用docker-compose进行部署。因而将我使用的docker-compose.yml
文件内容陈列于下:
1 | # MinIO |
AccsssKey
和SecretKey
修改成复杂无规则的长字符串,以便提升运行环境的安全性。/storage
目录是之前一台伺服器配置时期的遗留产物,如果您希望将文件存储在其他地方的话,请自行修改为您需要的路径,以防止之后找不到所需要的文件。配置完成后,使用 docker-compose up -d 启动服务。
之后是配置反向代理服务器,同样是按照标准流程即可。为了防止出现跨域的问题,建议使用和Misskey主站的主域名保持一致。
完成配置后访问设定的域名,我们能看到MinIO的管理控制台:
输入预先设定的AccsssKey和SecretKey,我们能进入管理界面。
单击右下角的加号,新建一个存储桶:
输入名称后按下回车键,即可确认创建。
再为存储桶指定相关的文件访问权限(不确定这一步是否需要)。
点击左侧存储桶列表右边的小三点,选择Edit Policy
:
由于我们是公有读取,因而Prefix部分直接使用*通配即可,单击Add即可完成权限配置项的添加。
此时,关于存储桶部分的配置就已经完成了。
之后,进入Misskey的实例管理界面,开启使用对象存储
的开关,并按照页面上的提示输入之前设定的配置项:
我没有填写被蓝色斜线划去的部分,如果您有相关的使用需要,您可以根据提示进行配置。
配置完成之后试着保存一下吧,新的文件应该就会上传到存储伺服器了。可以发送一张图片尝试一下哦。
但是这样的配置,多多少少存在着安全隐患:如果被遍历目录了怎么办?如果被试图爆破密码了又该如何处理?
经过仔细的研究发现,minio中存储的文件都会以原始文件的形式存在于磁盘上,因而在公开页面中,我们可以绕过minio,直接将nginx导向存储着静态文件的对应目录,以纯粹的文件服务器形式呈现在公众面前,进而规避可能出现的被爆破密钥的情况。
因此可以修改我们的nginx配置文件,将公开页面设置为静态路径:
(请注意修改root /storage/minio/data/nyaone;
行,使用您的实际静态文件路径)
1 | server { |
之后再修改Misskey后台的配置,将公开的链接导向我们的静态路径,来规避可能出现的源站泄露问题:
这样配置完成之后,所有的访问请求全部被导向了nginx提供的静态页面,也就不存在请求和源站暴露的问题啦~
]]>却莫名发现购买一年有余(半年吃灰)手柄拥有了自己的意识,两摇杆总是到处漂,且不说爬坡时总是无端跳下悬崖、战斗时莫名全场瞎跑,光是镜头原地360°大旋转就已经足够让人感到无端迷惑了。网上顺手找了找资料,看了一眼被晾在角落里遗忘掉的CRC02016,决定赌一把,看看能不能将其修好(还是彻底弄坏掉,反正过保了也已经无所谓了(猫猫摊手)
于是就在某个风和日丽的中午,怀着沉重的心情给给DS4拍摄完成兴许可能是最后的仪容之后——
先卸除底部的四颗螺丝。标签下真的没有螺丝,不必去折腾它。
使用撬片和撬棒使巧劲,在完美地掰断了一些卡扣之后,即可顺利打开后盖。反正螺丝也能把后盖锁紧,也就不怕卡扣的问题啦。
左边一条扁排线连接到后盖上的RGB氛围灯模块;中间是精心包装的电池,通过一条2p端子线连接到主板上;顶部的排线负责触摸板部分信息的传递;两颗振动电机通过焊接的方式直接连接到了主板上。
给排线和RGB模块来一个特写:(请无视那迷惑的对焦)
在拔下排线之前,请注意尽量先断开电池!
这也算是拆各种电子产品的规矩了吧,为了防止意外短路和静电击穿之类的诡异现象发生,最先断开电池总是一个好习惯的。
电池长这样,由一个塑料壳封装起来(可能是防止挤压导致损坏):
取下电池,再拧下一颗螺丝即可取下电池仓:
断开左侧的RGB模块排线和顶上的触摸板排线(请注意这些排线都是硬插的,没有紧固扣,拔的时候也请拔塑料片而不是排线体,以免造成损伤),即可将主体部分取出:
主体的正面,上面一层薄电路板是薄膜按键相关的电路区域,请记得不要污损哦:
前壳,从里面看似乎有些惊悚?
拔下摇杆帽,即可看见摇杆的元器件部分:
掏出神油,去外面喷:
如果是WD-40的用户,请使用黑绿大罐装的那种电路清洁专用型号,红蓝小罐装的是用于机械润滑用的,很有可能会损坏电路原有的功能!
完成之后等待晾干,之后将所有的零件再组装起来:
完成啦!试一试看看是不是正常了呢~
]]>git checkout --orphan new_branch
新建空白分枝npm run build
生成静态文件git branch -D master
删除旧主分枝git branch -m master
改名为主分枝git push -f origin master
强制推至上游干净的博客出现了!
]]>首先是去控制台分配IPv6地址,这没什么特别需要强调的,按照提示来即可。可以先顺带复制一份地址,准备之后填入DNS解析控制台与配置文件中。
之后切换到 Network Settings ,查询当前VPS所在v6网络的网关和位掩码:
编辑/etc/network/interfaces
,在文件末尾手动写入地址配置:
1 | iface eth0 inet6 static |
例如我的配置:
保存文件,重启VPS(只是重启服务似乎无法正确获取地址)即可完成配置。
参考资料:
]]>