Files
pages/feed.json
2025-12-31 16:00:29 +00:00

1 line
130 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

{"version":"https://jsonfeed.org/version/1","title":"Mayx的博客","home_page_url":"/","feed_url":"/feed.json","description":"Mayx's Home Page","favicon":"/favicon.ico","expired":false,"author":{"name":"mayx"},"items":[{"id":"/2026/01/01/summary.html","title":"年终总结","summary":"0 error(s), ∞ warning(s)\n\n","content_html":"<p>0 error(s), ∞ warning(s)<!--more--></p>\n\n<h1 id=\"2025年的状态\">2025年的状态</h1>\n<p>在2025年感觉状态不如去年……由于没能做出正确的选择还是有点糟糕。不过总的来说还没有引发关键性的错误至少还能继续坚持下去。 <br />\n 在这一年中感觉记忆和思考能力都有所下滑看来是没把自己照顾好😂不过看看这一年写的文章看起来似乎比以前更流畅了这也许是因为和AI聊得多了以至于思维有点偏向AI了吧。 <br />\n 总的来说感觉自己的稳定性还是有点低了,但这可能不是我能独自解决的,也不知会有什么转机……</p>\n\n<h1 id=\"2025年发生的事情\">2025年发生的事情</h1>\n<p>回顾了一下<a href=\"/2025/01/01/summary.html\">去年的年终总结</a>,发现自己还是没能做到知行合一,在这一年里全球各类资产突然开始大幅升值,也就是说钱真的开始不值钱了……那时候想着买黄金,这一年下来却没能下定决心,最终错过了资产保值的机会。至于现在,似乎什么也做不了了……当然这对我的生活并没有造成什么严重的打击,只是感受到环境对自己的影响罢了。 <br />\n 至于AI……依然是一天比一天强而各个公司对AI的投入相比去年也是极大的提升当然出来的效果也是非常强那时候的AI还是挺容易出错但是现在AI解决问题的能力已经可以替代很多人了不只是文本生成模型今年的图像与视频生成模型也真的是发展到了以往完全不能想象的地步真的可以做到一句话想要什么就有什么了。 <br />\n 另外,今年写的博客内容过于围绕博客本身了,以至于似乎不太跟得上时代,虽然我的博客也确实有点老旧了😆。只是看看以前的文章,都还有一些面向未来的趋势,而今年就有点“考古”了。相比于考古,去展望未来显然是更有意义的事情,只不过……真的感觉脑子不太好使,未来会发生什么,已经完全无法预测了。</p>\n\n<h1 id=\"展望2026年\">展望2026年</h1>\n<p>虽然不知道未来会发生什么,但毕竟还没有造成关键性的错误,还有修正的余地,只能希望未来能够做出正确的选择,不要让自己陷入危险的境地吧。</p>\n","url":"/2026/01/01/summary.html","date_published":"2026-01-01T00:00:00+08:00","date_modified":"2026-01-01T00:00:00+08:00","author":{"name":"mayx"}},{"id":"/2025/12/01/linux.html","title":"在浏览器中运行Linux的各种方法","summary":"这篇文章介绍了在浏览器中运行Linux的各种方法从最初的纯JS虚拟机JSLinux到后来的WASM虚拟机如v86、WebVM、WebCM再到容器化方案container2wasm以及直接将Linux内核编译为WASM的方案。作者详细对比了这些方案的优缺点包括性能、兼容性、功能和开发难度。文章还提到了模仿Linux环境的WebContainers和JupyterLite并最终认为虚拟机方案更靠谱但对WASM的未来充满期待。作者最后表示博客上添加类似功能的计划还在考虑中目前主要分享了各种方法的探索过程。","content_html":"<p>浏览器已经无所不能了!<!--more--></p>\n\n<h1 id=\"起因\">起因</h1>\n<p>前段时间跟网友交流时有人展示了他博客里的一个Linux终端模拟项目<a href=\"https://github.com/Erzbir/jsnix\">jsnix</a>看起来挺有意思的里面甚至还藏了一个CTF。不过我感觉他这个终端和博客本身并没有真正联动起来本质上只是一个模拟了Linux Shell行为的交互界面。除此之外我还发现了另一个风格类似的<a href=\"https://github.com/Luyoung0001/myWebsite\">个人主页</a>它虽然也走了终端风格但功能更简单还原度也不算高。不过它至少和博客内容做了一些基础联动——尽管目前也只是做到列出文章这种程度😂当然有这类功能的博客应该也不少只是我发现的不太多……于是我就想不如我也给自己的博客加一个类似的“命令行访问”功能应该会很有趣。当然如果真要做的话我肯定不会满足于只实现几个模拟指令——既然要做就要追求真实感至少得在浏览器上运行真实的Linux终端才不会让人觉得出戏吧😋。</p>\n\n<h1 id=\"在浏览器中运行linux\">在浏览器中运行Linux</h1>\n<h2 id=\"虚拟机方案\">虚拟机方案</h2>\n<h3 id=\"纯js虚拟机\">纯JS虚拟机</h3>\n<p>要说到在浏览器上运行Linux最先想到的应该就是<a href=\"https://bellard.org\">Fabrice Bellard</a>大神写的<a href=\"https://bellard.org/jslinux/\">JSLinux</a>吧这可能是第一个在浏览器中实现的虚拟机毕竟是最强虚拟机QEMU的作者编写的。现在他的个人主页中展示的这个版本是WASM版本而他最早写的是纯JS实现的。那个JS实现的版本现在在GitHub上有一个<a href=\"https://github.com/levskaya/jslinux-deobfuscated\">去混淆的版本</a>可以用作学习和研究于是我顺手Fork了一份在GitHub Pages上部署作为<a href=\"http://mabbs.github.io/jslinux/\">演示</a>。 <br />\n 作为纯JS实现的x86虚拟机性能估计是最差的但相应的兼容性也最好在Bellard当年写JSLinux的时候还没有WASM这种东西呢所以即使是在不支持WASM的IE11中也可以正常运行。假如我想把它作为终端用在我的博客上似乎也是个不错的选择即使我完全看不懂代码不知道如何实现JS和虚拟机的通信它也预留了一个剪贴板设备可以让我轻松地做到类似的事情比如我在里面写个Bash脚本通过它和外面的JS脚本联动来读取我的文章列表和内容那也挺不错。 <br />\n 当然Bellard用纯JS编写虚拟机也不是独一份他实现了x86的虚拟机相应的也有人用纯JS实现了RISC-V的虚拟机比如<a href=\"https://github.com/riscv-software-src/riscv-angel\">ANGEL</a>,看起来挺不错,所以同样也顺手<a href=\"https://mabbs.github.io/riscv-angel/\">搭了一份</a>。只不过它似乎用了一些更先进的语法至少IE11上不能运行。 <br />\n 另外还有一个比较知名的项目,叫做<a href=\"https://github.com/s-macke/jor1k\">jor1k</a>它模拟的是OpenRISC架构。只是这个架构目前已经过时基本上没什么人用了不过这里面还内置了几个演示的小游戏看起来还挺有意思。 <br />\n 除了这些之外其实能在浏览器上运行的Linux也不一定是个网页有一个叫做<a href=\"https://github.com/ading2210/linuxpdf\">LinuxPDF</a>的项目可以让Linux运行在PDF中它的原理和JSLinux差不多所以需要PDF阅读器支持JS看它的介绍貌似只能在基于Chromium内核的浏览器中运行而且因为安全问题在PDF中有很多功能不能用所以它的速度甚至比JSLinux还要慢功能还很少因此它基本上只是个PoC没什么太大的意义。</p>\n<h3 id=\"wasm虚拟机\">WASM虚拟机</h3>\n<p>那还有别的方案吗既然Bellard都选择放弃纯JS的JSLinux而选择了WASM显然还有其他类似的项目比如<a href=\"https://github.com/copy/v86\">v86</a>这也是一个能在浏览器中运行的x86虚拟机不过因为使用了WASM和JIT技术所以效率要比纯JS的JSLinux高得多。另外作为虚拟机自然是不止能运行Linux其他的系统也能运行在示例中除了Linux之外还有DOS和Windows之类的系统功能还挺强大如果能自己做个系统镜像在博客里运行似乎也是不错的选择。 <br />\n 另外还有一个相对比较知名的叫<a href=\"https://github.com/leaningtech/webvm\">WebVM</a>从效果上来说和v86几乎没有区别同样使用了WASM和JIT技术也都只支持32位x86然而它的虚拟化引擎CheerpX是闭源产品既然和v86都拉不开差距不知道是谁给他们的信心把它作为闭源产品😅。不过看它的说明文档其相比于v86的主要区别是实现了Linux系统调用考虑到它不能运行其他操作系统而且Linux内核也不能更换那我想它可能是类似于WSL1的那种实现方案也许性能上会比v86好一些吧……只不过毕竟是闭源产品不太清楚具体实现了。 <br />\n 既然纯JS有RISC-V的虚拟机WASM当然也有比如<a href=\"https://github.com/edubart/webcm\">WebCM</a>。这个项目相比于其他的项目有个不太一样的地方它把虚拟机、内核以及镜像打包成了一个单独的WASM文件……只是这样感觉并没有什么好处吧改起来更加复杂了。 <br />\n 以上这些虚拟机方案各有不同,但是想做一个自己的镜像相对来说还是有点困难,于是我又发现了另一个项目:<a href=\"https://github.com/container2wasm/container2wasm\">container2wasm</a>它可以让一个Docker镜像在浏览器中运行当然实际实现其实和Docker并没有什么关系本质还是虚拟机只是制作镜像的时候可以直接用Docker镜像方便了不少但Docker镜像一般也都很大所以第一次加载可能要下载很长时间。另外它还有一个优势可以使用<a href=\"https://bochs.sourceforge.io/\">Bochs</a>运行x86_64的镜像不像v86和WebVM只能模拟32位的x86虽然Bochs的运行效率可能会差一些而且可以使用WASI直接访问网络不像以上几个项目如果需要访问网络需要用到中继服务。当然访问网络这个还是要受浏览器本身的跨域策略限制。总之从项目本身来说感觉也算是相当成熟了尤其能用Docker镜像的话……我甚至可以考虑直接用<a href=\"https://hub.docker.com/r/unmayx/mabbs\">镜像</a>在线演示我曾经的<a href=\"https://github.com/Mabbs/Mabbs.Project\">Mabbs</a>项目😋。</p>\n<h2 id=\"纯wasm方案\">纯WASM方案</h2>\n<p>其实想要在浏览器中运行Linux也不一定非得要用虚拟机用虚拟机相当于是把其他指令集的机器码翻译为WASM然后浏览器还得再翻译成宿主机CPU支持的指令集然而WASM本身其实也算是一种指令集各种编译型语言编写的程序也能编译出WASM的产物比如<a href=\"https://github.com/ffmpegwasm/ffmpeg.wasm\">FFmpeg</a>。所以Linux内核也完全可以被编译成WASM正好前段时间我看新闻说<a href=\"https://github.com/joelseverin\">Joel Severin</a>做了这么一个<a href=\"https://github.com/joelseverin/linux-wasm\">项目</a>对Linux内核做了一些修改使其可以被编译为WASM程序我试了一下貌似在Safari浏览器中不能正常工作……Chrome浏览器倒是没问题不过即使这样用起来BUG也很多随便执行几条命令就会冻结体验不是很好。 <br />\n 沿着这个项目,我又找到一个由<a href=\"https://github.com/tombl\">Thomas Stokes</a>制作的<a href=\"https://github.com/tombl/linux\">项目</a>和Joel的项目差不多但我测了一下可以在Safari上运行感觉这个项目更完善不过之前那个项目上了新闻所以⭐数比这个更高😂。 <br />\n 于是我把它复制了一份在我的GitHub Pages上<a href=\"https://mabbs.github.io/linux/\">部署</a>了但直接用仓库中的源代码会显示“Error: not cross origin isolated”然而在Thomas自己部署的网站中可以正常打开我看了一眼貌似是因为在GitHub Pages中没有<a href=\"https://web.dev/articles/coop-coep\">COOP和COEP响应头</a>导致的。Linux作为多任务操作系统来说当然要运行多个进程而Linux要管理它们就需要跨线程Web Worker读取内存的能力所以用到了SharedArrayBuffer对象。不过由于CPU曾经出过“幽灵”漏洞导致现代浏览器默认禁止使用SharedArrayBuffer对象除非在服务器中配置COOP和COEP响应头才可以用但是Joel的项目也是在GitHub Pages上运行的啊为什么可以正常运行看了源代码后才发现原来可以<a href=\"/2025/08/01/sw-proxy.html\">用Service Worker作为反向代理</a>来给请求的资源加上响应头,他使用的是<a href=\"https://github.com/gzuidhof/coi-serviceworker\">coi-serviceworker</a>这个项目,所以我也给我部署的代码中加上了这个脚本,总算是解决了这个问题。 <br />\n 部署好这个项目之后我试用了几下虽然有些操作仍然会导致系统冻结但相比Joel的版本来说已经好多了。很遗憾的是目前这个WASM Linux还不能和外界通信所以作用不是很大另外如果想在里面运行其他二进制程序还是相当困难首先在WASM中不存在内存管理单元MMU不能实现隔离和分页的功能另外以WASM作为指令集的环境下编译的产物也得是WASM所以目前来说想用它做点什么还是不太合适。 <br />\n 以上的这两个将Linux内核编译为WASM的方案其实相当于给内核打补丁然后把浏览器看作是虚拟机来运行有点像Xen不过还有一种让Linux原生运行在WASM的<a href=\"https://github.com/okuoku/wasmlinux-project\">项目</a>,它将<a href=\"https://github.com/lkl/linux\">Linux kernel library</a>编译为了WASM。那么什么是LKL简单来说它有点像Wine就和我之前所说的<a href=\"/2024/12/08/simulator.html\">OS模拟器</a>差不多可以提供一个环境让程序以为自己在Linux下运行所以说它和之前的实现有一些不一样它不存在内核模式更像是一个普通的程序而不是系统了。 <br />\n 不过这个项目的体验也比较一般,它无论做什么都得按两次回车,看说明的意思貌似是因为没有实现异步信号传递,所以要手动打断<code class=\"language-plaintext highlighter-rouge\">read</code>函数而且也经常莫名其妙卡住总体体验不如Thomas的项目。</p>\n<h2 id=\"模仿的linux\">模仿的Linux</h2>\n<p>其实如果只是想做到和Linux类似的功能也有这样的项目比如<a href=\"https://github.com/stackblitz/webcontainer-core\">WebContainers</a>它没有运行Linux系统但是模拟了一个环境可以在浏览器中运行Node.js以及Python之类的脚本而且让脚本以为自己在Linux中运行除此之外它还能用Service Worker把环境中运行的端口映射给浏览器可以算是真的把服务端跑在浏览器上了。这个技术还挺高级不过想想也挺合理毕竟有WASI直接编译为WASM的程序也不需要操作系统就能运行所以用WASM去运行Linux本来就有点多此一举了😂。不过很遗憾的是WebContainers也不是开源软件要使用它只能引入StackBlitz的资源而且全网完全没有开源的替代品……也许在浏览器上进行开发本来就是个伪需求所以没什么人实现吧。 <br />\n 当然如果只是实现和WebContainers类似的功能<a href=\"https://github.com/jupyterlite/jupyterlite\">JupyterLite</a>也可以实现它可以在浏览器中像使用本地JupyterLab那样运行JS和Python还能用Matplotlib、Numpy、Pandas进行数据处理功能可以说非常强大而且还是开源软件。只不过它没有模拟操作系统的环境所以不能运行Node.js项目也不能提供终端所以不太符合我想要的效果……</p>\n\n<h1 id=\"总结\">总结</h1>\n<p>总的来说如果想要在博客上搞Linux终端目前来看似乎虚拟机方案会更靠谱一些虽然相对来说效率可能比较低但毕竟目前WASM方案的可靠性还是不够而且考虑到还需要配置额外的响应头感觉有点麻烦当然我觉得WASM还是算未来可期的如果成熟的话肯定还是比虚拟机要更好一些毕竟没有转译性能肯定要好不少。至于WebContainers这种方案……等什么时候有开源替代再考虑吧需要依赖其他服务感觉不够可靠。只是也许我的想法只需要模拟一个合适的文件系统然后给WASM版的Busybox加个终端就够了不过这样感觉Bug会更多😂。 <br />\n 至于打算什么时候给博客加上这个功能应该也是未来可期吧😝目前还没什么好的思路仅仅是分享一下在浏览器中运行Linux的各种方法。</p>\n","url":"/2025/12/01/linux.html","tags":["浏览器","Linux","虚拟机","WASM"],"date_published":"2025-12-01T00:00:00+08:00","date_modified":"2025-12-01T00:00:00+08:00","author":{"name":"mayx"}},{"id":"/2025/11/01/mirrors.html","title":"让博客永恒的探索","summary":"这篇文章讲述了作者为了提高博客的可靠性探索利用被滥用的Git平台进行博客镜像的想法和实践。作者发现一些Git实例存在大量空仓库和异常用户怀疑是SEO公司滥用因此决定利用这些平台进行博客镜像备份以应对平台倒闭或数据丢失的风险。作者选择Gitea和Forgejo平台作为目标编写脚本自动注册账号并导入博客仓库实现了自动化镜像分发。作者也意识到此类平台的稳定性存在不确定性并思考了“量”和“质”两种方式确保博客永恒性的优劣最终认为建立一个活跃的、自动执行维护操作的网络可能更有效。文章最后展示了作者创建的Git镜像列表并表达了对博客永恒性的思考。","content_html":"<p>Mayx Forever Project Phase II<!--more--></p>\n\n<h1 id=\"起因\">起因</h1>\n<p>在前段时间,我通过<a href=\"https://github.com/ecosyste-ms/repos\">Ecosyste.ms: Repos</a>找到了不少Git平台的实例也在探索的过程中发现和了解了<a href=\"/2025/08/10/tilde.html\">Tilde社区</a>。当然仅仅是这样显然还不够,里面的实例太多了,显然还有一些其他值得探索的东西。 <br />\n 在我查看这里面的某些Gitea实例时发现了一些奇怪的事情有些实例的仓库数和用户数多得离谱正常来说除了几个大的平台绝大多数应该只有几十到几百个仓库这就让我有点好奇了。于是当我点进去之后发现里面有一大堆仓库都是空的而且用户名和仓库名都非常有规律看起来都是一组单词加4位数字命名的显然这不是正常现象应该是一种有组织的行为。</p>\n\n<h1 id=\"被spam滥用的git实例\">被SPAM滥用的Git实例</h1>\n<p>于是我就简单看了一下这些异常的仓库和用户的规律可以发现每个用户都填了个人主页地址然后个人简介里大都是一段广告词。另外这些个人主页的地址看起来很多都是利用公开可注册的服务比如开源的有各种Git平台、Wiki以及论坛还有一些允许用户写个人主页的新闻网站。在这其中Git平台大多都没有广告文章基本上都是通过个人主页地址链接到网站而Wiki之类的就会写一些篇幅比较长的广告文章。 <br />\n 另外这些平台但凡还在开放注册就会被以大约每分钟一次的速度自动注册新账号……所以这种事情到底是谁在干呢我翻了几个仓库里面的广告多种多样有些看起来还算正常还有一些看起来有些黑产。其中我发现有一家叫做“悠闲羊驼SEO”的网站看介绍主要是给加密货币、对冲基金和博彩网站提供SEO优化的再加上这些被滥用的平台里也有不少类似的广告所以我怀疑这些滥用的行为就是这家SEO公司做的虽然没有证据😂。</p>\n\n<h1 id=\"永恒的探索\">永恒的探索</h1>\n<p>看到这么多Git平台被滥用我就有个想法之前为了保证可靠性给博客加了不少<a href=\"/proxylist.html\">镜像</a>,除此之外也在互联网档案馆、<a href=\"https://archive.softwareheritage.org/\">Software Heritage</a>、Git Protect等存档服务中上传了备份而且也在IPFS和Arweave等Web3平台上有相应的副本但是我觉得还不够再大的平台也有可能会倒闭IPFS不Pin还会被GC至于Arweave前段时间看了一眼整个网络才几百个节点感觉一点也不靠谱……所以我应该好好利用这些平台提高我博客的可靠性。 <br />\n 既然那些Spammer只是为了SEO去滥用这些平台不如让我利用这些平台给我的博客进行镜像吧至于使用哪个平台……显然用Git平台方便一些所以接下来就该考虑一下怎么样分发了。</p>\n\n<h1 id=\"镜像的分发\">镜像的分发</h1>\n<p>在Git平台中也有很多选择最知名的是GitLab不过GitLab有点复杂接口不太好用……而且很多实例没有开镜像仓库的功能毕竟如果我每次更新都给一堆仓库推送太费时间了我打算让各个平台主动从GitHub上拉取我的最新代码。正好Gogs系列的平台基本上都默认支持镜像仓库不过在我实际使用的时候发现Gogs默认情况下注册要验证码……写识别验证码感觉又挺麻烦而Gogs的两个分支——Gitea和Forgejo反倒没有……还挺奇怪所以接下来我的目标主要就是Gitea和Forgejo的实例了。 <br />\n 既然决定好目标我就得先发现它们了那些Spammer在注册的时候会在个人主页里写不同的网站其中也有一些类Gogs平台那么我可以先找一个Gitea平台用接口读取这些网站然后再调类Gogs专属的接口来检测这些网站哪个是类Gogs平台于是我就写了个<a href=\"https://github.com/Mabbs/spam_gogs-like_scanner/blob/main/main.py\">脚本</a>来找到它们。 <br />\n 找到这些平台之后就该注册了还好Gitea和Forgejo默认没有验证码注册起来也很简单随便写了个函数实现了一下</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">register_account</span><span class=\"p\">(</span><span class=\"n\">session</span><span class=\"p\">,</span> <span class=\"n\">url</span><span class=\"p\">,</span> <span class=\"n\">email</span><span class=\"p\">,</span> <span class=\"n\">username</span><span class=\"p\">,</span> <span class=\"n\">password</span><span class=\"p\">):</span>\n <span class=\"k\">try</span><span class=\"p\">:</span>\n <span class=\"n\">resp</span> <span class=\"o\">=</span> <span class=\"n\">session</span><span class=\"p\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span> <span class=\"o\">+</span> <span class=\"s\">\"/user/sign_up\"</span><span class=\"p\">)</span>\n <span class=\"n\">soup</span> <span class=\"o\">=</span> <span class=\"n\">BeautifulSoup</span><span class=\"p\">(</span><span class=\"n\">resp</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">,</span> <span class=\"s\">\"html.parser\"</span><span class=\"p\">)</span>\n <span class=\"n\">csrf_token</span> <span class=\"o\">=</span> <span class=\"n\">soup</span><span class=\"p\">.</span><span class=\"n\">find</span><span class=\"p\">(</span><span class=\"s\">\"input\"</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"s\">\"name\"</span><span class=\"p\">:</span> <span class=\"s\">\"_csrf\"</span><span class=\"p\">}).</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s\">\"value\"</span><span class=\"p\">)</span>\n\n <span class=\"n\">payload</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n <span class=\"s\">\"_csrf\"</span><span class=\"p\">:</span> <span class=\"n\">csrf_token</span><span class=\"p\">,</span>\n <span class=\"s\">\"user_name\"</span><span class=\"p\">:</span> <span class=\"n\">username</span><span class=\"p\">,</span>\n <span class=\"s\">\"email\"</span><span class=\"p\">:</span> <span class=\"n\">email</span><span class=\"p\">,</span>\n <span class=\"s\">\"password\"</span><span class=\"p\">:</span> <span class=\"n\">password</span><span class=\"p\">,</span>\n <span class=\"s\">\"retype\"</span><span class=\"p\">:</span> <span class=\"n\">password</span><span class=\"p\">,</span>\n <span class=\"p\">}</span>\n <span class=\"n\">headers</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s\">\"Content-Type\"</span><span class=\"p\">:</span> <span class=\"s\">\"application/x-www-form-urlencoded\"</span><span class=\"p\">}</span>\n <span class=\"n\">resp</span> <span class=\"o\">=</span> <span class=\"n\">session</span><span class=\"p\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"n\">url</span> <span class=\"o\">+</span> <span class=\"s\">\"/user/sign_up\"</span><span class=\"p\">,</span> <span class=\"n\">data</span><span class=\"o\">=</span><span class=\"n\">payload</span><span class=\"p\">,</span> <span class=\"n\">headers</span><span class=\"o\">=</span><span class=\"n\">headers</span><span class=\"p\">)</span>\n <span class=\"k\">if</span> <span class=\"s\">\"flash-success\"</span> <span class=\"ow\">in</span> <span class=\"n\">resp</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span>\n <span class=\"sa\">f</span><span class=\"s\">\"Successfully registered at </span><span class=\"si\">{</span><span class=\"n\">url</span><span class=\"si\">}</span><span class=\"s\"> with username: </span><span class=\"si\">{</span><span class=\"n\">username</span><span class=\"si\">}</span><span class=\"s\">, email: </span><span class=\"si\">{</span><span class=\"n\">email</span><span class=\"si\">}</span><span class=\"s\">, password: </span><span class=\"si\">{</span><span class=\"n\">password</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n <span class=\"p\">)</span>\n <span class=\"n\">save_to_file</span><span class=\"p\">(</span>\n <span class=\"s\">\"instances_userinfo.csv\"</span><span class=\"p\">,</span> <span class=\"sa\">f</span><span class=\"s\">\"</span><span class=\"si\">{</span><span class=\"n\">url</span><span class=\"si\">}</span><span class=\"s\">,</span><span class=\"si\">{</span><span class=\"n\">username</span><span class=\"si\">}</span><span class=\"s\">,</span><span class=\"si\">{</span><span class=\"n\">email</span><span class=\"si\">}</span><span class=\"s\">,</span><span class=\"si\">{</span><span class=\"n\">password</span><span class=\"si\">}</span><span class=\"s\">\"</span>\n <span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"bp\">True</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Failed to register at </span><span class=\"si\">{</span><span class=\"n\">url</span><span class=\"si\">}</span><span class=\"s\">.\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"bp\">False</span>\n <span class=\"k\">except</span> <span class=\"nb\">Exception</span> <span class=\"k\">as</span> <span class=\"n\">e</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Error registering at </span><span class=\"si\">{</span><span class=\"n\">url</span><span class=\"si\">}</span><span class=\"s\">: </span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"bp\">False</span>\n</code></pre></div></div>\n<p>注册完之后就该导入仓库了只是通过模拟前端发包的方式在Gitea和Forgejo中不同版本的表现可能不太一样所以我想用API实现但是API又得有API Key生成API Key还得模拟前端发包😥……所以怎么都绕不过。 <br />\n 不过这个生成API Key还挺麻烦有些版本不需要配权限范围有些配权限的参数还不一样……不过我就是随便一写凑合用吧像那些专业的Spammer应该是有更强大的脚本判断各种情况。 <br />\n 最后我还是选择用API导入又写了个函数</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">def</span> <span class=\"nf\">import_repos</span><span class=\"p\">(</span><span class=\"n\">token</span><span class=\"p\">,</span> <span class=\"n\">url</span><span class=\"p\">):</span>\n <span class=\"k\">try</span><span class=\"p\">:</span>\n <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">requests</span><span class=\"p\">.</span><span class=\"n\">post</span><span class=\"p\">(</span>\n <span class=\"n\">url</span><span class=\"o\">=</span><span class=\"n\">url</span> <span class=\"o\">+</span> <span class=\"s\">\"/api/v1/repos/migrate\"</span><span class=\"p\">,</span>\n <span class=\"n\">headers</span><span class=\"o\">=</span><span class=\"p\">{</span>\n <span class=\"s\">\"Authorization\"</span><span class=\"p\">:</span> <span class=\"s\">\"token \"</span> <span class=\"o\">+</span> <span class=\"n\">token</span><span class=\"p\">,</span>\n <span class=\"p\">},</span>\n <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"p\">{</span>\n <span class=\"s\">\"repo_name\"</span><span class=\"p\">:</span> <span class=\"s\">\"blog\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"mirror_interval\"</span><span class=\"p\">:</span> <span class=\"s\">\"1h\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"mirror\"</span><span class=\"p\">:</span> <span class=\"bp\">True</span><span class=\"p\">,</span>\n <span class=\"s\">\"description\"</span><span class=\"p\">:</span> <span class=\"s\">\"Mayx's Home Page\"</span><span class=\"p\">,</span>\n <span class=\"s\">\"clone_addr\"</span><span class=\"p\">:</span> <span class=\"s\">\"https://github.com/Mabbs/mabbs.github.io\"</span><span class=\"p\">,</span>\n <span class=\"p\">},</span>\n <span class=\"p\">)</span>\n <span class=\"k\">if</span> <span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">status_code</span> <span class=\"o\">==</span> <span class=\"mi\">201</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s\">\"Repository import initiated successfully.\"</span><span class=\"p\">)</span>\n <span class=\"n\">save_to_file</span><span class=\"p\">(</span><span class=\"s\">\"repo_list.txt\"</span><span class=\"p\">,</span> <span class=\"n\">url</span> <span class=\"o\">+</span> <span class=\"s\">\"/mayx/blog\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"bp\">True</span>\n <span class=\"k\">else</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Failed to initiate repository import. Status code: </span><span class=\"si\">{</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">status_code</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Response: </span><span class=\"si\">{</span><span class=\"n\">response</span><span class=\"p\">.</span><span class=\"n\">text</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"bp\">False</span>\n <span class=\"k\">except</span> <span class=\"nb\">Exception</span> <span class=\"k\">as</span> <span class=\"n\">e</span><span class=\"p\">:</span>\n <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s\">\"Error updating website: </span><span class=\"si\">{</span><span class=\"n\">e</span><span class=\"si\">}</span><span class=\"s\">\"</span><span class=\"p\">)</span>\n <span class=\"k\">return</span> <span class=\"bp\">False</span>\n</code></pre></div></div>\n<p>脚本写好之后我就只需要重复扫描、注册、导入的步骤就行了这样我的镜像就会越来越多而且用类Gogs的实例还有一个好处就是不需要我手动推送它会自动定时拉取我的仓库保持最新这样也许只要人类文明存在我的博客就会在某处存在吧🤣。 <br />\n 最后我创建的Git镜像可以在<a href=\"/other_repo_list.html\">这里</a>看到看起来还是挺壮观啊😋。只不过像这种会被Spammer随便注册的Git平台实例很难说它能活多久如果没人管而且是云服务器也许到期就没了有人管的话应该不会允许这么多Spam行为吧……</p>\n\n<h1 id=\"感想\">感想</h1>\n<p>不知道用“量”来确保博客的永恒更可靠……还是用“质”的方式更好呢?其实我觉得还得是活动的更好,就像我以前所说的,如果有<a href=\"/2024/11/02/trojan.html#%E6%84%9F%E6%83%B3\">僵尸网络</a>,自动帮我执行发现并推送的操作,也许比等着这些实例逐渐消失更好吧……只不过那样可能就不太友好了😂。</p>\n","url":"/2025/11/01/mirrors.html","tags":["Git","Gitea","镜像","Forever"],"date_published":"2025-11-01T00:00:00+08:00","date_modified":"2025-11-01T00:00:00+08:00","author":{"name":"mayx"}},{"id":"/2025/10/12/recover.html","title":"一次找回GitHub上被删除仓库的经历","summary":"这篇文章讲述了作者通过GitHub的Fork特性找回一个被删除的Brainfuck可视化演示仓库的经历。由于原仓库和作者主页都已消失作者推测GitHub在Fork时会共享对象库只要有任意一个Fork仓库存在GitHub就会保留所有对象从而可以通过找到一个Fork仓库的最新提交Hash值来还原目标仓库。作者通过Linux内核仓库的Fork进行验证随后在互联网档案馆上找到目标仓库的Fork以及其Hash值最终通过Git命令将本地仓库的HEAD指针指向目标提交成功恢复了该仓库的代码并将其部署到自己的GitHub Pages上。最后作者发现Software Heritage组织会保存所有代码因此在遇到类似情况时可以直接通过该平台进行查找。","content_html":"<p>在GitHub中寻找踪迹也许是非常简单的事情……<!--more--></p>\n\n<h1 id=\"起因\">起因</h1>\n<p>前段时间,有人和我聊天的时候提到了<a href=\"https://esolangs.org/wiki/Brainfuck\">Brainfuck</a>语言,让我回想起了高中时写的<a href=\"/%E6%BC%94%E8%AE%B2%E7%A8%BF/2018/06/20/Coding.html\">演讲稿</a>。那时候我在演讲时也介绍了Brainfuck语言。对于Brainfuck的解释器<a href=\"https://rosettacode.org/wiki/RCBF\">各种语言都可以实现</a>不过我当时为了方便理解用了一个在GitHub Pages上的网站用可视化的方式演示了它的运行过程效果很不错。现在既然聊到了自然就想分享一下这个<a href=\"https://fatiherikli.github.io/brainfuck-visualizer/\">演示的网站</a>但我正想打开时发现网站已经404了😰。 <br />\n 在GitHub Pages上的网站都有对应的仓库现在不仅原仓库消失了连作者的<a href=\"https://github.com/fatiherikli\">首页</a>都打不开看样子是完全退出GitHub了……那么我想找到这个网站的想法就无法实现了吗不过GitHub有些有意思的特性也许能帮助我找回这个网站。</p>\n\n<h1 id=\"github的特性\">GitHub的特性</h1>\n<p>在GitHub中一个普通的仓库可能没有什么特别的也许就是服务器上的一个文件夹。但是当仓库被其他人Fork的时候就不一样了在执行Fork时显然GitHub不会完整复制整个仓库。否则同一个仓库在服务器上会占用双倍空间这显然不合理。另外想想Git的结构它由提交对象和分支指针构成每次提交都有唯一的Hash值且不会冲突。因此可以推测GitHub在实现Fork时所有被Fork的仓库可能共享同一个对象库而每个用户仓库只保存指针这样所有仓库只会占用增量空间而不会存储重复内容。 <br />\n 但这样也会带来一个问题首先因为很多人可能要共用一部分对象所以也很难确认对象的所有权而且也因为这个原因所有的对象要能被所有人访问。因此在整个Fork网络中只要有一个仓库存在GitHub就必须保留所有的对象而且每个仓库都能访问这个网络中所有的对象。为了验证这一点我们可以用最知名的<a href=\"https://github.com/torvalds/linux\">Linux内核仓库</a>做个示例。 <br />\n 首先对Linux仓库进行Fork然后我们可以随便做一些改动比如在README中写“Linux已经被我占领了😆”之类的内容提交到自己的仓库并且记下提交的Hash值接下来就可以把自己的仓库删掉了。如果上面的猜想是正确的那么在这个Fork网络中的任何一个仓库查看我刚刚的提交应该都可以于是我直接在主仓库拼上了<a href=\"https://github.com/torvalds/linux/tree/78e1d0446b94012da8639aa2b157d4f2dee481ce\">提交的Hash值</a>(顺便一说只要值唯一,和其他的提交不冲突,<a href=\"https://github.com/torvalds/linux/tree/78e1d044\">短的Hash值</a>也可以果不其然能找到刚刚修改的内容这样一来只要GitHub和任意一个Linux仓库的Fork还存在这个提交就永远存在了😝。</p>\n\n<h1 id=\"找回仓库\">找回仓库</h1>\n<p>那么接下来找回之前网站的方案就很简单了我只要找到网站仓库的任意一个Fork然后只要知道最新的提交Hash我就可以还原最新的仓库了。Fork倒是好找随便搜一下<a href=\"https://github.com/ashupk/brainfuck-visualizer\">就能找到一个</a>。这个Fork的最新提交是2016年但要想找到我当年演讲的版本至少到2018年之后。不过这个Hash值也不太好找虽然理论上爆破短Hash值也可以但是感觉太麻烦了没有那个必要所以我干脆直接去互联网档案馆看看能找到的<a href=\"https://web.archive.org/web/20201229125043/https://github.com/fatiherikli/brainfuck-visualizer/\">最新的仓库页面</a>吧这样我就能找到它的Hash值了然后我再把Fork仓库的地址和Hash拼到一起就看得到最新代码了。 <br />\n 当然仅仅看到代码还不够。我想Fork这个项目并在自己的GitHub Pages上部署一份。有没有什么好办法可以将我仓库的HEAD指针指向最新的提交呢其实很简单首先我要Fork这个Fork仓库然后Clone我的仓库到本地。不过此时Clone下来的仓库并不包含GitHub上完整的对象库因此直接checkout或reset是不行的。这时Hash值就派上用场了通过fetch拉取对应提交后就可以进行上述操作。具体命令如下</p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>git fetch origin &lt;commit-hash&gt;\ngit reset <span class=\"nt\">--hard</span> &lt;commit-hash&gt;\ngit push origin master\n</code></pre></div></div>\n<p>最终我就获得了包含<a href=\"https://github.com/Mabbs/brainfuck-visualizer\">最新代码</a>的<a href=\"https://mabbs.github.io/brainfuck-visualizer/\">Brainfuck可视化演示</a>了🎉。</p>\n\n<h1 id=\"结局\">结局</h1>\n<p>后来我才知道,原来有一个专门的组织<a href=\"https://archive.softwareheritage.org\">Software Heritage</a>会保存所有代码,根本没必要搞这些花里胡哨的操作😂,像这个仓库也是能很轻易在<a href=\"https://archive.softwareheritage.org/browse/origin/directory/?origin_url=https://github.com/fatiherikli/brainfuck-visualizer\">上面</a>找到这下以后知道了再遇到类似情况就可以直接去Software Heritage查找而不必在互联网档案馆上找线索瞎折腾了🤣。</p>\n","url":"/2025/10/12/recover.html","tags":["GitHub","Git","代码恢复","软件存档"],"date_published":"2025-10-12T00:00:00+08:00","date_modified":"2025-10-12T00:00:00+08:00","author":{"name":"mayx"}},{"id":"/2025/09/01/quine.html","title":"关于ZIP Quine与自产生程序的探索","summary":"这篇文章主要介绍了作者在博客部署过程中对ZIP Quine自包含压缩包和自产生程序的探索过程。作者起初想利用压缩包实现离线浏览但遇到了压缩包不包含自身的问题。随后作者回顾了ZIP Quine的原理如droste.zip以及如何通过DEFLATE压缩算法的LZ77编码实现自包含。作者尝试了Russ Cox的方案但发现由于压缩格式限制实际操作中存在数据容量的限制无法存下整个博客。尽管如此作者还是研究了嵌套循环的ZIP Quine如Ruben Van Mello的论文中所描述的尽管空间仍然有限。探索过程中作者还学习了自产生程序Quine的概念包括其实现原理和各种编程语言中的例子。作者最后感慨探索过程中的收获比原本的目标更重要。","content_html":"<p>描述自己的代码……是一种什么样的感觉?<!--more--></p>\n\n<h1 id=\"起因\">起因</h1>\n<p>前段时间我在折腾<a href=\"/2025/08/10/tilde.html#%E4%BD%BF%E7%94%A8git-hooks%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%E5%8D%9A%E5%AE%A2\">博客部署</a>的时候,回顾起了好久以前写的<a href=\"/deploy.sh\">部署脚本</a>。对于全站打包的这个步骤,本来我打算利用这个压缩包结合<a href=\"/2025/08/01/sw-proxy.html\">Service Worker做离线浏览</a>,但因为没有合适的方案所以放弃了。而现在对于这个压缩包,我又有了一个特别的想法。事实上在这个下载全站的压缩包中,里面的内容和实际的网站并不完全相同,因为在这个压缩包里缺少了压缩包本身。所以把这个压缩包解压之后直接当作网站打开,会发现下载压缩包的链接是无效的,除非在解压之后把压缩包移动到网站里才行…… <br />\n 于是我就在想有没有一种可能可以让压缩包解压之后里面又包含了这个压缩包本身?似乎是个不太可能的事情,但我以前听过类似的东西,也许并非不可能?所以这次就来探索一下吧。</p>\n\n<h1 id=\"自包含压缩包的探索\">自包含压缩包的探索</h1>\n<p>在很久之前我见到过一个很知名的自包含压缩包又称为ZIP Quine叫做<a href=\"https://alf.nu/s/droste.zip\">droste.zip</a>是由Erling Ellingsen<a href=\"https://web.archive.org/web/20090106171423/http://tykje.com/code/useless/zip-file-quine\">在2005年制作</a>出来的。当时我只知道它很神奇,原理什么的并不清楚,另外在网上也基本上找不到类似的压缩包。现在再回看时发现<a href=\"https://alf.nu/ZipQuine\">介绍</a>里包含了一些相关的链接,甚至还有一篇能自己制作类似压缩包的论文,所以接下来就可以看一下这些链接来理解这种压缩包是如何制作的了。 <br />\n 关于原理方面,先看<a href=\"https://github.com/wgreenberg\">Will Greenberg</a>制作的一个<a href=\"https://wgreenberg.github.io/quine.zip/\">示例</a>在这里面有一个谜题使用“print M”原样输出接下来的M行输入内容和“repeat M N”从倒数第N行的输出内容开始重复M行这两个指令让最终执行的结果和输入的指令完全相同。这正是对DEFLATE压缩算法所使用的LZ77编码的一种简化模拟也就是说只要解决了这个问题就可以让压缩包在解压时原样输出自己了。 <br />\n 这个问题看起来还挺复杂,不过在仓库的<a href=\"https://github.com/wgreenberg/quine.zip/issues/1\">Issues</a>就有人给出了几种解法(当然,这个题目解法不唯一),所以在理论上应该是可行的,那么接下来就需要研究压缩文件的格式来实现它了。</p>\n<h2 id=\"实现zip-quine的探索\">实现ZIP Quine的探索</h2>\n<p>在<a href=\"https://swtch.com/~rsc/\">Russ Cox</a>写的《<a href=\"https://research.swtch.com/zip\">Zip Files All The Way Down</a>》文章中同样说明了这个原理而且给出了一个方案让上述这两个命令除了能够对命令本身的重复以外还可以添加一些额外数据这样才能做到构建一个压缩包文件。按照文章的描述如果用之前谜题的规则来说我们设头和尾的内容都是“print 0”那么Cox给出的方案如下</p>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>print 0\nprint 2\nprint 0\nprint 2\nrepeat 2 2\nprint 1\nrepeat 2 2\nprint 1\nprint 1\nprint 4\nrepeat 2 2\nprint 1\nprint 1\nprint 4\nrepeat 4 4\nprint 4\nrepeat 4 4\nprint 4\nrepeat 4 4\nprint 4\nrepeat 4 4\nprint 4\nrepeat 4 4\nprint 0\nprint 0\nprint 2\nrepeat 4 4\nprint 0\nprint 0\nprint 2\nrepeat 2 2\nprint 0\nrepeat 2 2\nprint 0\n</code></pre></div></div>\n<p>我们把这些指令粘贴到<a href=\"https://wgreenberg.github.io/quine.zip/\">quine.zip</a>这个谜题中就会发现输出和输入完全相同以此就能验证Cox方案的正确性。除此之外作者还给出了生成的源代码<a href=\"http://swtch.com/rgzip.go\">rgzip.go</a>,只是代码里面到处都是用来构建压缩包的十六进制数字,完全看不懂😂。 <br />\n 另外这个方案是针对使用基于LZ77与哈夫曼编码的DEFLATE压缩算法所以格式不重要。因此无论是ZIP还是GZIP以及TGZGZIP压缩后的TAR其实都是一样的因为他们都使用的是DEFLATE压缩算法。顺便一提<a href=\"https://github.com/honno\">Matthew Barber</a>写了一篇很棒的<a href=\"https://github.com/honno/gzip-quine\">文章</a>通过动画演示并详细讲解了如何实现一个简单的GZIP版ZIP Quine很值得一看。 <br />\n 还有一点普通的TAR文件能否实现类似功能呢从原理来说估计不行因为TAR文件本身并没有压缩也不包含指令就单纯是一堆文件和元数据的拼接所以就做不到自包含了。 <br />\n 这么来看既然TGZ可以那是不是在我博客网站的压缩包里放一份和自己一模一样的压缩包是可行的很遗憾按照这个方法来看是做不到的由于压缩格式和编码的限制这个方案在实际实现时发现操作码需要是5个字节最后发现最多只有类似<code class=\"language-plaintext highlighter-rouge\">repeat 64 64</code>这样的指令能够满足要求因此头尾区最多只能放64-5=59个字节的数据也就刚刚好能容纳压缩格式需要的内容几乎没法塞更多东西进去……显然这些限制导致这种方式对我来说意义就不大了何况作者的代码我也看不懂……而且还要考虑压缩包还存在校验用的CRC32需要找满足整个压缩包的CRC32正好在压缩包中的“不动点”。虽然从CRC32的原理来说应该有办法做到通过数学方式解决但这篇文章的作者因为解决了自包含的问题之后累了因此放弃继续研究选择直接暴力破解毕竟CRC32只有32位估计思考的时间都要比爆破的时间长吧😂。但如果是这样即使有方案能存下我博客的数据也不能在每次网站构建的时候都制作一次了…… <br />\n 虽然Russ Cox写的文章看起来做不到包含更多内容了但Erling Ellingsen制作的droste.zip却包含了一张图片说明并不是没办法加入更多数据只是没有找到正确的方法。在2024年<a href=\"https://github.com/ruvmello\">Ruben Van Mello</a>写了一篇论文《<a href=\"https://www.mdpi.com/2076-3417/14/21/9797\">A Generator for Recursive Zip Files</a>》,在这篇论文里他不仅解决了包含的额外数据过少的问题,还编写了一个通用工具,能让普通人也能生成这样的压缩包,而且他还创新性的做了一种像衔尾蛇一样的双层嵌套循环压缩包,非常的有意思,所以接下来我打算试试他的方案。 <br />\n 在这篇论文中里面简述了之前Russ Cox写的内容也提到了59字节的限制于是作者对原有的结构进行了一些改动让操作码可以超出5字节的限制具体可以看论文的表6从而解决了只能包含59字节额外数据的限制。但由于DEFLATE压缩格式本身的约束16位存储块长度以及32KiB回溯窗口即使能够添加文件最多也只能额外容纳32763字节的数据其中包括压缩包所需的文件头……显然这点空间完全存不下我的博客😭看来我只能打消这个想法了。但既然都研究了半天也不一定要存我的博客嘛可以看看还有没有别的东西可以存在这之前先继续阅读论文看完再说吧。</p>\n<h2 id=\"制作一个嵌套循环的zip-quine\">制作一个嵌套循环的ZIP Quine</h2>\n<p>在实现了常规的ZIP Quine之后接下来就是作者的创新点了如果光是解决存储限制这点创新点估计还不够发论文吧😂。作者接下来制作了一种循环压缩文件在压缩包内包含文件A和压缩包A而压缩包A中则包含文件B和最初的压缩包从而形成一个循环递归的结构。看论文的描述所说如果把外层的压缩包和内层的压缩包的开头和结尾按照一定的规则交替混合就可以看作是一个整体然后按照之前做ZIP Quine那样处理就可以……具体实现的细节得看论文的表10。只不过既然是把两个压缩包看作一个整体的话按照上面的限制自然每个压缩包能容纳的数据量就更小了每个最多只能容纳16376字节的数据…… <br />\n 另外既然这里面有两个压缩包那么每个压缩包还有自己的CRC32校验和理论上如果要爆破的话计算难度得是原来的平方这样难度就太大了。不过作者发现如果把数据的CRC32值取反即与“0xFFFFFFFF”取异或然后和原始数据拼到一起整个数据的CRC32校验和就会被重置为一个固定的值“0xFFFFFFFF”看起来挺有意思正常的哈希算法可没有这种特性。因此原本计算难度很大的爆破计算现在就可以和之前一样了…… <del>话说为什么不让两层的CRC32都这样计算包括之前单层的ZIP Quine这样就不需要爆破了……貌似是因为在普通的ZIP Quine中满足条件的CRC32需要出现两次所以不能用这个方案吧</del> <br />\n 现在所有的理论都足够了我需要挑一个文件来做这样嵌套循环的ZIP Quine既然博客的大小不可以……要不然我就用我写过的第一个大项目——<a href=\"https://github.com/Mabbs/Mabbs.Project\">Mabbs</a>吧这个项目的主程序是22KiB看起来似乎超出了嵌套循环ZIP Quine的限制其实没有它的限制指的是压缩后的大小我这个程序压缩之后是8KiB左右所以完全没问题。 <br />\n 接下来就该使用论文中提到的生成工具:<a href=\"https://github.com/ruvmello/zip-quine-generator\">zip-quine-generator</a>这是一个Kotlin编写的程序从发布中可以下载预构建的程序接下来只要按照README中的描述使用“<code class=\"language-plaintext highlighter-rouge\">--loop</code>”参数就可以用这个程序创建嵌套循环的ZIP Quine了。不过它原本的代码不能修改里面生成的压缩包的名字另外<a href=\"https://github.com/ruvmello/zip-quine-generator/blob/3b8cf977e7a93bb956ad966d5e3b4d503f410529/src/main/kotlin/zip/ZIPArchiver.kt#L845\">压缩后的文件属性是隐藏文件</a>,还有<a href=\"https://github.com/ruvmello/zip-quine-generator/blob/3b8cf977e7a93bb956ad966d5e3b4d503f410529/src/main/kotlin/zip/ZIPArchiver.kt#L29\">生成的压缩包中文件的创建时间总是当前时间</a>,以及<a href=\"https://github.com/ruvmello/zip-quine-generator/blob/3b8cf977e7a93bb956ad966d5e3b4d503f410529/src/main/kotlin/zip/ZIPArchiver.kt#L30\">给文件内填充额外数据的代码里面填的是作者的声明</a>表示文件是由他论文的所写的生成器生成的……这些情况让我感觉有点不爽还是希望这些部分能自定义一下所以我就小改了一下他的代码。顺便一说Kotlin编译起来还挺简单直接一句<code class=\"language-plaintext highlighter-rouge\">kotlinc src/main/kotlin -include-runtime -d output.jar</code>就可以了也不需要折腾Maven之类乱七八糟的东西。最终我修改并编译完程序之后就把文件丢到服务器上开始给我爆破CRC32了花了10个小时就算出来了倒是比想象中快😂。 <br />\n 2025.09.26更新在2025年9月15日的时候<a href=\"https://github.com/NateChoe1\">Nate Choe</a>给zip-quine-generator做了个<a href=\"https://github.com/ruvmello/zip-quine-generator/pull/3\">重大贡献</a>,他通过<a href=\"https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm\">数学的方式</a>让CRC32的值可以不需要通过爆破的方式算出来现在想要再制作这样的压缩包就可以瞬间生成了……要是我再晚点做这个压缩包就不需要花那么长时间了吧🤣。 <br />\n 最终我给我的<a href=\"https://github.com/Mabbs/Mabbs.Project\">Mabbs</a>项目创建了<a href=\"https://github.com/Mabbs/Mabbs.Project/releases/tag/Final-version\">Infinite Mabbs</a>这个发布,生成的文件也可以在<a href=\"/assets/Mabbs.zip\">这里</a>下载,这也算是不枉我研究半天这个论文了😆。</p>\n\n<h1 id=\"自产生程序的探索\">自产生程序的探索</h1>\n<p>说起来自包含压缩包为什么叫做ZIP Quine其中的Quine是什么意思呢其实这是一位美国哲学家的名字他提出了“自指”的理论概念所以为了纪念他有类似概念的东西就被称作Quine具体为什么也可以去看<a href=\"https://en.wikipedia.org/wiki/Quine_(computing)#Name\">维基百科</a>的说明。现在提到Quine一般代表的就是自产生程序而自包含压缩包因为实现的原理和自产生程序的原理差不多所以叫做ZIP Quine。因此接下来我打算探索一下自产生程序更深入地了解Quine。</p>\n<h2 id=\"实现quine的探索\">实现Quine的探索</h2>\n<p>那么什么是自产生程序?简单来说就是程序的源代码和程序的输出完全相同的程序,而且通常来说不允许通过读取/输入源代码的方式实现。按照一般的想法让程序输出自身就需要输出中有全部代码整个代码就会变长而更长的代码就要输出更多然后代码就会越来越长……所以这么想来似乎成了个死胡同。但其实这种程序实现起来并不复杂想想ZIP Quine的实现关键在于指令还需要以数据的形式表现并且能被引用这样输出的时候就会连着指令一起输出了。比如用Python的Quine举例</p>\n<div class=\"language-python highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">c</span> <span class=\"o\">=</span> <span class=\"s\">'c = %r; print(c %% c)'</span><span class=\"p\">;</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">c</span> <span class=\"o\">%</span> <span class=\"n\">c</span><span class=\"p\">)</span>\n</code></pre></div></div>\n<p>这里的变量中就以数据的形式存储了程序的代码,而在输出的时候除了变量内的代码,又通过引用的方式又把变量的内容放回到赋值的地方,所以它的输出就和原本的代码一样了。 <br />\n 其实Quine的实现思路都差不多是这样可以在<a href=\"https://rosettacode.org/\">Rosetta Code</a>中找到<a href=\"https://rosettacode.org/wiki/Quine\">各种语言实现的Quine</a>在这其中能够发现大多数高级语言的写法都是类似的除了一些低级语言以及esolang……这些我也看不懂😂主要是有些语言没有变量的概念不知道是怎么区分代码和数据……除了那个网站在<a href=\"https://esolangs.org/wiki/List_of_quines\">这里</a>还能找到更多由esolang编写的Quine可以看出来基本上很难看懂其中最令人望而生畏的还得是<a href=\"https://lutter.cc/malbolge/quine.html\">用Malbolge写的Quine</a>这个代码看起来不仅很长而且像乱码一样。至于什么是Malbolge这就是Malbolge程序</p>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>D'&lt;;_98=6Z43Wxx/.R?Pa\n</code></pre></div></div>\n<p>代码就像加了密似的顺便一说这个执行的输出结果是“Mayx”关于Malbolge的具体细节可以看它的<a href=\"http://www.lscheffer.com/malbolge_spec.html\">规范</a>,另外虽然这个语言写起来很复杂,但还是有人能用它编出程序的,甚至还有人用<a href=\"https://esolangs.org/wiki/Malbolge_Unshackled\">Malbolge Unshackled</a>Malbolge不限内存的变种写过<a href=\"https://github.com/iczelia/malbolge-lisp\">Lisp解释器</a>,实在是恐怖如斯😨。</p>\n<h2 id=\"只能quine的语言\">只能Quine的语言</h2>\n<p>其实想要做出Quine还有一种更加无聊的方案那就是设计一种只能Quine的语言🤣。根据Quine的定义代码输出的结果就是它本身……所以我们可以把任何内容都看作代码然后这种语言的行为就是输出所有代码……听起来是不是有点无聊但是想想看如果把Linux中的cat命令当作解释器就可以实现这种语言了比如</p>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>#!/bin/cat\nHello, world!\n</code></pre></div></div>\n<p>作为脚本执行的结果就是原样输出这段内容不过把内容当作代码算不算作弊呢……如果看作是cat的输入显然是作弊但如果是当作源代码的话应该就不算了吧😋……但这就不是能写出逻辑的语言了。所以说Quine的趣味并不在“能不能实现”而在于如何在限制条件下实现。正是因为大多数语言不会直接“自我输出”才会觉得那些精巧的Quine程序如此有意思。</p>\n<h2 id=\"quine-relay的探索\">Quine Relay的探索</h2>\n<p>还有一个更加复杂的Quine变种是“Quine接力”Quine Relay即一个程序输出另一个程序的源代码另一个程序又输出下一个程序的源代码最后回到原始程序就和之前所说的嵌套循环ZIP Quine有点类似。最著名的例子是<a href=\"https://github.com/mame\">Yusuke Endoh</a>(这位还是<a href=\"https://www.ioccc.org/\">IOCCC</a>的冠军之一)创建的<a href=\"https://github.com/mame/quine-relay\">quine-relay</a>项目它包含了128种编程语言的循环。 <br />\n 这种程序写起来会更复杂一些不过原理都差不多通常除了当前运行的部分是可执行代码外其他的代码都需要以额外包含的数据形式如字符串存储在变量中。如果想自己做个类似简单的Quine Relay除了去看<a href=\"https://en.wikipedia.org/wiki/Quine_(computing)#Ouroboros_programs\">维基百科</a>之外,前段时间我还看到过一个不错的<a href=\"https://blog.mistivia.com/posts/2024-09-21-quine/\">文章</a>里面就讲了如何用“笨办法”编写Quine和Quine Relay通过把变量中的内容编码为16进制来避免不同语言可能存在的特殊字符转译问题思路不错对于理解如何编写这类程序的问题很有帮助。当然这只是个<strong>简单</strong>的方案,仅适用于一些常规的编程语言,像上面那个<a href=\"https://github.com/mame/quine-relay\">quine-relay</a>项目中甚至还包含Brainfuck之类的esolang这种估计得要想办法让相对高级一些的语言通过“生成”的方式得到输出下一种代码的代码而不是简单的赋值了所以只靠这点知识想去完全理解大佬的作品还是想多了😆。 <br />\n 顺便一说quine-relay并不是那位大佬唯一的Quine作品他还做过<a href=\"https://github.com/mame/radiation-hardened-quine\">有冗余的Quine</a>以及<a href=\"https://mamememo.blogspot.com/2010/09/qlobe.html\">动态的Quine</a>,真的是相当的厉害……</p>\n<h2 id=\"polyglot-quine的探索\">Polyglot Quine的探索</h2>\n<p>除了Quine Relay之外还有一种很复杂的Quine叫做<a href=\"https://en.wikipedia.org/wiki/Polyglot_(computing)\">Polyglot</a> Quine与Quine Relay需要在程序执行后才能切换到其他语言接力不同Polyglot Quine的源代码本身即可同时属于多种语言而且用这些语言的解释器每个执行后的输出全都一样都与源代码完全一致。由于不同的编程语言的格式既有些相同之处也有很多不同之处所以让同一份代码表示不同语言就会很容易产生歧义这时候就只能想办法通过一些特别的方式比如将可能会对当前语言产生干扰的代码看作是注释的方式来规避语言之间的差异。 <br />\n Quine本身就已经很困难了再加上这些限制就变得更加复杂了所以制作Polyglot Quine的编程语言基本上都得精挑细选而且通常只有两种语言比如<a href=\"https://github.com/TrAyZeN/polyglot-quine/blob/master/main.c\">这段代码</a>就是C和Python的Polyglot Quine它巧妙利用了C预处理器指令在Python中可视为注释的特性使两种语言互不干扰非常有趣。当然并不是说只能是两种语言像<a href=\"https://github.com/2KAbhishek/polyquine\">这个</a>项目甚至使用了五种语言C、Perl、PHP、Python、Ruby可以说是相当厉害了。除此之外更令人惊叹的则是<a href=\"https://github.com/d0sboots/PyZipQuine\">PyZipQuine</a>项目在这其中LZ77编码也可以作为一种语言所以既可以被当作压缩包也可以作为Python2.7代码而且二者都是Quine实在是令人赞叹。</p>\n\n<h1 id=\"感想\">感想</h1>\n<p>虽然这次探索最终没能完成让包含博客所有内容的压缩包自包含但是在探索的过程中我还是收获了不少尤其是Ruben Van Mello制作的ZIP Quine生成工具实在是太棒了。很久以前我见到droste.zip这个压缩包的时候就想整一个属于自己的ZIP Quine现在我不仅用那个生成工具做了一个还是对我来说很有意义的第一个项目——Mabbs而且更关键的还是生成的是比普通的ZIP Quine更高级的嵌套循环ZIP Quine也算是圆了小时候的心愿了。 <br />\n 另外在探索自产生程序的时候,也发现了一些很有意思的网站,比如<a href=\"https://rosettacode.org/\">Rosetta Code</a>以及<a href=\"https://esolangs.org/\">Esolang wiki</a> <del>(虽然这个网站里被好多小学生写了一堆无聊的东西😂)</del> ,里面有不少有趣的东西,也算是让我大开眼界了。 <br />\n 所以有的时候探索不一定要完成目标,在这个过程中也会收获到很多不错的东西吧😊。</p>\n","url":"/2025/09/01/quine.html","tags":["压缩包","Quine","自产生程序","Quine Relay"],"date_published":"2025-09-01T00:00:00+08:00","date_modified":"2025-09-01T00:00:00+08:00","author":{"name":"mayx"}},{"id":"/2025/08/10/tilde.html","title":"在Tilde社区的游玩体验","summary":"这篇文章介绍了作者在Tilde社区的体验这是一类基于类Unix环境的公共服务器社区类似于家目录提供预装的软件、开发环境和公共服务如聊天室、邮件、BBS论坛等强调了社区的互动性和共享精神。作者通过申请、审核过程加入了几个社区并详细描述了在这些社区中的个人主页、编程支持如Gemini和Gopher协议、博客发布、代码托管Git支持、CI/CD部署以及使用Git hooks自动化博客更新等功能。尽管作者受限于语言和工具使用体验未能充分参与社区交流但对社区学习新知识和丰富博客内容印象深刻。","content_html":"<p>Tilde社区如“家”一般的感受😝<!--more--></p>\n\n<h1 id=\"起因\">起因</h1>\n<p>在<a href=\"/2025/08/01/sw-proxy.html\">上一篇文章</a>里,我说到给我的博客增加了不少网站<a href=\"/proxylist.html\">镜像</a>也在这个过程中发现了不少Git平台实例。顺便一提我找到了个不错的<a href=\"https://github.com/ecosyste-ms/repos\">仓库</a>可以全网搜索各种Git平台实例。在这探索的过程中我发现了一种神奇的社区——Tilde社区体验之后感觉非常有意思所以来分享一下。</p>\n\n<h1 id=\"什么是tilde社区\">什么是Tilde社区</h1>\n<p>Tilde社区之所以叫Tilde是因为在类Unix系统如Linux、BSD波浪号Tilde“~”代表家目录。因此Tilde社区就是基于类Unix系统环境并且可以公共登录的服务器又被称为<abbr title=\"public access unix systems\">pubnixes</abbr>。一般这些社区的管理员会预装很多软件、开发环境以及一些公共服务比如聊天室、邮件、BBS论坛等这些构成了社区互动的基础。不过并不是所有类似这样提供Shell访问的公共服务器都可以被称作社区比如知名的免费网站托管商<a href=\"https://www.serv00.com\">Serv00</a>虽然也提供可以登录的FreeBSD服务器并且在服务器上安装了非常多的工具和环境从表面来看和Tilde社区提供的服务几乎一模一样但是它少了一个很重要的东西那就是社区它的权限管理非常严格不允许服务器的用户互相串门也没有互相交流的平台而且它的本质是商业服务尽管是免费的所以它不算Tilde社区。 <br />\n 至于Tilde社区的加入方式一般可以通过填写在线申请表、私信或发送邮件申请有些比较有特色的社区会用SSH交互等方式。审核通过后管理员就会在服务器上为你创建账户即可获得属于自己的“家”一般的Tilde社区在这个过程中不需要付一分钱因为他们通常都是反商业化的如果遇到了需要付钱才能激活账户的公共服务器那就不是Tilde社区即使它历史悠久可能是别的什么东西😆。 <br />\n 那么在哪里可以找到它们呢?有一个不错的网站,叫做<a href=\"https://tildeverse.org\">tildeverse</a>这不仅是一个Tilde社区的集合它自身也提供了很多服务。不过总的来说各个社区之间也是互相独立的tildeverse只是提供了一个平台让大家可以互相沟通所以这个网站叫做“loose association”就相当于博客中的博客圈一样。 <br />\n 于是我在tildeverse的成员列表中随便挑选了几个Tilde社区提交了注册申请过了一段时间申请通过了那么接下来就来说说我在Tilde社区的体验吧。</p>\n\n<h1 id=\"tilde社区的体验\">Tilde社区的体验</h1>\n<p>虽然我加入了不少Tilde社区不过各个社区提供的服务都差不多首先最重要的就是个人主页一般Tilde社区基本上都会提供一个像<code class=\"language-plaintext highlighter-rouge\">~/public_html</code>这样的目录存放个人主页的网页文件,并且可以通过类似<code class=\"language-plaintext highlighter-rouge\">example.com/~username</code>这样的地址访问,还有些社区会允许通过二级域名的方式访问,类似<code class=\"language-plaintext highlighter-rouge\">username.example.com</code>这样像我博客好多地方写的都是从根路径开始就很适合用二级域名的方式。这些主页大多也支持使用PHP之类的网页不过不像虚拟主机那样有个面板可以轻松安装扩展和切换版本有些可能要自己写配置文件有些可能要管理员才可以操作毕竟是社区所以不太注重用户体验。 <br />\n 当然除了HTTP协议的个人主页通常他们还可以创建一些Gemini协议和Gopher协议的个人主页这些协议不支持普通浏览器访问需要用<a href=\"https://github.com/rkd77/elinks\">ELinks</a>之类的文本浏览器才能打开,这个浏览器甚至可以在终端里用鼠标操作😆。不过因为协议非常简单,所以内容也就只能整些文本内容了。 <br />\n 除了个人主页外,一般还会提供编写博客的程序,比如<a href=\"https://github.com/cfenollosa/bashblog\">bashblog</a>用这个编写好之后就可以直接生成HTML网站能直接发布到自己的主页上让别人访问。这个脚本还是纯Bash的就和我当年的<a href=\"https://github.com/Mabbs/Mabbs.Project\">Mabbs</a>一样,看起来还挺酷,当然功能上肯定比不上正经的静态博客生成器😆。 <br />\n 当然博客是一方面,还可以写微博,他们一般提供一款叫<a href=\"https://github.com/buckket/twtxt\">twtxt</a>的软件,用这个软件可以使用命令发微博,还能关注其他人,查看时间线,而且这还是去中心化的,可以跨服务器进行关注,感觉就和<a href=\"https://github.com/mastodon/mastodon\">Mastodon</a>一样。 <br />\n 除此之外作为社区当然就会有聊天室和论坛了不过这些聊天室和BBS论坛通常不会像大多数人使用的那种通过Web或者图形界面来查看而是纯文本的那种比如论坛通常会用<a href=\"https://github.com/bbj-dev/bbj\">Bulletin Butter &amp; Jelly</a>聊天室会用IRC可以使用<a href=\"https://github.com/weechat/weechat\">WeeChat</a>只是我对IRC的印象不太好在终端使用的IRC客户端没有一个使用体验好的😅相比于其他在终端使用的软件操作通常只需要一些快捷键而且界面上通常会有提示而IRC客户端就只能敲命令而且还担心敲错了当成普通内容发出去……所以尽管我加入了Tilde社区受限于聊天软件的使用体验以及我的英文水平所以并不能和在服务器上的其他人聊天没法参与到社区中这么来看似乎我只能把Tilde社区当作普通的共享服务器来看待了😭。 <br />\n 在Tilde社区中既然都是用类Unix系统自然大都是会写程序的人所以托管代码也很重要不过因为大多Tilde社区的主机性能很垃圾所以很多都不会提供Git平台服务即使有可能也只会提供Gitea像GitLab这种对服务器要求比较高的基本上就不会有了。但很多人可能对Git有误解其实绝大多数情况下都不需要Git平台来托管代码之所以用Gitea、GitLab的工具是因为它们有比较完整的用户管理以及代码协作能力比如Issue和Wiki之类的但是大多数人其实根本没必要用到这些功能有问题发邮件就好了像Linux的开发就完全没有用Gitea、GitLab之类的平台。所以在Tilde社区中托管代码非常简单直接新建个文件夹执行<code class=\"language-plaintext highlighter-rouge\">git init --bare</code>那就是个仓库另外很多Tilde社区提供<a href=\"https://git.zx2c4.com/cgit/about/\">cgit</a>方便让公众在网页上查看和克隆自己的仓库,一般只要放到<code class=\"language-plaintext highlighter-rouge\">~/public_git</code>目录下就可以。至于自己如果想要提交代码,可以用<code class=\"language-plaintext highlighter-rouge\">git remote add tilde ssh://example.com/~/public_git/repo.git</code>添加远程仓库本地改完之后push上去就可以。 <br />\n 不过用那些Git平台还有一个地方可能会用到那就是CI/CD直接用命令创建的仓库它可以做到CI/CD吗其实是可以的Git有hooks功能如果想要类似CI/CD的功能就可以直接用post-receive这个钩子提交完成之后就会执行这个脚本所以接下来就讲讲我是如何用Git hooks在服务器上自动部署我的博客吧。</p>\n\n<h1 id=\"使用git-hooks自动部署博客\">使用Git hooks自动部署博客</h1>\n<p>我的博客使用的是<a href=\"https://github.com/jekyll/jekyll\">Jekyll</a>框架这是一个使用Ruby编写的静态博客生成器。所以要想构建我的博客至少要有Ruby的环境还好几乎所有的Tilde社区都预装了不用担心环境的问题。 <br />\n 不过Tilde社区一般不提供root权限所以Ruby的包需要放到自己的目录下比如可以执行这样的命令</p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>bundle2.7 config <span class=\"nb\">set</span> <span class=\"nt\">--local</span> path <span class=\"s1\">'/home/mayx/blog-env'</span>\n</code></pre></div></div>\n<p>然后再在我的仓库下执行<code class=\"language-plaintext highlighter-rouge\">bundle2.7 install</code>就可以了。 <br />\n 接下来就需要编写构建的脚本,这个倒是简单,直接用我的<a href=\"/deploy.sh\">部署脚本</a>改改就行:</p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\">#!/bin/bash</span>\n<span class=\"nb\">cd</span> /home/mayx/\n<span class=\"nb\">rm</span> <span class=\"nt\">-rf</span> public_html\ngit <span class=\"nt\">--work-tree</span><span class=\"o\">=</span>/home/mayx/blog <span class=\"nt\">--git-dir</span><span class=\"o\">=</span>/home/mayx/blog.git checkout <span class=\"nt\">-f</span>\n<span class=\"nb\">cd </span>blog\n<span class=\"nb\">mkdir </span>Mabbs\ncurl <span class=\"nt\">-L</span> <span class=\"nt\">-o</span> Mabbs/README.md https://github.com/Mabbs/Mabbs/raw/main/README.md\nbundle2.7 <span class=\"nb\">exec </span>jekyll build <span class=\"nt\">-d</span> ../public_html\n<span class=\"nb\">tar </span>czvf MayxBlog.tgz <span class=\"nt\">--exclude-vcs</span> ../public_html/\n<span class=\"nb\">mv </span>MayxBlog.tgz ../public_html/\n</code></pre></div></div>\n<p>写完之后把这个脚本放到仓库的<code class=\"language-plaintext highlighter-rouge\">hooks/post-receive</code>下然后加上执行权限就可以用了以后每次push之后都会直接更新我在Tilde社区的主页也就是我的镜像站。这样部署不像一般CI/CD还要额外装环境直接使用提前装好的环境构建速度会快不少。 <br />\n 不过既然有机会构建了我就可以把一些不支持构建的Pages用起来了有些Forgejo实例支持Pages功能但是仓库里只能包含构建后的代码还有Bitbucket Cloud也是一样的问题所以我可以把构建后的文件夹转为仓库然后推送到这些Git平台上。 <br />\n 考虑到我的网站每次构建基本上所有的页面都有改动因此我不打算保留提交记录所以我每次都会重新初始化git仓库不过在我实际测试的时候发现钩子触发的脚本执行<code class=\"language-plaintext highlighter-rouge\">git init</code>的时候创建的是裸仓库……查了一下貌似是环境变量的问题,只要把<code class=\"language-plaintext highlighter-rouge\">GIT_DIR</code>变量删掉就没问题了,以下是实际的代码:</p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">cd</span> ../public_html/\n<span class=\"nb\">unset </span>GIT_DIR\ngit init\ngit add <span class=\"nb\">.</span>\ngit commit <span class=\"nt\">-m</span> <span class=\"s2\">\"update\"</span>\ngit remote add codeberg ssh://git@codeberg.org/mayx/pages.git\ngit remote add gitgay ssh://git@git.gay/mayx/pages.git\ngit remote add bitbucket ssh://git@bitbucket.org/unmayx/unmayx.bitbucket.io.git\ngit push <span class=\"nt\">-f</span> codeberg master\ngit push <span class=\"nt\">-f</span> gitgay master\ngit push <span class=\"nt\">-f</span> bitbucket master\n</code></pre></div></div>\n<p>除了这些Pages之外还有一些平台只支持使用他们自己的软件上传网站代码比如surge既然我可以在构建的时候执行命令那就顺带一起上传吧比如我可以这样执行</p>\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>/home/mayx/blog-env/node_modules/surge/bin/surge /home/mayx/public_html/ mayx.surge.sh\n</code></pre></div></div>\n<p>其实除了这个之外我还想上传到sourcehut pages这个也需要用他们自己的软件上传但是sourcehut pages的CSP太严格了居然禁止脚本访问其他网站😭这样我的文章点击计数、文章推荐、AI摘要之类乱七八糟的功能就全用不了了所以只好作罢……</p>\n\n<h1 id=\"感想\">感想</h1>\n<p>总的来说这次在Tilde社区的各种体验还挺有意思虽然没能和各个社区的成员进行对话但是在探索的过程中也了解到了不少新知识而且也给我的博客增加了不少镜像。不知道会不会有哪个社区成员在闲逛的时候看到我的博客然后对里面的内容感兴趣😝……要是有哪个成员看到然后给我评论那也算是社区互动吧😋。虽然我的文章内容都是中文但现在翻译软件也足够强大了应该不至于拦住外国人。只是在国内似乎没有见过类似的社区在国内也有的话那就可以用中文和大家对话了吧。</p>\n","url":"/2025/08/10/tilde.html","tags":["tilde","服务器","git","体验"],"date_published":"2025-08-10T00:00:00+08:00","date_modified":"2025-08-10T00:00:00+08:00","author":{"name":"mayx"}},{"id":"/2025/08/01/sw-proxy.html","title":"用Service Worker实现一个反向代理","summary":"这篇文章介绍了作者如何利用Service Worker在现代浏览器中实现一个简单的反向代理功能以提供博客的备份和离线访问。作者原本希望通过Service Worker在用户浏览器中运行一个Web服务器来存储博客副本但发现 tar.gz 文件的处理需要第三方库且难以找到适用的解决方案尤其是对于tar文件的处理。作者最终选择使用Service Worker作为反向代理将请求转发到其他网站如GeoCities风格的静态网站托管平台实现了类似的效果。这个过程中作者体验到了浏览器功能的强大并认为Service Worker在离线场景中有更大的潜力尽管在他的例子中并没有充分展示这种优势。","content_html":"<p>现代浏览器真是强大,可以替代一些服务器的功能了!<!--more--></p>\n\n<h1 id=\"起因\">起因</h1>\n<p>前段时间在和群友聊天的时候,提到了我博客的<a href=\"/2022/02/14/move.html\">分发方案</a>,这么多年过去之后我已经在很多平台上<a href=\"/proxylist.html\">分发</a>了我的博客不过这只是多重冗余并不算去中心化虽然我也有向IPFS同步不过IPFS还得pin也不太可靠……所以这么看来我的博客似乎还不算极其可靠😂但其实不完全是这样。因为除了向不同平台的分发我的博客还有一个全文搜索的功能。更重要的是之前做<a href=\"/2024/10/01/suggest.html\">文章推荐功能</a>时会把整个博客所有文章的文字存到访客浏览器的localStorage中。这么说来只要有人访问了我博客的文章他们的浏览器中就会保存一份我博客文章的完整文本副本。从这个角度看可靠性应该算是相当高了吧 <br />\n 不过我之前的分发方案里还记录了一点在GitHub Pages以外的平台我还打包了一份全站生成后的代码之所以要全站打包也是希望我的博客能尽可能的分发考虑到几乎所有的Linux发行版一定有tar而不一定有zip所以我最终打包成了tgz格式。如果能让访客下载这个全站打包好的副本相比于浏览器里只存储了文章文字的全文数据这应该是一个更好的备份方式吧毕竟我的博客本身也是我的作品……所以这个压缩包到底有什么地方可以用到呢 <br />\n 这时候我想起来现代的浏览器功能已经非常强大了甚至在浏览器里直接运行一个Web服务器也完全没问题。如果能让访客在浏览器里下载那个压缩包并运行一个Web服务器那就相当于在他们本地设备上部署了一份我的博客副本。这样一来除了我自己搭建的网站之外这些访客的本地也运行着一个我的博客实例😆当然这份副本只有访客自己能看到。</p>\n\n<h1 id=\"研究实现方案\">研究实现方案</h1>\n<p>想要在浏览器上运行Web服务器其实很简单那就是使用Service Worker它可以完全离线在浏览器上工作。格式的话和以前写过的Cloudflare Worker非常相似毕竟Cloudflare Worker就是模仿Service Worker的方式运行啊😂所以我要是想写Service Worker应该很简单。 <br />\n 有了执行的东西之后就是存储在Service Worker上存储可以用Cache Storage用它的话不仅可以保存文件的内容还可以保存响应头之类的东西用来和Service Worker配合使用非常的方便不过既然是Cache它的可靠性就不能保证了浏览器很可能在需要的时候清除缓存内容所以相比之下用IndexedDB应该会更可靠一些。 <br />\n 那么接下来就该处理我的tgz文件了tgz的本质是tar文件被gzip压缩之后的东西。浏览器解压gzip倒是简单可以用Compression Stream API但它也只能处理gzip了……对于tar的处理似乎就必须用第三方库。而tar的库在网上搜了搜似乎很少网上找了个<a href=\"https://github.com/gera2ld/tarjs\">tarjs</a>库文档写的也看不懂也很少看来是有这个需求的人很少啊而且还要用现代JS那种开发方式要用什么npm之类的。在<a href=\"/2025/07/24/screenshot.html\">上一篇文章</a>我就说过我不是专门写前端的对在自己电脑上安装Node.js之类的东西很反感。后来问AI也完全写不出能用的代码估计这个功能还是太小众了……另外又想到除了这个问题之外还要处理网站更新的时候该怎么通知Service Worker之类乱七八糟的事情……所以只好作罢😅。</p>\n\n<h1 id=\"使用service-worker进行反向代理\">使用Service Worker进行反向代理</h1>\n<p>这么看来离线运行我的博客似乎有点麻烦不过既然都研究了一下Service Worker不如想想其他能做的事情……比如当作反向代理虽然在浏览器上搞反向代理好像意义不是很大……但值得一试。我之前见过一个项目叫做<a href=\"https://github.com/EtherDream/jsproxy\">jsproxy</a>它是用Service Worker实现的正向代理这给了我一些启发。我在之前研究分发方案的时候发现了一些模仿GeoCities的复古静态网站托管平台比如<a href=\"https://neocities.org\">Neocities</a>和<a href=\"https://nekoweb.org\">Nekoweb</a>。它们需要通过网页或API才能上传网站不太方便使用CI/CD的方式部署。但是我又觉得它们的社区很有意思所以想用Service Worker的方式反代到我的网站显得我的网站是部署在它们上面一样。 <br />\n 这个做起来非常简单,其实就和我以前用<a href=\"/2021/03/02/workers.html#%E9%A6%96%E5%85%88%E7%BB%99%E8%87%AA%E5%B7%B1%E6%90%AD%E4%B8%AA%E5%8F%8D%E4%BB%A3\">Cloudflare Worker搭建反代</a>几乎完全一样遇到请求之后直接通过Fetch获取内容然后再返回就行唯一不同的就是浏览器存在跨域策略在跨域时只有对应网站存在合适的响应头才可以成功请求还好我用的Pages服务大多都允许跨域。但是在我实际测试的时候发现这个允许跨域的等级不太一样比如GitHub Pages的响应头里包含<code class=\"language-plaintext highlighter-rouge\">Access-Control-Allow-Origin: *</code>但是不允许OPTIONS方式请求另外如果要修改请求头在响应头里还要一一允许相应的请求头才行……当然对于这种问题解决起来很简单就和我之前写的<a href=\"/2025/04/08/feed.html\">订阅源预览</a>一样,用<a href=\"https://github.com/Zibri/cloudflare-cors-anywhere\">cloudflare-cors-anywhere</a>搭建的CORS代理就可以有了这个就可以轻松使用Service Worker反代其他网站了。 <br />\n 当然对我来说其实有<code class=\"language-plaintext highlighter-rouge\">Access-Control-Allow-Origin: *</code>就够了,我也不需要花里胡哨的请求方式,也不需要在请求头和请求体里加什么莫名其妙的东西,所以对我来说直接请求我的某一个镜像站就可以,于是代码如下: <br />\n <strong>index.html</strong></p>\n<div class=\"language-html highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cp\">&lt;!DOCTYPE html&gt;</span>\n<span class=\"nt\">&lt;html&gt;</span>\n\n<span class=\"nt\">&lt;head&gt;</span>\n <span class=\"nt\">&lt;meta</span> <span class=\"na\">charset=</span><span class=\"s\">\"UTF-8\"</span> <span class=\"nt\">/&gt;</span>\n <span class=\"nt\">&lt;title&gt;</span>Mayx的博客<span class=\"nt\">&lt;/title&gt;</span>\n<span class=\"nt\">&lt;/head&gt;</span>\n\n<span class=\"nt\">&lt;body&gt;</span>\n <span class=\"nt\">&lt;script&gt;</span>\n <span class=\"c1\">// 注册 Service Worker</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">serviceWorker</span><span class=\"dl\">'</span> <span class=\"k\">in</span> <span class=\"nb\">navigator</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"nb\">navigator</span><span class=\"p\">.</span><span class=\"nx\">serviceWorker</span><span class=\"p\">.</span><span class=\"nx\">register</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">/sw.js</span><span class=\"dl\">'</span><span class=\"p\">)</span>\n <span class=\"p\">.</span><span class=\"nx\">then</span><span class=\"p\">(</span><span class=\"nx\">registration</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nx\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">Service Worker 注册成功:</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">registration</span><span class=\"p\">.</span><span class=\"nx\">scope</span><span class=\"p\">);</span>\n <span class=\"c1\">// 刷新网页</span>\n <span class=\"nx\">location</span><span class=\"p\">.</span><span class=\"nx\">reload</span><span class=\"p\">();</span>\n <span class=\"p\">})</span>\n <span class=\"p\">.</span><span class=\"k\">catch</span><span class=\"p\">(</span><span class=\"nx\">error</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nx\">error</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">Service Worker 注册失败:</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">error</span><span class=\"p\">);</span>\n <span class=\"nx\">location</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">https://mabbs.github.io</span><span class=\"dl\">\"</span><span class=\"p\">;</span>\n <span class=\"p\">});</span>\n <span class=\"p\">}</span> <span class=\"k\">else</span> <span class=\"p\">{</span>\n <span class=\"nx\">location</span><span class=\"o\">=</span><span class=\"dl\">\"</span><span class=\"s2\">https://mabbs.github.io</span><span class=\"dl\">\"</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"nt\">&lt;/script&gt;</span>\n <span class=\"nt\">&lt;h1&gt;</span>Redirecting<span class=\"ni\">&amp;hellip;</span><span class=\"nt\">&lt;/h1&gt;</span>\n <span class=\"nt\">&lt;a</span> <span class=\"na\">href=</span><span class=\"s\">\"https://mabbs.github.io\"</span><span class=\"nt\">&gt;</span>Click here if you are not redirected.<span class=\"nt\">&lt;/a&gt;</span>\n<span class=\"nt\">&lt;/body&gt;</span>\n\n<span class=\"nt\">&lt;/html&gt;</span>\n</code></pre></div></div>\n<p><strong>sw.js</strong></p>\n<div class=\"language-javascript highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">const</span> <span class=\"nx\">TARGET_SITE</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">被反代的网站</span><span class=\"dl\">'</span><span class=\"p\">;</span> <span class=\"c1\">//也可以用CORS代理</span>\n\n<span class=\"nb\">self</span><span class=\"p\">.</span><span class=\"nx\">addEventListener</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">install</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">event</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n <span class=\"c1\">// 强制立即激活新 Service Worker</span>\n <span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">waitUntil</span><span class=\"p\">(</span><span class=\"nb\">self</span><span class=\"p\">.</span><span class=\"nx\">skipWaiting</span><span class=\"p\">());</span>\n<span class=\"p\">});</span>\n\n<span class=\"nb\">self</span><span class=\"p\">.</span><span class=\"nx\">addEventListener</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">activate</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">event</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n <span class=\"c1\">// 立即控制所有客户端</span>\n <span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">waitUntil</span><span class=\"p\">(</span><span class=\"nb\">self</span><span class=\"p\">.</span><span class=\"nx\">clients</span><span class=\"p\">.</span><span class=\"nx\">claim</span><span class=\"p\">());</span>\n<span class=\"p\">});</span>\n\n<span class=\"nb\">self</span><span class=\"p\">.</span><span class=\"nx\">addEventListener</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">fetch</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">event</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"k\">new</span> <span class=\"nx\">URL</span><span class=\"p\">(</span><span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">request</span><span class=\"p\">.</span><span class=\"nx\">url</span><span class=\"p\">).</span><span class=\"nx\">origin</span> <span class=\"o\">==</span> <span class=\"nb\">self</span><span class=\"p\">.</span><span class=\"nx\">location</span><span class=\"p\">.</span><span class=\"nx\">origin</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">respondWith</span><span class=\"p\">(</span><span class=\"nx\">handleProxyRequest</span><span class=\"p\">(</span><span class=\"nx\">event</span><span class=\"p\">.</span><span class=\"nx\">request</span><span class=\"p\">));</span>\n <span class=\"p\">}</span>\n<span class=\"p\">});</span>\n\n<span class=\"k\">async</span> <span class=\"kd\">function</span> <span class=\"nx\">handleProxyRequest</span><span class=\"p\">(</span><span class=\"nx\">request</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"c1\">// 构建目标 URL</span>\n <span class=\"kd\">const</span> <span class=\"nx\">targetUrl</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nx\">URL</span><span class=\"p\">(</span><span class=\"nx\">request</span><span class=\"p\">.</span><span class=\"nx\">url</span><span class=\"p\">);</span>\n <span class=\"kd\">const</span> <span class=\"nx\">proxyUrl</span> <span class=\"o\">=</span> <span class=\"nx\">TARGET_SITE</span> <span class=\"o\">+</span> <span class=\"nx\">targetUrl</span><span class=\"p\">.</span><span class=\"nx\">pathname</span> <span class=\"o\">+</span> <span class=\"nx\">targetUrl</span><span class=\"p\">.</span><span class=\"nx\">search</span><span class=\"p\">;</span>\n\n <span class=\"c1\">// 创建新请求(复制原请求属性)</span>\n <span class=\"kd\">const</span> <span class=\"nx\">proxyRequest</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nx\">Request</span><span class=\"p\">(</span><span class=\"nx\">proxyUrl</span><span class=\"p\">,</span> <span class=\"p\">{</span>\n <span class=\"na\">method</span><span class=\"p\">:</span> <span class=\"nx\">request</span><span class=\"p\">.</span><span class=\"nx\">method</span><span class=\"p\">,</span>\n <span class=\"c1\">// headers: request.headers,</span>\n <span class=\"c1\">// body: request.body</span>\n <span class=\"p\">});</span>\n\n <span class=\"c1\">// 发送代理请求</span>\n <span class=\"kd\">const</span> <span class=\"nx\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">fetch</span><span class=\"p\">(</span><span class=\"nx\">proxyRequest</span><span class=\"p\">);</span>\n\n <span class=\"c1\">// 返回修改后的响应</span>\n <span class=\"k\">return</span> <span class=\"k\">new</span> <span class=\"nx\">Response</span><span class=\"p\">(</span><span class=\"nx\">response</span><span class=\"p\">.</span><span class=\"nx\">body</span><span class=\"p\">,</span> <span class=\"p\">{</span>\n <span class=\"na\">status</span><span class=\"p\">:</span> <span class=\"nx\">response</span><span class=\"p\">.</span><span class=\"nx\">status</span><span class=\"p\">,</span>\n <span class=\"na\">statusText</span><span class=\"p\">:</span> <span class=\"nx\">response</span><span class=\"p\">.</span><span class=\"nx\">statusText</span><span class=\"p\">,</span>\n <span class=\"na\">headers</span><span class=\"p\">:</span> <span class=\"nx\">response</span><span class=\"p\">.</span><span class=\"nx\">headers</span>\n <span class=\"p\">});</span>\n\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"nx\">error</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nx\">error</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">Proxy error:</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">error</span><span class=\"p\">);</span>\n <span class=\"k\">return</span> <span class=\"k\">new</span> <span class=\"nx\">Response</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">Proxy failed</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"na\">status</span><span class=\"p\">:</span> <span class=\"mi\">500</span> <span class=\"p\">});</span>\n <span class=\"p\">}</span>\n<span class=\"p\">}</span>\n</code></pre></div></div>\n<p>最终的实际效果: <a href=\"https://mayx.nekoweb.org\">https://mayx.nekoweb.org</a></p>\n\n<h1 id=\"感想\">感想</h1>\n<p>虽然折腾了半天没能增强我博客的可靠性……但是体会到了现代浏览器的强大之处难怪前几年会提出ChromeOS和PWA之类的东西原来浏览器功能还是相当强大的用了Service Worker以后即使是纯前端也可以有和使用服务器一样的体验在过去的浏览器中要是想实现这样的功能……好像也不是不可能😂用AJAX加服务器使用伪静态策略其实是可以做到的……其实Service Worker的功能更多还是在离线时使用的我这个例子好像没体现它的优势😆。 <br />\n 但总的来说相比以前想要实现这种反代的功能代码还是更清晰也更简单了也许以后如果有机会我又有心思让博客在访客浏览器上离线运行那就可以体现Service Worker真正的优势了🤣。</p>\n","url":"/2025/08/01/sw-proxy.html","tags":["浏览器","Service Worker","Worker","反向代理"],"date_published":"2025-08-01T00:00:00+08:00","date_modified":"2025-08-01T00:00:00+08:00","author":{"name":"mayx"}},{"id":"/2025/07/24/screenshot.html","title":"使用Cloudflare制作自动更新的网站预览图","summary":"这篇文章介绍了如何利用Cloudflare的“浏览器呈现”功能创建一个自动更新的网站预览图服务。作者发现这个新功能可以用来展示网站在不同设备上的显示效果通过在Cloudflare Workers中使用iframe和CSS缩放技术以及调用Cloudflare的接口抓取浏览器渲染的截图。虽然免费用户每天只有10分钟的使用时间限制了实时更新但作者通过缓存实现了每天自动更新一次的预览图并分享了具体的实现代码和使用方法。作者赞赏Cloudflare提供的这项强大且免费的服务。","content_html":"<p>Cloudflare的功能真是越来越多了而且还免费<!--more--></p>\n\n<h1 id=\"起因\">起因</h1>\n<p>前段时间我在登录Cloudflare的时候发现Workers上多了一个“浏览器呈现”的功能可能已经出来一段时间了不过之前一直没关注看介绍这个功能可以让Worker操作运行在Cloudflare服务器上的浏览器。这功能挺有意思而且免费用户也能用不如想个办法好好利用一下。 <br />\n 一般来说这个功能可以干什么呢既然是在AI盛行的时候出现……估计是为了搞Agent之类的吧不过看<a href=\"https://developers.cloudflare.com/browser-rendering/platform/limits/\">文档</a>对免费用户来说一天也只有10分钟的使用时间估计也没什么应用价值……那除了这个之外还能做些什么我发现有好多博客主题喜欢给自己的README里添加一个能查看主题在多种设备上显示效果的预览图以展示主题的自适应能力。那么既然现在能在Cloudflare上操作浏览器那么我也可以做一个类似的而且这个预览图还可以自动更新。</p>\n\n<h1 id=\"制作自适应的网站预览\">制作自适应的网站预览</h1>\n<p>既然打算做预览图那么我应该用什么方案按照不同尺寸的视口截几张图再拼起来吗这显然就太复杂了况且在Cloudflare Workers中处理图片也相当困难。这时我想起来曾经见到过一个工具只要输入网址就可以在一个页面中同时展示网站在四种不同设备手机、平板、笔记本电脑、台式机上的显示效果叫做“多合一网页缩略图”实现原理是使用iframe和CSS缩放模拟多种设备视口。搜了一下发现这套代码被不少网站使用所以就随便找了其中一个工具站把代码和素材扒了下来稍微改了一下然后放到<a href=\"https://github.com/Mabbs/responsive\">GitHub</a>上方便等一会用Cloudflare访问这个部署在<a href=\"https://mabbs.github.io/responsive/\">GitHub Pages</a>上的页面来进行截图。</p>\n\n<h1 id=\"使用cloudflare浏览器呈现进行截图\">使用Cloudflare浏览器呈现进行截图</h1>\n<p>接下来截图就简单了不过Cloudflare有两种截图的办法<a href=\"https://developers.cloudflare.com/browser-rendering/workers-bindings/\">用Workers</a>的话可以直接用Puppeteer之类的库连接浏览器但用这个库需要安装要本地搭环境……我毕竟不是专门搞JS开发的一点也不想在本地安装Node.js环境所以就不想用这种方式。另外一种是通过<a href=\"https://developers.cloudflare.com/browser-rendering/rest-api/\">调用Cloudflare的接口</a>这种非常简单只需要填几个参数请求就行唯一的问题就是要填一个Token……我一直觉得Worker调用Cloudflare自己的服务不应该需要Token之类的东西毕竟内部就能验证了没必要自己搞但是我看了半天文档貌似无论如何只要想调接口就必须搞个Token……那没办法就搞吧其实也很简单只需要在“账户API令牌”里添加一个有浏览器呈现编辑权限的令牌就行。 <br />\n 至于展示……这个接口调用比较耗时而且一天只能调用10分钟截图的话估计也就够30次左右还有每分钟3次的限制😓所以实时更新肯定是不行了图片肯定得缓存一天更新一次感觉应该就够了。另外次数这么少的话写成接口给大伙用貌似也没啥意义所以我就把地址写死了于是以下就是最终实现的代码</p>\n<div class=\"language-javascript highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export</span> <span class=\"k\">default</span> <span class=\"p\">{</span>\n <span class=\"k\">async</span> <span class=\"nx\">fetch</span><span class=\"p\">(</span><span class=\"nx\">request</span><span class=\"p\">,</span> <span class=\"nx\">env</span><span class=\"p\">,</span> <span class=\"nx\">ctx</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"kd\">const</span> <span class=\"nx\">cache</span> <span class=\"o\">=</span> <span class=\"nx\">caches</span><span class=\"p\">.</span><span class=\"k\">default</span><span class=\"p\">;</span>\n <span class=\"kd\">const</span> <span class=\"nx\">kv</span> <span class=\"o\">=</span> <span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">SCREENSHOT</span><span class=\"p\">;</span>\n\n <span class=\"kd\">const</span> <span class=\"nx\">url</span> <span class=\"o\">=</span> <span class=\"dl\">\"</span><span class=\"s2\">https://mabbs.github.io/responsive/</span><span class=\"dl\">\"</span><span class=\"p\">;</span>\n <span class=\"kd\">const</span> <span class=\"nx\">date</span> <span class=\"o\">=</span> <span class=\"k\">new</span> <span class=\"nb\">Date</span><span class=\"p\">().</span><span class=\"nx\">toISOString</span><span class=\"p\">().</span><span class=\"nx\">split</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">T</span><span class=\"dl\">\"</span><span class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">];</span>\n <span class=\"kd\">const</span> <span class=\"nx\">cacheKey</span> <span class=\"o\">=</span> <span class=\"nx\">url</span><span class=\"p\">;</span>\n <span class=\"kd\">const</span> <span class=\"nx\">datedKey</span> <span class=\"o\">=</span> <span class=\"s2\">`</span><span class=\"p\">${</span><span class=\"nx\">url</span><span class=\"p\">}</span><span class=\"s2\">?</span><span class=\"p\">${</span><span class=\"nx\">date</span><span class=\"p\">}</span><span class=\"s2\">`</span><span class=\"p\">;</span>\n\n <span class=\"c1\">// 工具函数:构建 Response 对象</span>\n <span class=\"kd\">const</span> <span class=\"nx\">buildResponse</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"nx\">buffer</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span>\n <span class=\"k\">new</span> <span class=\"nx\">Response</span><span class=\"p\">(</span><span class=\"nx\">buffer</span><span class=\"p\">,</span> <span class=\"p\">{</span>\n <span class=\"na\">headers</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"dl\">\"</span><span class=\"s2\">content-type</span><span class=\"dl\">\"</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">image/png</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n <span class=\"dl\">\"</span><span class=\"s2\">cache-control</span><span class=\"dl\">\"</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">public, max-age=86400, immutable</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n <span class=\"p\">},</span>\n <span class=\"p\">});</span>\n\n <span class=\"c1\">// 工具函数:尝试从 KV 和 Cache 中加载已有截图</span>\n <span class=\"kd\">const</span> <span class=\"nx\">tryGetCachedResponse</span> <span class=\"o\">=</span> <span class=\"k\">async</span> <span class=\"p\">(</span><span class=\"nx\">key</span><span class=\"p\">)</span> <span class=\"o\">=&gt;</span> <span class=\"p\">{</span>\n <span class=\"kd\">let</span> <span class=\"nx\">res</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">cache</span><span class=\"p\">.</span><span class=\"nx\">match</span><span class=\"p\">(</span><span class=\"nx\">key</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"nx\">res</span><span class=\"p\">)</span> <span class=\"k\">return</span> <span class=\"nx\">res</span><span class=\"p\">;</span>\n\n <span class=\"kd\">const</span> <span class=\"nx\">kvData</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">kv</span><span class=\"p\">.</span><span class=\"kd\">get</span><span class=\"p\">(</span><span class=\"nx\">key</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"na\">type</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">arrayBuffer</span><span class=\"dl\">\"</span> <span class=\"p\">});</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"nx\">kvData</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"nx\">res</span> <span class=\"o\">=</span> <span class=\"nx\">buildResponse</span><span class=\"p\">(</span><span class=\"nx\">kvData</span><span class=\"p\">);</span>\n <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nx\">waitUntil</span><span class=\"p\">(</span><span class=\"nx\">cache</span><span class=\"p\">.</span><span class=\"nx\">put</span><span class=\"p\">(</span><span class=\"nx\">key</span><span class=\"p\">,</span> <span class=\"nx\">res</span><span class=\"p\">.</span><span class=\"nx\">clone</span><span class=\"p\">()));</span>\n <span class=\"k\">return</span> <span class=\"nx\">res</span><span class=\"p\">;</span>\n <span class=\"p\">}</span>\n <span class=\"k\">return</span> <span class=\"kc\">null</span><span class=\"p\">;</span>\n <span class=\"p\">};</span>\n\n <span class=\"c1\">// 1. 优先使用当日缓存</span>\n <span class=\"kd\">let</span> <span class=\"nx\">res</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">tryGetCachedResponse</span><span class=\"p\">(</span><span class=\"nx\">datedKey</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"nx\">res</span><span class=\"p\">)</span> <span class=\"k\">return</span> <span class=\"nx\">res</span><span class=\"p\">;</span>\n\n <span class=\"c1\">// 2. 若缓存不存在,则请求 Cloudflare Screenshot API</span>\n <span class=\"k\">try</span> <span class=\"p\">{</span>\n <span class=\"kd\">const</span> <span class=\"nx\">payload</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n <span class=\"na\">url</span><span class=\"p\">:</span> <span class=\"nx\">url</span><span class=\"p\">,</span>\n <span class=\"na\">viewport</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">width</span><span class=\"p\">:</span> <span class=\"mi\">1200</span><span class=\"p\">,</span> <span class=\"na\">height</span><span class=\"p\">:</span> <span class=\"mi\">800</span> <span class=\"p\">},</span>\n <span class=\"na\">gotoOptions</span><span class=\"p\">:</span> <span class=\"p\">{</span> <span class=\"na\">waitUntil</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">networkidle0</span><span class=\"dl\">\"</span> <span class=\"p\">},</span>\n <span class=\"p\">};</span>\n\n <span class=\"kd\">const</span> <span class=\"nx\">apiRes</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">fetch</span><span class=\"p\">(</span>\n <span class=\"s2\">`https://api.cloudflare.com/client/v4/accounts/</span><span class=\"p\">${</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">CF_ACCOUNT_ID</span><span class=\"p\">}</span><span class=\"s2\">/browser-rendering/screenshot?cacheTTL=86400`</span><span class=\"p\">,</span>\n <span class=\"p\">{</span>\n <span class=\"na\">method</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">POST</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n <span class=\"na\">headers</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n <span class=\"na\">Authorization</span><span class=\"p\">:</span> <span class=\"s2\">`Bearer </span><span class=\"p\">${</span><span class=\"nx\">env</span><span class=\"p\">.</span><span class=\"nx\">CF_API_TOKEN</span><span class=\"p\">}</span><span class=\"s2\">`</span><span class=\"p\">,</span>\n <span class=\"dl\">\"</span><span class=\"s2\">Content-Type</span><span class=\"dl\">\"</span><span class=\"p\">:</span> <span class=\"dl\">\"</span><span class=\"s2\">application/json</span><span class=\"dl\">\"</span><span class=\"p\">,</span>\n <span class=\"p\">},</span>\n <span class=\"na\">body</span><span class=\"p\">:</span> <span class=\"nx\">JSON</span><span class=\"p\">.</span><span class=\"nx\">stringify</span><span class=\"p\">(</span><span class=\"nx\">payload</span><span class=\"p\">),</span>\n <span class=\"p\">}</span>\n <span class=\"p\">);</span>\n\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"o\">!</span><span class=\"nx\">apiRes</span><span class=\"p\">.</span><span class=\"nx\">ok</span><span class=\"p\">)</span> <span class=\"k\">throw</span> <span class=\"k\">new</span> <span class=\"nb\">Error</span><span class=\"p\">(</span><span class=\"s2\">`API returned </span><span class=\"p\">${</span><span class=\"nx\">apiRes</span><span class=\"p\">.</span><span class=\"nx\">status</span><span class=\"p\">}</span><span class=\"s2\">`</span><span class=\"p\">);</span>\n\n <span class=\"kd\">const</span> <span class=\"nx\">buffer</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">apiRes</span><span class=\"p\">.</span><span class=\"nx\">arrayBuffer</span><span class=\"p\">();</span>\n <span class=\"nx\">res</span> <span class=\"o\">=</span> <span class=\"nx\">buildResponse</span><span class=\"p\">(</span><span class=\"nx\">buffer</span><span class=\"p\">);</span>\n\n <span class=\"c1\">// 后台缓存更新</span>\n <span class=\"nx\">ctx</span><span class=\"p\">.</span><span class=\"nx\">waitUntil</span><span class=\"p\">(</span><span class=\"nb\">Promise</span><span class=\"p\">.</span><span class=\"nx\">all</span><span class=\"p\">([</span>\n <span class=\"nx\">kv</span><span class=\"p\">.</span><span class=\"nx\">put</span><span class=\"p\">(</span><span class=\"nx\">cacheKey</span><span class=\"p\">,</span> <span class=\"nx\">buffer</span><span class=\"p\">),</span>\n <span class=\"nx\">kv</span><span class=\"p\">.</span><span class=\"nx\">put</span><span class=\"p\">(</span><span class=\"nx\">datedKey</span><span class=\"p\">,</span> <span class=\"nx\">buffer</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"na\">expirationTtl</span><span class=\"p\">:</span> <span class=\"mi\">86400</span> <span class=\"p\">}),</span>\n <span class=\"nx\">cache</span><span class=\"p\">.</span><span class=\"nx\">put</span><span class=\"p\">(</span><span class=\"nx\">cacheKey</span><span class=\"p\">,</span> <span class=\"nx\">res</span><span class=\"p\">.</span><span class=\"nx\">clone</span><span class=\"p\">()),</span>\n <span class=\"nx\">cache</span><span class=\"p\">.</span><span class=\"nx\">put</span><span class=\"p\">(</span><span class=\"nx\">datedKey</span><span class=\"p\">,</span> <span class=\"nx\">res</span><span class=\"p\">.</span><span class=\"nx\">clone</span><span class=\"p\">()),</span>\n <span class=\"p\">]));</span>\n\n <span class=\"k\">return</span> <span class=\"nx\">res</span><span class=\"p\">;</span>\n <span class=\"p\">}</span> <span class=\"k\">catch</span> <span class=\"p\">(</span><span class=\"nx\">err</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n <span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nx\">error</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">Screenshot generation failed:</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"nx\">err</span><span class=\"p\">);</span>\n\n <span class=\"c1\">// 3. 回退到通用旧缓存</span>\n <span class=\"nx\">res</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"nx\">tryGetCachedResponse</span><span class=\"p\">(</span><span class=\"nx\">cacheKey</span><span class=\"p\">);</span>\n <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"nx\">res</span><span class=\"p\">)</span> <span class=\"k\">return</span> <span class=\"nx\">res</span><span class=\"p\">;</span>\n\n <span class=\"k\">return</span> <span class=\"k\">new</span> <span class=\"nx\">Response</span><span class=\"p\">(</span><span class=\"dl\">\"</span><span class=\"s2\">Screenshot generation failed</span><span class=\"dl\">\"</span><span class=\"p\">,</span> <span class=\"p\">{</span> <span class=\"na\">status</span><span class=\"p\">:</span> <span class=\"mi\">502</span> <span class=\"p\">});</span>\n <span class=\"p\">}</span>\n <span class=\"p\">},</span>\n<span class=\"p\">};</span>\n</code></pre></div></div>\n<p>使用方法很简单创建一个Worker把以上代码粘进去然后把从“账户API令牌”中生成的令牌填到Worker的密钥中名称为<code class=\"language-plaintext highlighter-rouge\">CF_API_TOKEN</code>,另外再加一个名称为<code class=\"language-plaintext highlighter-rouge\">CF_ACCOUNT_ID</code>的密钥内容是账户ID就是打开仪表板时URL中的那串16进制数字除此之外还需要创建一个KV数据库绑定到这个Worker上绑定的名称是<code class=\"language-plaintext highlighter-rouge\">SCREENSHOT</code>。如果想给自己的网站生成可以Fork我的<a href=\"https://github.com/Mabbs/responsive\">仓库</a>然后把里面首页文件中的网址替换成你的网站然后再把Worker中的url替换成Fork后仓库的GitHub Pages地址就可以了。 <br />\n 最终的效果如下: <br />\n <img src=\"https://screenshot.mayx.eu.org\" alt=\"ScreenShot\" /></p>\n\n<h1 id=\"感想\">感想</h1>\n<p>Cloudflare实在是太强了虽然这个浏览器呈现免费用量并不多但是有这么一个功能已经吊打很多Serverless服务了毕竟浏览器对服务器资源的占用也不小小内存的服务器甚至都不能运行如果要自己搭的话成本可能也不小而现在Cloudflare能免费提供应该说不愧是赛博活佛吗🤣。</p>\n","url":"/2025/07/24/screenshot.html","tags":["Cloudflare","Workers","网站截图","自动化"],"date_published":"2025-07-24T00:00:00+08:00","date_modified":"2025-07-24T00:00:00+08:00","author":{"name":"mayx"}},{"id":"/2025/07/13/hacked.html","title":"一次服务器被入侵的经历","summary":"这篇文章讲述了作者在登录一台闲置服务器时发现被入侵的经历。通过观察服务器负载和进程作者意识到存在恶意软件。作者使用了netstat、lsof等工具进行调查发现了一个名为gs-dbus的木马进程和libprocesshider.so文件这隐藏了其他恶意工具。作者通过查找自启动服务、清理木马、修改密码等方式进行了应对并分析了入侵者使用的工具如Global Socket项目和logclean。作者认为这次入侵虽然造成了损失但也学到了一些知识。","content_html":"<p>即使是被入侵了也可以学到一些知识!<!--more--></p>\n\n<h1 id=\"起因\">起因</h1>\n<p>前几天,我闲来无事登录了一下一台之前一直闲置的服务器,登录上去后,乍一看似乎没有任何问题,然后习惯性的执行了一下<code class=\"language-plaintext highlighter-rouge\">top</code>命令看了一眼。从进程列表来看似乎没有什么明显异常的地方但是服务器的load值很高cpu的us值也很高。 <br />\n 以前我倒也遇到过几次load值很高的情况一般是硬盘或NFS等网络存储挂了但是依然有程序在读写挂载的目录会有这种问题但那种情况一般高的是cpu的wa值而不是us值us值是软件正常用掉的……但是进程列表里根本没有占CPU的程序啊……看来服务器是被入侵了😰。</p>\n\n<h1 id=\"检查服务器\">检查服务器</h1>\n<p>虽然说是要查但其实我根本不知道进程隐藏的原理😂虽然听说过有恶意软件会这样做现在遇到了一时半会又想不出来怎么找。还好这是台闲置的服务器上面什么东西都没有跑所以正常来说除了ssh连接之外这个服务器不该有任何其他的连接于是我执行了一下<code class=\"language-plaintext highlighter-rouge\">netstat -tanp</code>看了一眼发现有个奇怪的进程使用一个境外的IP和我的服务器建立了连接用<code class=\"language-plaintext highlighter-rouge\">ps -ef</code>查了一下这个 PID结果进程名显示为<code class=\"language-plaintext highlighter-rouge\">[kcached]</code>……这下给我整不会了。 <br />\n 后来查了些资料知道了可以用<code class=\"language-plaintext highlighter-rouge\">lsof -p</code>查看进程读取的文件,才看到木马的本体:<code class=\"language-plaintext highlighter-rouge\">/usr/bin/gs-dbus</code>。不过如果我只是杀掉这个进程然后删除文件,那攻击者肯定会重新回来,所以我得排除一下是不是还有别的木马文件。 <br />\n 一般来说攻击者权限维持的方式大多是crontab不过我看了一下配置文件里似乎没有root下的<code class=\"language-plaintext highlighter-rouge\">authorized_keys</code>倒是有个陌生的公钥于是顺手删掉了……也没有其他文件夹下有<code class=\"language-plaintext highlighter-rouge\">gs-dbus</code>文件……难道没有别的木马文件了吗?后来我仔细找了一下,发现有个很可疑的文件<code class=\"language-plaintext highlighter-rouge\">/usr/local/lib/libprocesshider.so</code>一看就不是什么好东西🤣后来在GitHub上搜了一下是<a href=\"https://github.com/gianlucaborello/libprocesshider\">libprocesshider</a>这个项目就是它让我在top中什么也没找到的看文档中应用是添加一个<code class=\"language-plaintext highlighter-rouge\">/etc/ld.so.preload</code>文件,所以解除隐藏效果我也只需要删掉这个文件就好啦。 <br />\n 不过感觉还是不够……所以我全盘搜索了一下<code class=\"language-plaintext highlighter-rouge\">libprocesshider.so</code>文件,果不其然还有,通过那个文件在/usr/games里找到了木马的大本营里面有一堆这个入侵者的工具于是就顺手保存了一份然后从服务器上删掉了。 <br />\n 另外还有自启动到底是怎么实现的既然不是crontab……应该是systemd。看了一下果不其然有个服务在保持<code class=\"language-plaintext highlighter-rouge\">gs-dbus</code>的运行,不过程序我已经删了,所以它现在只会不停尝试重启,接下来只需要停止并禁用这个服务就行了。 <br />\n 至于为什么会被入侵……我也很清楚,其实并没有什么漏洞,单纯是设置的密码太简单了,被嘿客扫到啦!所以解决起来也很简单,把这些垃圾清除掉之后设置个稍微复杂一点的密码就行了。</p>\n\n<h1 id=\"入侵分析\">入侵分析</h1>\n<p>既然这个嘿客都不删他的工具,留下来就是给我分析的吧?那么我就像<a href=\"/2024/11/02/trojan.html\">上次</a>一样分析一下他使用的工具吧~首先里面有个<code class=\"language-plaintext highlighter-rouge\">deploy-all.sh</code>文件,看起来应该是登录服务器之后最先执行的程序,在这里面有个压缩包,解压出来之后搜了一下里面的文件,发现是<a href=\"https://github.com/hackerschoice/gsocket\">Global Socket</a>项目看起来应该是包含反弹Shell、伪装以及权限维持之类功能的一个小工具。看了下源代码才知道原来用<code class=\"language-plaintext highlighter-rouge\">exec -a</code>就可以伪装进程的名称,而且那个<code class=\"language-plaintext highlighter-rouge\">gs-dbus</code>就是这个项目里的程序……这么看来挖矿的操作应该是入侵者远程执行的代码,所以在查找进程的时候发现了它吧。 <br />\n 除此之外里面还有个logclean项目看了一眼是<a href=\"https://github.com/infinite-horizon219/mig-logcleaner-resurrected\">mig-logcleaner-resurrected</a>项目,看起来应该是清除日志用的,不过我根本没从日志找它🤣,即使入侵者用了对我来说也没起到什么作用。不过倒也是个挺有用的项目,也许在某些扫尾工作很有用。 <br />\n 最后就是<a href=\"https://github.com/gianlucaborello/libprocesshider\">libprocesshider</a>这个项目,也许还有其他隐藏进程的方式,不过知道这个项目之后最起码以后再遇到类似的情况我就会优先去看<code class=\"language-plaintext highlighter-rouge\">/etc/ld.so.preload</code>文件了。 <br />\n 至于其他的就是一些爆破SSH的工具估计是用来横向渗透的看起来有点原始……也没啥用处另外还有连接XMR矿池的一些配置文件以及我也看不出来的玩意应该就这么多有用的东西了。</p>\n\n<h1 id=\"感想\">感想</h1>\n<p>虽然被入侵是没有预料的事情,但还好这个服务器是闲置的,装完系统之后上面什么有用的东西都没有,所以除了入侵者让它不太闲置赚了点小钱之外对我倒是没什么损失,另外还了解到了一些不错的小工具,这么看来入侵者赚的这点小钱就当是给他的学费吧🤣。</p>\n","url":"/2025/07/13/hacked.html","tags":["Linux","安全","服务器","入侵"],"date_published":"2025-07-13T00:00:00+08:00","date_modified":"2025-07-13T00:00:00+08:00","author":{"name":"mayx"}},{"id":"/2025/07/01/xslt.html","title":"使用XSLT为博客XML文件编写主题一致的样式","summary":"这篇文章讲述了作者如何为博客的XML订阅文件添加一个与博客主题一致的XSLT样式以提高整体风格的统一性。作者遇到的问题包括Jekyll引擎的限制、XML格式的规则、命名空间对输出的影响以及如何解决样式问题。通过模仿现有样式、自定义XSLT布局和doctype-system设置作者最终成功地为订阅文件和Sitemap创建了定制的XSLT样式。整个过程不仅提升了博客细节也让作者学习到了关于XML和XSLT的新知识。","content_html":"<p>虽然XML是机器读的内容……不过加上和主题一致的XSLT样式也算是一种细节吧<!--more--></p>\n\n<h1 id=\"起因\">起因</h1>\n<p>在<a href=\"/2025/06/02/optimize.html#%E5%AF%B9%E4%BA%8E%E8%AE%A2%E9%98%85%E8%BD%AF%E4%BB%B6%E7%9A%84%E5%85%BC%E5%AE%B9%E6%80%A7%E6%94%AF%E6%8C%81\">上一篇文章</a>中我提到在提高订阅源兼容性的时候给博客的订阅文件增加了一个XSLT样式。当时使用的样式是从<a href=\"https://github.com/genmon/aboutfeeds/\">About Feeds</a>下的一个<a href=\"https://github.com/genmon/aboutfeeds/issues/26\">Issue</a>中找的,里面有个基于<a href=\"https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl\">Pretty Feed</a>修改成能同时支持RSS和Atom格式的样式。虽然那个样式倒也说不上难看但总觉得与我的博客整体风格有些割裂所以这次打算制作一个和我博客主题完全一致的XSLT样式。</p>\n\n<h1 id=\"制作订阅文件的xslt样式\">制作订阅文件的XSLT样式</h1>\n<p>虽然想搞这么一个样式但是我用的Jekyll引擎不能在引用的布局外添加额外内容……如果我要自己写要么把我的默认布局拆成头和尾两部分然后用include引用要么把默认布局的代码直接复制一份到XSLT样式中。这两个方案我都不太满意第一种我以后在修改默认布局时需要同时从两个文件检查上下文很不方便而第二种方案违反了DRY原则也会增加以后修改的难度。所以要怎么办呢 <br />\n 后来我想了想如果不能通过直接引用默认布局在外面增加XSLT的代码那干脆让默认布局引用一个XSLT布局吧这样我就能在不复制默认布局也不进行过多修改的情况下在外面套XSLT的代码了。于是我就在最外面写了个符合XSLT格式的XML布局让默认布局引用它。然后再写一个布局引用默认布局让最外面的布局根据这个布局的名字来判断是否需要使用XSLT的布局具体的实现可以看我的<a href=\"https://github.com/Mabbs/mabbs.github.io/tree/master/_layouts\">layout目录</a>。另外有一些地方需要注意一下作为XML内容中不能包含未闭合的标签所有自闭合标签结尾必须添加斜杠属性必须有值以及所有标签和属性大小写要一致……还好我平时修改布局文件以及编写内容的时候基本上都遵循了这些规则所以没什么太多需要改动的地方。 <br />\n 当时修改时,是模仿之前的那个样式进行的,原来那个样式在<code class=\"language-plaintext highlighter-rouge\">html</code>元素上加了XML命名空间但是<code class=\"language-plaintext highlighter-rouge\">xsl:output</code>配置的输出却是按照HTML的方式输出结果导致内容中用于换行的<code class=\"language-plaintext highlighter-rouge\">br</code>标签在实际转换中全部变成了两个标签……我猜应该是转换器看到XML命名空间后先按照XHTML的规则把<code class=\"language-plaintext highlighter-rouge\">br</code>解析成了一开一闭的一对标签然后又根据HTML的转换规则把这对标签当作两个单独的标签输出了吧……但奇怪的是只有<code class=\"language-plaintext highlighter-rouge\">br</code>标签出现了这个问题,像<code class=\"language-plaintext highlighter-rouge\">hr</code>等其他自闭合标签则没有……既然如此只要把XML命名空间删掉就OK了。 <br />\n 在改完之后虽然整体看上去和其他页面似乎已经很相似了但总感觉还有些样式不太对劲……我猜应该是和文档类型声明有关系我平时写的是HTML5而XSLT默认转出来是HTML4.0……但是我不太清楚怎么解决这个问题于是问了问AIAI说在<code class=\"language-plaintext highlighter-rouge\">xsl:output</code>中加上<code class=\"language-plaintext highlighter-rouge\">doctype-system=\"about:legacy-compat\"</code>就行。最终改完试了下确实有效😂,样式上也没有出现奇怪的偏移了。 <br />\n 最后把写好的布局应用到<a href=\"/feed.xslt.xml\">/feed.xslt.xml</a>中就可以了,之所以是这个路径是因为我用的<a href=\"https://github.com/jekyll/jekyll-feed\">jekyll-feed</a>只支持这个位置至于我自己搞的RSS格式的订阅只需要在开头用<code class=\"language-plaintext highlighter-rouge\">xml-stylesheet</code>指令声明一下就行了。</p>\n\n<h1 id=\"给xslt样式自己的样式\">给XSLT样式自己的样式</h1>\n<p>在写好给订阅文件用的XSLT样式之后我发现XSLT样式本身也是个XML文件……既然我给订阅文件做了样式那么也得给XSLT样式文件本身做个样式才对但如果我单独写一个给它的样式那岂不是要给样式的样式再写一个样式😂所以肯定不能这样做。不过仔细想一下还有个办法可以让XSLT样式文件自引用自身的样式这样就能避免之前担心的套娃问题了。所以接下来我应该在XSLT中写一个检测应用样式的XML文件是不是XSLT样式文件的代码方法很简单既然XSLT样式中肯定包含<code class=\"language-plaintext highlighter-rouge\">xsl:stylesheet</code>这个元素那么我可以判断如果存在这个元素就可以确定这就是XSLT样式了如果有人点开看了我就可以展示一个提示信息告诉访客这是一个样式文件这样访客就不会看到那句“This XML file does not appear to have any style information associated with it. The document tree is shown below.”了😝。</p>\n\n<h1 id=\"制作sitemap的xslt样式\">制作Sitemap的XSLT样式</h1>\n<p>既然给XSLT样式也加了样式……那我博客还有其他XML文件需要处理吗似乎还有个Sitemap我的Sitemap是<a href=\"https://github.com/jekyll/jekyll-sitemap\">jekyll-sitemap</a>插件生成的……那它支持加样式吗?虽然文档上没有写,不过看了眼源代码发现可以通过创建<a href=\"/sitemap.xsl\">/sitemap.xsl</a>文件添加所以就顺手套用之前的样式搞了一个虽然应该没有访客去看Sitemap😂毕竟这是给搜索引擎用的。可惜这些地址都是插件硬编码的如果可以自己修改位置我就只写一个XSLT样式文件就可以了……</p>\n\n<h1 id=\"感想\">感想</h1>\n<p>折腾了这么多整体展示效果还不错虽然这些文件也许根本没人看😂本来就不是给人读的但也算展现了一下博客的细节之处吧而且在折腾的时候至少还了解了不少关于XML和XSLT的知识尽管在现代这些好像没啥用了。当然重要的也许不是了解这些知识而是这个过程吧……总的来说还是挺有意思的。</p>\n","url":"/2025/07/01/xslt.html","tags":["XSLT","博客优化","XML","Feed"],"date_published":"2025-07-01T00:00:00+08:00","date_modified":"2025-07-01T00:00:00+08:00","author":{"name":"mayx"}}]}