yellow Volkswagen van on road

失望的横店之旅

很久没有出来玩,就飞了一趟杭州,见了老朋友妙正灰,就匆匆来到了横店,毕竟作为中国影视城的头把交椅,横店影视城还是让我比较期待的。

不过,实际来到横店后,却没有那么美好。

  1. 交通不便:横店全称应该是浙江省义乌市东阳市横店镇,没错。名满全国的横店只是一个镇。而且是一个离义务比较远的镇。我从义乌站出发,到横店我住的酒店大概花了一个小时多一点。着实不算近。除非你是自己开车来玩,不然确实体验一般。
  2. 基础设施一般:横店影视城固然声名远扬,但毕竟横店只是一个镇,在生活的各方面基础上还是不如我们所熟悉的大城市。吃的方面完全不如我们熟悉的大城市。如果你指望来横店吃点好的,可能是没有戏了。
  3. 影视城粗制滥造:横店影视城拍了很多电影,所以我以为会非常不错,但实际去了明清宫苑和香港街·广州街之后,我觉得他们在影视城的实际建设上可能并没有花费的太多心思,我只能感叹说,可能现在的影视剧集的滤镜给的太足了,我在电视中完全看不到这么粗制滥造的基础设施。

不过,横店还是有一些不错的:

  1. 艺人确实多:吃晚餐和吃早餐的时候,我各自碰到一组艺人,相比于你在其他城市(除北京外),可能确实会更容易碰到艺人。
  2. 酒店还不错:我这次来住的是双十一买的横店酒店 +门票套餐,住的是百老汇大厦,这个酒店虽然离市区稍微远了一些,但也更加安静。服务什么的也不错。作为一个度假酒店应该还是比较舒服的。

总结

总的来说,横店如果你有更好的选择,不值得去。不过这个地方蛮适合带小孩子来,一下子带小孩子们拍拍照,也不错。

black and white penguin toy

LCTT 从Travis CI迁移到 GitHub Action 实践

LCTT 的 CI 已经在 Travis CI 上运转了多年,一致保持着良好的使用体验。自 2019 年 Github 推出了自家的 CI 工具 Github Action 后,我们就在考虑将 CI 从 Travis-CI 迁移到 Github,以降低维护和沟通的成本,并借助于 GitHub Action Marketplace 实现更强的功能。

项目首页
项目首页

最近,因为 TravisCI 屡屡部署出错,而我们的账户因为使用的较多,已经超出了免费使用的限制,以此为契机,将 CI 从 Travis CI 迁移到 GitHub Action。

Travis CI 的提醒
Travis CI 的提醒

项目介绍

Translate Project 是 LCTT 翻译组的主要协作项目,几百位译者通过 GitHub 进行围绕开源、Linux、软件工程等领域的文章翻译,从 2013 年来,累计了大量的 Commit,致使项目下有非常多的文件。

Transalte Project 借助于 CI 帮助译者进行基本的文章格式进行检查;并定时执行命令,以进行所有的文章检查,对于超时未完成翻译的工作进行回收;对于文章的状态进行标记,生成相应的 Badge。

生成 badge
生成 badge

迁移思路

Travis CI 和 Github Action 在使用方面,其实总体差异不会太大,都是基于 YAML 文件格式来编写配置文件。不过,和 Travis CI 不同的是,Github Action 支持多个不同的配置文件,因此,你可以根据不同的场景,设定不同的配置文件,降低单个配置的文件的复杂度。

此外,由于我们的脚本中依赖了一些 Travis CI 的环境变量,也需要将其替换为 Github Action 中的相应环境变量,从而确保脚本可以运转。

改造实践

1. 分析之前的 CI 流程

我们在 TravisCI 上的 CI 配置文件如图

配置文件
配置文件

总体可以分为三块:

  1. 命令区:说明了安装阶段和执行阶段的操作有哪些
  2. 条件区:指定了这个配置文件在哪些条件下会生效
  3. 部署区:写明了构建产物如何进行部署

在命令区中,有预置的安装过程和后续的执行过程。在安装过程中,安装了一些依赖,并将当前的 pages 资源克隆到本地,以继承上一次构建生成的资料。

在条件区则指明了仅作用于 master 分支

在部署区便是将前面命令区的执行结果进行部署。

基本流程
基本流程

在实际的执行过程中,还会根据环境变量不同,决定是否要执行特定的命令,这部分在后续的改造过程中,就可以调整部署,拆分到不同的文件中。

构建流程
构建流程

2. 直译配置文件

在完成了基本的分析后,就可以建立新的 Action 配置文件了。由于基本的语法很类似,对于其中的不少内容可以进行直译。

比如,我们的配置文件在直译后,结果如下

直译后的结果
直译后的结果

直译的文件已经可以直接运行,不过,这里有很多不满足需要的地方,所以需要做一些修改。

3. 恢复 Travis CI 的环境变量

由于我们使用的 Badge 等生成脚本并非我所编写,所以在这一次的迁移中,并不打算对齐进行调整,以避免出现故障。而脚本中依赖了一些变量,需要将其重新设置出来。

Github Action 提供了一些方法,可以让你手动设置环境变量。你可以在你的构建的 step 中,加入如下代码,从而在构建环境中设定 TRAVIS_BRANCH 和 TRAVIS_EVENT_TYPE 环境变量,确保你可以使用这个环境变量。

 - <strong>name</strong>: Set ENV variables
        run: |
          echo "::set-env name=TRAVIS_BRANCH::${TRAVIS_BRANCH:-$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')}"
          echo "::set-env name=TRAVIS_EVENT_TYPE::$(if [ "schedule" == "$" ]; then echo "cron"; else echo "$"; fi)"

Code language: JavaScript (javascript)

此外,由于 set-env 这个方法相对比较危险,你还需要在环境变量中,开启危险函数的执行。

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      ACTIONS_ALLOW_UNSECURE_COMMANDS: true

Code language: JavaScript (javascript)

4. 拆分配置文件

Github Action 和 TravisCI 不同的一点是你可以将你的 CI文件拆分成多个文件,从而降低每一个单独的配置文件的复杂度。

根据前面对于流程的分析,可以将我们的 CI 流程拆分成三个部分:

  1. 生成 badge 文件,应当跟随每一次 commit 进行
  2. 生成 status 文件,执行时间较长,可以定期执行
  3. 根据 Pull Request 内容进行整理,做核验

则将我们的配置文件拆分成三个不同的文件

也得益于拆分开,则在 checker 中就可以免于安装一些必要的依赖,从而精简 CI 流程,提升 CI 的执行时间。

5. 测试 CI 的运行情况

在完成了配置文件的编写和拆分后,就可以进行本地的执行测试。Github Action 写完了,难免要执行一下,确保整个流程是正常的。

这个时候你可以安装工具(https://github.com/nektos/act),来在本地执行 action ,从而确认你的代码执行是正确的。

bkpl8

如果你是 macOS ,只需要执行 brew install act 就可以安装 act 工具,来完成 act 的安装。

安装完成 act ,就可以通过执行 act 命令来在本地执行 action ,比如,执行 act pull_request来触发 GitHub 的 Pull Request 事件

mt4ca

通过本地测试后,再将你的配置文件推送到 GitHub 上,进行生产环境的测试即可。

6. 移除 Travis-CI

通过上述的一些步骤,我们完成了从 Travis CI 到 GitHub Action 的迁移,此时,就可以移除项目根目录中的 .travis.yml 文件,彻底关闭 travis-ci。

7. 修改 GitHub 的 Branch 保护条件

为了确保修改符合标准,我们限制了新的 Pull Request 必须通过 CI 检查,才能合并进入 master,因此,也需要修改相应的分支保护规则,确保设定相应的保护。

b5cwa

一些注意事项

1. 环境变量不同

如果你依赖了环境变量,则需要进行相应的修改。或者可以像我一样,先通过 set-env 来让本地拥有临时的环境变量,后续再逐步进行迁移。

2. Action 运行依赖要注意安全

Action 执行过程中会有两个部分。action 本身流程依赖于 master,但执行过程中调用的脚本是根据 source 决定的,因此,从安全角度来看,你应当尽可能将所有的流程放在 Action 中,而不是放在你的源码目录中。

总结

通过对 TravisCI 的流程整理、代码修改等流程,我们将之前的 Travis-CI 迁移至速度更快、性能更好的 GitHub Action ,一方面可以优化我们的工作流,另一方面,也让我们的代码更加简洁明了。

对于还在使用 Travis CI 的项目来说,也可以考虑迁移到 GitHub Action 中,来优化自己的工作流。

参考阅读

  • https://mauricius.dev/run-and-debug-github-actions-locally/
  • https://jeffrafter.com/working-with-github-actions/
  • https://developer.okta.com/blog/2020/05/18/travis-ci-to-github-actions
code 1076536 640

拯救老旧 Discuz 的 Flash 上传

Discuz 目前依旧是一个非常重要的建站程序,不少老的站点依旧会使用 Discuz 上运转。
由于 Discuz 是一个多年的程序,所以在系统的设计上,有很多地方充满了对老旧系统的兼容。

比如,Discuz 的上传使用的是 Flash 上传,但在 2021 年,Flash 已经彻底停止使用了,这个时候你就需要使用自己的方式来进行上传。

这里我以 Linux 中国进行的相关改造为例来介绍。

Linux 中国的文章发布系统就是基于 Discuz 实现的,编辑器层面使用的是 Discuz 提供的 TinyMCE,

erf5f

Discuz 提供了一个基于 Flash 的上传组件,但在2021 年,现在这个 Flash 按钮已经完全显示不出来了。

上传组件

这个时候,你就需要自己实现一套上传系统。

由于 Linux 中国的很多底层实现都是基于 Discuz 进行的,因此,此次改造希望继续沿用 Discuz 的系统。

实现路线

在对 Discuz 默认的 Flash 插件进行抓包后,很轻松的就找到了 Discuz 的 Flash 上传组件的接口:/misc.php?mod=swfupload&action=swfupload&operation=album

这个接口接受 Form 表单作为参数,并在表单中接受一个 Hash 作为身份校验,用来判断请求的合法性。

因此,只需要在 TinyMCE 自带的上传功能中加入相应的 Hash 和表单提交的能力,就可以将 Discuz 实现的上传功能改为用 TinyMCE 的原生组件实现。

而 Hash 可以通过分析得出,其算法为 md5(substr(md5($_G['config']['security']['authkey']), 8).$_G['uid'])

因此,需要做的便是,在页面中注入 Hash ,并在 TinyMCE 中调用此值,实现上传功能即可。

样例代码

html/template/default/portal/portalcp_article.htm 文件顶部加入如下代码,从而在文章发布页面注入 hash

<span class="hljs-comment"><!--{eval$swf_hash =  md5(substr(md5($_G['config']['security']['authkey']), 8).$_G['uid']); }--></span><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span>></span><span class="language-javascript"><span class="hljs-keyword">var</span> <span class="hljs-variable constant_">FORMHASH</span> = <span class="hljs-string">"{$swf_hash}"</span></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
Code language: JavaScript (javascript)

html/static/js/editor_tinymce.js 中的 tinyMCE 配置中添加如下配置项目,即可调用 HASH 来上传文件。从而实现文件的上传。

        images_upload_url: "/misc.php?mod=swfupload&action=swfupload&operation=album",
        images_upload_credentials: true,
        images_upload_handler:function(blobInfo, success, failure, progress) {
            var xhr, formData;
            xhr = new XMLHttpRequest();
            xhr.withCredentials = true;
            xhr.open('POST', '/misc.php?mod=swfupload&action=swfupload&operation=album');
            xhr.upload.onprogress = function(e) {
                progress(e.loaded / e.total * 100);
            };
            xhr.onload = function() {
                var json;
                if (xhr.status === 403) {
                    failure('HTTP Error: ' + xhr.status, {
                        remove: true
                    });
                    return;
                }
                if (xhr.status < 200 || xhr.status >= 300) {
                    failure('HTTP Error: ' + xhr.status);
                    return;
                }
                json = JSON.parse(xhr.responseText);
                if (!json || typeof json.bigimg != 'string') {
                    failure('Invalid JSON: ' + xhr.responseText);
                    return;
                }
                success(json.bigimg);
            };
            xhr.onerror = function() {
                failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
            };
            filenameArray = blobInfo.filename().split(".");
            formData = new FormData();
            formData.append('Filedata', blobInfo.blob(), blobInfo.filename());
            formData.append('Filename',blobInfo.filename() );
            formData.append('type','image');
            formData.append('Upload','Submit Query');
            formData.append('uid',discuz_uid);
            formData.append('filetype',"." +filenameArray[filenameArray.length-1]);
            formData.append('hash',FORMHASH);
            xhr.send(formData);
        },
        file_picker_callback: function(cb, value, meta) {
            var input = document.createElement('input');
            input.setAttribute('type', 'file');
            input.setAttribute('accept', 'image/*');
            input.onchange = function() {
                var file = this.files[0];
                var reader = new FileReader();
                reader.onload = function() {
                    var id = 'blobid' + (new Date()).getTime();
                    var blobCache = tinymce.activeEditor.editorUpload.blobCache;
                    var base64 = reader.result.split(',')[1];
                    var blobInfo = blobCache.create(id, file, base64);
                    blobCache.add(blobInfo);
                    cb(blobInfo.blobUri(), {
                        title: file.name
                    });
                };
                reader.readAsDataURL(file);
            };
            input.click();
        },
Code language: JavaScript (javascript)

总结

老旧系统的升级改造不可怕,只要你用心,从业务的底层抽丝剥茧,就能找到要解决的问题。剩下,不过是代码层间的问题罢了。

black and white penguin toy

被 GitHub Action 坑了半天…

我这几天在忙着搞 LCTT 的 Travis-CI 迁移到 Github Action 。整体的 CI 的流程都已经迁移完成了,但是一个特定的检查脚本死活无法正常运行。

在这个过程中我尝试了多种办法,比如修改代码、调整里面的参数、打印其中的参数,似乎都是工作运行正常的。

将项目代码 clone 到本地后,发现代码在本地运转也是正常的。

我一直不知道问题出在哪里,百思不得其解。直到我在跑了几百次 Action 构建后,我发现,Github 默认的 checkout 插件是有问题的。

和标准的 git clone 不同, Github 默认的 Checkout 插件在实际 clone 项目是使用的不是 git clone 命令,而是采用先在本地 init 一个目录,并添加相应的 remote ,并 fetch 代码下来。

github action
GitHub Action 的 Clone

这样的好处是在处理的时候,只会 fetch 到特定的分支到本地,而不会将默认的其他分支一同 clone
到本地。但坏处就是你在执行的时候,只能针对特定的分支进行操作。

而检查脚本则是基于 git 本身的命令进行执行的,因此是需要比如 master 这样的分支的,这就导致在使用了默认的 checkout 插件的时候,检查脚本无法使用。

Travis-CI 的表现
Travis CI 的 Clone

与之对比的,是 Travis CI 在执行 Clone 的时候,采用的是全量 Clone ,再单独 Fetch 某个特定分支。从这个角度上来看,我可以理解 Github Action 为什么会 Clone 的更快一些。不过,这种 Clone 的方式确实给一些 CI 在 Check 时留下隐患。

总结

如果你在 Github 中使用默认的 Checkout 插件获取项目以后,执行 Git 操作出现了问题,很有可能是插件自己的问题,而不是你的问题。你可以选择自己构建 clone 命令,避免这个问题。

person holding purple and white card

GTD 熔断系统

我目前用的清单是 Things ,很好用。

不过,当我有一些突发事项进入时,就需要放弃原有优先级来处理。这个时候我就需要一个方案来处理焦头烂额的各项事务。

我将这个方案称之为 —— GTD 熔断系统。

GTD 熔断系统是在我的任务系统崩溃之后,切换的新的任务方案。

新的任务方案很简单,基于纸笔进行,

我会将我现在需要做的最紧急的若干事项,摘抄到笔记本上,并根据当下的紧要程度进行排序,逐一完成相关的工作和任务。直到完成后,再重新打开 Things ,提取任务事项,摘抄并复制到笔记本上。

熔断系统对于我来说,最大的好处是

  1. 让我关注当下最紧要做的事情,从而尽快从熔断的状态中离开。
  2. 让我不需要被 Todo List 中长长的项目所困扰,而是更加关注我眼前的几个项目,从而减少自己的焦虑。

为什么不直接用 Things?

Things 目前要实现这样的功能,只能使用修改时间的方式,但修改时间会出现忙完会忘记,从而让不紧急的事情变得紧急,独立出来的新的系统则可以有效的避免这个问题。

two people playing Sony PS4 game console

要不要为了健康放弃爱好?

我一直以来对于喝酒的态度都是:

酒是一种社交工具,如何你需要我陪你喝且我觉得我应该陪你喝,那我可以陪你喝。断片为止

我平日里喝的比较多的是咖啡,然后偶尔换为茶叶(因为我经常会因为咖啡喝的太多而失去了提神的效用)。对于酒来说,我基本上是能不喝酒就不喝酒。能以茶代酒就以茶代酒。

今天在群里大家在聊喝酒的问题,我就在想,如果知道喝酒不健康,你还会选择喝酒么?

那我觉得这个问题的一个 Point 是你在关注什么?

我们的一生中有很多关注的点,有些点重要,有些点不重要。我们可能会权衡其中的一些点。

那对于我来说,酒是一种工具,我不对酒有太多的想法,仅仅是一种饮料,一种工具。

对可能对于一些人来说,酒本身就是目标,因此,他们可能会在酒上面去花很多功夫。


群友精彩讨论

健康还是相对的

群友 Phoenix·HDR
black and silver laptop computer

在 macOS 下创建启动 U 盘

因为要重装 Mac mini ,所以研究了一下怎么配置启动 U 盘。

依赖

想要给 U 盘制作一个 macOS 的启动盘,首先,你需要有一个 macOS 的系统,并且有相应的安装软件(Install macOS Catalina 之类的)。此外,还需要有相应的容量 U 盘。

根据 macOS 的系统大小,我比较建议你使用 8G 或 16G 以上的 U 盘。

获取安装软件

由于安装软件比较大,所以一般情况下我们也不会保留这个软件,但当我们需要的时候,就要去安装对应的软件了。

你可以访问 Apple 的官网,找到相应的软件下载地址

系统下载地址:https://support.apple.com/zh-cn/HT211683

如果你已经升级了 Big Sur ,却希望制作 Catalina 的启动盘,那么你需要看看 这篇文章

选择你需要使用的系统

image

会自动打开下载界面

g645c

你只需要点击其中的获取,就可以下载相应的系统镜像。等软件自动下载并安装完成后,就可以进行安装操作了。

查看 U 盘挂载路径

想要制作启动 U 盘,自然要说明对应的路径,这个时候你需要先找到你自己的 U 盘。

你可以在终端中执行 df -h ,在其中找到你自己的 U 盘,比如我这里的是 /Volumes/install

制作启动盘

准备好软件和U盘后,剩下的比较简单,直接执行命令即可。

以 Catalina 为例,只需要执行如下命令

sudo /Applications/Install\ macOS\ Catalina.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume

其他版本的系统可以参考 https://support.apple.com/zh-cn/HT201372

black and silver laptop computer

如何从 macOS 系统中启动到恢复模式

macOS 可以在开机的情况下通过按 Control + R 启动到 恢复模式,那是否有不按 Control + R 就能进入到 Recovery 的方式呢?

答案是,有的

你在 macOS 的 Terminal 中输入如下命令,即可进入到恢复模式中。

sudo nvram "recovery-boot-mode=unused"
sudo reboot
Code language: JavaScript (javascript)

在操作完成后,你可以执行如下命令来移除添加的 Flag,重新进入到正常操作的系统中。

nvram -d recovery-boot-mode

来源:https://apple.stackexchange.com/questions/367336/can-i-initiate-a-macos-restart-to-recovery-mode-solely-from-the-command-line

turned on LED projector on table

精灵旅社系列观后感

精灵旅社一

精灵旅社 1讨论了关于成长的问题。

德库拉对于自己的孩子过度保护,不愿意让自己的孩子梅维斯接触到人类,怕自己的孩子受到伤害,但最终,梅维斯还是和一个突然闯入的人类男孩子约翰尼一见钟情。

德库拉从中作梗,让约翰尼离开,但一见钟情的两人都因离开而陷入了低潮。

德库拉意识到,自己的从中作梗,让孩子失去了快乐,于是走出自己的酒店,来到人类世界中,并在白天,飞上天空,挽回了约翰尼,让自己的女儿,重获快乐。

精灵旅社二

精灵旅社二的故事延续了一,依然是关于成长,但不同的是,这一次的成长,是梅维斯和约翰尼的儿子。梅维斯希望让自己的孩子获得和普通人一样的生活,但德库拉相信自己的外孙可以是一个吸血鬼,能够变成蝙蝠,并做出了不少的努力,始终无法成功。

最终,在孩子的庆生会上,一场意外,让外孙最终变成了吸血鬼。德库拉希望通过恐惧让孩子变成蝙蝠,但最终还是愤怒让孩子变成了蝙蝠。

精灵旅社三

精灵旅社三关乎爱,也关乎成长。

精灵旅社三的故事其实还挺接近我们现在的生活,讨论的是德库拉觉得孤单,希望续弦。但又担心梅维斯因此而生气。

这部电影的寓意很好,我们能做的,便是让他们更高兴,我们之间的链接,是无法被斩断的。

特别是如今这个时代,这样的想法难能可贵,也不得不面对,未来,一定会有类似的事情出现。

希望我未来也可以像梅维斯一样,宽容。

white biplane

[探店]上海浦东机场 S2国内卫星厅 190 贵宾室

点评

这个休息室位于浦东机场的 S2 国内卫星厅的 190 登机口附近,如果你乘坐的飞机需要到 S2 国内卫星厅,可以前往这个贵宾室。

不过,这个贵宾室的体验其实很一般,原因是空间一般,不算很大,同时也没有提供单独的网络和特别特别好的餐饮。因此,如果你去休息室的成本比较高,可以考虑不去。

网络

无,需要使用机场的 Wi-Fi

餐饮

提供了面条(每天有三种不同的浇头)

提供了一些简单的小吃和牛奶、啤酒等常规饮料

照片

y0m1g
5w3zt