2024年年度总结

博客竟然整整一年没有更新了,而上一篇博文,还是 2023 年年度总结。 这一年也许是这一生中最转折的一年,亦或是更加狂野的旅途开始的一年。我带着家人和两只猫,移居到了日本,继续自己在东京的职业生涯。 说实话,这次回来,少了几分陌生感,更多的其实是一种熟悉的感觉,毕竟曾经在东京生活过不短时间。在 COVID 大流行之后,这里的一切似乎也没有太多变化。过去爱去的小店,依然静静开在街角;过去爱逛的街道,仿佛尘封了六七年。 年初坐在办公室楼下大厅的咖啡馆,和现在老板第一次面对面聊天的时候,我其实还一度问自己:“在完全英语的环境下办公,真的可以吗?”。好在事实上上手以后,这事儿出乎意料的顺利。 在国内满打满算干了也快六年的 DevOps 和 Infrastructure,今年机缘巧合,以 SDE 的角色主导了一个挺有意思的项目重构。以前在一堆大部头书籍里学了不少分布式、数据结构优化、性能提升的纸面技巧,今年也算是都用上了。当然,更重要的是遇到了非常不错的同事,以我浅薄的职业经验来说,职场舒适度里“和谁一起工作”是占比最高的因素。希望来年可以继续做一些数据库和 Infra 相关的项目吧。 相比于职场,其实今年的健康管理做得非常不好。也许是去年的暴力面试消耗了太多的生命力,今年充满了摆烂的气息,整整一年都没有好好坚持运动。年初冲的高尔夫练习场会员卡,到今天为止也就去了 5 次。夏天体重虽然一度减下去 5 公斤,但是秋膘又贴回去了。 好在家属时不时会拉我去隅田川边跑个 5 公里,周末天气好会在市区进行大暴走。尽管身体指标并没有显著改善,但是也没有变糟糕。而立之年将至,真的得狠狠有氧了。 不过,得益于海外工作的 Work Life Balance,今年真的拥有了太多时间来做自己感兴趣的事情。年初开始入了明信片的坑,注册了 Postcrossing,至今为止也收发了上百张明信片;重新捡起了纸币收藏,尽管今年没有入手很多漂亮的钞券,不过花时间补了下近 15 年全球主流国家银行券的发行日志,争取明年把日本的非高价券都集齐了;开始了中古掌机的收藏,基本把任天堂从 1989 年到现在所有型号的掌机都收集齐全了,而且是箱说全对码,这算是最有成就感的一项了。 另外也是蹭了一下家属的手账坑。我自己其实一直有记录每日事项的习惯,不过之前用的都是烂本子,写完一年就丢掉。这次终于有一个很不错的手账用了。 我个人觉得对兴趣的投资还是挺重要的,毕竟这是保持心理健康最实惠的办法。而心理健康在人生路上的重要性,不亚于肉体的健康。总结的话就是,今年心理健康很用功,肉体健康很马虎。 那么最后,祝大家 2025 年身体健康!

2024/12/30
articleCard.readMore

2023年年度总结

是的这有点奇怪。为什么已经离 2023 年过去快两周了我才开始写年终总结。 其实原因并不复杂,有一句话叫做 “事以密成,语以泄败”。今年的年末年始确实是我一个非常重要的人生节点,因此我无法在那个时刻对任何不必要的人讲任何关于自己的事情,包括年度总结。 还好最近在妻子、家人和好友的支持下,一切逐渐稳定下来了。因此也算是一个合适的时间回顾一下自己的 2023 了。 ¶去年做了什么 简而言之,去年除了做好本职的工作之外,我和妻子完成了带着宠物移居东京的计划。 我无意于过多展开关于为什么做这个决定的具体原因。如果非要说的话,2022 年的 Lockdown 让我对某些地方最后的怜悯和期待也完全消失了。 另外,就像香港投资家曹仁超先生在那个广为流传的视频里所言,个人天赋和能力其实在大势面前,和路边野狗拉的粪便没有本质的区别。而我觉得自己多少还是一个胆小如鼠的投机分子,因此还是和易于预测的环境为伍比较安心。 移居海外一直是一个热门的话题,我并不想在这样相对容易引起争议的话题上说太多暴论。每个人有每个人最适合的路径,与其向他人取经,不如重新内观一下自己。 我的移居之路本质上和在国内跳槽没有太大的区别,面试、继续面试、坚持面试,拿到 Offer 然后下签高度专门职,飞到东京入职。除了行李多了点、需要提前 225 天办理宠物的检疫手续之外,说到底,主要还是时间成本。 除了面试的准备之外,去年主要干了两件有趣的事: 第一次向 Kubernetes 官方文档提了 PR。当然,对于任何开源项目,给文档提 PR 就是给刚接触的小朋友上手的,但是说实话能够有机会感受到那么好的社区氛围,每天睡觉前看一眼 dev 的邮件列表,确实也够了。如果真的想给 CNCF 的项目提交一些真正有价值的 feature PR 的话,说实话并不是一件容易的事情。可能在短期内成为一个出色的 Kubernetes 的使用者会是一个更加现实的目标。 作为主要贡献者开发了一个多系统初始化项目。受限于签署的 NDA 条款,没有办法聊项目的具体内容,但是这种利用过去工作中用过的诸多工具和它们的最佳实践经验,和许多有着数十年开发经验的大佬一起头脑风暴,最后迭代出一份会被大规模使用,服务于万千游戏玩家的内部工具链,确实是一件让人难以抑制兴奋的事情。如果不是移居大业当前,我大概会在前东家长久服务。可惜人生就是这样充满不可逃避的变化。 ¶去年吃喝玩乐了什么 去年因为非常忙碌,其实并没有太多时间专注于玩乐。除了过年的时候去北方感受了一下久违的年味之外,只出了一次远门。 秋天和妻子一起去了趟泰国,感受了一下久违的南国海滩。尽管普吉岛的沙滩和水质在整个亚洲都算不上优秀,但是和爱人一起在海边散步和休憩的浪漫,与学生时代跟着父母去浮潜胜地游玩的体验,是截然不同的。从这个角度来说,海滨的质量已经不是最重要的因素了。 也许是热带海岛去多了疏忽了,在充满热带植被覆盖的酒店居住期间,我们被携带登革热的蚊虫叮咬了,我爱人离奇地成为了今年静安区第四例登革热患者,在结束了泰国之行后的两周,我们不得不在上海的疾控中心度过。而我们在离开曼谷的前一天,刚好去过第二天发生了枪击案的暹罗广场。这段经历揉在一起看,极度的不真实。可能这也预示着人有时候还是得今朝有酒今朝醉吧。 之后又去了几趟南京和老家,主要还是以吃本地平价菜为主,顺便辱骂几句沪国离谱的物价。 ¶有在运动吗? 还是有一点的,但是不多。 今年因为招行信用卡送了 12 次高尔夫练习场的场次,就硬着头皮买了套入门球杆去练习场丢人现眼了。但是意外地,高尔夫这东西相当的好玩。首先这东西并不是看上去那么慢条斯理,抽打开球木和长铁杆的时候,是非常暴力的。每小时能耗能破 1000 大卡的东西只能说和优雅是半毛钱关系都没有。 但是高球本身又极度追求精度和可控,这导致参与者需要用最大的爆发力打出最精确的飞行曲线。结果就导致高尔夫的练习是永无止境的,而即便到了专业级,依然会屈服于随机性的魅力之下。 因此在 12 次练习场用完之后,又去了不少次室内的练习场,虽然成年人搞运动进度肯定不如童子功,但是起码三杆洞也能稳定上果岭了。至于推杆的练习,未来有机会再说吧,不是现在要解决的问题。 至于跑步的话,倒是断断续续进行了几次,但是终归是没有维持住频率。尤其是八月份健身卡到期后,更是荒废了。希望今年可以重新振作,再次跑起来(心虚)。 ¶2024 有展望吗? 有的吧,就是五个 “好好干” 好好工作,做一个更好的工程师。 好好吃饭,但是别吃的太胖了。 好好打球,五杆洞稳定上果岭。 好好学习,日语多讲讲。 好好拉屎,不要得痔疮。 最后。祝大家,身体健康。

2024/1/11
articleCard.readMore

让我们粗鄙一点

这两天看到很多人聊到了防 “杠精”。 其实我在相当长一段时间里也是杠精,甚至现在都会偶发杠精。这在和我妻子相处的多年时光里被无数次验证。更不用说那些依然在互联网可查的我的暴论发言。 还好现在改善了那么一点点。 如果要我说为什么彼时的自己会是一个杠精,大概是脱敏训练做的不够。是的,今天我想说的就是社交环境的 脱敏。 很多次在内观冥想的时候思考过,人为什么会产生去怼别人,或者去杠一下别人的话题和观点的冲动。根源的因,应当是这段话题和观点,蹭疼了自己的某个安全区或者固有观念。 人类作为一种社会化的灵长类,本能地会把这种场景等同于对于领地/栖息地的侵犯。自然而然就会进行抬杠反抗。 所以,抬杠本身我倒是觉得和道德人品的直接关联没有那么巨大。 我想关注的是如何和自己的安全区和解,或者这么讲: 强化真正的不能妥协的雷点,能做到如被触犯必翻脸。 扩大自己的观点包容度,甚至做到你的观点很棒,下一秒就是我的了 互联网的抬杠和争吵,仅以我有限的观察来说,往往就来自于这两点的混沌: 一开始是讨论某一个观点的,结果快速转进到对方的不能妥协的雷点上了。 把所有自身的观点都变成了雷点,这样不用说和人聊天,仅仅是看到别人的观点,就会无法抑制本能上去杠。 那么,什么是不能妥协的雷点呢? 抱歉,我没有标准答案。每个个体都有每个人不同的成长轨迹,每个人都有每个人不同的人生经历,虽然雷点往往围绕在原生家庭、儿时伤痛、情感波折、人格尊重这些方面,但是具体的雷点,真的是千人千面。 但是这也足够了,少问别人家庭,少问别人的童年,少问别人的投资,足够规避掉日常会话中 99% 的雷点。 即便不慎在聊天中触及了到了对方的雷点,我相信,只要在一开始就以尊重对方的立场去开始对话,这些意外的摩擦,并不会让社交关系直接堕入互相屏蔽的冰点。 如果一开始怀着恶意,即便有再多的社交技巧,最后也一定会一脚踩爆对方那颗雷。 然后是下一个点,如何扩大自己的观点包容度,让自己的变得更加 “粗鄙”? 我的观察是,人类的本质都是人类。 听上去是一句十足的废话,不过且容我列举一些人群: 无论是创办家族企业的企业家,还是山野的农夫; 无论是基督徒,佛教徒,伊斯兰教徒; 无论是步履匆匆白领,还是随心的自由职业者; 无论是西雅图的工程师,还是东京药妆店的打工者,还是陕北的农民; 无论是南亚终生漂泊海上的船老大,还是瓦拉纳西将同胞送归往生的焚尸者; 无论是 MtF,FtM,同性恋,异性恋,机性恋,武装直升机性恋; 无论是 XX 陛下,XX 公爵,XX 博士; 无论是… 让我们把这些标签去掉: 无论是 Ta,还是 Ta; 无论是 Ta,Ta,Ta; 无论是 Ta,还是 Ta; 无论是 Ta,还是 Ta,还是 Ta; 无论是 Ta,还是 Ta; 无论是 Ta,Ta,Ta,Ta,Ta,Ta; 无论是 Ta,Ta,Ta; 无论是… 让我们把性别和神性也去掉,最终只剩下一个标签:人 是的,大家都是人。大大咧咧、多愁善感、快意恩仇、钻营苟且、过河拆桥、富有担当、深谋远虑、快乐阳光,都会同时存在于任何一个集合体的标签上,甚至出现在任何一个个体上。 人无法被神化,人也无法被标签化。 而对这一切 脱敏 的办法,就是在保证自身安全的情况下,多观察人。 如果您喜欢和人面对面,出去吧,和各种背景的人交朋友,坐下来聊聊天。 如果您更喜欢独处,这个时代有无数的时光之隙可以提供无尽的观察切入点。 这个过程中,会感觉到大脑升级,会感觉到自身的观念被大浪冲刷,会感动于人类的伟大,会愤怒与人类的粗鄙。 最后在某一个节点,你环顾四周发现,你丢失了自身观点的边界,你构建了一套兼容无数冲突的观点的思考方法,你似乎变得更加漠然,但是也变得更加纤细。你成为了一个 粗鄙 之人。 … 这个时候,你喝了口水,突然觉得当杠精好无聊。 最后,祝大家身体健康

2023/8/23
articleCard.readMore

写好项目技术文档 - CH02. 文档标准设计

大家好,时隔两个月,我又来了。 时隔这么久一方面是最近事情比较多,已经到了并行处理的极限;一方面也是因为,文档标准总归是一个比较枯燥的话题,所以拖到了现在。 那么话不多说,我们书接上文 写好项目技术文档 - CH01. Repository 设计,接着聊聊怎么设计文档的标准和规范。 ¶为什么要有标准? 在日常中,其实很少有人喜欢写文档;而愿意写文档的朋友,经常也会忽视如何设计文档的标准这一话题。毕竟能够愿意去持续地为公司内部项目贡献文档,已经弥足珍贵,有得是玩命糊屎山不写文档的人(当然,代码如文档是最好的状态;但是 99% 的从业者无法达到 Linux Kernel Docs 所描述的理想的状态)。 不过我倒是觉得,文档这东西是有传染性的。当你能够以一个比较高的标准去持续输出文档的时候,身边的其他人多多少少也会被你感染,潜移默化地体会到一个有规范的文档所带来的好处。 这里一个比较常见的案例就是,一个工作上的伙伴来问你一个曾经遇到过的 TroubleShooting,而你直接把相关的文档链接/复盘案例直接在 Thread 里甩给了他。原本需要冗长的 DM 或者 Thread 讨论的问题,直接被终结了。可以说,这是与人合作的过程中最爽的事情之一。 另一个场景是,组里来了一个萌新,他正在进行 Onboarding。这个时候他来向你询问一个内部工具的用法,而你粗暴地甩给了他一份事无巨细、Step-by-step、我奶奶来了都能跟着操作的文档的时候,你的潜意识会清晰的告诉你,这些文档为自己和萌新节省了无可计量的时间,这亦是另一种爽快。 如果说文档的有无带来的爽感是第一层级的话,那么文档的标准和规范,就是到达第二层级的爽感的必经之路。 当一个文档逐渐壮大,或者整个团队的文档意识开始提升之后,一定会遇到一个问题:文档的协作 不像代码,无论项目使用什么语言、版本管理工具、开发框架,总归有一系列非常成熟的开发规范和工具链可以使用。这意味着: 作为下限,项目的 OWNER 可以通过一个很低的代价,确保项目代码的基本一致性。 作为上限,通过 Reviewer 的严苛程度以及单元测试的高覆盖,可以让所有贡献者保持高度一致的提交风格。 而文档的协作,却更加复杂: 贡献者的行文风格都大相径庭。 不同语言的遣词造句规范,有很大的差异。 对于长期项目,甚至会被语言本身的变迁所困扰。 同义词和近义词混杂问题。 因此,文档的标准和规范的目的,就是希望在可控的范围内,尽可能地减少这些问题的出现。让多人协作的大型文档,变得更加工业化,从而实现一套囊括了文档新增和更新(以及本地化)的流水线工程,并且和上一篇 写好项目技术文档 - CH01. Repository 设计 中的文档 Repository 设计相结合,充分利用版本控制工具的优势,结合现代的 CI/CD 流水线,实现一个可跟踪、可维护、行文统一、用词标准无歧义的文档贡献系统。 ¶文档标准,分哪些部分呢? 这个分类,我更多其实是参考了 Kubernetes 官方文档以及 Jenkins 官方文档的一些内容以后,自己琢磨的。 简单来说我觉得可以分为这么四块: Reference & Concepts —— 名词与概念参考表。 Style Conventions —— 行文/i18N 风格指南。 Sync Check —— 适用于多语言的 commit 同步检查。 Ownership —— 文档/文档分支的所有权管理。 传统意义上的文档规范,其实主要指的就是 References 和 Style Conventions。前者是对整个文档中出现的专业术语和概念的标准解释,从而其他贡献者撰写的时候可以用类似于 {{\}} 这样的形式进行快捷引用。 后者是对整个文档的行文风格,遣词造句的规范。举个例子,在 Kubernetes 官方中文文档中,明确描述了,不允许在翻译中使用 “您” 作为读者的指代。这就是一个很明确的行文风格的规范。 ¶当我们说标准化的时候,到底说的是什么 相信多数的读者在阅读各种开源/商用工具的文档的时候,多少遇到过一个问题: 在不同的页面中,同一个概念或者术语,居然有不同的描述 这并不是一个罕见的问题。无论是 Kubernetes,Splunk,Jenkins 这类超级项目的文档,还是说遍布 GitHub 的各种小型开源项目的文档,都会遇到这样的问题。 举个很直接的例子,在 Kubernetes 中 control-plane 是一个非常基础的概念。然而即便是如此基础的概念,在所有中文翻译版本的页面中,其实有 控制面 和 控制平面 两种翻译方法。从提交记录来看,这显然是一个因为早期批量翻译提交和行文风格规范没有完善所导致的问题。 因此这里也引申出了小节标题。当我们说标准化的时候,到底说的是什么? 我认为在这个场景里,标准化代表的就是精确、达意、全局统一的术语词典表。这个表应当包含了当前这个项目/软件所有的专业术语和概念。如果有余力,甚至应该对这个术语词典表中的每一项,进行完整的描述和概念边界的限制。 Reference & Concepts,也就是这个术语词典表,我认为是文档规范中一切的基础,是大楼的根基,是巨树的树根。 有了这个形而上的认识,下一个问题就是,如何把 Reference & Concepts 动手做起来。其实很多时候大家都会觉得把一个 PPT 或者概念上很好的东西落地到实际的 Repository 里,很困难。但是很多时候我觉得我们是被自己束缚住了。 我很喜欢一位厨子(高寒)的说法:“按照我说的做,不要按照我做的做。” 事实上在项目文档里推进标准化也是一个道理。不要在一开始想着就和 Kubernetes 文档一样,设计一个非常成熟而夸张的目录树,用来承载多如繁星的各种概念和术语。这没有必要,因为你根本做不到,并且你的所有热情会在这个过程中燃烧殆尽: 很多时候,作为一个好的开始,一张 MarkDown 写的术语表或个 CSV 文件可能就完全够用了。你完全可以写一个检查工具,放在 CI/CD 管道中用来对文档的术语进行初步的检查。这样的工具只要稍微利用正则就很容易制作。 ...namespace, 命名空间control-pane, 控制平面ingress, 入口... 文档和业务代码最大的不同是,文档更像是一种活的东西,一种随着贡献者的来来去去自由生长的东西。Reference & Concepts 作为保证其符合要求生长的最底层的规则,很多时候最大的意义在于其存在本身。 ¶如何让无数人,按一个风格说话 说实话,这个副标题让人绝望。 莫说让无数人按照同样的行文标准来说话;哪怕让我这个月和上个月用完全一样风格的说话,恐怕都不太容易。 所以这里就产生了一个问题,语言、或者说文章的标准化生产方法是什么。对了,就是车轱辘话。不管什么人说出来的车轱辘话,基本味道都是差不太多的。那么怎么说车轱辘话呢? 答案就是堆砌简单陈述句。 - A 是 B- A 有 B- A 可以 B- A 用于 B- A 用 B 来 C 其中 B 和 C 这样的名词或者动词,至多再加一个简单的修饰词。这样的句子,不仅简单,而且容易理解,也容易记忆。好的文档是消除歧义的,而简单句就是消除歧义最好的办法。 但是规定是规定,人是人。所以我一直认为(当然很多人不这么觉得),文档的 Reviewer 团队对于文档长期的风格一致性,至关重要。人类其实是非常善于模仿的,甚至在自身是新参与者的情况下,会主动对集体的行为进行无意识的模仿。引申到文档文风上,历史已经存在的文档的风格,对后来的贡献者影响会非常巨大。因此有一个稳定的 Reviewer 团队的文档,往往能够在比较长的时间线上,保持了很好的风格一致性。这里可以参考 Splunk,RedHat,Microsoft 这些商业公司的文档。且不论文档内容纰漏的多少,但是它们确实相较于大量开源项目,有着更加工业化的统一文风。 另外有一个非常好的例子是 Linux Kernel Docs。你会发现在内核文档中,随处可见夸张的谩骂和对糟糕提交行为的批评。按照普适的技术文档的眼光来看,这样的文档是让人皱眉的;因为 Linus Torvalds 以及他早期的小伙伴们的个人印记实在太过于强烈。但是即便如此,由于 Linux Kernel 直至今日依然有着这颗星球上最成熟老练的维护团队,面对五万人级别的贡献者团队,仍然可以将文档和 Code as Document 理念贯彻到底。 聊到这里其实大家估计也品出来了,关于行文风格,真正的规范可能只有一条:尽量使用简单陈述句。剩下都是关于 人 的问题。 再缩小点范围,是一个关于 维护团队 成员稳定性的问题。他(她)/他(她)像破冰船的船头一样,带领整个项目的文档贡献者,一起向着一个方向前进,撰写一页又一页得到用户和社区认可的文档,那么这个文档的文风本身也会像一棵被妥善照料和修剪得到树木一样,长期维持一个不变的风格。 正所谓,不要尝试去教成年人如何说话,好的文档本身就是最好的老师。 而维护者要做的,就是照料好最初得种子和幼苗。 ¶多语言,我们怎么跟踪母语言版本的修改? 几年前其实思考过这个问题,当时我觉得容易想不容易做。原理是利用 git diff 和 git log。 多语言内容无论是放在不同分支,还是在同一个分支的不同目录内,都可以用这个方法进行跟踪,但是需要一些巧思设计。当时写了一个简单的 demo,用了一下勉强可以用。直到有一天,我看到了一个终结问题的答案: Kubernetes i18n sync check 这个脚本稍作修改,可以迁移到几乎任何形式的文档中,用来跟踪多语言翻译的变更。无需多言,大家直接看代码就行。 没必要重复造轮子。 ¶维护者,亦是苦行僧 聊到这里,本文也快到尾声了。我想说的最后一点,是关于维护者的话题。 正如上面提到的,行文风格的核心是维护者团队的稳定性。无论是文档刚刚开始产生的时候,还是后期有越来越多贡献者参与的阶段,稳定的维护团队是保证文档不发生锈化的核心要素。很多商业公司,甚至开源基金会的团队建设过程,都忽视了这点。 大家往往很重视项目业务代码仓库的核心团队的组建和发展,但是到了文档这个环节,由于没有直观的业务价值,往往被降级。要么是由开发团队的人员兼任,要么由团队的萌新来做。萌新连项目本身都看不明白,就要来写文档,这不是在扯淡么? 这也是为什么国内好几个规模很大的厂子,文档却一泡浆糊的原因。 说实话,我一直坚信技术文档是任何开源/商业项目的第一个门面,而不是他们的官网。FFmpeg 和 VIM 的官网一点都不酷炫,但是不影响他们成为伟大的软件。他们的文档写的都很清爽和开发者友好,对这些工具真正的用户来说,这实在是太重要了。 如果鄙人有幸,真有开源项目的创始者或者技术行业的大佬看到这篇文章,我真心希望大家,少去评几个没用的奖项,少去在官网弄一堆酷炫的合作友商和优秀客户采访;一份出色项目文档,能为项目带来巨大的潜在声誉,长期价值远远超过那些虚浮的东西。 最后祝大家,身体健康。

2023/8/20
articleCard.readMore

窜访游记:红山森林动物园

我是一个业余动物园爱好者。 南京红山森林动物园的大名,不管是不是动物园爱好者,多少都是有所耳闻的。互联网上红山和台湾市立动物园被并称为两岸双雄,那自然应当是有一些独到的东西在的。 但是去之前我比较担心的一个事情:期望会不会被拉太高了 人类便是这样,说要掀掉屋顶乘凉,便觉得开窗也不算过分了。旅游也是如此,当觉得自己要去一个屎一样的景点的时候,意外发现比想象中好得多,就会收获难忘的记忆。 去年窜访云南的时候,就有了这样的体验。但是这次出行之前,内心比较忐忑。 不过好在,红山到底是没有让我失望。 动物园本身其实千园千面,把红山拿来和亚洲的其他动物园对比,意义并不大。如果非要说为什么我觉得红山值得它一直以来受到的盛名的话,主要是一点: 红山愿意牺牲游客观看动物的体验,来提高丰容水平 红山本身并不大,難以从动物的种类和游乐设施的水平上和其他动物园竞争,因此红山曾经一度濒临倒闭。 直到红山利用社交媒体的影响力,加上本身对园区丰容出色的运营,获得了社会层面的支持,才慢慢走出了困境。 红山绝大多数的中小型园区,尤其是本土动物区和冈瓦纳区的丰容设计,似乎从一开始就没有考虑过游客的能不能看到动物的问题。 这一点,和国内大多数的动物园区别极大。这些园区内设置了大量人工或者自然的绿植,让整个区域的可见度非常差。 但是这对动物来说是非常好的,因为欧亚大陆的原生物种对于人类会本能恐惧。 人类作为盘踞地球史无前例的统治性生物,在上千个世纪的时间里屠戮了大半的陆生中大型生物。 动物园只有真正考虑过动物本身的需求,才会在游客的体验下刀,做出更偏向动物的取舍。这不是利于营收的决策,因此更加难能可贵。 在浏览过程中,其实如果眼尖一些,是能够发现动物喜欢藏在能够看到人类,又不容易被发现的隐蔽处。安全感的满足,让动物可以表现出更多自然行为,例如正常的进食、较少的刻板行为、亲子间互动。 回想过去的参观经历,这类行为的出现频率还是很稀有的。 另外,红山接近九成的园区指示牌和信息牌,都是工作人员手写手绘的。这种细节也说不出所以然好在哪里,但是国内并没有看到其他动物园,能做到类似的程度。 今天太晚了,就懒得写多了。传了些视频到油管,下面是最有溫情的一個,大家有兴趣的可以去看看。 金絲猴媽媽和幼崽的互動 最後祝大家身体健康,有空也可以去红山逛逛。

2023/8/8
articleCard.readMore

写好项目技术文档 - CH01. Repository 设计

大家好,今天让我们来聊聊项目技术文档。 正如我在推特上所说的,我将会在接下来的一段时间里,来分享我在过去几年里参与内部或者开源项目技术文档的时候,学到的一些有用的经验。 我绝不会说我是技术文档方面的专家,但是从个人习惯来说,我确实算是比较喜欢写文档的那类工程师。 与其说是为了说教式地描述如何写文档,不如说是兴趣使然地对这个主题的讨论。 那么言归正传,我们先来聊聊项目技术文档是什么。 从我的观察结果来说,计算机行业的项目技术文档可以分为两类: 手册,也就是纯粹的 Manual Book,包含了你执行 man <cmd> 或者 Swagger API 这样列表式的指南。 综合文档,类似于 Kafka Docs 或者 Kubernetes Docs 这样的,同时面向用户和开发者,并且更专注于教程和概念的综合文档。 本系列的博文会着重于 综合文档 这一类,这也是大家在工作上更常见、更有概率参与维护的一类文档。在接下来的篇幅中,没有特别说明的话,技术文档这个词都会指代 综合文档。 而 手册 类的文档一般来说都已经直接包含在了项目本体的代码仓库里,结构化程度也没有那么多层。 ¶我所理解的技术文档 其实很难精确定义技术文档是什么,因为 CNCF, Apache, Linux Foundation 等开源机构自身的文档可以说是各有各的思路和设计哲学。 但是如果要找出技术文档在底层框架上和其他类型的文档的区别的话,还是容易的。计算机行业的技术文档,多半具备这些特点: 文档应由版本控制工具进行托管的,无论是 Git 还是 Mercurial。 文档应具备一系列根据实际需求创建的校验和开发者工具,并且配置与之对应的 CI/CD 流程。 文档应具备预发布/渲染机制,例如在 Merge Request 中提供新版本的 Preview 渲染。 任何开源/商业项目的开发,抽象到最后本质都是 工程学 的一个分支。 用工程学的思维进行文档代码库的设计,并且根据实际环境进行长期的迭代和跟踪,也许就是技术文档和常规文档的最大的区别。 ¶版本控制 正如标题所言,今天这篇博文我们不谈技术文档的内容怎么写,而只谈工程学上,怎么给技术文档打好地基。 技术文档区别于散文、议论文乃至于学术论文,有几个独特的点: 对 “精确” 的要求极高。 迭代速度极快。热门项目的技术文档往往有五位数甚至六位数的 PR。 参与人数极多。公司内部项目几十上百人参与,开源热门项目往往上千人。 这些特点决定了,如果技术文档不托管在版本控制软件中,很容易在短时间里失控(内容失控 + 管理失控)。 因为不同人的行文风格差异很大,对于热门项目来说,确保行文和用词的一致性是很困难的。 所以不可避免地,需要使用 Merge Request 或者 Pull Request 这样的机制来进行文档的审核。这样,不同分支的管理者,就可以根据开发者规范和本地化风格规范,对文档的风格进行长期的跟踪和维持。 作为案例,Kubernetes 中文文档的开发者规范就做得很不错。 在 Repository 中,大家使用 script/lsync.sh 来对上游分支(也就是英语分支)进行提交的跟踪。对于单个文档页面,只要最后一次的中文同步提交早于最后一次的英语上游提交,便是做没有完成上游同步。 举个例子,当页面 Ingress 控制器的上游英语版本发生了改变以后,文档贡献者就可以非常轻易地发现更新内容,根据中文本地化样式指南的规范,修改并且提交 PR 来确保下游简中分支的同步: $ scripts/lsync.sh content/zh-cn/docs/concepts/services-networking/ingress-controllers.mddiff --git a/content/en/docs/concepts/services-networking/ingress-controllers.md b/content/en/docs/concepts/services-networking/ingress-controllers.mdindex 7391839f85..fc19265b39 100644--- a/content/en/docs/concepts/services-networking/ingress-controllers.md+++ b/content/en/docs/concepts/services-networking/ingress-controllers.md@@ -41,6 +41,7 @@ Kubernetes as a project supports and maintains [AWS](https://github.com/kubernet * [Easegress IngressController](https://github.com/megaease/easegress/blob/main/doc/reference/ingresscontroller.md) is an [Easegress](https://megaease.com/easegress/) based API gateway that can run as an ingress controller. * F5 BIG-IP [Container Ingress Services for Kubernetes](https://clouddocs.f5.com/containers/latest/userguide/kubernetes/) lets you use an Ingress to configure F5 BIG-IP virtual servers.+* [FortiADC Ingress Controller](https://docs.fortinet.com/document/fortiadc/7.0.0/fortiadc-ingress-controller-1-0/742835/fortiadc-ingress-controller-overview) support the Kubernetes Ingress resources and allows you to manage FortiADC objects from Kubernetes * [Gloo](https://gloo.solo.io) is an open-source ingress controller based on [Envoy](https://www.envoyproxy.io), which offers API gateway functionality. * [HAProxy Ingress](https://haproxy-ingress.github.io/) is an ingress controller for..... 类似的好处其实远不止在本地化的过程中。 当面对多个版本或者 Tag 的文档的管理的时候,文档的 Owner 可以非常灵活地编辑不同版本和 Tag 的管理员和审核者名单,并且保证不同分支之间内容的共享和隔离,能够被精确记录。 当尝试接入 CI/CD 流程来确保审核的流畅性,以及支持页面预渲染和预发布的时候,利用版本控制软件,也可以更好地引入 GitLab CI, GitHub Action 和 Jenkins 等特性或功能。 更进一步,我们可以导入独立的测试和流程 Repository,来模块化你的文档开发流程,并提升 CI 流程的可读性。 ¶辅助工具和 CI/CD 辅助工具,指的是文档编写者用于初始化、编写、校验、测试的脚本或者可执行库。 之所以和 CI/CD 放在一起,是因为在 PR 管理和 CI/CD 流程中,这些工具往往是不可或缺的。 有时候辅助工具和 CI/CD 存在互相平替换的关系。例如 verify_spelling.sh 这样的拼写检测脚本,往往可以被 super-linter 来替代,或者协同使用。 我们很难用一个统一的框架来告诉文档设计者如何制作这些辅助工具,正如开头所说,不同的文档设计哲学,让每一个技术文档都有自身独特的流程需求和测试用例,而不同的前端框架也会让文档有完全不同的 build 环境和依赖。 但是我认为存在一些通用的技术文档工具/脚本的设计原则: 需要有机制跟踪提交记录的变化,如果存在多个分支或者 Tag,能够检测上下游的同步状态。 需要有机制检测拼写和格式的正误。 提供完全本地的 Build,Deployment 和验证工具。 这么说有点抽象,大家可以参考下面两个具体的案例: Docker Documentation 下的 *.sh 脚本 K8s Documentation Scripts 拥有这些工具之后,贡献者可以非常舒服的在本地进行文档的编写和测试,从而显著提高 Merge Request / Pull Request 的质量。 这对于文档的迭代效率和代码审核者的心智负担,都是非常有好处的。 到这里开始,我们就可以考虑为文档接入 CI/CD 流程了。考虑到多数热门项目的技术文档,都是基于现代的前端框架,这方面的流程大家都比较熟悉了。显然不需要我过多文字介绍。 举个例子,如果你的文档是用 Hugo 来构建的,那么可以很容易利用 Netlify 和 GitHub Pages 来进行自动化构建和部署: ---name: Scheduled Netlify site buildon: schedule: - cron: '4 0,12 * * *'permissions: {}jobs: build: runs-on: ubuntu-latest steps: - name: Trigger build on Netlify env: TOKEN: ${{ secrets.NETLIFY_BUILD_HOOK_KEY }} run: >- curl -s -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d "{}" "https://api.netlify.com/build_hooks/${TOKEN}" 这样你就安心将文档的构建和发布交付给自动化的 Pipeline,而只需要关注于内容本身了。 ¶预发布 在软件开发和发布的过程中,我们经常提到 “金丝雀发布” 这个概念。 事实上在技术文档,尤其是开源项目的技术文档的发布过程中,预发布(金丝雀)发布也很重要。 我们很容易忽视这个事情的严重性:假如一个新的 PR 在合并后,立刻反映到了线上的文档中,而这个 PR 包含了在 Review 的时候未发现的事实错误和样式渲染错误,导致读者因为这些错误,严重误解了项目部分特性的使用方式,乃至进一步造成了严重的线上事故,那就非常糟糕了。 所以,我比价推荐在 MR / PR 被创建之后,执行一次预发布,将文档部署到一个预发布环境中。 这个环境可以是一个基于容器镜像的环境,也可以是一个基于静态网站的环境。 当你为企业内部的项目工作时,这快预发布可以做的非常随意。 而当你为开源项目工作时,可以尝试 Azure DevOps,Cloudflare Pages 或者 Netlify 这样的付费服务。它们可以为你的预发布环境提供非常便捷的公共访问入口,从而让多人协作和审核变得特别简单。 如图所示,在向 Kubernetes Docs 创建新的 Pull Request 时,CI Bot 会自动为这个 PR 创建一个预发布环境,从而让 Reviewer 可以在预发布环境中查看文档的变化。 点击查看详细内容,可以看到 Netlify 执行了完整的预设流程,Build 了整个技术文章站点,并且在和 PR Number 绑定的 URI 下创建了公开的访问地址。 体验非常舒服。 ¶结语 版本控制,CI/CD 流程和预发布被引入的初衷,其实是 “这么做会更好”。 更何况像 Confluence 这种广泛用于内部技术文档的平台,反而版本控制的支持一言难尽。即便提供了还不错的 Scroll Version 功能,也远说不上是特别工程化的设计逻辑。毕竟 Confluence 的用户也不完全是工程师。 引申开来,这里还有一个潜在的点值得思考。 我们聊了那么多关于文档工程化和代码库化的讨论,目的是使文档更易于长期维护和跟踪,从而提高内容的质量。 从第一性原理来说,技术文档的内容,是评判技术文档好坏的唯一标准。 切勿在追求工程化的过程中买椟还珠。事实上很多人,包括我自己,都犯过这个错误。 认真书写的 *.TXT 远好过结构精致内容空洞的工程化技术文档。这点和大家共勉。 下一篇,我会和大家聊聊有关 Convention,也就是文档规范的话题。 感谢阅读,祝大家身体健康。

2023/6/25
articleCard.readMore

NuGet 应用实践:手搓 Vitual Studio Choco 安装包

“微软的文档,真的太棒啦” ¶引言 大家用过 Chocolatey 吗?我相信答案一定是 Yes,因为它是 Windows 下最好用的包管理工具,恩,大概率没有之一。 虽然当下大多数国内的互联网大厂和游戏公司为员工配发的设备以 MacOS 为主,但是大量的日常工作场景,依然会在 Windows 上进行。无论是 C# 相关的开发,以及跨 OS 应用的开发,亦或者 Unity3D、Unreal 引擎的游戏打包,开发者都需要在 Windows 环境下进行开发和调试。 因此,Chocolatey NuGet 包的统一管理和维护的必要性,也愈发凸显。 事实上,相对于 YUM 和 APT 这样的包管理工具,Chocolatey 的包管理与开发是相当容易的。本质上,Chocolatey 的包管理就是 NuGet 包管理和开发。而对于熟悉 dotnet 开发的人来说,学习成本非常低,近乎为零。 枯燥地讲概念和丢代码是没什么意思的,所以不如跟着我的脚步,一起来写一个属于你自己的 Visual Studio 2019 Professional 的 Chocolatey 安装包 (尤其是当我发现 Google 上似乎并没有找到比较好的教程的时候) ¶准备工作 在开始动手之前,我还是非常建议大家看一下 Chocolatey 官方的 Create Packages 文档。这篇文章相当详细的描述了在开发 Chocolatey 包的时候需要遵守的一些规范,以及一个 Chocolatey NuGet 仓库的最小基本结构。 正如官方文档所言,事实上一个 Chocolatey 包中真正强制需要的文件,只有一个 *.nuspec 声明文件。其他所有的内容都不是必须的。当然,为了让我们的包可以被简单得安装、部署和卸载,在实践上我们还需要提供 chocolateyInstall.ps1 和 chocolateyUninstall.ps1 ,从而实现安装和卸载的自动化。 下面这个表描述了所有 Custom 脚本被调用的环节: Script NameInstallUpgradeUninstall chocolateyBeforeModify.ps1YesYes chocolateyInstall.ps1YesYes chocolateyUninstall.ps1Yes 同时,为了便于在 CI 中进行打包,一个 *.csproj 文件往往也是推荐创建的。 那么在有了以上知识准备以后,我们就可以开始部署构建 Chocolatey 包的开发环境了。对于首次尝试的朋友,我们还是推荐 Win10_22H2 作为开发系统首选。 首先需要安装一些必要的组件: ¶安装 Chocolatey Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) 请注意,上面这条安装命令可能因版本的迭代而发生变化,请以官方文档为准。Chocolatey 官方的安装文档请参考 Chocolatey Installation 。 ¶安装 .Net SDK 和 .Net Core 请注意,这里并不是固定的版本,只是在今天的案例里, *.csproj 调用的 <TargetFramework> 是 netcoreapp 2.2。 访问 Download .NET Core 3.1,下载并安装对应的 Dotnet SDK 3.1 安装包。 使用 Chocolatey 安装 dotnet Core 2.2 choco install dotnetcore --version=2.2.8 ¶安装 NuGet 依赖 请访问 Ways to install a NuGet Package 来选择适合你的方法,在本地开发环境安装 NuGet 到此位置,打包一个 NuGet 包的开发环境就已经准备好了。 ¶构建 NuGet Repository 的基本结构 因为这次我们要手搓一个 Visual Studio 的 NuGet 包,所以不妨将我们的 NuGet 仓库命名为 my_vs2019pro。注意,在 Chocolatey 中,我们的包名主要靠 *.nuspec 文件定义,和仓库名并不是必须一致的。 进入你的开发目录,我们先初始化我们的 NuGet 仓库: cd <my_dev_dir>mkdir my_vs2019procd my_vs2019progit init 然后按照以下的目录树结构,创建必要的空白文件: C:.├───tools│ ├───chocolateyinstall.ps1│ └───chocolateyuninstall.ps1├───.gitignore├───vs2019-myproject.csproj├───vs2019-myproject.nuspec└───vs └───myproject 稍微解释一下这个 vs/myproject 目录。我们有时候可能希望为自己的多个项目和分支创建不同的 NuGet 包,这时候就可以使用这个目录来存放不同的项目。然后,通过 *.nuspec 文件中的 <files> 标签来进行挂载。 当然,这个目录也不是必须的。 那么到此位置,我们的 NuGet 仓库的基本结构就已经创建好了。下面开始写最关键的 Spec 部分。 ¶配置你的 NuGet 仓库的基本信息 首先是整个 NuGet 仓库中最重要的 *.nuspec 文件。这个文件中包含了我们的 NuGet 包的基本信息,以及一些必要的配置。 <?xml version="1.0"?><package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>vs2019-myproject</id> <version>16.11.26</version> <title>Visual Studio 2019 Professional</title> <authors>Microsoft</authors> <owners>Kivinsae Fang - dev@kivinsae.com</owners> <licenseUrl>https://visualstudio.microsoft.com/license-terms/mlt031619/</licenseUrl> <projectUrl>https://visualstudio.microsoft.com/</projectUrl> <iconUrl>https://rawcdn.githack.com/jberezanski/ChocolateyPackages/21d70aedb9304792378a9f68d07d704cd0855827/icons/vs2019.png</iconUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description>Visual Studio 2019 Professional @ MyProject</description> <summary>Visual Studio 2019 Professional @ MyProject</summary> <releaseNotes></releaseNotes> <copyright>https://www.microsoft.com/en-us/legal/intellectualproperty/permissions</copyright> <tags>microsoft visual studio visualstudio vs vs16 2019 professional ide admin</tags> <dependencies> <dependency id="chocolatey-visualstudio.extension" version="1.10.2" /> <dependency id="KB3033929" version="1.0.5" /> <dependency id="KB2919355" version="1.0.20160915" /> <dependency id="KB2999226" version="1.0.20161201" /> <dependency id="dotnetfx" version="4.7.2" /> <dependency id="visualstudio-installer" version="2.0.2" /> </dependencies> </metadata> <files> <file src="tools\**" target="tools" /> <file src="vs\myproject\**" target="tools\vs" /> </files></package> 关于 *.nuspec 的写法和合法参数列表,本篇不打算展开,因为在 .nuspec reference 里已经写的非常棒了。 如果只是希望能打出一个正确的 Visual Studio 安装包,直接抄上面的配置,然后替换成读者自己的信息即可。在本文书写的节点,Visual Studio 2019 Professional 的最新版本为 16.11.26。 请注意,切勿修改 <dependencies> 部分的内容,这些是 Visual Studio 2019 Professional 安装所必须的依赖项。该列表本身可以在 Visual Studio 2019 Pro - Dependencies 中找到。 其次是 *.csproj 打包文件。需要强调说明的是,即便这个文件没有,也不影响我们使用 nuget 命令进行打包。这个项目文件本身只是提供了一个通过 dotnet 命令进行打包的选项而已,从而让我们在 CI/CD 的时候有更多的选择。 直接来看文件内容吧: <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp2.2</TargetFramework> <NuspecFile>./vs2019-myproject.nuspec</NuspecFile> <Version>16.11.26</Version> <NoWarn>NU5111</NoWarn> <NuspecProperties>version=$(Version)</NuspecProperties> <NoBuild>true</NoBuild> <NoDefaultExcludes>true</NoDefaultExcludes> <OutputPath>publish</OutputPath> </PropertyGroup></Project> 关于 NuGet *.csproj 文件的写法,微软同样提供了非常不错的文档说明:Create a NuGet package using MSBuild。我们只需要根据自己的实际需求来进行填写即可,也可以参考上方的案例。最重要的一条参数其实就是 <NuspecFile>,这个参数指定了我们的 *.nuspec 文件的位置。 在开始打包 NuPKG 之前,我们还有 2 件事情要做: 完成 chocolateyInstall.ps1 和 chocolateyUninstall.ps1 的编写。 在 vs\myproject 目录下放置 Visual Studio 2019 Professional 的离线安装包和 Mainfest 配置。 ¶如何编写安装和卸载脚本 正如上面所说,chocolateyInstall.ps1 和 chocolateyUninstall.ps1 这两个脚本虽然不是绝对必须的文件,但是 99.9% 的 Chocolatey NuGet 包都会在 tools 目录下放置这两个文件。从而便于用户在安装和卸载的时候能够一把梭了。 这两个文件的编写,说实话灵活性非常高,高到理论上你可以用写出几乎任何你能想象的安装流程,无论是复杂还是简单。当然,即便如此,依然有两个 Chocolatey 的命令行工具在这里扮演了至关重要的角色: Install-ChocolateyPackage Uninstall-ChocolateyPackage 上面的文档里其实都给出了这两个命令行的用法,以及一些最佳实践类的代码案例。我们拿来修改一下,添加一些自己的需求,就可以开始调试了。 以下是我们的 chocolateyInstall.ps1 文件: $ErrorActionPreference = 'Stop'; # stop on all errors$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)\vs"$fileLocation = Join-Path $toolsDir 'vs_Setup.exe'$responseFileLocation = Join-Path $toolsDir 'Response.json'$packageArgs = @{ packageName = $env:ChocolateyPackageName fileType = 'EXE' #only one of these: exe, msi, msu file = $fileLocation softwareName = 'vs2019*' checksum = 'AC87102F794643547F61B1EFD8D8A9F2E201872D8EEB308FBDD84299E5DCA962' checksumType = 'sha256' #default is md5, can also be sha1, sha256 or sha512 checksum64 = 'AC87102F794643547F61B1EFD8D8A9F2E201872D8EEB308FBDD84299E5DCA962' checksumType64= 'sha256' #default is checksumType # MSI silentArgs = "--in $responseFileLocation --quiet --force --wait --norestart" validExitCodes= @(0, 3010, 1641)}Install-ChocolateyInstallPackage @packageArgs 在安装脚本中,我们实际上只是构造了一个 $packageArgs 结构体,然后将其传给 Install-ChocolateyInstallPackage 命令行工具而已。在这个结构体里,我们可以通过 silentArgs 往执行安装过程的二进制文件传入需要的参数。例如,上面的脚本里,我们甚至可以添加一个函数,通过读取服务器上的 vault 配置,从 Vault Enterprise 上动态获取 Visual Studio 2019 Professional 的 ProductKey,然后以 --productKey 的方式,通过 silentArgs 传递给安装程序 vs_Setup,从而在完成安装的同时,做好 License 的注册。 至于 chocolateyUninstall.ps1,其实就更简单了,我们只需要调用 Uninstall-ChocolateyPackage 命令行工具,然后传入一个 $packageArgs 结构体即可。具体还是需要根据你的实际需求来进行,尤其需要注意两点: 检测当前包是否真的被正确安装,以避免卸载报错。 传入正确的 $packageArgs。 下面为一个简单的例子: $ErrorActionPreference = 'Stop'; # stop on all errors$vsinstallLocation = '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional"'$packageArgs = @{ packageName = $env:ChocolateyPackageName softwareName = 'vs2019*' fileType = 'EXE' #only one of these: MSI or EXE (ignore MSU for now) # MSI silentArgs = "uninstall --installPath $vsinstallLocation --quiet --force --wait --norestart" validExitCodes= @(0, 3010, 1605, 1614, 1641) # https://msdn.microsoft.com/en-us/library/aa376931(v=vs.85).aspx}[array]$key = Get-UninstallRegistryKey -SoftwareName $packageArgs['softwareName']if ($key.Count -eq 1) { $key | % { $packageArgs['file'] = "$($_.UninstallString)" if ($packageArgs['fileType'] -eq 'MSI') { $packageArgs['silentArgs'] = "$($_.PSChildName) $($packageArgs['silentArgs'])" $packageArgs['file'] = '' } Uninstall-ChocolateyPackage @packageArgs }} elseif ($key.Count -eq 0) { Write-Warning "$packageName has already been uninstalled by other means."} elseif ($key.Count -gt 1) { Write-Warning "$($key.Count) matches found!" Write-Warning "To prevent accidental data loss, no programs will be uninstalled." Write-Warning "Please alert package maintainer the following keys were matched:" $key | % {Write-Warning "- $($_.DisplayName)"}} 类似的案例可以在 GitHub 上大量公开的 Chocolatey NuGet 库中找到,例如 jberezanski/ChocolateyPackages 这个仓库,作者维护了几乎所有的 Chocolatey Visual Studio 的 NuGet 包。 而本篇中的这两个文件,大家也可以在以下的 GitHub 仓库中找到,都是 Chocolatey 官方的 Templates: tests/packages/msi.template/templates/tools/chocolateyinstall.ps1 tests/packages/msi.template/templates/tools/chocolateyuninstall.ps1 ¶Visual Studio 离线安装包和 Mainfest 配置 接下来已经到了本次 NuGet 包构建的最后一步了,那就是准备好 Visual Studio 2019 的最小化离线安装包和 Mainfest 配置文件。之所以说是 最小化,是因为我们在获取 Visual Studio 2019 的离线安装包时,默认会下载所有的组件,而我们需要的仅仅是 vs_Setup.exe 和 Mainfest JSON 配置而已。 下面是操作的步骤,首先在你的临时目录中下载 Visual Studio 2019 Professional 的在线安装包 vs_Professional.exe: Download VS2019Pro 16.11.26 然后参考微软官方的 Control updates to network-based Visual Studio deployments,利用 vs_Professional.exe 生成本地的离线安装目录。 vs_Professional.exe --layout C:\vsoffline --lang en-US 之后,你会在 C:\vsoffline 看到有大量的文件被下载和生成,而我们需要的 vs_Setup.exe 以及 Mainfest 配置文件,都会在一开始就被下载完毕,所需文件列表为: Catalog.jsonChannelManifest.jsonLayout.jsonResponse.jsonvs_installer.version.jsonvs_setup.exe 这些文件一旦下载完毕,我们就可以直接把 vs_Professional.exe 的离线包生成进程干掉了。 之后将这些文件移动到 vs\myproject 目录下,通过 *.nuspec 的 <file> 挂载,在安装的时候就可以直接被刚才写好的 chocolateyinstall.ps1 调用了。 需要额外注意的是,我们可以在 Response.json 中定义我们希望安装的 Visual Studio 组件,这对于自动化和自定义安装非常有帮助。 ¶打包 NuPKG 并上传到 NuGet 源 正如上面所说,我们有两种打包 NuPKG 的方法: 使用 nuget 命令打包: nuget pack .\vs2019-myproject.nuspec 使用 dotnet 命令打包: dotnet pack .\vs2019-myproject.csproj 之后如果没有报错和意外,我们应该可以在 my_vs2019pro 目录下看到一个打包好的 vs2019-myproject<Verison>.nupkg 文件。这个文件便是之后需要上传到 NuGet 源的唯一文件。 打包的输出大致是这个样子: Microsoft (R) Build Engine version 16.7.3+2f374e28e for .NETCopyright (C) Microsoft Corporation. All rights reserved. Determining projects to restore... All projects are up-to-date for restore....Logs... Successfully created package '<YOUR_DEV_DIR>\my_vs2019pro\publish\vs2019-myproject.16.11.26.nupkg'. 同样的,我们有两种上传 NuPKG 的方法。 使用 nuget 命令上传: nuget setApiKey <YOUR_NUGET_APIKEY>nuget push vs2019-myproject<Verison>.nupkg -Source <YOUR_NUGET_SERVER> 使用 dotnet 命令上传: dotnet nuget push --skip-duplicate --api-key <YOUR_NUGET_APIKEY> -s <YOUR_NUGET_SERVER> ./publish/vs2019-myproject*.nupkg 关于上传的详细内容,可以参考微软官方的 Publish NuGet packages ¶最后祝大家身体健康,玩的开心

2023/5/27
articleCard.readMore

关于 初码先生 某推中典型诡辩论部分的拆解

最近简中技术圈发生了一件不小的事情:知名技术分享者左耳朵耗子因心梗不幸去世。 说实话,我从未和耗子哥有任何交集,也未曾受益于任何他过去的分享和文章。但是,至少从绝大多数缅怀他的人口中,可以多少感受到他的人格魅力和技术能力。这是一个值得尊敬的人。 然而今天看到一位推主 初码@chumacn 发了一条推文,为了防止他之后修改,直接复制内容如下: TweetID:1658453862561501184 虽然死者为大,但是这篇回忆文章,再次印证了我的观点,陈皓的范本,是对一线程序员无比巨大的毒害,一点不为过。 推主我对你没有任何恶意,从私人的角度看,这篇回忆文章里,有上下级的交流和学习的过程,有对一些重要事情的回忆和佐证,更有作为朋友的相互情谊,文字间温情流露,精彩人生。 但是,既然发在了公众平台,那我就真的有必要说几句了,站在程序员、技术人员、产品人员、大公司平台、大厂资本等公共利益角度,我认为你的上级陈皓,在一些大是大非上,他并没有帮助到你。反而给你做出了非常错误的示范,而你的回忆文章恰好很清晰的证明了这些问题。并且你到现在为止都没有意识到这些行为非常越界,非常错误。 我想帮助你复盘一下: 首先问一个最核心的问题,我反对陈皓、冯大辉等人,反对的到底是什么? 我反对的是: 他们作为技术KOL,自己本身不学无术,把大量精力和时间花在了社交媒体上,花在了花样繁多的新技术、新产品、新想法上。巨大的信息流涌入他们,让他们从窄而深的技术通道转向了宽但散的纷杂路口。不以为耻反以为荣,并在互联网上布道这种劳逸结合、工学一体的玩法。 这种路径犯法吗,是不被允许的吗?当然不是!但是这种是自由职业者、自媒体人士、财务自由者的娱乐技术玩法,这种玩法,不仅不适合初级程序员,更严重违背入职工作者的职业道德。 当然了,冯大辉老师的不学无术,这个已经反复证明了,无需多说,他不认错的话,水货CTO的耻辱帽子一辈子都摘不掉。 但陈皓的不学无术,则是一种更常见的、更隐蔽的,但是伤害更大的不学无术。这种不学无术总结起来就是: 没有工作上的边界意识,在自己所拿薪资的岗位上,没有真正窄向、深度的钻研自己权责范围内的事情,而是用自己的阅历、见识、想法,跨越到远超自己能力上限的地方,去指点迷津、夸夸其谈,这是一种非常隐晦的、隐藏的,但大面积存在的严重职业道德缺失的行为,不仅在程序员领域,也在产品、运营领域出现。 如果真正的想实现一个想法、平台,真正的技术职业工作者,无非两种情况,1是遵循自己的Leader的想法和架构,老老实实的写代码、做模块。2个就是自己担负着研发的重担,了解并深度认真的去评估自己的技术水平,评估自己能否基于想法去实现完整的闭环,并模拟这样的过程,做好完整的计划,带领团队逐级拆解任务,做好管理,去实现这个过程。 而陈皓,就是非常典型的,不愿意做1,所以在面试和找工作的事情,都奔着2的岗位去找、去靠,但是在2的岗位上,不具备2的能力,但是既不愿意直面现实,也不和团队说真话,做真正的评估拆解,绑着整个团队一起走向必然的失败。 这段过程,就是陈皓在阿里云的典型失败过程,也是为啥闹纠纷的主管因素。 要知道,你作为一个P7、P8、P9的程序员,你之所以能拿着3万-7万的基本工资,还能闲暇之余上网,动辄就是什么流行、我得学什么新东西、我要捣鼓什么东西,我要拿公司平台去实现我什么什么想法。 陈皓同学,你得知道,你之所以可以异想天开,那是因为你在阿里,吃着时代的红利,你的试错,是阿里在给你承担,而不是你在承担。这是互联网红利下,社会给的红利,大厂给的功利。建立在千千万万的淘宝商家血汗上的红利。公司付钱给你,不是让你去做自己的舞台,而是让你当小蚂蚁,帮助公司创造价值,一个程序员,如果连这点最基本的自知都没有,路不可能走的长。 所以对于陈皓这些违背职业道德,远超能力范围的异想天开,推主竟然觉得陈皓某些想法很感人。这也就是为什么我觉得有必要站出来说几句,真的是被带远了,带偏了。 另外怕推主有疑惑,我再补充一些信息。 因为推主也提到了,陈皓让他刷题,对他的成长帮助非常大,这是当然了,但是你得搞明白这个里面的因果关系。陈皓让你刷题,做对了的地方时:刚好他意识到了刷题对程序员是有好处的,做错了的地方是,刷题本来就是普世价值,普世价值应该发生在学习阶段、入职前,而不是占用工作时间(不用杠,忙的时候,想要坚持刷题,必然占用工作时间),而且按照陈皓、冯大辉这样的技术KOL的逻辑,上班时间,如果事情做完了,也可以去刷题。这已经是何等扭曲的职场观,首先我国的大厂里的996高薪岗位,如果HR体系管理得当,就不可能出现闲暇的上班时间,其次就算做完了,也应当在Leader的允许下,在同事的知情下去刷题,这是最基本的职业道德边界感。但是陈皓等传递给一线程序员价值观就是,我心随我欲,技术感悟的风飘到哪,我就可以做到哪,我认为这简直太卑劣了。 那么程序员是否有标准路径呢,当然是有一些的。 在学校的时候,深度学习好数据结构与算法、编译原理、数据库原理、操作系统原理、计算机网络等基础学科。 在工作的时候,老老实实的在自己公司、在代领人的框架内学习和钻研使用方法,把工作上的细节,包括业务代码设计、算法优化、代码review、自测等等等等各项工作做好。 在接到研发需求的时候,优先寻找并顺着成熟轮子开展工作,如果在得到上级和公司允许并做好充分预算和评估的情况下, 可以尝试自行或者激进的研发。 以上标准路径,其实在10多年前的互联网上,默默的布道者非常多,大家在CSDN、ITEye、博客园等地方,在某个具体的技术上、组件上,发表连载教程,而不是陈皓、冯大辉之流,以技术之名谈天说地,获得时代红利后,把自己的成功也试图强加到普通程序员头上。 在我看来,他们简直是技术圈最坏的榜样,最糟糕的示范,我不断地发声,就是希望技术回归技术,程序员,能得到真正的指导,而不是这样站在时代的风口里左右摇晃。 看完整条 Tweet 的第一反应是,2023 年怎么还会有机会看到这么经典的诡辩案例。不妨就此来盘一盘,这篇文章让人浑身不舒服的地方到底在哪里。 在开始之前,我们还是花一分钟简单复习一下诡辩论,也就是 Sophism 里最最经典的一些方法: 滑坡论证,也就是所谓的蝴蝶效应。这条也是危害最大的一条 从众式论证 言论者个人行为推断 模糊化推论,也就是强行建立无因果要素之间的关系。 不恰当类比 特殊推论的一般推广 错误因果关系 下面直接一段段开始盘。在 我想帮助你复盘一下 前面的那三段,姑且作为推主本人的主观观点的发表,虽然也有诡辩嫌疑,但暂且跳过。 首先是第一段: 他们作为技术KOL,自己本身不学无术,把大量精力和时间花在了社交媒体上,花在了花样繁多的新技术、新产品、新想法上。巨大的信息流涌入他们,让他们从窄而深的技术通道转向了宽但散的纷杂路口。不以为耻反以为荣,并在互联网上布道这种劳逸结合、工学一体的玩法。 这段直接进行了滑坡论证,直接把这段的口风推到一个不可控的方向上去了。具体来说,因为陈皓和冯大辉在社交媒体上花了大量精力和时间,所以他们从窄而深的技术通道转向了宽但散的纷杂路口。这里的因果关系是完全没有证据的,但是作者却把这个因果关系当成了铁板钉钉的事实来推论。 同时还说到 不以为耻反以为荣,并在互联网上布道这种劳逸结合、工学一体的玩法。这句话首先对于推主的论点没有任何帮助,其次也是一个诡辩,因为这句话的前提是 他们不以为耻,但是这个前提是作者自己加上去的,等于说通过自己设定一个被推测对象的主观态度,来进行立靶子批判。 接下来是第二段: 这种路径犯法吗,是不被允许的吗?当然不是!但是这种是自由职业者、自媒体人士、财务自由者的娱乐技术玩法,这种玩法,不仅不适合初级程序员,更严重违背入职工作者的职业道德。 这里又用了几个诡辩技巧。首先是 这种路径犯法吗,是不被允许的吗?当然不是!,这里的诡辩在于,作者把自己的主观论点 这种玩法,不仅不适合初级程序员,更严重违背入职工作者的职业道德 作为了一个事实。在计算机这个行业,有无数的岗位需要有不同特质的程序员进行合作和贡献,海外几家著名的企业无一例外,数十年如一日强调 Diversity 在一个企业的发展过程中的重要性。在这样的背景下,自由职业者、自媒体人士、财务自由者的娱乐技术玩法,和计算机从业者的职业道德的冲突之间的联系,作者并没有给出任何的可信的证据来进行说明。 接下来是第三,四段,因为是对冯大辉和陈皓的纯粹人身批评,毫无内容,所以我就不多说了。 再看第五段: 没有工作上的边界意识,在自己所拿薪资的岗位上,没有真正窄向、深度的钻研自己权责范围内的事情,而是用自己的阅历、见识、想法,跨越到远超自己能力上限的地方,去指点迷津、夸夸其谈,这是一种非常隐晦的、隐藏的,但大面积存在的严重职业道德缺失的行为,不仅在程序员领域,也在产品、运营领域出现。 这段诡辩术用得比较隐晦,很多人可能并不容易第一眼看出来其中得问题。这里的诡辩在于,作者把 没有工作上的边界意识 和 没有真正窄向、深度的钻研自己权责范围内的事情 作为了一个事实。然而糟糕的是,作者并没有给出任何的证据来证明这两个事实,而是直接把这两个事实作为了一个前提,然后在这个前提的基础上进行了推论。作为一个没有给当事人进行过长期上下游代码审核,没有给当事人进行过深入技术交流,乃至于没有进行过大量学术和技术方面得公开辩论的人,我们无法认为其拥有足够的 Credit 在这两个点上进行事实判断。 并且,作者还进一步将这两点和 严重职业道德缺失的行为 进行了联系,这里的联系也是没有任何的证据的。因为此处的 职业道德 的定义,本身就很大程度依赖度第二段中通过诡辩构建出来的定义,而基于诡辩得到的定义的二次诡辩引申,其可信度是非常低的。 然后是第六、七、八段: 如果真正的想实现一个想法、平台,真正的技术职业工作者,无非两种情况,1是遵循自己的Leader的想法和架构,老老实实的写代码、做模块。2个就是自己担负着研发的重担,了解并深度认真的去评估自己的技术水平,评估自己能否基于想法去实现完整的闭环,并模拟这样的过程,做好完整的计划,带领团队逐级拆解任务,做好管理,去实现这个过程。 而陈皓,就是非常典型的,不愿意做1,所以在面试和找工作的事情,都奔着2的岗位去找、去靠,但是在2的岗位上,不具备2的能力,但是既不愿意直面现实,也不和团队说真话,做真正的评估拆解,绑着整个团队一起走向必然的失败。 这段过程,就是陈皓在阿里云的典型失败过程,也是为啥闹纠纷的主管因素。 这里的诡辩术就用得比较明目张胆了。首先,作者把 真正的技术职业工作者 通过一定程度的从众式论证 + 言论者个人行为推断的方法,进行了片面化定义。其次,作者把 真正的技术职业工作者 和 无非两种情况 进行了联系,这里的联系也是没有任何的证据的。 到底什么是真正的技术工作者,没有深刻和详实的讨论和证明,是几乎不可能用所谓的两种情况来进行概括的。我们往往只能用一些普世的、共同的人类良好品质,在不同的行业和领域的从业者身上进行对照和印证。比如,对于一个医生来说,我们可以用 对病人负责 来进行判断,对于一个教师来说,我们可以用 对学生负责 来进行判断,对于一个程序员来说,我们可以用 对用户负责 来进行判断。但是,这些判断的标准,都是基于对于 负责 这个词的共同理解的基础上的。而这个共同理解,是需要结合时代背景,通过大量的观察事实,来进行共同的认知和建立的。绝不可能通过一篇文章,或者一段文字,或者一个人的个人行为,来进行建立的。 第七段中其实用的诡辩术和第五段中用的诡辩术是一样的,甚至有点审美疲劳。 最后来看看九、十、十一段: 要知道,你作为一个P7、P8、P9的程序员,你之所以能拿着3万-7万的基本工资,还能闲暇之余上网,动辄就是什么流行、我得学什么新东西、我要捣鼓什么东西,我要拿公司平台去实现我什么什么想法。 陈皓同学,你得知道,你之所以可以异想天开,那是因为你在阿里,吃着时代的红利,你的试错,是阿里在给你承担,而不是你在承担。这是互联网红利下,社会给的红利,大厂给的功利。建立在千千万万的淘宝商家血汗上的红利。公司付钱给你,不是让你去做自己的舞台,而是让你当小蚂蚁,帮助公司创造价值,一个程序员,如果连这点最基本的自知都没有,路不可能走的长。 所以对于陈皓这些违背职业道德,远超能力范围的异想天开,推主竟然觉得陈皓某些想法很感人。这也就是为什么我觉得有必要站出来说几句,真的是被带远了,带偏了。 首先是陈皓本人拿着的高收入。从现代企业和被雇佣者的关系而言,企业开出来的薪酬是基于企业在雇佣和面试过程中的人才价值判断。被雇佣者本人对于这个定价本身并不存在道德和判断的责任。因此这里事实上再一次使用了模糊化推论,强行建立了一个 高收入 和 对高收入的定价责任 的关系,然后进一步建立了 鼓捣新技术 和 企业责任 的矛盾。 具体过程发生在第十段,直接通过上一段模糊推论的关系,进一步进行了滑坡推论和错误因果,将陈皓本人的合法劳务所得的数额,首先建立到了 阿里 这个企业的身上,然后又建立到了 千千万万的淘宝商家血汗上,最后又建立到了 公司付钱给你,不是让你去做自己的舞台,而是让你当小蚂蚁,帮助公司创造价值这条罪责上。这里只能说诡辩的技巧用得还不够熟练,一般来说在进行诡辩的时候我们要尽可能避免在较短的篇幅使用超过三种以上的诡辩技巧。 这里推主的本意较为明显,就是希望将陈皓和对普通劳动者的压迫进行关联,从而在道德层面构建起谴责和声讨的合理性,可惜技巧不够纯熟,反而使这段成为了整条推中最糟糕的表演。 然后就是最后一段,在通过上述若干条的诡辩,构建起陈浩本身违背职业道德这条没法站得住脚跟的推论后,再次进行了言论者个人行为推断,否定了所有对陈皓持支持和认可的人的道德和判断能力,最后又通过 被带远了,带偏了 这样的词语,对他们进行了一次全面的贬低和否定。 补充信息部分来来回回还是那些诡辩套路,就不赘述分析了。 最后想告诉大家,日常阅读的文章、推文,听到的发言、通告、业内的大量论文,以及自己日常脱口而出的大量发言,本身都是诡辩的聚合体。在发表内容中适当存在诡辩,本身也很常见,不值得大书特书,但是如同这条推文一般,通篇拥挤着大量雷同的诡辩技巧,确实也较为罕见。 以上,祝大家身体健康。

2023/5/18
articleCard.readMore

关于说话:不要叠加态,不要反问句

今天在网上冲浪的时候,看到有人转发了关于河森堡的一条微博,聊到了现在简中内容量子化的问题。 也就是出于一些众所周知、令人发笑的原因,目前在中文网络上发表评论最安全的方式其实就是让自己的内容进入叠加态。所谓的正着理解没什么问题,反着理解也充满了讽刺的趣味,甚至中性地理解也有一丝智慧的奇怪感觉。 我并不打算过多去描述这种现象在文化上带来的长远影响,但是我想说的是,这种做法在日常的工作中,也是极度糟糕的。甚至很多人应该有所感觉,在过去的几年中,不好好说人话这个现象,已经先行一步开始侵蚀企业的公开沟通环境了。 而这样的侵蚀,也确实在过去很多年给我和我的同事们(至少是我欣赏的那批)带来了非常糟糕的工作体验。 不知大家有没有过这样的经历。在企业招聘的过城中,经常会招来一些技术水平并不是非常匹配其职级或报酬的同事,而这类同事中往往分为两类: 在意识到自己的技能树欠缺以后,在尽可能不犯错,不发表误导建议的情况下,玩命补课。 为了防止别人识别出自己的水分,打肿脸冲胖子,依赖出色的口舌把老板蒙在鼓里,最后捂不住了赶紧跑路。 事实上,我觉得企业给候选人开虚高这件事情并无问题,定级本身只是对寥寥几次的面试的一个总结反馈。无所谓对错,招聘部门和技术面试官也都不是火眼金睛。 根据观察,第一类的同事往往会在一段时间的积累后,爆发出远超公司所开报酬的生产力和效能。这种情况无疑对企业来说是非常划算的交易。 但是糟糕的是第二类人。这类人最显著的特点有两个: 绝对不承认自己不懂。 将业绩的包装视作比真实结果更重要的点,并擅长叠加态说话法。 这两点的持有者能在职场大行其道并不是没有道理,大家也并非懵懂刚毕业的学生,但是个人的观察来看,这种现象的根因是整个社会舆论大环境对企业的一种污染。 一个怕说错话的舆论环境,一个充斥着火药味并习惯于拉旗圈地、标签异己的简中社交圈,必然会让这样的说话习惯渗透进入企业、学校、科研机构。 《左传·宣公二年》有言:人谁无过,过而能改,善莫大焉。这本是人人皆知的道理,但是现在整个舆论场已经越来越不能容忍有人发出和自己想法不同的声音。无论是客观的事实错误,还是观点上的意见分歧,都可以招致狂风骤雨一般的声讨和辱骂。 以此,进入了职场,暴露自身错误的代价也不知从什么时候开始,越来越巨大。但是通过欺骗,掩盖自己的错误的收益,也日益诱人。在模糊的中间地带,说话或者报告的时候,尽可能留足后续的扯皮空间,成为了所有职场人稔熟于心的必修课。 但是不幸的是,这些东西在计算机科学上行不通,你唯一可以期待的扯皮空间是高能的宇宙射线把你内存里的某一位打反转了,然而我们用的都是 ECC 内存,反弹你的反转。 尽管我不理解其他行业的运作方式,但是每每看到有人在计算机相关的事件上尝试用叠加态说话法的时候,一种强烈的滑稽感是难以避免的。 正如磁盘并不能因为模棱两可的描述,让 MBR 分区可以给你识别出 6 TB 的空间;数据库不会因随便乱糊的索引而减少慢查询发生的次数;某些知识不会因为说两句模棱两可的话变成可以己用的东西;靠着喊赋能、对齐、抓手,除了可以让 PPT 和说话者看上去更像是一个语言能力存在障碍的人之外,什么都无法实现。 计算机行业是一个逃避可耻且无效的领域。承认自己在某个领域是个菜鸟,并且多看一些相关的文档和论文,比说任何叠加态的浑话都有建设性太多太多。这也是为什么我喜欢它的原因。 除了上面说的这些以外,其实我觉得很多人也容易混淆 坦诚/直率 和 说话冲 的边界。这里有一个很简单的例子: “我认为对于 Ansible 执行结果的每一个点都进行 Molecule 测试是一个不划算的 trade-off” “哪有那么多人力和开发周期给每一个 Ansible 执行结果做 Molecule 验证啊?” 两句话的内容其实差不多,但是很多时候我们更常听到的是反问的那句。而反问句往往还会伴随着上面的叠加态语言发生。往往会是: 一大段滚咕噜话 + 几句反问句 没反应过来的,甚至会觉得说话者很直率。而事实上,说话者同时犯了两个破坏坦诚交谈原则的错误。而这种说话方式的长期积累的结果,就是企业跨部门合作关系的崩解。 从我这些年的习惯来说,做到坦诚说话是一件并不困难的事情,并没有什么特别的技巧。非要说的话,做好两点就够: 坦诚承认对某一点不懂,但是愿意去花时间看文档。 一律不准用反问句。 不过我自己也经常犯这类错误,自勉之。希望未来更少给人带来迷惑感和不适感。 最后,祝大家身体健康,工作顺利。

2023/4/14
articleCard.readMore

杂谈:聊聊高尔夫球杆

最近趁着招行卡送了很多练习场,就尝试着入坑玩了玩高尔夫。竟意外地发现这个被妖魔化已久的运动,还是非常有意思的。 尤其是球杆的分类,让人感觉非常有器具的美。我想,单从这点来说,理工学生应该挺容易对这个运动产生兴趣。因此花了一些时间,学习了一下球杆的分类,以及相关的一些基本的知识。这里就简单记录一下顺便写写关于这项运动祛魅的一些想法。 但是在开始之前,我必须承认目前的自己只能说新手中的新手,连入门都算不上。高尔夫本身作为对精确度和器具要求极高的运动,说实话没有上万次击球的练习的话,别说下场地上果岭了,连做到稳定大力击球都不太可能。这点上和台球倒是有几分相像。 另一条想吐槽的还是国内的场地问题。诚如上海这样的城市,市中心竟也仅有寥寥数家练习场,主要还集中于古北新区这样的传统外国人聚居区。由此可见高尔夫的发展,在国内处于一个非常尴尬的状态。说好听点是还有巨大的市场可以挖掘,说难听点就是因为种种原因,压根就是畸形发展的。 说起为啥觉得这东西好玩,其实就是原始的暴力。只要你打出第一下暴力铁杆挤压击球,这种泄压感是忘不掉的。言归正传,我们来聊聊球杆。 ¶球杆分类 高尔夫球杆粗略来说其实就分为五大类: 木杆 (Wood) 混合杆 (Hybrid) 铁杆 (Iron) 挖起杆 (Wedge) 推杆 (Putter) 边看图边聊会更加直观: ¶木杆 Wood 一般来说,木杆分为两类,分别是开球木 Driver、球道木 Fairway Wood。 其中,Driver 往往特指 1 号木。这个木杆的特点是头部比较大,重量比较轻,主要用于开球。说实话第一次打 1 号木的时候,真的有被其重量给惊讶到。巨大的头部居然是如此的轻,仿佛空气般的存在。但是抽打的时候声音的暴力程度却远超铁杆。 而 Fairway Wood 则是 3-5 号木,主要用于球道上的开球。头部小了非常多,随之而来的也是使用上难度的陡增。有说法是,三号木是最难打好的一根杆,很多业余玩家可能从未打出过一次完美的三号木。 不过主流球杆生产商通常提供从 3 号木杆到 9 号木杆的不同型号。4 号木有时会代替 3 号木(主要调整距离),而5号木杆则是为那些喜欢在果岭上使用球道木杆而不是长铁杆的球手准备的。 更高序号的木杆往往为女士或老年人这样力量相对更小的球手准备。 一般来说,常规的业余玩家的全套杆里,也就是 3 木 9 铁套里面,木杆主要就是 #1,#3,#5 这三根。 ¶混合杆 Hybrid 混合杆主要是 1990s 末期开始出现的,算是很经典的场地杆。怎么说呢,如果完全是练习场玩家,真没啥必要买 Hydrid。 混合杆借鉴了铁杆和木杆的特点,具有两者都具备的有利特征。换句话说,结合了铁杆挥杆力学和木杆更宽容的打击域。 因为长铁杆( 1 - 4 号)即使配备了现代化的杆头,也很难用。尤其是在一些深草坪或者树下这样复杂的地形,对于击球点和长球杆都有严格要求的情况下,挺多 Pro 的解决方法是用混合杆取代1-4号铁杆。 这么讲其实也印证了 Hydrid 算是高手球杆这件事。 ¶铁杆 Iron 铁杆是高尔夫球杆中最核心的一组球杆。一般来说,铁杆分为 1-9 号,其中 1-4 号是长铁杆,5-9 号是短铁杆。 其中,7 号铁又是卖的最好的入门级铁杆。因为 7 号铁杆的特点是比较容易击中,而且击球点也比较宽松,所以也是最适合新手的一根铁杆。还有一个野鸡理由是,台湾教练们喜欢用 7 号铁杆来教新手,所以 7 号铁杆也就成了新手的标配。 Iron123456789 Loft[nb 1]14°17°20°23°26°29°33°37°41° Lie[nb 2]59.5°60°60.5°61°61.5°62°62.5°63°64° Length (in)[nb 3]40.039.539.038.538.037.537.036.536.0 Length (cm)10110099.197.896.595.394.092.791.4 上表里的 Loft 指的是球杆的挡板角度,也就是球杆的球面和垂直面之间的夹角。通常来说,Loft 越大,球杆的弧度就越高,球飞行的高度就会更高。可以看到编号越大,击球面弧度越高,球的飞行的高度最高。 Lie 指的是球杆的摆角,也就是球杆与地面之间的夹角。通常来说,铁杆球杆的 Lie 角度可以使得击球面和地面接触的位置找平并稳定。如果摆角不对或者不稳定,球杆与地面之间的夹角会改变,会导致球的方向和高度发生偏差。 另外就是随着摆角的增加,球杆的长度相应的变短。简单的勾股定理。 ¶挖起杆 Wedge 又是一类下场才会用的杆。 应该说,挖起杆是铁杆 Iron 的一个子集,专门设计用于特殊救球的情况。因为挖起杆的球面角度最大,杆身最短,重量最重。这些特性可以让球员准确地打出短距离高球,从而上果岭或从柔软的地面把球救回来。 因为挖起杆带有改良后的底板,可以把嵌入或甚至被掩埋的球直接从地里打出来。楔子有多种配置,并一般分为四类:攻击杆 PW、沙坑杆 SW、间隙/进攻杆 GW/AW 和高球杆 LW。 常见的半套杆组里,往往带了一根 P 杆或者 S 杆。 ¶推杆 很多电视剧和电影里经常出现什么反派 Boss 或者大佬在巨大办公室的模拟球道上练高尔夫,他们使用的球杆就是 Putter。 推杆杆是通过轻轻挥杆,从较短的距离将球滚入洞中的球杆。 它非常平坦,低轮廓,有一个低镂角的球头,并具有一些仅限推杆使用的特性: 弯曲杆柄 非圆形握把 定位导杆 推杆一般用于距离杆孔非常近的地方,基本都在果岭上使用,不过少部分球场在果岭边缘和草丛也有适合推杆的点。 如果说下场打的时候有哪根杆是绝对必不可少的话,那只能是推杆。所有市售的半套杆和套杆,一定带了一根推杆。 ¶新手怎么玩 从自己的体验和查阅信息来看,新手期下场地的必要性可能没那么高,除非钱多到烧不完或者有教练无限耐心带你打,否则只会让你怀疑自己不听话的四肢。 在练习场里把开球木、7号铁练好,有条件的话买个地毯练练推杆,就可以了。这样的话,一组入门级半套杆完全够用,而且也不会太贵。一般来说,这样的套杆价格在 1000-3000 之间,足够陪着自己练上万球了。等技术精进之后,再考虑买一套更好的杆,以及开始下场地的练习。 再说了,球杆这玩意儿,有一点一份价格一分货,二毛价格两分货,三块价格三分货的味道。更何况高尔夫入门周期非常长,普通人除非天天有空去练习场练习,否则很难在短时间内精进到可以下场地练习的程度。所以,初学的话更推荐弄一套不那么贵的半套杆,先把五类球杆的特性熟悉了再说。 我也不打算推具体品牌。低价区间国产 PGM 的套杆也不能不能玩。中间档 Taylermade,Callaway,Mizuno 的套杆在 5000 ~ 10000 这个区间,也绝对够普通玩家玩到死了。 如果真的对更上级的手感有所追求,类似于 HONMA, Titleist 这样比较顶的套杆,总归也能满足。至于定制化,那就是专业选手的领域了,不是普通人能玩的。 然后最后一点就是怎么学,高尔夫严格来说还是需要教练的运动,但是如果自己有一定的运动和人体结构知识基础,也可以自己学。这里强烈推荐油管上一些台湾和日本教练出的视频,更加适合东亚人的体态,并且讲得远比国内的教练清楚易懂。 或者换个说法,不是说国内教练水平特别差,而是国内网站上压根就没多少人在做新手入门这块的视频,基数都这么小了,还能期待有什么出色的教学视频呢? 总的来说,从上海目前的物价和消费来说,高尔夫的入门门槛还是非常低的。比什么健身卡、网球场、足球场的练习成本要低多了,而且最重要的时候一个人就能玩。宅宅时代,找人才是成本最高的事情。 希望大家天天运动,健康生活。

2023/4/2
articleCard.readMore

TCC 数据库:开发者的噩梦,攻击者的狂欢

苹果,我完全无法理解你 ¶前言 最近几周,我花了不少时间在 macOS 的自动化流程以及 MDM (Mobile Device Management) 的开发上。 事实上我一直以来都难以理解为什么有那么多企业选择使用 macOS 作为员工的工作电脑。 姑且可以认为多数的科技公司从业者都具备一些必要的计算机常识和命令行技能,但是 macOS 本身其实是基于 XNU 内核构建的 Darwin ,而 Darwin 本身又包含了大量来自 BSD 的特性,这些特性意味着用户在深入使用的时候,往往不得不面对一些在 BSD 和 Linux 中同名同姓却完全不同的命令,例如 mount。 而更加好死不死的是,Apple 还额外贴心地从 macOS 10.11 开始加入了 SIP (System Integrity Protection),也就是著名的苹果是你的爸爸组件。 这意味着无论你是卑微的个人设备还是财大气粗的企业采购设备,都要面临你的 root 不是真正的 root 这样的糟糕体验。而更加搞笑的是,有时候 SIP 不但没有真正保护用户安全,还接二连三的爆出涉及 TCC.db 的各种权限漏洞,给黑客大开便捷之门。有时候真觉得 macOS 上开发工作流太噩梦了。 本篇我就稍微聊一聊 TCC.db 这个数据库。 ¶TCC - Transparency, Consent, and Control TCC.db 是 macOS 10.9 之后引入的一个数据库,用于记录用户对于各种系统服务的授权情况。系统级 TCC.db 的完整路径是 /Library/Application Support/com.apple.TCC/TCC.db。而对于每一个单独的用户,其实还有一个 TCC.db,位于 $HOME/Library/Application Support/com.apple.TCC/TCC.db。 这个数据库的结构非常简单,只有 6 个表: access active_policy expired access_overrides admin policies 事实上这 6 个表里,在默认情况下有且只有 access 这一个表是有有效数据的。其他的表利用 sqlite3 查看的话可以发现都是空表,而 admin 表也仅有一行: sqlite3 "./Library/Application Support/com.apple.TCC/TCC.db" "select * from access_overrides;"sqlite3 "./Library/Application Support/com.apple.TCC/TCC.db" "select * from active_policy;"sqlite3 "./Library/Application Support/com.apple.TCC/TCC.db" "select * from expired;"sqlite3 "./Library/Application Support/com.apple.TCC/TCC.db" "select * from policies;"sqlite3 "./Library/Application Support/com.apple.TCC/TCC.db" "select * from admin;"version|20 PS: 无端推测,iOS 的权限实现大概率也是基于类似的机制。 然而这一切对开发者友善的前提是,macOS 需要在 Darwin 的命令行支持或系统开发接口中,也复刻一套这样的授权机制。然而事实上是,macOS 没有做到这一点,也似乎并不打算做好这些支持。 稍微对 SIP 有所接触的人应该会很容易察觉到,苹果对于用户可以在自己的机器上可以做什么这件事情上,做了非常多的限制。在最近的几个版本的 macOS 中,对于所有系统相关的目录,无论用户本身是否是 Administrator,都仅能做只读操作;即便用户通过 sudo su 提权到 root,也无法对这些目录进行任何的写操作。 然而 macOS 就仿佛脑子神经搭错了一样,把 TCC.db 放在了一个普通用户可以进行读写的位置。这就留下隐患了。 当然,macOS 在正常情况下对 TCC.db 还是进行了许多的保护,但是在过去的几年中,这些保护可以被二十多种方法[1]绕过,TCC 提权漏洞在几乎每一个版本的 macOS 中都有出现。这些方法包括且不限于: 插件注入 进程注入[4] /Library & $HOME 挂载 [5], [6] App 行为漏洞利用 /usr/bin/grep 注入 这样就呈现出了一个非常怪异的状态。macOS 在每一个版本里都留下了方便攻击者的 TCC 提权方式,却对有着 Administrator 权限的用户进行了严格的命令行指令的限制。举几个例子,以下操作就必须通过往 TCC.db 中写入数据来实现: 通过 /usr/bin/env 在系统后台静默更新特定用户的壁纸。 通过 /bin/bash 静默禁用&启用用户的麦克风和摄像头。这条用过 OBS 的人应该不陌生。 而这些操作本身理应由 Administrator 通过命令行是可以轻松实现的。但是由于 macOS 的糟糕的权限设计,用户不得不深入到 TCC.db 里去,用各种很 Tricky 的方式来实现。 ¶详解 access 表结构 在上面的章节里,我们查看了 TCC.db 所包含的数据表。而里面最有用的 access 表的结构大概是这么个样子: CREATE TABLE access ( service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL,-- allowed INTEGER NOT NULL, -- Removed in Big Sur-- prompt_count INTEGER NOT NULL, -- Removed in Big Sur auth_value INTEGER NOT NULL, -- Added in Big Sur auth_reason INTEGER NOT NULL, -- Added in Big Sur auth_version INTEGER NOT NULL, -- Added in Big Sur csreq BLOB, policy_id INTEGER, -- Added in Mojave indirect_object_identifier_type INTEGER, indirect_object_identifier TEXT NOT NULL DEFAULT "UNUSED", indirect_object_code_identity BLOB, flags INTEGER, last_modified INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER))) 这样的多维结构,使得用户可以在非常细致的颗粒度上控制自己的设备。例如,你可以授权某个应用访问你的摄像头,但是不授权它访问你的麦克风;你可以授权某个应用访问你的通讯录,但是不授权它访问你的日历;你可以授权某个应用访问你的照片,但是不授权它访问你的照片库。 以下为每个字段的详细解释: service: 受 TCC 管理限制的服务名。比如 kTCCServiceMicrophone,kTCCServiceCamera,kTCCServicePhotos 等等。完整的列表我放在本文末尾了。 client: 申请访问服务的应用的 Bundle Identifier 或者绝对路径(例如 com.apple.finder 或者 /usr/libexec/sshd-keygen-wrapper) client_type: 申请访问服务的应用的类型。0 代表 Bundle Identifier,1 代表绝对路径。 allowed: (本字段仅存在于 Big Sur 之前的版本) 是否允许访问(1)或者拒绝(0) prompt_count: (本字段仅存在于 Big Sur 之前的版本) 用户被提示的次数。如果程序在第一次被拒绝后,仍然不断地申请访问,那么这个字段就会不断地增加。 auth_value: (本字段仅存在于 Big Sur 以及之后的版本) 访问权限的值。0 代表拒绝,1 代表未知,2 代表允许,3 代表有限制。例如,允许应用选择照片,但是不允许它访问整个照片库。 auth_reason: (本字段仅存在于 Big Sur 以及之后的版本) 用于描述 auth_value 是因何理由被设置的。一个常见的值是 3,代表 用户设置。完整的列表我也放在本文末尾了。 auth_version: (本字段仅存在于 Big Sur 以及之后的版本) 默认为 1,也可能会随着未来的 macOS 版本而改变。 csreq: 二进制代码签名要求 blob 必须满足特定的格式,以便获得访问权限。这是用于防止攻击者的欺骗/冒充。我会在下一个章节描述以下如何进行这部分内容的生成和解码。这里真得感谢 Keith Johnson,即便在英文互联网上,可能也就他那条回答真正解释清楚了这个字段。可以简单理解为对 client 目标进行 csreq 处理后的 Blob 值,我会在下一节详细解释。 policy_id: 这个字段与 MDM(Mobile Device Management) 策略相关,carlashley/tccprofile 可以用于生成这些配置文件。 indirect_object_identifier: 用于指定某个服务(例如 kTCCServiceAppleEvents)的目标客户端。这个字段可以是 Bundle Identifier 或者绝对路径,就像 client 字段一样。在某些情况下,这个字段会被设置为 UNUSED。 indirect_object_identifier_type: 用于指定 indirect_object_identifier 字段的类型。0 代表 Bundle Identifier,1 代表绝对路径。 indirect_object_code_identity: 和 csreq 字段一样,这个字段也是用于防止攻击者的欺骗/冒充。但是这个字段的作用于 indirect_object_identifier 字段指定的客户端。可以简单理解为对 indirect_object_identifier 目标进行 csreq 处理后的 Blob 值,我会在下一节详细解释。 flags: 未知作用。值总是为 0,可能与 MDM 策略一起使用。 last_modified: 最后一次修改的时间戳。 如果你还不知道什么是 Blob,可以参考 The BLOB and TEXT Types. 有了这些字段的详细解释,我们就可以读懂甚至构造一条 TCC.db access 语句了。当然在开始之前,我们还需要补充一个知识点,就是 csreq。利用 csreq,我们可以解码一个二进制代码签名 Blob,亦或者从零开始构造一个 Blob。 ¶关于 csreq[3] 很多人一看到要构造一个 Blob 第一反应就是慌,事实上我也是一样。 不过我们在插入数据到 TCC.db 的时候,只需要构造一个满足特定格式的、非常短的 Blob 即可。这个 Blob 的格式是由 Apple 的 libsecurity_codesigning 库定义的,源代码可以在这里找到:libsecurity_codesigning/lib/requirement.h 比较粗略的看了一下,这个头文件定义了一个叫 Requirement 的类,用于表示苹果的代码签名要求(Code Signing Requirements)。Requirement 类的成员函数包括用于验证是否合法和满足格式要求的 void validate 和 bool validates;还有用于声明格式的 kind 函数,不过目前唯一支持的表达式的类型是 opExpr。 不过实际上我们并不需要手动写 csreq 的生成 & 翻译工具,macOS 本身就自带了一个同名的命令行工具 csreq。这个工具可以用来生成 Blob,也可以用来解码符合格式要求的 Blob。这个工具一个旧版本的源代码在这里:csreq.cpp。 下面主要来说说怎么用吧。就以 TCC.db 插入时最常用的一条 Blob 为例,来看看怎么用 csreq 来生成和解码这个 Blob。 # Convert the hex string into a binary blob$ BLOB="FADE0C000000003000000001000000060000000200000012636F6D2E6170706C652E5465726D696E616C000000000003"$ echo "$BLOB" | xxd -r -p > terminal-csreq.bin# Ask csreq to tell us what it means$ csreq -r- -t < terminal-csreq.binidentifier "com.apple.Terminal" and anchor apple 从解码的结果来看,这条 Blob 代表了一个通过苹果官方签名的 com.apple.Terminal 对象。 那这条信息 identifier "com.apple.Terminal" and anchor apple 本身是怎么来的呢?或者说,我们应该怎么写这条原始文本,并确认其符合 Blob 的解析原文的格式要求呢?其实也很简单,使用另一个命令行工具 codesign 就可以获得任意已签名对象的 designated 字段,也就是 Blob 的合法描述原文: $ codesign -d -r- /Applications/Utilities/Terminal.appExecutable=/Applications/Utilities/Terminal.app/Contents/MacOS/Terminaldesignated => identifier "com.apple.Terminal" and anchor apple 这里再举一个类似的例子,也就是游戏开发团队非常常用的 P4V 客户端,这玩意儿的 Blob 是: fade0c000000009c00000001000000060000000600000006000000060000000200000010636f6d2e706572666f7263652e7034760000000f0000000e000000010000000a2a864886f763640602060000000000000000000e000000000000000a2a864886f7636406010d0000000000000000000b000000000000000a7375626a6563742e4f550000000000010000000a505959465359353453370000 我们来依样画葫芦地解码一下: # Convert the hex string into a binary blobBLOB="fade0c000000009c00000001000000060000000600000006000000060000000200000010636f6d2e706572666f7263652e7034760000000f0000000e000000010000000a2a864886f763640602060000000000000000000e000000000000000a2a864886f7636406010d0000000000000000000b000000000000000a7375626a6563742e4f550000000000010000000a505959465359353453370000"echo "$BLOB" | xxd -r -p > p4v-csreq.bin# Ask csreq to tell us what it means$ csreq -r- -t < p4v-csreq.binidentifier "com.perforce.p4v" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = PYYFSY54S7# ask codesign what the requirement text from the application itself is$ codesign -d -r- /Applications/p4v.appExecutable=/Applications/p4v.app/Contents/MacOS/p4vdesignated => identifier "com.perforce.p4v" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = PYYFSY54S7 可以看到,就是这样简单的处理,就能获得任意对象合法的 Blob 描述原文。 那么第二个问题来了,既然可以通过 codesign 来获得 Blob 的描述原文,那么我们应该如何获得 Blob 本身呢?这个问题其实也很简单,只要把 Blob 的描述原文通过 csreq 转换成二进制格式即可。 这里我们继续用 p4v.app 为例: # Get the requirement string from codesign$ REQ_STR=$(codesign -d -r- /Applications/p4v.app/ 2>&1 | awk -F ' => ' '/designated/{print $2}')# Convert the requirements string into it's binary representation(sadly it seems csreq requires the output to be a file; so we just throw it in /tmp)$ echo "$REQ_STR" | csreq -r- -b /tmp/csreq.bin# Convert the binary form to hex, and print it nicely for use in sqlite$ REQ_HEX=$(xxd -p /tmp/csreq.bin | tr -d '\n')$ echo "X'$REQ_HEX'"X'fade0c000000009c00000001000000060000000600000006000000060000000200000010636f6d2e706572666f7263652e7034760000000f0000000e000000010000000a2a864886f763640602060000000000000000000e000000000000000a2a864886f7636406010d0000000000000000000b000000000000000a7375626a6563742e4f550000000000010000000a505959465359353453370000' 如你所见,刚才的 Blob 描述原文,就这样简单的获取到了。看到这一步的你,应该已经有能力自由地获得任意目标的 Blob 描述原文,并将其转换成 Blob 本身。 ¶动手构造一条 TCC.db access 语句 既然万事具备,说再多不如动手构造一条 TCC.db 的插入语句来得记忆深刻。 这里我们以 kTCCServiceAppleEvents 服务为例,构造一个允许 /usr/bin/env 通过 AppleEvents 服务访问 /System/Library/CoreServices/System Events.app 的 access 表的插入语句。 这个插入语句的作用呢,一般来说是用来帮助 osascript 命令在执行 Apple Script 的时候,强制跳过一些用户 GUI 层的确认对话框,从而达到静默执行 Login Items 的目的。在类似 JAMF Pro 这样的企业级管理软件中,有不少类似的骚操作。 好,我们开始。 首先是 service 字段,这个字段的值是 kTCCServiceAppleEvents,是我们的目标服务,也就是用于跳过一些强制行的用户确认 Prompts 的服务对象。下一节为参考表 然后是 client 字段,这个字段的值是 /usr/bin/env,这个是我们的目标客户端,也就是我们要让它通过 kTCCServiceAppleEvents 服务访问 /System/Applications/System Preferences.app 的客户端。这里无论是 /usr/bin/env 还是其对应的 identifier,都是可以的,所以也可以写成 com.apple.env。 client_type 字段,如果你 client 填的是 /usr/bin/env,也就是绝对路径,那就这个字段的值是 1;如果填的是 com.apple.env,也就是 identifier,那就这个字段的值是 0。 auth_value 字段,那肯定是允许嘛。所以这个字段的值是 2。 auth_reason 字段,这个字段的值是 3,也就是 User Set。表示是用户自己设置的(笑)。下一节为参考表 auth_version 字段,默认就是 1。别问,别管。 csreq 字段,这个字段就是对 /usr/bin/env 的 Blob 描述原文的二进制表示。构造方法上面一节已经说的清清楚楚了。 policy_id 字段,我们暂时用不到,设置为 NULL。 indirect_object_identifier_type 也就是被访问对象的类型,这里是 0,也就是 identifier。 indirect_object_identifier 字段,这个字段的值是 /System/Library/CoreServices/System Events.app/ 的 identifier,也就是 com.apple.systemevents。 indirect_object_code_identity 字段,这个字段的值是 /System/Library/CoreServices/System Events.app/ 的 Blob 二进制表示。构造方法也是参考上一节。 flags 字段,我们暂时用不到,设置为 NULL。 last_modified 字段,这个字段的值只要是合法的时间戳就行,我自己一般喜欢用 2022-01-01 00:00:00,也就是 1642634565。 那么现在,我们的插入语句已经全部完成了,写出来就是这么个样子: INSERT INTO access VALUES( 'kTCCServiceAppleEvents', -- service '/usr/bin/env', -- client 1, -- client_type 2, -- auth_value 3, -- auth_reason 1, -- auth_version -- csreq X'fade0c000000002c0000000100000006000000020000000d636f6d2e6170706c652e656e7600000000000003', NULL, -- policy_id 0, -- indirect_object_identifier_type 'com.apple.systemevents', -- indirect_object_identifier -- indirect_object_code_identity X'fade0c000000003400000001000000060000000200000016636f6d2e6170706c652e73797374656d6576656e7473000000000003', NULL, -- flags 1642634565 -- last_modified); 之后我们就可以用 sqlite3 命令行工具,或者 DB Browser for SQLite 这样的 GUI 工具,将这条语句插入到 TCC.db 中了。 然后,你就可以通过构造一个 plist 文件和 launchctl 命令,给用户加载一些 Login Items,例如更换壁纸、更换 Dock 图标、更换桌面图标等等,而不需要经过用户在 GUI 的窗口确认了。 以下为一个简单的 plist 文件示例: <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>Label</key> <string>{{ plist_name }}</string> <key>Program</key> <string>{{ apple_script_path }}</string> <key>RunAtLoad</key> <true/></dict></plist> 吐槽:明明非常正常的设备和系统管理操作,非要被苹果弄得像是黑客入侵操作一样。真有你的,库克。 ¶Service & Auth_Reason 参考表[2] SERVICE List Value Description kTCCServiceAddressBook client would like to access your contacts. kTCCServiceAppleEvents client wants access to control indirect_object. Allowing control will provide access to documents and data in indirect_object, and to perform actions within that app. kTCCServiceBluetoothAlways client would like to use Bluetooth. kTCCServiceCalendar client would like to access your calendar. kTCCServiceCamera client would like to access the camera. kTCCServiceContactsFull client would like to access all of your contacts information. kTCCServiceContactsLimited client would like to access your contacts basic information. kTCCServiceFileProviderDomain client wants to access files managed by indirect_object. kTCCServiceFileProviderPresence Do you want to allow client to see when you are using files managed by it? It will see which applications are used to access files and whether you are actively using them. It will not see when files that are not managed by it are accessed. kTCCServiceLocation client would like to use your current location. kTCCServiceMediaLibrary client would like to access Apple Music, your music and video activity, and your media library. kTCCServiceMicrophone client would like to access the microphone. kTCCServiceMotion client Would Like to Access Your Motion & Fitness Activity. kTCCServicePhotos client Would Like to Access Your Photos kTCCServicePhotosAdd client Would Like to Add to your Photos kTCCServicePrototype3Rights client Would Like Authorization to Test Service Proto3Right. kTCCServicePrototype4Rights client Would Like Authorization to Test Service Proto4Right. kTCCServiceReminders client would like to access your reminders. kTCCServiceScreenCapture client would like to capture the contents of the system display. kTCCServiceSiri Would You Like to Use client with Siri? kTCCServiceSpeechRecognition client Would Like to Access Speech Recognition. kTCCServiceSystemPolicyDesktopFolder client would like to access files in your Desktop folder. kTCCServiceSystemPolicyDeveloperFiles client would like to access a file used in Software Development. kTCCServiceSystemPolicyDocumentsFolder client would like to access files in your Documents folder. kTCCServiceSystemPolicyDownloadsFolder client would like to access files in your Downloads folder. kTCCServiceSystemPolicyNetworkVolumes client would like to access files on a network volume. kTCCServiceSystemPolicyRemovableVolumes client would like to access files on a removable volume. kTCCServiceSystemPolicySysAdminFiles client would like to administer your computer. Administration can include modifying passwords, networking, and system settings. kTCCServiceWillow client would like to access your Home data. kTCCServiceSystemPolicyAllFiles Full Disk Access kTCCServiceAccessibility Allows app to control your computer kTCCServicePostEvent Allows to send keystrokes kTCCServiceListenEvent Input Monitoring; to monitor input from your keyboard kTCCServiceDeveloperTool Allows app to run software locally that do not meet the system’s security policy kTCCServiceLiverpool Related to location services kTCCServiceUbiquity Related to iCloud kTCCServiceShareKit Related to the share feature(presumably from iOS)(ShareKit) kTCCServiceLinkedIn LinkedIn kTCCServiceTwitter Twitter kTCCServiceFacebook Facebook kTCCServiceSinaWeibo Sina Weibo kTCCServiceTencentWeibo Tencent Weibo ‍ AUTH_REASON List Value Description 1 Error 2 User Consent 3 User Set 4 System Set 5 Service Policy 6 MDM Policy 7 Override Policy 8 Missing usage string 9 Prompt Timeout 10 Preflight Unknown 11 Entitled 12 App Type Policy ¶相关文献 20+ ways to bypass your mac os privacy mechanisms – Csaba Fitzl A deep dive into macOS TCC.db – Keith Johnson How to get csreq of macOS application on command line? – Keith Johnson CVE-2020–9934 CVE-2021-1784 CVE-2021-30920

2023/3/26
articleCard.readMore

加密货币杂谈(三):虚妄和繁荣 | Illusion and Prosperity

其实最开始想写的标题是《虚妄的繁荣》。但是在此时此刻的世界,如此描述似乎也不完全准确,将其称为虚妄和繁荣可能是一个更好的做法。 Actually, the original title I wanted to write was “The Illusion of Prosperity”. But in the current world, such a description doesn’t seem entirely accurate. It might be better to call it “Illusion and Prosperity”. ¶前言 本篇会写的相对短一些,主要还是聊聊加密币这个被社会舆论严重扭曲的东西,我自己是怎么看的。 This article will be relatively short, mainly discussing how I personally view cryptocurrencies, which have been heavily distorted by public opinion. ¶妖魔化和反妖魔化 从 2008 年首次被提出以来,虚拟货币在过去的 15 年里的多数时间,包括比特币本身,都被当作过玩具币。而在 21 年大冲顶后,彻底成为韩国老太太都在玩的东西以后,加密货币正式完成了被妖魔化的过程。 对于散户来说,论点在于加密货币本身是不是一个真正的货币,是不是一种未来支付方式的替代,以及是否是一场场庞氏骗局。 对于纯理性的投资者来说,论点在于如何将其看作是一种新兴的投资对象,正如股票、期货、国债一样,以及如何评定其信用。 对于技术从业者来说,论点可能更多在于其背后的加密技术和去中心化模式对未来产品形态、资产持有方式和协作方式的改变会有多大。 从个人的观点来看,这些论点都有其本身的价值,我无法像一个真正专家一般去回答这些问题,但是我还是认为围绕这些论点展开的无穷无尽的讨论是导致加密货币本身被剧烈妖魔化的根源。抑或是说,这三方的讨论者里,一定有大量的人其实是希望这种妖魔化持续下去,并且为加密货币加上一层朦胧的面纱,来满足他们画大饼和讲故事的空间。 从我国一贯教育的价值和价格角度来讨论加密货币其实是有一些问题的,马克思的那套劳动价值论早就遇到了时代的壁垒。在 70 年代美元黄金体系破灭后,各个国家竞相通过无限寅吃卯粮循环来增加流动性,并且基尼系數在无尽的放水中越走越远的现在,可能皮耶罗·斯拉法在其《用商品生产商品》更能片面的阐述加密货币的价格锚定逻辑。 无论是否是虚拟货币的信徒,亦或者是希望其归零,破除妖魔化的迷雾,尽可能用理性人的观点来接受事实从而再进行分析,才是正确的面对事物的处理法门。 Since it was first proposed in 2008, cryptocurrencies, including Bitcoin itself, have been regarded as toy currencies for most of the past 15 years. After the 2021 GREAT Peak, which made cryptocurrencies something even Korean grandmothers were playing with, cryptocurrencies officially completed the process of being demonized. For normal investors, the argument is whether cryptocurrencies themselves are a real currency, a replacement for future payment methods, or a series of Ponzi schemes. For rational investors, the argument is how to view it as a new investment object, just like stocks, futures, and government bonds, and how to evaluate its credit. For technical practitioners, the argument may be more about how the encryption technology and decentralized model behind it will change the future product form, asset holding method, and collaboration method. From my personal perspective, these arguments all have their own value, and I cannot answer these questions like a real expert. However, I still believe that the endless discussions surrounding these arguments are the root cause of the severe demonization of cryptocurrencies. Or perhaps, among the three parties of discussants, there must be a large number of people who actually hope that this demonization will continue and add a layer of vague veil to cryptocurrencies to satisfy their storytelling. From the perspective of values and prices that have always been emphasized in Chinese education, there are actually some problems with discussing cryptocurrencies. Marx’s labor theory of value has long encountered the barrier of the times. After the collapse of Bretton Woods system in the 1970s, various countries competed to increase liquidity through endless printing, in the meanwhile the Gini coefficient has also been increasing in the endless flood of liquidity. Now, Piero Sraffa’s “Production of Commodities by Means of Commodities” may better explain the logic of price anchoring for cryptocurrencies. Whether you are a believer in cryptocurrencies or hope to see them failed to zero, to dispel the fog of demonization, it is correct to accept the facts and analyze them as rationally as possible. ¶阳光下从来没有新事物 其实和很多人的直觉相悖,虚拟货币这个东西除了将其进行实现的区块链(事实上区块链主要也是应用创新而非理论创新)之外,其实是一个非常非常传统且古老的东西,古老到明末清初就有几乎一样的东西出来了 ———— 股票。 首先来看看传统的股票机构是如何记录股票的买卖过程并且保证其可信的: 股票交易的记录和可信度由证券交易所和相关监管机构负责。证券交易所会通过其交易系统记录每一笔股票交易的细节,包括交易时间、交易价格、交易数量等信息,并确保这些信息公开透明、可查询。监管机构会对证券交易所的交易数据进行监管,确保交易过程的合法性和公正性,以维护市场的稳定和投资者的利益。在一些国家,也有专门的证券中央存管机构来记录股票交易过程和持股人信息,以确保证券交易的安全和透明。 可以看到,这里本质有两个关键机构,分别是 证券交易所 和 证监会。 之所以有证监会的原因,是人类其实并不能确保 证券交易所 的绝对廉洁,否则也不会有如此之多禁止证券业从业者参与股票和投资的规范条文了。如果有看过我前两篇关于加密货币的论文,应该能想起来,区块链这个技术本身同时完成了 记录 和 确保正确(监督) 的作用,也就是其产生的方式本身,就是 证券交易所 和 证监会 的合体,并且这个过程里理论上排除了人类进行贪污和漏洞攻击的可能(此处说理论上是因为并不是所有货币的 Mrkt Cap 都能达到不被多数攻击的程度)。 所以这里是我第一个想破除的妖魔化的点:加密货币是一种货币。 加密货币本质和货币本身没有半毛钱的关系,只是一个 T+0 且流动性远超传统股票的…嗯…股票。 也因为这个极高的流动能力,导致了在不少场合被很多机构赋予了货币的职能。 然后是第二个妖魔化的点:加密货币本身在整个过程中不创造价值,凭什么值钱。 这里我感觉很多人还是陷入了学生思维,甚至很多站在很高位置的人也犯了这个错误。他们明明是最理解这个世界运行规律的人。加密货币有无价值创造这件事情本身,根本就没有意义。现在,加密货币可以带来流动性,能有洗钱之作用,能在自由市场维持流动性和变现能力,那便是 既定之事实。如果哪天全部加密货币全部 归零 了,那也是 既定之事实。无论是巴菲特还是韩国大妈,呼喊号召本身改变不了任何事情,投资的第一课就是学会和现实世界共存。 事实上加密货币本身是有创造价值的,太多人仅仅看到了挖矿这件事情浪费的巨大算力和电量上了,这里还是片面了。其本身作为一种类股票的形态,自身在避税、突破汇率监管、黑市交易、匿名交易,以及 DAO 开发这类定义新的资产从属关系的地方,找到了自身的价值。更不用说我觉得 合约 本身其实挺适合多个 AI 实例之间进行资产的所有权和交易关系声明的。 其本身的原罪更多是在于,先有了加密货币,多年后才被各种实体赋予了价值锚点,在交易所发展之后,才逐渐成熟了价格体系。 事实上,这可比大片 ST 套壳上市公司的锚点扎实多了(笑)。当然了,大量山寨币的故事对冲了黄饼、USDT 等加密货币本身的信用,这确实是目前的一个无解的硬伤。 Contrary to many people’s intuition, besides the blockchain technology used to realize it (in fact, blockchain is more of an application innovation than a theoretical one), cryptocurrency is actually a very traditional and ancient thing. It is so ancient that something almost identical to it emerged in the late Ming and early Qing dynasties - stocks. First, let’s take a look at how traditional stock institutions record and ensure the credibility of stock transactions: The recording and credibility of stock transactions are the responsibility of the securities exchange and relevant regulatory agencies. The securities exchange records the details of each stock transaction, including the time, price, and quantity of the transaction, through its trading system, and ensures that this information is publicly transparent and searchable. Regulatory agencies supervise the trading data of the securities exchange to ensure the legality and fairness of the trading process, in order to maintain market stability and protect the interests of investors. In some countries, there are also special central depository institutions for securities to record the stock trading process and shareholder information, in order to ensure the safety and transparency of securities trading. It can be seen that there are essentially two key institutions here, namely the securities exchange and the Securities Regulatory Commission (SRC). The reason why the SRC exists is that humans cannot guarantee the absolute cleanliness of the securities exchange, otherwise there would not be so many regulations prohibiting securities industry practitioners from participating in stocks and investments. If you have read my previous two blogs on cryptocurrency, you should be able to recall that the blockchain technology itself simultaneously fulfills the roles of recording and ensuring correctness (supervision), and the way it is produced is a combination of the securities exchange and the SRC. This process theoretically eliminates the possibility of human corruption and vulnerability attacks (the reason why I say theoretically is because not all cryptocurrencies can achieve a level of market capitalization that is not susceptible to majority attacks). So here is the first demonized point I want to debunk: cryptocurrency is a currency. Cryptocurrency has nothing to do with currency itself, it is just a stock that is T+0 and has much higher liquidity than traditional stocks. Because of its high liquidity, it has been given the function of currency by many institutions in many occasions. The second demonized point to debunk is that cryptocurrency itself does not create value, so why is it valuable? I feel that many people are still stuck in a student mindset, and even many people in high positions have made this mistake, while they are actually the people who understand the laws of the world best. Whether or not cryptocurrency creates value is meaningless in itself. Now, cryptocurrency can bring liquidity, can be used for money laundering, can maintain liquidity and liquidity and cashing ability in a free market, and that is the established fact. If all cryptocurrencies were to go to zero one day, that would also be an established fact. Whether it’s Warren Buffett or a Korean grandma, shouting and calling for something doesn’t change anything. The first lesson in investing is to learn to coexist with the real world. In fact, cryptocurrency itself DOES create value. Many people only see the huge computing power and electricity wasted in mining, which is a one-sided view. As a form of stock, it has found its value in tax avoidance, breaking through exchange rate regulation, black market transactions, anonymous transactions, and DAO development to define new asset ownership relationships. Not to mention, I think the contract itself is quite suitable for multiple AI instances to declare asset ownership and trading relationships. Its original sin is more about the fact that it was given a value anchor by various entities many years after cryptocurrency was established, and it gradually developed a price system after being traded on exchanges. In fact, this is much more solid than the value anchor of many shell listed companies lol. Of course, the fact that a large number of copycat coins diluting the credit of cryptocurrencies such as Bitcoin, USDT, etc. is currently an unsolvable problem. ¶投资决策 我其实不想给任何人关于加密货币的投资决策,但是如果非要说的话,大概就三点: 定投,不要一次 All-In 锁死成本。除非确定的历史事件。 远离杠杆和合约。大宗期货和外汇交易使用杠杆是有深刻的历史原因的,而加密货币没有。 远离 USDT,BTC,ETH 之外的任何加密货币。当然买个几百刀的山寨币权当买着玩也没事。 这样的话,你一定不会有一夜暴富的神话,但是你至少不会死得像条路边丑陋的贱狗。 祝大家,早日财富自由。 I actually don’t want to give anyone investment decisions about cryptocurrencies, but if I have to, there are probably three points: Dollar-cost averaging, don’t lock in costs all at once unless it’s based on a certain historical event. Stay away from leverage and contracts. The use of leverage in bulk futures and forex trading has deep historical reasons, and cryptocurrencies don’t. Stay away from any cryptocurrencies other than USDT, BTC, and ETH. Of course, buying a few hundred dollars of a copycat coin for fun is also okay. This way, you won’t have the myth of getting rich overnight, but at least you won’t die like a cheap dog on the side of the road. I wish you all financial freedom some day. Best Regards. ¶Links 加密货币杂谈(一):创生与加密 加密货币杂谈(二):黄金矿工

2023/3/19
articleCard.readMore

如何合法申请一张欧盟 N26 银行信用卡用于 ChatGPT

DEPRECATED 博文内容失效警告 - 2023/06/22 根据评论区反馈,目前德国的长期居留证明已经是必须的了。因此,本文所描述的申请方式,不再可行。望周知。 ¶前言 近期随着 ChatGPT 以及 ChatGPT API 的爆火,越来越多人需要使用 API Token 来进行开发和研究。但是很快开发者们就会发现,所有中国大陆和港澳地区发行的 MasterCard & VISA 行用卡,都会被 OpenAI 拒绝支付。我国的信用卡经过这么多年的努力,也算是成为了著名的不被信用卡。所以对于身在国内的人来说,如何安全稳定的获取一张可以使用海外 MasterCard & VISA 信用卡就成了一种刚需。 事实上很多人说可以申请一些加密货币信用卡,例如 Depay 或者 Nobepay 之类的。但是说实话这类信用卡基本都有一大堆特别蠢的硬伤: 是中国人开的 和 DeFi 交易密切相关 开卡手续费高昂 野鸡 上面任何一条单独拿出来就足够打消开卡的必要,安全性更是一塌糊涂。在目前这个时间点,笔者调研了一大圈,谨慎觉得对于开发者最好的选择可能确实就是 N26 银行的虚拟卡了。 ¶N26 银行简介 本章节由 ChatGPT 书写 - N26 是一家来自德国的全数字化银行,成立于 2013 年,总部位于柏林。N26 的目标是重新定义银行,使它变得更加简单,透明和实用。 N26 通过其移动应用程序提供银行服务,包括:存款账户,借记卡和信用卡,转账,保险和投资产品等。其应用程序具有清晰简洁的界面,使用户可以方便地查看他们的账户余额和交易历史记录,并且提供了安全,快速和便捷的支付方式。 N26 还提供 24/7 的客户支持服务,并采用强大的安全措施来保护用户的账户。此外,N26 还与其他银行合作,以提供跨境转账和汇款服务。 当你在 N26 银行注册账户时,你可以选择三种不同的账户类型:N26 Standard,N26 You 和 N26 Metal。每种账户类型都有不同的功能和特点。 N26 Standard: 该账户类型是免费的,并提供基本的银行服务,包括免费的借记卡和5次免费提现。另外,该账户还提供保险服务,如手机保险和出行保险。 N26 You: 该账户类型需要付费,并提供更多的特殊功能,如免费提现和免费的外币交易,以及更多的保险和旅行服务。此外,N26 You账户还提供可自定义的借记卡和选择颜色。 N26 Metal: 该账户类型是最高端的账户类型,需要付费,但提供的特殊功能更为丰富。该账户提供无限免费提现和免费外币交易,以及更多的高级保险和旅行服务。另外,N26 Metal 账户还提供金属质感的借记卡和个性化服务。 在使用N26银行账户时,你可以通过N26移动应用程序进行转账、支付和管理账户等操作。应用程序具有直观的界面和易于使用的功能,允许用户随时随地查看账户余额和交易历史记录,并可以轻松地设置支出限额和预算等功能。此外,N26 还提供与其他银行合作的跨境转账和汇款服务,方便用户进行国际转账。 最后,N26 银行采用了强大的安全措施,保护用户的账户安全。例如,用户可以设置额外的安全密码来保护账户,每笔交易都需要确认,N26 应用程序还具有实时警报和防欺诈措施等功能。总之,N26 银行是一家以数字化为核心的现代银行,旨在提供方便,安全和高效的银行服务。 ¶申请前准备 护照 一张美国的手机卡,可以是 Google Voice 关于如何申请 Google Voice 号码:GV 申请全攻略 可以听懂土耳其口音英语的能力和正常英语口语交流能力 尽可能全程保持欧洲或者北美网络环境 ¶申请流程 本章节内容大量参考了 YouhuiMa 先生所写的 N26银行常见问题2022年版,但是由于时间有过去了大半年,有些内容有些许差异。 1. 首先打开 N26 官方网站 网站地址:N26.com。点击右上角的 Login,然后点击 Create a Account。 2. 开始填写基本注册信息 Nationality 这一栏强烈推荐选择德国 Germany。因为德国在认证的时候不需要你的手机开GPS定位,只要你会一点英语或者德语,他们的简单问题你都可以完成。申请德国账户通过率高,而且因为 N26 银行总部就在德国,德国的账户服务和各种功能都是最全最新的。 自 2022 年开始,申请者如果使用中国大陆的手机号注册,往往会不成功或者接受不到验证码。而美国和香港的手机号包括虚拟号都可以顺利接受验证码。 3.填写个人信息 所有信息必须真实有效。尤其是姓名一定要和你护照上一致。 笔者因为疏忽第一次注册的时候填错了姓名,直接导致浪费了一个邮箱和一个手机。因为一旦完成注册,在做完所有的认证之前,是无法登入账号管理页面的,也因为无法通过人工以外的方式删除错误的账号,会导致手机号码直接被废掉。而一个手机只能绑定一个账号,这里一定要小心再小心。 4. 填写申请N26银行账户的个人住址信息 如果要申请西班牙或者意大利的 N26 账户,你需要在当地有一个居住地址,方便接受邮寄给你的银行卡。如果你仅申请普通免费账户,N26 是不会给你寄实体卡片的,所有操作都在手机 app 上完成。因此,申请人是否真的有一个所在国家的地址就不那么重要了。在网上找一个看起来像居民住宅的地址填入就可以。比较推荐 Random German Addr 5. 填写申请人的国籍和出生/性别信息 这部分同样一定要和你的护照内容一致,如果你是中国护照或者其他国家护照,请如实填写,因为之后的视频认证需要核对的。 6. 你的工作状态相关问题 简单如实选择就可以,并不是很重要。一般来说有需要的人都填 Employment。老板们自然有合法获取全球各国信用卡的方法。主要会咨询目前雇主是哪个行业的,这个问题不重要,简单选择一个就可以,一般的码农都是 Media, technology & consulting。以及最后会询问以下你申请 N26 的用途,一般都是个人项目开发,如实选择即可。 7. 美国相关的问题 这里回答是否申请人有美国身份或者绿卡,以及询问是否有美国纳税义务。如果没有持有绿卡或者美国国籍,第一个问题选择 NO。同时也没有美国纳税的义务,第二个问题选 NO。 这里想到一个笑话,如果有一天美军和解放军还有俄罗斯军队联合起来,世界上还有什么可以阻止他们;这个时候,IRS 出*手消灭了三国联军,因为他们供应链没有缴纳足额税款。 8. 申请人税务地相关的问题 此时会询问申请人在哪个国家纳税,可以选填自己的税务号码 Tax ID,或者 Tax ID 留空也可以。不影响后续申请。 9. 设置账号的密码 密码设定推荐使用比较复杂的,至少 11 位含有数字、字母和特殊字符。如果你有推荐人,可以填写推荐号码。然后点击去下一步 10. 确认所有注册信息 确认一下所有的申请信息是否正确,然后点击同意合同条款。到这里其实网上申请就基本结束了,N26银行会立刻发Email给你,请打开你的邮箱,点击绿色确认按钮进行确认。点击开户后您的账户就完成了。 ¶认证流程 完成申请账号只是 N26 申请过程中比较简单的一环,相对恶心的步骤是认证阶段。不过毕竟是欧盟最大的网络银行,严格一点也是情有可原。我们在完成注册后,首次登陆会提示你选择一些必要信息,以及信用卡套餐。这里我们应该选择没有任何费用的 N26 Standard 方案。之后页面中会出现以下提示,意味着之后的步骤都应该在手机 App 上完成。 在 App store 下载 N26 App后,登录你的账户,然后需要绑定你的手机,然后选择使用 Germany Resident VISA (只是一个选项,实际并不需要看)验证身份,一直点击继续直到开启视频验证。 验证的时候,你要找一个安静的地方并且网络畅通。目前 Verify Your Identity 稍微有些问题的更新,需要着重讲一下。 在 Verify Your Identity 视频认证的时候,可以选择德语或者英语,工作时间是德国时间早9晚5,认证专家会询问你以下几个问题: 确认姓名 确认是否本人申请(回答 Yes) 确认是否在一个安静的,没有任何其他人的环境(回答 Yes) 确认是否有任何人告诉你申请 N26 后会有报酬(回答 No) 确认并拍摄你的照片 确认并拍摄你用手张开五指从面前上下移动的片段 确认护照号码及把护照的首页在摄像头前给客服看一看 确认并拍摄护照上下移动的片段 确认并拍摄护照被手指部分遮住的片段 确认并拍摄护照号码区域的详细内容 确认并拍摄申请人念出护照号码的片段 再次发送短信到你的手机,通过 App 内进行输入确认 整个过程大约10分钟不到,之后便会提交你的认证信息到 N26 审核部门。如果在周末,你可能需要等到工作日后才会收到审核通过的状态信息;如果是工作日,应该会比较快就审核完毕。一旦视频认证成功,N26 银行账户马上就打开使用了,如果申请了实体银行卡,就会在 3 天内收到邮局寄来的银行卡。(如果本人不在欧洲,建议不要申请实体卡)。 即使开户人不在德国,通过 Email 你也会知道你的 N26 银行帐号 IBAN 和 BIC 等关键信息。之后打开你的 App 已经可以操作了。 ¶结尾 现在,愉快地拿着你全新的欧盟 MasterCard 开始 ChatGPT API 的开发之旅吧。

2023/3/6
articleCard.readMore

加密货币杂谈(二):黄金矿工

阅读预警:本篇除了让你挖到一点点连提取都做不到的比特币以外,没有任何作用。但是还是会告诉任何对比特币有兴趣的人一些足够珍贵的知识。 ¶前言 从这篇文章开始,我的博客应该会逐步使用 ChatGPT 作为辅助工具来进行写作。当然我会故意保留一些自己行文的语言特色,这样读者才会知道,他读的是我的大肠文章。 就像预警里所说的,我希望通过这篇博客让读者对于经典虚拟货币的开采过程,也就是 工作证明(PoW) 模式有一个简要的了解,对于比特币的开采,存储,记账有一个最简单的认知。大段大段的文论摘抄没什么必要,没有什么会比自己手动亲自开采几聪(Satoshi - 比特币最小单位)比特币来得直观。 如果能对从来没有了解过虚拟币开采和交易的朋友有一些启发,或者搏君一笑,那也不枉费写这些废话的时间了。 ¶如何储存虚拟货币 就如我们在 加密货币杂谈(一):创生与加密 里动手实践的一样,一个虚拟币钱包是一种存储数字货币的地址所有权申明,也是区块链上交易发生的对象。对于广义的虚拟币钱包定义,可以扩大理解成包含钱包管理软件和交易机构在内的一切可以帮助用户管理其加密货币钱包地址的私钥和公钥的工具和平台。广义的虚拟币钱包可以分为三种类型:冷钱包,热钱包和交易所钱包。它们之间有一些显著的差异和特点。 ¶冷钱包 冷钱包是一种完全脱机的钱包,可以将数字货币的私钥和公钥存储在离线设备中,例如硬件钱包或纸钱包。它们通常与互联网隔离,因此无法通过网络访问。由于冷钱包的私钥从未也永远不会连接到互联网,因此它们更安全,可以防止黑客攻击。但是,使用冷钱包进行对外转账时,需要将其连接到互联网上,并且需要手动签署交易,因此交易速度可能会变慢。 个人建议是真正用来存手头库存货币的那个钱包,永远不要触网。要么使用自己的钱包群将其隐藏起来,要么用之即弃。对于自己最重要的那个冷钱包,可以无上限追求安全性。 利:安全性高,私钥未连接到互联网,可以防止黑客攻击。 弊:需要手动签署交易,交易速度较慢。 ¶热钱包 热钱包是一种连接到互联网的钱包,可以帮助用户快速和方便地进行交易。热钱包可以存储数字货币的私钥和公钥,但是由于它们在线或者触网,因此存在被黑客攻击的风险。当然了,一些热钱包还可以与硬件钱包配合使用,以提高安全性。 利:快速和方便地进行交易。 弊:安全性较低,易受黑客攻击。 ¶交易所钱包 交易所钱包是一种由加密货币交易所提供的在线钱包。与热钱包类似,交易所钱包连接到互联网,可以快速进行交易。但是,由于交易所管理用户的私钥,因此用户无法完全控制其数字资产。此外,一些交易所因为管理不善或黑客攻击而导致数字资产丢失的事件也时有发生。 梭哈猿人的这篇 虚拟币交易所跑路史 记录了过去 20 年比较知名的中大型交易所的跑路历史。可以说只要是交易所,跑路的概率就不小。但是没了交易所,确实也不行,流动性和交易难度直接陡增。 币安和火币会跑路吗?在他们跑路前那天谁都不知道。这也是为什么我三番五次不建议 任何人 参与虚拟币杠杆投资和垃圾币交易的原因。当然自己要选择富贵险中求的话,也没人拦着。 利:快速进行交易,不需要自己管理私钥。 弊:安全性不高,用户无法完全控制其数字资产,存在管理不善或黑客攻击导致数字资产丢失的风险。 综上所述,不同类型的虚拟币钱包各有优劣,用户可以根据自己的需求和风险承受能力选择适合自己的钱包。如果用户的数字资产数量较大,冷钱包可能是更好的选择。 ⚠️ 只有当虚拟币存入你作为唯一的私钥拥有者的冷钱包后,这笔钱才真正属于你 ⚠️ 但是少量放在交易所提升流动性,是合理的 ¶记录过程和一致性 在 加密货币杂谈(一):创生与加密 没有特别详细的描述链上交易的过程的细节。这里大致解释一下为什么比特币交易过程理论上几乎不可被篡改。 比特币交易的记录和处理是通过一个称为“区块链”的公共分布式账本系统实现的。每个区块链都由许多个区块组成,每个区块包含一些新的交易信息,以及一个指向前一个区块的链接(称为 哈希 Hash),从而形成一个链式结构。 每个比特币交易都由一个或多个输入和一个或多个输出组成。输入是之前的比特币交易的输出,这些输出被发送到接收方的地址。输出是比特币交易的接收方地址和交易金额的组合。 每个比特币交易都需要经过网络中的矿工进行验证和记录。矿工通过解决一个复杂的数学问题来验证交易,并将其添加到区块链上。 一旦交易被验证,它就会被添加到一个区块中。这个区块包含多个交易,以及一个称为"区块头"的元数据,其中包括上一个区块的哈希值、时间戳、难度目标和其他信息。 一旦区块被创建,它就会被广播到整个比特币网络中。其他矿工会收到这个新的区块并验证其中的交易是否合法,如果合法就会将其添加到自己的区块链中。 当大多数矿工都将这个新的区块添加到自己的区块链中时,这个区块就被认为是“确认”了。确认的交易记录是不可更改的,因为任何试图修改它们的尝试都会被其他节点拒绝。(当然这里其实涉及一个被时常提到的概念,就是绝对多数篡改攻击。不过仅限比特币这类主流币的话,多数篡改是不可能的,在完成多数篡改以前会先生成一个质量超过猎户臂的超级黑洞。参考 Bremermann’s limit) 比特币实现一致性的关键是通过共识机制,即 工作量证明(Proof of Work)。矿工需要解决一个复杂的数学问题来验证交易并添加到区块链上,这个过程需要大量的计算能力和时间。因此,当一个新的区块被添加到区块链上时,它需要其他矿工的验证和认可,这样才能保证这个区块是合法的,并被整个网络认可。这个过程需要时间,但一旦确认的交易被添加到区块链上,它们就是不可更改的,因此整个网络的一致性得到了保障。 在下一章会详细讲一下 PoW。 ¶虚拟货币是如何被开采出来的 在描述比特币的开采过程之前,我觉得还是得稍微介绍以下工作证明。工作证明我个人的理解来说,是一种对 比特币交易的记录和验证 这件苦差事的奖励。通过动态难度的哈希碰撞问题来让矿工们被动地维护整个庞大地比特币交易记录链。 ¶工作证明 PoW 工作量证明(Proof of Work,简称PoW)是比特币和其他一些加密货币所使用的一种共识算法。它是一种解决分布式系统中恶意行为问题的方法,通过要求参与者完成计算任务来证明他们的贡献,从而获得记账权和奖励。以下是工作量证明的实现原理: 难度目标:工作量证明的核心是一个难题,该难题是一个哈希碰撞问题,即找到一个特定的哈希值,使得该哈希值的前缀满足一定的条件。这个条件是由网络中所有节点共同维护的,称为难度目标。难度目标通常会根据整个网络的算力而调整,以确保平均每10分钟只能生成一个新的区块。 矿工的计算:参与者称为矿工,他们需要完成这个哈希碰撞问题,以便将新的交易打包到区块中,并将其添加到区块链中。矿工通过在不断尝试不同的随机数的同时,计算出包含所有交易的区块头的哈希值,该哈希值必须满足难度目标。 激励:完成区块的计算任务的第一个矿工,将获得新的比特币作为奖励,并可以将新的区块添加到区块链中。这就是工作量证明的核心思想,即完成计算任务的矿工将获得奖励,因此会有更多的人参与到计算中来。 确认交易:一旦区块添加到区块链中,其中包含的所有交易将被确认,并在比特币网络中广播。这样,其他节点就可以验证交易的有效性,并确保网络的安全性和去中心化。 总之,工作量证明是一种在分布式系统中实现共识的方法,它通过要求参与者完成计算任务来证明他们的贡献,从而获得记账权和奖励。尽管工作量证明算法是比特币和其他加密货币使用的最常见的共识算法之一,但它也因为需要大量的计算能力和能源消耗而受到一些争议。因此,现在也有一些替代的共识算法出现,如权益证明(Proof of Stake)和权益分享证明(Proof of Burn)。 ¶开采过程 其实开采过程正是工作证明过程的另一种描述,如果理解了工作证明的概念,则会非常容易想明白。开采过程主要分以下四个步骤: 验证交易:比特币网络中的节点会验证交易的有效性。验证通过后,交易会被广播到网络中的所有节点。 选择交易:矿工需要选择要包含在他们的区块中的交易。一般来说,矿工会选择交易费用最高的交易,因为这可以增加他们的收益。 打包交易:一旦选择了要包含的交易,矿工需要将它们打包成一个区块。这个过程涉及到解决一个难题,也就是工作量证明。这个难题要求矿工在计算中使用大量的计算资源,以便为比特币网络提供安全性和去中心化。 提交区块:一旦矿工成功地解决了难题,他们会将新的区块广播到整个比特币网络中。如果其他节点也确认了该区块的有效性,则该区块将被添加到区块链中,并奖励该矿工一定数量的比特币。 ¶什么是难题,如何计算 对于这个问题,其实往简单了说,就是一个 SHA-256 Brute Crack。恰好这里有一个视频非常生动的描述了比特币打包交易难题的计算过程,嗯,手动笔算过程。Salute Mining Bitcoin with pencil and paper 关于这个视频的文字解释可以参考:Mining Bitcoin with pencil and paper: 0.67 hashes per day 也就是说,通过手动计算,每天可以完成 0.67 Hashes 的计算量。而现在全球的矿机的计算总量大约是每秒 290,000,000,000,000,000,000 Hashes。如果你想手动算出一个比特币的话,可以算到质子衰变发生那天。放弃吧,呆头鹅,有那个寿命道祖都能给你按脚了,挖什么垃圾加密币啊。 ¶难度和工具的旷古之争 比特币挖矿难度除了在 2010 年之前的原初混沌时期,其实一直都是比特币减半周期和矿机算力的螺旋竞争,因此本章也大概聊聊这两个东西到底在搞什么。 ¶比特币减半 众所周知,比特币的总量是固定的,21,000,000 个。开采完就没了。而比特币减半是指比特币网络中每 21 万个区块(约 4 年)就会发生一次减半事件,即比特币的区块奖励减半。具体来说,比特币网络中的矿工通过完成哈希碰撞问题来计算新的区块,完成后会获得比特币奖励,这个奖励每21万个区块就会减半一次。比特币的区块奖励从最初的50个比特币减半到现在的 6.25 个比特币。 比特币减半的原理是由比特币协议的设计所决定的。比特币的发行总量是有限的,最大上限是2100万个比特币,而这个总量是由比特币协议中的发行规则决定的。比特币的发行是通过矿工挖矿所获得的奖励来实现的,这些比特币奖励每隔一段时间就会减半。通过减半机制,比特币的发行速度逐渐减缓,最终会达到 2100 万个比特币的上限。 比特币减半的目的是确保比特币的稀缺性和价值,从而鼓励更多的人参与到比特币挖矿中来。随着比特币的区块奖励的减半,挖矿变得更加困难,但挖到新的比特币的价值也会随之增加。这意味着只有最优秀的矿工才能挖到新的比特币,而其他矿工则可能会放弃挖矿或寻找更加有利的方式来挖掘比特币。因此,比特币减半是一个重要的机制,可以帮助比特币网络保持稳定和安全,并且确保比特币的稀缺性和价值。 因此我们并不难理解比特币在较长的周期里保持价格相对稳定(69000 刀跌到 17000 刀,我写这里的时候差点笑出声)的根源逻辑,以及可以预见的未来这个东西潜在的价格锚定方式。 ¶矿机史 比特币矿机的发展历程可以分为以下几个阶段: CPU挖矿阶段(2009年-2010年)- 在比特币刚开始的时候,由于参与者比较少,使用普通计算机的CPU也可以进行挖矿。比特币创始人中本聪在刚开始时也是使用普通计算机挖矿,当时的难度系数很低,每个区块的奖励也很高。然而,随着比特币的知名度和参与者数量的增加,CPU 挖矿很快变得不够快和有效了。 GPU挖矿阶段(2010年-2013年)- 由于 CPU 挖矿效率不高,比特币矿工开始尝试使用图形处理器(GPU)来加速挖矿。GPU 比 CPU 更擅长进行并行计算,因此可以在相同时间内完成更多的哈希运算,从而提高挖矿效率。这一阶段的发展阶段比较长,期间出现了很多厂商开始生产专门用于挖矿的GPU矿机,比如 ATI(现在的AMD)和 Nvidia 等。GPU 挖矿在挖矿效率上的提高让越来越多的人开始关注比特币,这也导致比特币价格的不断上涨。本冤种也是在这个阶段挖了几十个 BTC,然后让他们消散在了尘埃里。千万元,谈笑间,灰飞烟灭。 FPGA挖矿阶段(2013年-2014年)- 随着比特币的知名度和参与者数量的增加,GPU挖矿也开始变得不够快和有效了。于是,矿工们开始尝试使用基于现场可编程门阵列(FPGA)的挖矿硬件。FPGA可以根据不同的挖矿算法进行编程,因此具有更高的灵活性和效率。FPGA挖矿硬件的出现也让比特币网络中的算力得到了进一步提升。 ASIC挖矿阶段(2014年至今)- ASIC(专用集成电路)挖矿硬件是目前比特币矿机的主流,也是比特币挖矿的最高效方法。ASIC是为特定算法设计的硬件,比如比特币挖矿算法SHA-256。与通用计算机不同,ASIC芯片可以大幅度提高计算速度,使得比特币的挖矿效率得到了极大的提高。现在的ASIC矿机通常由专业制造商生产,包括比特大陆、嘉楠耘智、英特尔等,这些专业制造商生产的ASIC矿机通常在挖矿效率、能耗等方面都有着非常大的优势,使得个人矿工逐渐被淘汰。此外,由于ASIC矿机的价格相对较高,需要投入大量资金购买才能参与比特币挖矿,因此比特币矿业也逐渐从个人矿工转向专业矿业,而矿池的兴起也加速了这一趋势。目前,比特币矿机的发展已经进入到 ASIC 的多代产品迭代和优化的阶段,各家厂商不断推出更高效、更节能的 ASIC 矿机,以争夺市场份额。 事实上到了阶段 4 以后,个人无论用什么非专业设备进行比特币挖矿,都是镜花水月纯粹徒劳。挖一年可能连最低限度的转账数额都达不到,仿佛和笑话一样。 总的来说,比特币矿机的发展经历了从 CPU 挖矿到 GPU、FPGA、ASIC 挖矿的演进过程,随着技术的不断进步,挖矿硬件的效率也在不断提高。目前,比特币挖矿已经成为一项需要大量投入资金、专业技术和优秀运营能力的产业,而且在未来,挖矿的环境和形势也将面临更大的挑战和变化。而且从实际现状来看,政策和管制方面的压力占比会越来越高。 ¶动手开采你的第一个 0.00000001 BTC 如果你能读到这里,我首先还是得赞叹以下您的耐力。如果看完上面那么多晦涩的术语以后你还希望尝试挖一下比特币过个瘾,或者说哪怕获取一聪(satoshi,最小比特币单位,为 0.00000001 BTC)的比特币都能让你不再抱有遗憾的话,那么我会帮助你。 仅从个人的观点来说,此时此刻 2023 年春季,最适合个人参与比特币挖矿的方式,一定不是像个傻逼一样在那边假如算力池进行开挖。而是通过类似 NiceHash 这样的算力贩卖巨头,来出售你手头图形卡的浮点算力,并且让 NiceHash 之流对你的算例进行 BTC 结算。你可以很容易通过一两天的挂机获得上百聪的比特币。 本文无意于搬运冗长的 Step-by-step NiceHash 搬砖教程,如果真的感兴趣,可以参考以下视频,如果看的走火入魔,本人仅为无意间分享,对读者具体行为概不知情。 教你每天自动挖收益最高的币种 用Hive OS挖NiceHash收益最大化教程 那么,祝诸君财运昌隆,显卡早日冒烟。 ¶一些重要的忠告 挖矿在中华人民共和国境内违法 挖矿损害广大游戏玩家和人工智能行业的基本权益 挖矿现在基本不赚钱,触发停机价格是常态 个人挖矿,有一个是一个,都是傻逼

2023/2/27
articleCard.readMore

加密货币杂谈(一):创生与加密

其实这是一个钱包制作教程(不) ¶本人和比特币 其实写下这个标题的时候我还挺害怕有人觉得我进了诈骗团伙的,不过我更多的还是想从旁观者的角度写一下加密货币和区块链,顺便聊聊现在互联网上到处充斥的,让人厌烦的 DeFi。假如这个系列的文章可以帮到哪怕一个对加密货币有兴趣的人,拯救一个可能陷入加密货币合约深渊的人,那么这些码字的时间就不仅仅是自娱自乐。 其实我和比特币的渊源应该始于 2010 年的某个夏天(也许是秋天),班级里的一位董姓同学在聊天中提到了比特币,并且发表了对其未来前景的巨大期待,认识我的人应该知道这是哪位神仙。我当时的第一反应是 “小可爱”。作为一个稚气未脱的青少年,当时的自己不但对密码学一无所知,对微观/宏观经济依然一无所知,更对人性一无所知,所以只是应和了一下。不过好奇心驱使之下,在那个周末我还是照着网上的 Step-by-Step 教程在家里的破电脑上挂了个 Miner 开始挖币。 后来接触比特币的人应该无法想象当时有多容易挖币,没记错那会还是单 GPU 挖的动的时代,很短时间内我就挖了数十个币,然后就再也没有打开那个 Miner。直到不久之后电脑重装,那几十个币就随着数据消散在这片星空里了。后来再一次接触 BTC 应该是 15 年那轮牛市的时候,在大学的寝室里尝试下了一个开源的 Miner 挖了挖,发现收益几乎为 0,就完全忘记这回事情了。后面的事情大家也都知道了,比特币的挖掘很快进入了单矿工贡献归零的时代,挖比特币这件事情再也不属于普通人了。最后就是趁着今年春节的空闲,我再次花了一些时间看了一下加密货币这些年的发展和历史,倒觉得这是一个相当有意思的历史故事了。 聊到这里,我并不是惋惜错过了一笔千万巨款,而是惊讶于通过未来的回看,我们会看到一个多么光怪陆离的世界。这也是为什么在加密币成为路边老太都能说上两句的东西的时候,我一直拒绝再次进入这个市场的原因之一(恐惧亦为没有投资才能的一种);当然另一个拒绝的理由是 DeFi 本身的恶。我会尝试在未来的更新谈谈里对 DeFi,山寨币,合约这些东西的个人看法。 把话题拉回来,让我们来聊聊加密货币的创生与加密。 ¶原初的创世 很多人在提到加密货币和区块链的时候经常会提到创世,无论是创世区块还是创世交易,亦或者是比特币这个被尊为创世货币本身的存在。但是我想说的是,一个人只要对 中本聪 (サトシナカモト) 创世论文 Bitcoin: A Peer-to-Peer Electronic Cash System 进行过最粗略的主题阅读,就不可能忽视在论文引用列表之首的 “b-money”。这里不得不提到其作者,Dai Wei。 在 Dai Wei 的个人主页,我们可以看到他引以为豪的成就主要集中于密码学和匿名技术,而在 “b-money” 本篇里更是直接描述了现代加密货币的三大根基。在文章内容 The creation of money 中首次提出了工作证明Proof of Work的概念: Anyone can create money by broadcasting the solution to a previously unsolved computational problem. The only conditions are that it must be easy to determine how much computing effort it took to solve the problem and the solution must otherwise have no value, either practical or intellectual.任何人都可以通过广播以前未解决的计算问题的解决方案来赚取加密货币。唯一的条件是必须很容易确定为了解决这个问题消耗了多少算力,否则这个解决方案将毫无价值。 而在 The transfer of money 交易的定义里,则直接提出了广播的雏形,而这毫无疑问可以认为是后世加密货币交易算法的核心概念: If Alice (owner of pseudonym K_A) wishes totransfer X units of money to Bob (owner of pseudonym K_B), she broadcaststhe message "I give X units of money to K_B" signed by K_A. Upon thebroadcast of this message, everyone debits K_A's account by X units andcredits K_B's account by X units, unless this would create a negativebalance in K_A's account in which case the message is ignored.如果 Alice(笔名 K_A 的所有者)希望转账 X 单位的钱给 Bob(化名 K_B 的所有者),她将广播由 K_A 签名的消息“我给 K_B X 个单位的钱”。经这条消息的广播,每个人都从K_A的账户中扣除X个单位,并且以 X 单位记入 K_B 的帐户,除非这会导致 K_A 帐户中的余额变成负数,而在这种情况下,交易广播将被忽略。 而令人震惊的是,“b-money” 这篇文章发布于 1998 年。这也是我个人更倾向于将 Dai Wei 认定为是区块链和加密货币的那位炼就风火雷电大千世界之人。 10 年后的创世论文 Bitcoin: A Peer-to-Peer Electronic Cash System (比特币白皮书) 更像是一个集大成者,在实践的意义上真正定义了加密货币从 挖掘 - 验证 - 一致性 - 网络协议 - 交易 的完整链条。我无意于抄一遍白皮书内容到博客,但是我确实希望每一个对区块链有兴趣的人读一下白皮书,白皮书早已被翻译成了多种语言的版本,中文版详见 比特币白皮书 - zh_cn。 PS 白皮书中一定要仔细阅读交易和时间戳这两个个章节的描述,这就是信任交易和根本定义。 至于后续比特币的价格神话,那就是另一个庸俗的脂粉故事了。 ¶小窥密码学 终于来到了正题,加密货币里的密码学。我觉得对多数读者来说,经典密码学著作的内容如果长篇累牍的出现在任何一篇博客里,符合直觉的做法一定是直接关闭页面。所以我想通过更有趣的,手把手的方法,来让大家对密码学在区块链的作用有一丢丢的察觉。因此我觉得手把手制作一个属于自己的冷钱包就非常合适。这个过程不涉及太多的复杂密码学知识,同时又足够有趣,有足够的正反馈,作为回报可以收获一个非常安全的比特币冷钱包,也不算是浪费太多时间。 对于那些对密码学有兴趣的怪胎,我无责任推荐以下相对公认的入门课程: Applied Cryptography (公开课) Introduction to Modern Cryptography, edition 3 Foundations of Cryptography Blockchain tutorial (Youtube) 至于比特币钱包的制作过程,下面几个加密算法是强烈建议看一下的,这会对理解整个钱包的制作有巨大帮助。目前比特币使用了SECP256K1、SHA256、RIPEMD-160、Base58 等加密算法。其中SECP256K1属于椭圆曲线签名算法(ECDSA);SHA-256和RIPEMD-160属于 HASH 摘要算法;Base58 则是比特币自己定义的编码方式,去除了Base64编码中的特殊字符和容易看错的字符。如果你理解的够深刻,甚至可以不借助任何现成的函数包,在程序中直接手撸一个比特币钱包生成函数。下面来看看各个算法的视频讲解: 什么是 SECP256K1 椭圆曲线签名算法(ECDSA) Blockchain tutorial 11: Elliptic Curve key pair generation 什么是 SHA-256 加密算法 SHA 256 | SHA 256 Algorithm Explanation 什么是 RIPEMD-160 加密算法 Hash Function: RIPEMD-160 什么是 BASE-58 加密算法 Blockchain tutorial 13.1: Base-58 encoding 当然,以上内容我自己都没完全看完。而密码学的探索是无止尽的深渊,真诚建议量力而行。 ¶制作比特币冷钱包 这里我们以 Python 为例,有两种方法可以比较快捷的创造一个本地的冷钱包。 其中最简单的一种是使用 bitcoin 包来直接创建,这么做的好处是没几行代码就可以瞬间构建一个你自己的比特币冷钱包。而稍微能够暴露钱包生成过程的方法的话,就是用 esdca, hashlib, base58 这些密码库来一步步制作钱包。 ¶0. 到底他妈的什么是钱包 我认为钱包这个叫法烂透了。因为现在大家口中的加密货币钱包本质是 一对密钥对 + 以此生成的地址字符串。这东西要更合适的说法应该用地址凭证可能会更好。无论是英语还是其他语言翻译里叫做钱包都挺让人有歧义。这东西本质是一个在交易链上证明拥有该地址字符串的凭证。里面既没有存钱,也没有容量。 所谓的钱包余额,只是整个交易链上所有经过这个地址,并且被其所对应的公钥加密的交易广播记录的 unit 总和,且这个 unit 永不为负。当然,如果不想纠结这么多,单纯理解成现实意义的钱包也不是不行,只是这个钱包需要用公私密钥对或者由此衍生的助记词-密码对来证明拥有权限。 ¶1. 使用 bitcoin 快速生成冷钱包 如果使用 bitcoin 是如此的简单,以至于仅需要以下几步: 安装 Python3 请根据 Python Setup and Usage 的指引来进行安装。 安装 pip 请根据 pip install 的指引来进行安装。 安装 bitcoin 库 pip install bitcoin 创建一个 Python 文件,并填入以下内容来利用 bitcoin 包生成公私钥匙对并生成钱包地址 touch WalletGen.pyvim WalletGen.py 在文件 WalletGen.py 中写入以下内容并运行 python3 WalletGen.py from bitcoin import *#生成私钥priv_key = random_key()print("私钥:", priv_key)#使用私钥生成公钥pub_key = privtopub(priv_key)print("公钥:", pub_key)#使用公钥生成地址address = pubtoaddr(pub_key)print("地址:", address) 如果一切顺利你将得到类似这样一个结果 私钥: b8cebbff9b1f23554a3ff0854dc8e061861e7fa62510af9c013c29880ade9149公钥: 04cde2815421ebbc1c843ce0391583d05f5dbf511afa591303ad6aff5115bb11ae18e8d8227ceef05766ddaa9cf385b428cb7b22e5e133da4075954be416520885地址: 13F7uqbhS2ja18PDGbYCpmY8QRdQP6qZDU 这里的地址就是你全新的冷钱包的地址了,我们可以前往 blockchain.com 进行验证 ¶2. 使用加密函数包来生成冷钱包 除了使用 bitcoin 这样的懒人包,其实我们可以用一些更加接近钱包生成算法的过程来实现冷钱包的制作。在开始之前我们先看一下比特币钱包的计算流程: 然后让我们开始按照下面的步骤动手制作新的比特币钱包: 首先安装各类必要的加密库和字符处理库 pip install hashlib ecdsa base58 binascii 生成一个新的 WalletGen2.py 文件,写入以下脚本 import hashlibimport ecdsaimport base58import binascii# 使用 ECDSA 库生成一个私钥:private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)print("ECDSA PrivateKey: ", private_key.to_string().hex())# 从私钥生成一个新的公钥:public_key = '04' + private_key.get_verifying_key().to_string().hex()print("ECDSA PublicKey: ", public_key)# 获取公钥的SHA256哈希值:hash_public_key = hashlib.sha256(binascii.unhexlify(public_key)).hexdigest()print("SHA256 - ECDSA PublicKey: ",hash_public_key)# 对上一步获得的哈希值,再次进行 RIDEMP160 哈希计算:ripemd_public_key = hashlib.new('ripemd160', binascii.unhexlify(hash_public_key))print("RIDEMP160 - SHA256 - ECDSA PublicKey: ", ripemd_public_key.hexdigest())# 在 RIDEMP160 哈希值得头部添加一个网络类型比特:versioned_public_key = '00' + ripemd_public_key.hexdigest()print("Prepend Network Byte to RIDEMP160: ", versioned_public_key)# 对带有头比特的 RIDEMP160 做两次 SHA256 哈希:hash = versioned_public_keyfor x in range(1, 3): hash = hashlib.sha256(binascii.unhexlify(hash)).hexdigest() print("\t| >SHA256 #", x, " : ", hash)# 截取两次 SHA256 后的哈希值的头 4 个字节并且添加到 带有头比特的 RIDEMP160 的尾部checksum = hash[:8]append_checksum = versioned_public_key + checksumprint("Checksum first 4 bytes: ", checksum)print("Append Checksum to RIDEMP160: ", append_checksum)# 用 BASE58 加密延长后的有头比特的 RIDEMP160 哈希,获得比特币钱包地址:btc_address = base58.b58encode(binascii.unhexlify(append_checksum))print("BitCoin Wallet Address: ", btc_address.decode('utf8'))# 利用私钥转换获得用于导入各种钱包软件的 WIF 密钥 (例如 imToken 等)private_key_hex = '80' + private_key.to_string().hex()first_sha256 = hashlib.sha256(binascii.unhexlify(private_key_hex)).hexdigest()second_sha256 = hashlib.sha256(binascii.unhexlify(first_sha256)).hexdigest()final_key = private_key_hex + second_sha256[:8]WIF = base58.b58encode(binascii.unhexlify(final_key))print("*** This WIF key could be import to any wallet app (eg. imToken) ***")print("Private Key WIF: ", WIF.decode('utf8')) 运行上面的新脚本我们可以得到以下内容: ECDSA PrivateKey: d405813b38050471677c03431f64a1769736677de8fe264ed30377ccb45b4cf0ECDSA PublicKey: 04a81282bbdd4d5034e762e272eee45bf7cf989a8b5760909cf6a6f1c53a5fd60a6fa1f0e640b5e877599d4d0783d0707e82273458758c48406efa6f486eabe7b1SHA256 - ECDSA PublicKey: 063a742946b430411855e3fa6c9a43d1312d1a199e8bc5f32c0f56b9096b633dRIDEMP160 - SHA256 - ECDSA PublicKey: 65646a8b7e051a28aa0fae829d9bf6c880f570cdPrepend Network Byte to RIDEMP160: 0065646a8b7e051a28aa0fae829d9bf6c880f570cd | >SHA256 # 1 : ef7c802427b65d8d5e7900cf44f7f8a84e17d786f03d2b908d4df6cc55b2d80e | >SHA256 # 2 : 16c210b39dd7fb2229162450a371d9af25e6c0a6cfed5da2ef63ee1a0b1cf542Checksum first 4 bytes: 16c210b3Append Checksum to RIDEMP160: 0065646a8b7e051a28aa0fae829d9bf6c880f570cd16c210b3BitCoin Wallet Address: 1AF7YR2ADVwjNv6rhV6vkBpATJoicKrqJr*** This WIF key could be import to any wallet app (eg. imToken) ***Private Key WIF: 5KRfPMacY6ahNb6kBFAVBgzYPXipsWNzCBEd31p7uiD2VTu4aQ4 我们可以再次前往 blockchain.com 进行地址验证 也可以将 WIF 导入 imToken 等软件进行后续的交易等行为 详细可以参考 Wallet import format ¶3. 別人的才是最好的 事实上不管你怎么样努力的去写一个尽善尽美的钱包生成脚本,结果都会是一个破破烂烂简陋不堪的东西。如果真的希望有一个趁手的工具包可以帮你生成任意想要的钱包,不限于比特币、以太坊这种主流钱包,那么我建议尽快前往 Top Wallet Generator 页面,找个 Star 数量高的成熟生成工具。本博客提供的脚本主要还是为了展示比特币钱包的生成原理和过程。 ¶结语 到这里为止,如果你跟着上面的步骤一步步做下来的话,应该已经成功获得第一个本地的冷钱包了。甚至可以多生成几个构成你自己的钱包组合来完成一些更有趣的交易操作。 那么在下一篇文章 加密货币杂谈(二):Mining 里我会详细讲讲如何最没有门槛的开始挖你第一个毫无意义且无法提现的 0.00000001 BTC

2023/1/29
articleCard.readMore

2022年年度总结

说来挺离谱的,万万没想到会在 COVID 的凌虐之下开始写今年的年终总结。不过总归老婆和自己的状态也从半死不活恢复到了可以勉强活动,那不如趁着年末大长假写点东西。 今年对自己来说算是一个非常非常重大的年份了。除却一些重要的人生变化之外,可能更多的是花了一些时间去思考自己到底想要什么样的生活方式。这其实是非常重要的,事实上我认为在过去二十多年的绝大多数时间里,我并没有认真的从自己和家庭的角度去思考过这个问题。当然这个展开讲有点长,不如先拉年度流水账吧。 ¶家庭 今年和老婆算了一个良辰吉日,领了结婚证。是一个数字 2 比较多的日子。当然因为一整年比较扯淡的封控,短时间里婚礼暂时没做打算。和老婆从大二上日语课的时候认识到现在也有七年了,真的是没想到时光过的那么飞快。 还记得第一次约会在古老的哈尔滨电影院,看的还是 4D 的《疯狂动物城》,座位一摇起来我就觉得完球了。结果眼睛一眨,一路走到了现在。 还有就是家里的两只咪咪马上快要 5 岁了,从家猫的年龄来说,也算是进入了壮年。虽然还称不上老猫,但确实明显感觉到一天里睡觉的时间在慢慢变长。希望两只咪咪可以健健康康吧。 ¶生活 惭愧的讲,今年的生活质量是下降的。上半年的超长封控自不必说,每天能吃饱就不错了;同时也是因为有一些中期的规划,导致提高了攒钱的比例。所以和之前大吃大喝每周黑珍珠米其林打卡的时候比,确实吃喝用度方面节约了不少。不过攒钱的快乐也是挺意料之外的。所以还是希望能够顺利完成中期的一些计划吧。 然后就是健身,在停了有差不多一年之后,再次开始了日常的健身。和之前不同,不再追求三大项的数据,而是开始尝试加长有氧的占比。人体毕竟是为了有氧运动设计的,短短半年也从 06:00 配速 1km 都很累开始,跑到了 24min 4km。明年上半年的目标的话,就先把心率降低下来。看看能不能练到 5km 6分钟配速心率不超过 150。 不过由于感染了新冠,最近一个月肯定是不会去健身了,就当是理直气壮的偷懒吧。 ¶工作 今年在工作上的变动还是比较巨大的。离开了成为サラリーマン之后的第一家公司,心动。说实话这些年对心动的感情还是非常深厚的,无论是在心动经历的项目还是遇到的人,都对我的职业生涯有非常巨大的帮助。而心动本身作为一家中型企业,确实有着相当不错的雇主口碑。 在心动这些年,首先是遇到了几位非常不错的 Mentor,给了我一些很不错的职业建议和指导。也遇到了几位非常有才华的朋友,和他们共事和吹逼的时光无疑是构建起我整个职业认知的重要过程。然后也是有幸重度参与了一个TPS项目的运维架构。说实话也只有在中型企业里,年轻从业者才有机会拿到这样的机会。 虽然五月的时候离开了心动,但是依然非常开心看到项目在 2022 的尾声拿到了国内的版号。祝福能取得好成绩和好流水吧。毕竟也是一同奋战了一年半的日日夜夜。 五月因为机缘巧合,加入了 Electronic Arts。说实话我还是挺惊讶自己一个从没去五眼留学的人能拿到这张纯英语工作环境的 Offer 的。非要说的话,每天洗澡的时候用英语自言自语好多年还是有点用处,但是这个习惯不管从什么角度看都有点精神疾病的特质。 外企的体验怎么说呢,确实如外界所说一般的爽。多到难以置信的假期,及其舒服的开发流程和 Review 体验,完善到离谱的 CICD 流程。对开发者来说是绝对的天堂。无论是文档还是沟通模式,都可以说是一种享受。希望在新的一年可以写更多高质量的工具链和业务代码吧。 今年主要的收获是花了不少时间写 Ruby 和 C,但是相对的,写 Go 和 Python 的时间大幅减少了。也许这就是一种冥冥之中的宿命,每年只能有时间专注一门强类型语言和一门脚本语言。然后就是花了相当长时间玩 Chef Infra。怎么说呢,这东西确实非常强大,Proxy-Client 的模式对于企业级的超大规模服务器管理,有着天然的优势。但是也是因为这个优势,以及其本身的设计特点,导致这东西的大规模升级是一场噩梦。但是工具就是这样,没有完美的存在,很多时间用的不好也只是在有些环节因为历史债,没有做好最佳实践罢了。云的方面主要还是在玩 Azure Pipeline,用的还不算精通,未来有空再分享吧。 还有就是本来今年打算好好看看 TypeScript,果不其然还是鸽了。明年再说吧。 至于来年的展望,大概是希望能够给 Linux Kernel 做一次有效提交。哪怕被邮件列表骂成狗也值得了。好在 Linus 写的开发规范文档还是非常不错的,Kernel Newbie 的文档也非常完备,应该能够顺利起步。然后就是如果有时间的话想看看 Kubernetes 的源代码,感觉对这玩意儿的理解遇到了瓶颈,再这么下去必然沦为 yaml 苦力不可避。 ¶结语 有一条没一条的写了一堆流水账,今年也算是结了。希望朋友们来年健健康康,事业顺利吧。 以上です。

2023/1/1
articleCard.readMore

LeetCode Weekly Contest 301 自我解题思路

这周的周赛 Weekly Contest 301 难得一扫之前两次 1AC 惨败的阴霾,比较顺利的干掉了前三道。简单聊聊自己的思路。 ¶Q1 2335. Minimum Amount of Time to Fill Cups 这道题其实属于 Tricky 题,也就是说严格来说并不是考察特定算法技巧,更多的其实是找特殊情况和归纳能力。 简单看一下题目条件: You have a water dispenser that can dispense cold, warm, and hot water. Every second, you can either fill up 2 cups with different types of water, or 1 cup of any type of water. You are given a 0-indexed integer array amount of length 3 where amount[0], amount[1], and amount[2] denote the number of cold, warm, and hot water cups you need to fill respectively. Return the minimum number of seconds needed to fill up all the cups. 总的来说就是有三种杯子, 冷、温、热, 各有若干个。每次可以往两个 不同的 水温要求的杯子的倒水;或者只往一个杯子里倒水。看看最少需要倒多少次可以倒满。 这个题目唯一需要注意的主要还是一类特殊情况。也就是量最多的那类杯子比其余两种杯子的总和还要多。那意味着多出来的部分两两相消是绝对消不掉的,只能一个个倒水。 然后其余情况下,就是两两相消的问题了,也就是直接 sum/2 + sum%2 就是需要操作的次数。那根据上述思路,代码自然就写出来了: int cmp(const void *a, const void *b) { return *(int*)a - *(int*)b;}int fillCups(int* amount, int amountSize){ qsort(amount, 3, sizeof(int), cmp); int res = 0; if (amount[2] > amount[0] + amount[1]) { res += amount[2] - amount[0] - amount[1]; amount[2] = amount[0] + amount[1]; } int sum = amount[0] + amount[1] + amount[2]; res += sum/2; res += sum%2; return res;} 这里稍微需要对杯子的 array 做一下 qsort() 处理。这样可以更方便获得数组的最大值。 ¶Q2 2336. Smallest Number in Infinite Set 这次第二道倒是挺非主流的。并不是让你实现一个功能函数,而是一串函数。让人回想起在 C 里面手撸优先队列的恶心感。不过题设倒是比较完备的 PQ 要简单不少: You have a set which contains all positive integers [1, 2, 3, 4, 5, ...]. Implement the SmallestInfiniteSet class: SmallestInfiniteSet() Initializes the SmallestInfiniteSet object to contain all positive integers. int popSmallest() Removes and returns the smallest integer contained in the infinite set. void addBack(int num) Adds a positive integer num back into the infinite set, if it is not already in the infinite set. 说白了就是会有一个 int 数组,然后里面都是正整数。然后需要写一系列函数 SmallestInfiniteSet(Init)、Pop、AddBack,作用分别是初始化一个正整数数组,移除并返回当前数组种最小的数,往数组里加入一个数(如果此时不存在这个数的话)。 然后测试用例的话本质会输入两个显式入参和一个隐式入参。显示的分别为: 描述了函数调用数据的字符串数组。 描述上一数组入参的字符串数组。 隐式入参为: 完备无限正整数合集。 由于本题的 int 数组是有元素数量上限的,所以解决起来其实就比较舒服: 1 <= num <= 1000 At most 1000 calls will be made in total to popSmallest and addBack. 思路首先是描述 SmallestInfiniteSet 的变量结构。本质就是一个长度超过 1000 的 int 数组。没什么可以说的。 然后来到了初始化函数,这里初始化函数本质是就是创建一个上面刚刚定义的 SmallestInfiniteSet 结构体,然后对其进行 malloc 和初始化赋值。 接下来就是最困难的部分,Pop 函数赋值。这里就是我经常吐槽的 C 语言恶心的地方了,其他语言一行切片语法就能解决的问题, C 你必须手撸一个 for 循环来处理。内容倒是很简单,把数组中遇到的第一个非 0 整数找出来。 由于后续说明里提到了 C 语言下系统会帮你调 Free 函数,所以只要找值为 0 的 index 就可以了。 而 AddBack 就更简要了,只需要把下一次 Pop 的触发指标加入数组即可。也就是把在 index = num 的元素设置为 0 即可。 typedef struct { int nums[1001];} SmallestInfiniteSet;SmallestInfiniteSet* smallestInfiniteSetCreate() { SmallestInfiniteSet *sis; sis = malloc(sizeof(SmallestInfiniteSet)); memset(sis, 0, sizeof(SmallestInfiniteSet)); return sis;}int smallestInfiniteSetPopSmallest(SmallestInfiniteSet* obj) { int i, tmp; for (i = 1; i < 1001; ++i) { if (obj->nums[i] == 0) { tmp = i; obj->nums[i] = i; return tmp; } } return i;}void smallestInfiniteSetAddBack(SmallestInfiniteSet* obj, int num) { obj->nums[num] = 0; return;}void smallestInfiniteSetFree(SmallestInfiniteSet* obj) { free(obj); return;}/** * Your SmallestInfiniteSet struct will be instantiated and called as such: * SmallestInfiniteSet* obj = smallestInfiniteSetCreate(); * int param_1 = smallestInfiniteSetPopSmallest(obj); * smallestInfiniteSetAddBack(obj, num); * smallestInfiniteSetFree(obj);*/ ¶Q3 2337. Move Pieces to Obtain a String 这道题是这次 Contest 我自己最喜欢的一道题目。怎么说呢,个人偏好更喜欢 Tricky 的题目,这可能也是因为自己基础不扎实的原因。不过言归正传,来说说着这道题。 题干不复杂,就是给你两个字符串 start & target,长度一致,且只由三种字符 L、R、_ 组成。然后我们可以对 start 里的 L、R 字符做操作。其中: L 能且只能和左边的 _ 做位置交换,次数无限。 R 能且只能和右边的 _ 做位置交换,次数无限。 如果经过不限次的位置交换后,start 可以变成 target,则返回 true,反之返回 false。 这里其实思路是,要把 _ 看作一个特殊字符,因为是可以被交换的。同时,由于 L 和 R 的位置不能交换,所以 start 和 target 必须满足以下条件,返回才是 true: start 和 target 去掉所有的 _ 字符后,应该是完全一模一样的数组。 任何一个 start 中的 L 都不应该比其在 target 中对应的那个 L 更靠左。 任何一个 start 中的 R 都不应该比其在 target 中对应的那个 R 更靠右。 上面三个条件一出,那其实伪代码都已经有了,所以直接写答案即可: bool canChange(char * start, char * target){ int len = strlen(start); int p1 = 0, p2 = 0; while (p1 < len && p2 < len) { while (start[p1] == '_') {p1++;} while (target[p2] == '_') {p2++;} if (start[p1] != target[p2]) { return false; } else { if (start[p1] == 'L') { if (p1 < p2) { return false; } } else { if (p1 > p2) { return false; } } p1++; p2++; } } return true;} Thank u for reading. Regards,

2022/7/10
articleCard.readMore

聊聊排版和『盘古之白』

知识的诅咒 (Curse of knowledge),我和不止一位同事或朋友料到过这个词。通俗的解释是: 专家盲点(Curse of knowledge)是一种认知偏差。指人在与他人交流的时候,下意识地假设对方拥有理解所需要的背景知识,Robin Hogarth 首先提出该名词。专家盲点也是教育的重大阻碍之一。 而今天在互联网冲浪的时候,非常后知后觉地从一位推友处听到了一个新词 “盘古之白”,经过一番 Wiki 翻阅,很快豁然开朗。所谓的盘古之白,最通俗的解释莫过于: 每次看到網頁上的中文字和英文、數字、符號擠在一塊,就會坐立難安,忍不住想在它們之間加個空格。這個外掛(支援 Chrome 和 Firefox)正是你在網路世界走跳所需要的東西,它會自動替你在網頁中所有的中文字和半形的英文、數字、符號之間插入空白。漢學家稱這個空白字元為「盤古之白」,因為它劈開了全形字和半形字之間的混沌。 事实上这并不是我第一次接触到盘古之白这个概念,大概在 2-3 年前的文档写作中,以及在 LaTeX 学习的过程中,面对中英文混排的标准时,便已经或多或少被灌输了中英文之间留白的概念。但是这种模糊或者过于繁复的概念或者标准,并没有办法让一个低级的碳基生物能够极度直观的描述这样一种写作规范,直到今天看到了这个词:“盘古之白”。 这个词是如此的精妙与准确,以至于直接触发了 知识的诅咒 (Curse of knowledge) 效应。就在这西元 2022 年 06 月 23 日的夜晚,我丧失了容忍非标准排版的中文文档的能力。这种感觉好似丧失了初夜,又好似第一次进入 Goregrish 看到了人类之恶一般,那道门一旦跨过去了,就有种世界变天的质感。 我无法遏止地回看过去几年自己写过的技术文档,看到满篇粘连在一起的中英文字符,看到在 root 命令之前的 $ 和普通用户之前的 # 引4,看到杂乱无章的盘符引4。这些混沌的文字让人一刻都无法容忍,就如同诅咒一般萦绕在脑海。 然而文档浩淼,早已没有精力逐一修复,能做的也仅仅不过是在未来所有的文档编写和迁移时,为这一隅 “盘古之白”,留下该有的 Space 罢了。 恰好今日迁移了一篇旧时的博客,可以一窥有无 “盘古之白” 所带来的极大的阅读观感的区别: Switch atmosphere 大气层 9.2.0 升级 10.0.2 踩坑详解(盘古之白修复版) Switch atmosphere大气层9.2.0升级10.0.2 踩坑详解(简书原文) 在 这里 引2 可以看到目前有在认真严格执行这个标准的公司,属实我辈楷模。 愿君今夜与我一同浑身发痒,辗转难眠 (乐)。 引用 vinta / pangu.js sparanoid / chinese-copywriting-guidelines mzlogin / chinese-copywriting-guidelines OpenSUSE / 格式 Wikipedia / 知识的诅咒

2022/6/24
articleCard.readMore

LeetCode 括号问题详解

如果经常刷LeetCode的话,其实应该会对有一道题目印象非常深刻。那就是括号问题 [20] Valid Parentheses 。第一次看到这道题的话多少都会楞一下,因为乍一看题目内容非常的简单,无非是判断括号闭合的合法与否,但是仔细想想其实也有不少坑。尽管这道题是LeetCode中序号最小的堆栈问题,但是第一次做不一定能反应过来是堆栈。 这类题在LeetCode中一共有三道。分别是: [20] Valid Parentheses Easy [921] Minimum Add to Make Parentheses Valid Medium [1541] Minimum Insertions to Balance a Parentheses String Medium 但是很离谱的是,我个人做下来的感觉 [20] Valid Parentheses 才是Medium的难度,剩下两道毫无疑问是Easy难度的, 甚至它们因为太简单可能做的时候不一定能直观感受到堆栈的思想。当然,后两题如果追求极限的低复杂度(比如O(1))的话,还是很有挑战的。 还是来聊聊这类题目的基本思路吧,这里先以 [20] Valid Parentheses 为例。其实基本思路不复杂,如果你理解堆栈的概念,其实一瞬间就能想明白是怎么一回事情。 +────+────+────+────+────+────+| { | [ | ( | ( | [ | ( | <-- ')' Accept+────+────+────+────+────+────+| +────+────+────+────+────+────+--> | { | [ | ( | ( | [ | X | <-- '('&')' Clear +────+────+────+────+────+────++────+────+────+────+────+────+| { | [ | ( | ( | [ | ( | <-- ']' Dismatch+────+────+────+────+────+────+ 从上图可以看到对于每一个左括号,我们都按照顺序(先进后出,后进先出)放入堆栈中。但是对于右括号,我们要做的是两个核心步骤: 将其和堆栈最上层的那个左括号进行对比,如果成对,则安全匹配。 完成匹配后,将这个最上层的左括号消掉。然后如此循环往复,如果能够消除干净,则字符串Syntax合法。 从上面两个步骤又可以分别写出两个对应的逻辑,对于步骤一,所谓的对比就是对右括号本身取对应的左括号: char left(char c) { if (c == 41) {return 40;} else if (c == 93) {return 91;} else {return 123;}} 对于步骤二,只需要对整个输入的字符串进行逐字对比,并不停调用步骤一的left(char c)。 for (int i = 0; i < len; ++i) { if (s[i] == 40 || s[i] == 91 || s[i] == 123) {++idx; status[idx] = s[i];} else { if (left(s[i]) == status[idx]) {--idx;} else {return false;} }} 将两个函数进行拼合,并对边界情况进行处理之后,便是 [20] Valid Parentheses 的答案了(个人习惯用数字替代ascii码的对比): char left(char c) { if (c == 41) {return 40;} else if (c == 93) {return 91;} else {return 123;}}bool isValid(char * s){ int len=strlen(s); if (len == 1) {return false;} int *status = (int *)malloc(sizeof(int) * 10001); memset(status, 0, 10001); int idx = 0; for (int i = 0; i < len; ++i) { if (s[i] == 40 || s[i] == 91 || s[i] == 123) {++idx; status[idx] = s[i];} else { if (left(s[i]) == status[idx]) {--idx;} else {return false;} } } bool res; return res = (idx == 0) ? true : false;} 其实这类括号题目,最重要就是第一时间要意识到是堆栈的思路,其次是需要对括号字符串有取左或者取右的直觉。这样其实就可以超快速解题了。 下面来看看另外两道标注了Medium,实则更简单的括号类问题。 先以 [921] Minimum Add to Make Parentheses Valid 为例,这道题真的不知道是怎么被评定为Medium的,其实这里本质就两种情况:入栈的是左括号 & 入栈的是右括号。对于入栈的是左括号的情况,用一个状态变量status记录一下还欠缺右括号的数量。对于入栈的是右括号,如果左括号还有结余,也就是status > 0,直接进行堆栈抵消;对于左括号没有结余的情况,就必须让左括号进行增补,也就是++res;。事实上这已经是一个一维堆栈了,而 [20] Valid Parentheses 至少还是一个二维堆栈。 int minAddToMakeValid(char * s){ int len = strlen(s), status = 0, res = 0; for (int i = 0; i < len; ++i) { if (s[i] == 40) {++status;} else { if (status > 0) {--status;} else {++res;} } } return res + status;} 接下来我们再来聊聊 [1541] Minimum Insertions to Balance a Parentheses String。这道题完完全全就是 [921] Minimum Add to Make Parentheses Valid 的轻度升级版。唯一区别是把 ( & ) 的括号对异化为了 ( & )) 。这就意味着我们需要多做一件事情:把字符串先还原为Q921中的字符串,然后执行Q921的解法即可。这真的称不上是Medium难度。 需要新增的格式化步骤如下,主要是增加了对于单个右括号的增补处理,需要注意单个右括号在字符串最末尾的特殊情况: for (int i=0,j=0; i < len; ++i, ++j) { if (s[i] == 40) {new[j] = s[i];} else { if (i != len - 1 && s[i + 1] == 41) {new[j] = ')'; ++i;} else {new[j] = ')'; ++res;} }} 然后把格式化步骤和 [921] 的答案结合一下,即为 [1541] 的正确解法: int minInsertions(char * s){ int res = 0,len = strlen(s); char *new = (char *)malloc(sizeof(char) * 3 * len); memset(new, '\0', 3*len); for (int i=0,j=0; i < len; ++i, ++j) { if (s[i] == 40) {new[j] = s[i];} else { if (i != len - 1 && s[i + 1] == 41) {new[j] = ')'; ++i;} else {new[j] = ')'; ++res;} } } int i = 0, status = 0; while (new[i] != '\0') { if (new[i] == 40) {++status;} else { if (status > 0) {--status;} else {++res;} } ++i; } return res + status*2;} 总结: 这类括号问题的关键是什么相信也比较明了了,其实就是堆栈思路。无论是 [20] 的二维堆栈,还是后面两道题目的一维堆栈,其实本质是一样的。而难度本身也至多围绕其他方面展开,比如增加字符串预处理之类的。不过还是有点期待会不会在未来出现三维的堆栈。

2022/6/18
articleCard.readMore

聊聊个人博客这件事(三)

长文预警 好了,终于来到了聊聊个人博客系列的完结篇。本篇我会尽可能简单明了的聊一下我的个人博客所使用的博客框架的具体部署细节、所使用的主题、所进行的调优。如果能够在读者未来想到搭建自己的博客的时候,起到一丁半点的帮助,那便是不虚这些笔墨了。 我大体会从以下几块内容进行展开,同时这些内容也应当是一个从框架到细节的过程。当然由于篇幅所限制,会有一些内容可能无法被详细描述,我会在未来考虑针对某一些技术细节,单独写个Post来说清楚: 一、搭建和部署 安装、部署和生成 HTTPS和CertBot GitHub Action和自动化 防火墙问题 二、插件的选择和优化 三、CSS样式调整和EJS修改 折叠块的渲染 Tags和Categories页面支持 ¶一、搭建和部署 ¶安装、部署和生成 其实对于Hexo的部署来说,本质上并不是搭建一个Server,而是一个静态页面目录的生成器。对于个人博客这种载体来说,我个人认为静态页面是一个比Server更优的解法。理由有很多,比如静态页面博客可以很自由的部署到任何形式的服务器甚至SaaS平台上,甚至于诸如S3、OSS这样的对象存储。换句话说,一旦你选择了静态页面框架作为你的博客方案,你可以把你的博客运维框架大幅度的简单化。而简单化也会带来安全、管理、维护等多个方面的便利性,你可以关注于更少的事情,把有限的事情做的更好。 言归正传,开始介绍Hexo生成器。在开始部署之前,我们最好还是比较仔细的阅读一下以下几个页面: 什么是Hexo Hexo家目录结构 Hexo根配置详解 Hexo生成器简述 下面着重讲讲所谓的生成器:这其实就是Hexo框架在安装后,在HEXO_HOME目录下,快速生成静态页面目录的命令。这个操作是如此简单,以至于一共只有两个命令,就可完成重新生成的逻辑: /usr/bin/hexo clean/usr/bin/hexo generate 之后,如果配置和模块没有异常的话,生成器就会在$HEXO_HOME/public/下生成博客所需要的所有静态页面和依赖文件。但是需要注意的是,假如你通过类似Apache2、Nginx这类的负载均衡器来作为你的服务端口进程,那么在site-available的配置文件中,请尽可能不要直接把$HEXO_HOME/public/作为你的web服务的根目录。因为这样的话在页面重生成期间,会短暂的导致页面的显示异常。更加合适的方式是单独使用一个其他位置的目录,然后每一次在生成新版本的页面之后,通过rsync或者cp等命令,把新版的页面内容覆盖过去,这样可以大幅缩短博客的不可用时间。例如这样: cp -r $HEXO_HOME/public/* /var/www/hexo/public/ ¶HTTPS和CertBot 拥有了自己的博客以后,我们还是需要稍微为自己的博客的安全性负责的。这里并不是说这个博客有什么流量吸引歹人来攻击,而是关闭HTTP 80访问、强制HTTPS 443访问、使用全球授信的证书链作为SSL证书,是任何一个网站搭建过程中最最最根本的基本常识。是的,这里在明示我国大量政企事业单位的IT从业者(或部门的领导)是缺乏基本常识的(乐)。 相对于企业级昂贵的SSL证书,其实个人对于证书的选择面非常广阔且廉价。在完成页面的部署之后,假如你使用的是Apache2、Nginx这种主流的负载均衡,那么完全可以用CertBot来进行一键自动HTTPS配置、证书签发和部署,非常非常方便。此处以Nginx为例: # Install essential packages.$~ sudo apt install certbot python3-certbot-nginx# Edit nginx config files.$~ sudo nano /etc/nginx/sites-available/example.com#Find the existing server_name line. It should look like this:#/etc/nginx/sites-available/example.com#...#server_name example.com www.example.com;#...# Check Nginx config syntax errors.$~ sudo nginx -t# Reload Nginx config$~ sudo systemctl reload nginx# Check firewall rules if it is running.$~ sudo ufw status# Change rules to allos both http/https access.$~ sudo ufw allow 'Nginx Full'$~ sudo ufw delete allow 'Nginx HTTP'# Start obtaining an SSL certificate.$~ sudo certbot --nginx -d example.com -d www.example.com#Please follow the guidance from certbot to finish all the steps.# Check ssl auto renew status.$~ sudo systemctl status certbot.timer#You are supposed to see output like this.#● certbot.timer - Run certbot twice daily# Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; vendor preset: enabled)# Active: active (waiting) since Mon 2020-05-04 20:04:36 UTC; 2 weeks 1 days ago# Trigger: Thu 2020-05-21 05:22:32 UTC; 9h left# Triggers: ● certbot.service#To test the renewal process, you can do a dry run with certbot:$~ sudo certbot renew --dry-run 完成上面一系列命令以后,正常情况下代理Hexo静态页面的负载均衡服务,已经设置了有效的SSL证书,并且会不断的自动更新SSL证书,同时负载均衡的HTTP 80端口,也会被强制rewrite到HTTPS 443端口提供服务。 题外话,如果你想拥有一个有效期更长的个人证书,其实CloudFlare提供了长达15年的个人证书供免费用户下载,只能说强者恐怖如斯。 ¶GitHub Action和自动化 我是个懒狗。 所以我喜欢在尽可能少的页面完成博客的写作。 我喜欢MarkDown和LaTeX这样的标记式语言所带来的内容和排版的确定性,当然,LaTeX写起来有点点麻烦,所以常规情况下MarkDown是博客写作的不二之选。如果可以,我希望能够以这样的方式来进行博客的写作和发布: 在任意我喜欢的代码编辑器中,进行可以Preview的MarkDown文档编辑。 在完成任意文档、草稿、CSS和EJS调整之后,只需要一个简单的命令,就可以在远程的服务器和对象存储中,生成、发布并更新博客内容。 由于目前这个博客的代码托管在了GitHub上,所以最简单的方法就是直接使用GitHub Action来作为自动化的方案。事实上现代的代码托管平台,例如GitHub、GitLab、乃至于Azure这样的云服务,对CICD Pipeline的支持都已经非常出色了,任何有稍许计算机知识的人都可以快速搭建自己的CICD工作流。 那么言归正传,在这个博客搭建的一开始,我就写好了对应的GitHub Action,当时写的时候主要是两个目标: 不引入第三方的容器镜像,原因是我不想再审一遍别人的dockerfile,而且引入镜像会让Action很慢。 尽量少的Step,这样无论是debug还是执行都会很简单。 因此,在这个工作流中,只使用了官方ubuntu-latest作为运行镜像。以下便是本博客在使用的GitHub Action Workflow: name: Publish Blogon: [push, pull_request]jobs: build: name: "Publish My Blog" runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/master' # needs: test steps: - name: Configure SSH run: | mkdir -p ~/.ssh/ echo "$SSH_KEY" > ~/.ssh/kivinsae.key chmod 600 ~/.ssh/kivinsae.key chmod 700 ~/.ssh cat >>~/.ssh/config <<END Host kivinsae-blog HostName $SSH_HOST User $SSH_USER Port 22 IdentityFile ~/.ssh/kivinsae.key StrictHostKeyChecking no END env: SSH_USER: ${{ secrets.KIVINSAE_BLOG_USERNAME }} SSH_KEY: ${{ secrets.KIVINSAE_BLOG_RSAKEY }} SSH_HOST: ${{ secrets.KIVINSAE_BLOG_HOST }} - name: Clone the master branch of kivinsae-blog run: git clone git@github.com:KKtheGhost/kivinsae_blog.git --config core.sshCommand="ssh -i ~/.ssh/kivinsae.key" - name: Generate the config.yml from templates run: | cp -rf _config.module.yml _config.yml cp -rf node_modules/hexo-theme-landscape/_config.module.yml node_modules/hexo-theme-landscape/_config.yml working-directory: ./kivinsae_blog - name: Replace the SECRETS in config.yml run: | sed -i "s/TAG_ENCRYPT_PSWD/$ENCRYPT_PSWD/g" _config.yml sed -i "s/TAG_PRIVATE_PSWD/$PRIVATE_PSWD/g" _config.yml sed -i "s/LEANCLOUD_APPID/$LC_APPID/g" node_modules/hexo-theme-landscape/_config.yml sed -i "s/LEANCLOUD_APPKEY/$LC_APPKEY/g" node_modules/hexo-theme-landscape/_config.yml working-directory: ./kivinsae_blog env: ENCRYPT_PSWD: ${{ secrets.BLOG_ENCRYPT }} PRIVATE_PSWD: ${{ secrets.BLOG_PRIVATE }} LC_APPID: ${{ secrets.LEANCLOUD_APPID }} LC_APPKEY: ${{ secrets.LEANCLOUD_APPKEY }} - name: Install NPM and OSSUTIL run: | curl -fsSL https://deb.nodesource.com/setup_19.x | sudo -E bash - &&\ sudo apt-get install -y nodejs sudo -v ; curl https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash - name: Generate the Blog content run: | npm install ./node_modules/hexo/bin/hexo clean ./node_modules/hexo/bin/hexo generate working-directory: ./kivinsae_blog - name: Configure OSS run: | touch ~/.ossutilconfig chmod 644 ~/.ossutilconfig cat >>~/.ossutilconfig <<END [Credentials] language=EN endpoint=oss-accelerate.aliyuncs.com accessKeyID=$OSSID accessKeySecret=$OSSSECRET END env: OSSID: ${{ secrets.ALIYUN_KEYID }} OSSSECRET: ${{ secrets.ALIYUN_KEYTOKEN }} - name: Upload artifacts to OSS run: ossutil64 sync ./kivinsae_blog/public/ oss://kivinsae-blog-web/ --delete --force 实质上,这个工作流一共只有一个step,三个主要任务。分别是: 初始化容器,配置容器的ssh rsa私钥和.ssh目录权限,配置目标服务器信息。 确保远程服务器上的更新脚本拥有可执行权限。 远程执行服务器上的自动更新、清理、Hexo生成脚本。 同时,工作流文件声明了只有在push到主分支,或者PR到主分支的前提下,才会触发运行。 通过这样的工作流,我只需要在本地IDE进行文档的书写和编辑,然后通过COMMIT_MASTER脚本进行主分支推送就可以了。然后在10-20秒后,我的博客就会无感且自动地更新所有新编辑的内容。而且我可以确保所有的变更,都是通过版本管理进行跟踪的,永远不需要担心任何一个修改发生在了自己想不起来的地方。 ¶防火墙问题 由于一些众所周知的问题,绝大多数脑回路正常的工程师都不会把自己博客服务器的本体放在中国大陆。但是大量的海外机房由于另一个众所周知的原因,国内直接访问是会有很大的问题的。所以这个月我花了一些时间来处理从中国大陆访问这个博客的问题。 这个时候,Hexo的静态页面的好处就体现出来了。我不希望花不必要的服务器费用在国内重新搭建一个镜像服务器,同时我也希望国内的访问能够保持稳定快速。那么这个时候,阿里云海外地域的OSS的静态网站托管服务似乎成了一个最佳的选择,因为阿里云的海外节点只要你不作死是基本不会被墙的,算是一个非常好的选择。 说到阿里云OSS静态网站托管,具体的部署方式可以参阅这篇文章。其本质上就是阿里在OSS的基础上为用户额外提供了一层负载均衡代理,同时用户还可以白嫖阿里云OSS加速节点的全球加速效果(严格来说是要钱的,但是考虑个人站点的一丝丝流量,约等于不要钱) 而我这边其实只需要在自动生成脚本的最后面增加一行命令,就可以完成这个节点的同步: ossutil64 sync /var/www/hexo/public/ oss://<bucket_name>/ --delete --force 利用阿里云提供的ossutil64工具,我们可以非常方便把本地静态页面目录的所有内容同步到OSS上,并立刻生效。至此,我的博客的全部部署问题已经全部解决。下面是一张架构图,有助于读者更好理解这个架构的拓扑结构: ¶插件的选择和优化 Hexo拥有相当数量的插件开发者,因此Hexo的插件选择是很丰富的。不过我个人还是遵循了就简原则,尽量少的引入第三方插件,以下为目前的插件列表: "hexo-bilibili-card": "^0.6.0","hexo-blog-encrypt": "^3.1.6","hexo-renderer-ejs": "^2.0.0","hexo-renderer-markdown-it": "^6.0.1","hexo-ruby-character": "^1.0.6","hexo-theme-landscape": "^0.0.3","nodejieba": "^2.6.0","valine": "^1.4.18" 其中hexo-bilibili-card、hexo-blog-encrypt、hexo-ruby-character的效果可以在 Hexo Blog plugins test field 中查看,分别用于页面内容的丰富化呈现。 hexo-renderer-ejs为Hexo默认主题landscape自带的ejs解析插件。hexo-renderer-markdown-it为MarkDown解析加强插件。hexo-theme-landscape为Hexo默认主题本身。nodejieba为中文分词函数库。 需要着重讲一下是评论插件valine,具体的插件介绍和安装指南可以参考 Valine官网 。随着韩国几款主流的论坛评论插件例如livere之流对网络的要求愈发变态,而Gitalk又是需要评论者登录GitHub的,这些插件的使用变得越来越麻烦,无论是对博主还是访问者来说。 目前看来,valine算是中文博客目前相对更好的一个评论插件选择了。但是诡异的是,valine本身的插件配置中,CDN却选择了墙外的服务,因此墙内用户在访问装有valine插件的页面的时候,很可能会遇到加载极慢的问题。所以,对于这个插件我是做了一些优化的。在当前使用的主题的_config.yml中找到valine对应的配置块,额外添加一条子配置valine并按照如下面格式添加jsdelivr的CDN加速地址即可。 valine: valine: //cdn.jsdelivr.net/npm/valine@1.4.18/dist/Valine.min.js #为了cd加速 enable: true 按照官网的指引流程和上面的优化方法进行配置之后,博客的所有评论都将存储到LeanCloud的服务实例中,并且拥有相当不错的加载速度。 ¶CSS样式调整和EJS修改 这里的调优其实很多时候是出于对landscape这个默认皮肤的无奈。Hexo毕竟还是一个开源项目,在这段时间的使用过程中,还是发现了很多其本身的问题。好在应对方法也不复杂,只要对html、css和JavaScript有最低限度的了解和语法知识就可以轻松解决。 ¶折叠块的渲染 首先是<details>语法块的问题,Hexo对折叠内容的默认渲染可以说用惨绝人寰来形容,就是压根没有css式样,所以当博客需要使用折叠快的时候,我们需要手动添加一些关于折叠块的属性来确保视觉上勉强能看: <details style="box-shadow: 2px 2px 5px; border-radius: 6px; padding: .5em .5em .5em;"> <summary><b>【鬼谷说】菊石(其一):旧神的涅槃</b></summary> <div style="position: relative; padding-bottom: 75%; height: 0;"> <iframe width="600" height="450" src="https://player.bilibili.com/player.html?aid=597067931&bvid=BV16B4y1X7ap&cid=734651916&page=1&high_quality=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe> </div> <br></details> 具体的呈现就会是这样一个效果。虽然依然很丑,但是至少能看。 【鬼谷说】菊石(其一):旧神的涅槃 ¶Tags和Categories页面支持 非常糟心的一件事是,landscape主题似乎没有对Tags和Categories的根页面进行ejs的支持。如果用户单纯地跟着官方文档通过hexo命令行创建一个source/tags/index.md或者source/categories/index.md页面的话,在博客中其实是无法正常显示的。而在主题的layout目录中,原始的tag.ejs和category.ejs又被其他页面功能调用,所以,尽可能不要随意修改它们。 那么,为了让博客在landscape主题中可以正常显示Tags和Categories页面,我在layout目录下需要额外创建两个ejs模板文件,用于让这两个页面可以正常渲染和呈现,代码如下: <!-- node_module/hexo-theme-landscape/layout/tags.ejs --><article id="post" class="article article-type-post" itemscope itemprop="blogPost"> <div class="article-inner"> <header class="article-header"> <h1 class="article-title" itemprop="name"> <%= page.title %> </h1> </header> <div class="article-entry" itemprop="articleBody"> <% if (site.tags.length){ %> <%- list_tags({show_count: true}) %> <% } %> </div> </div></article> <!-- node_module/hexo-theme-landscape/layout/categories.ejs --><article class="article article-type-post show"> <div class="article-inner"> <header class="article-header"> <h1 class="article-title" itemprop="name"> <%= page.title %> </h1> </header> <div class="article-entry" itemprop="articleBody"> <% if (site.categories.length){ %> <%- list_categories(site.categories, { show_count: true, class: 'category', style: 'list', depth: 3, separator: '' }) %> <% } %> </div> </div></article> 在添加了上述ejs模板后,只要在source/tags/index.md或者source/categories/index.md中分别添加对应的layout配置即可: # source/tags/index.md---title: 𝑩𝒍𝒐𝒈 𝑻𝒂𝒈𝒔date: 2022-06-14 10:00:13layout: tagscomments: false--- # source/categories/index.md---title: 𝑩𝒍𝒐𝒈 𝑪𝒂𝒕𝒆𝒈𝒐𝒓𝒊𝒆𝒔date: 2022-06-14 10:00:13layout: categoriescomments: false--- 之后修改node_module/hexo-theme-landscape/_config.yml,在顶部菜单栏中添加对应的页面路径,然后重新生成页面后,就可以在博客中访问正常的Tags和Categories页面了。 以上就是关于本博客建设的简要技术说明,未来如果有时间我会更新一些关于博客建设的细节和Hexo本身调优的心得。 感谢阅读

2022/6/15
articleCard.readMore