Recent Posts

依云's avatar

寻找最快的 GitHub IP

本文来自依云's Blog,转载请注明。

部分国内网络访问 GitHub 会很慢,严重拖慢了学习和开发效率。除了使用代理绕路之外,有没有什么简便的办法呢?最近我写了个脚本,用来测试所有已经的 GitHub IP 并计时,然后就可以挑一个访问快的写在 hosts 文件里了。

获取脚本请访问 gh-check。脚本依赖 Python 3 近期版本及 aiohttp。

中国大陆目前自然解析 github.com,通常会得到位于新加坡的 IP。然而这几个 IP 的访问速度经常不怎么好。我之前是手动尝试使用西雅图或者阿什本的 IP,但是它们也并不总是很流畅。现在,终于可以让数据说话了:

IP 来源于四个 GitHub 区域域名的解析结果,以及另外两个我自己通过 DNS 发现的。

检查分为两种:HTTP 和 SSH。默认两者都测试,可如上图中那样通过参数指定只测试一种协议。HTTP 测试时,会验证服务器的 TLS 证书。

IceHoney Blog's avatar

Vue中如何设计面包屑

null
's avatar

驾照

今天我考了科目四,一次过关。

我从 2017 年 6 月就在驾校报名了,但是因为我的拖延症和一些客观原因,我的战线延续了整整两年:

  • 科目一:2017 年 8 月
  • 科目二:2019 年 1 月
  • 科目三:2019 年 7 月
  • 科目四:今天(7 月 26 日)

太丢人了,同样是 2017 年参加完高考的学生,我居然在两年后的夏天才拿本(捂脸

不过结果实现了,我也就这样变成了一位有证司机了,还是可喜可贺的。

顺便最近重新开始看动画 (Anime) 了。小时候还对那些动画一脸懵比的我,现在感觉它们也很有趣啊。

依云's avatar

火狐远程调试火狐

本文来自依云's Blog,转载请注明。

F12。F1。开启 chrome 调试。开启远程调试。汉堡菜单,Web 开发者,浏览器工具箱。

如上操作,就可以打开一个专用火狐实例,连接到之前的火狐上,用来调试火狐的界面(chrome)了。

然而这里有个问题:这样你没法调试任何独占式的弹出菜单。不管是各种右键菜单,还是地址栏补全、汉堡菜单,它们一出现,就会捕获键盘鼠标,你根本不能操作别的窗口了。

遇到这种占用整个 X 服务器的情况,一种策略就是我上一篇文章里调试 fcitx 时那样,通过 ssh 连过来调试。然而火狐的 devtools 显然需要一个 X 服务器才能运行的。所以可以在另一台电脑上调试。当然单机也是有办法的啦——xephyr 就是为此而生的。pacman -S xorg-server-xephyr 就可以了。然后 Xephyr :1 -screen 1024x768 启动。

可还有个问题:之前的 devtools 打开的时候没给机会指定它在哪个 X 服务器上运行呀。虽然 GTK 有能力把窗口从一个服务器转移到另一个,但那也是需要程序主动配合的。不过既然叫「远程调试」的,应该也能像 Firefox for Android 那样通过网络连接才对?

于是搜了一下,果然找到篇 MDN 的文章讲这个的。简单地说就是加个参数就好啦:

DISPLAY=:1 firefox-nightly -no-remote -profile tmp --start-debugger-server 6100

最后要加个端口号,因为默认是 6000,和 X 服务器的冲突了……(我是说怎么明明在监听怎么就是连不上呢,原来连错程序了 QAQ)

然后在另一个火狐实例里打开 about:debugging 去连接就好啦。本地(localhost)连接的话,不需要额外的配置也不需要确认的。如果不是本地连接,还需要去 about:config 改两个选项。


花了些时间,把显示成两行的地址栏补全给弄回来啦~

样式表在这里。想用的话记得要把 toolkit.legacyUserProfileCustomizations.stylesheets 设置为 true 才会加载 userChrome.css 哦。

依云's avatar

fcitx 扩展:使用键盘粘贴选区(以及X选区原理科普)

本文来自依云's Blog,转载请注明。

之前的文章中介绍过,X Window 中有个很方便的名叫 PRIMARY 的「剪贴板」,选中即复制,中键即粘贴。

然而问题来了:我在输入的过程中需要之前选中的内容怎么办?又或者我的内容是通过 xsel、Vim 等程序以键盘的方式选中的,我还要继续打字,要怎么粘贴才比较流畅呢?

我之前的解决方案是 fcitx 自带的 fcitx-clipboard 扩展。启用之后可以按 Ctrl-; 来显示几条最近复制的内容。如果启用了的话,第一条、第三至最后,都来自常见的 CLIPBOARD 选区,而第二条(如果用了够久,第一条及本条有的话)是 PRIMARY 选区的内容。于是我只需要按 Ctrl-; 2 就可以粘贴了。

这么用了很久之后,我读到了一篇讲 X Window 选区是如何工作的文章。然后突然意识到一个问题:fcitx-clipboard 是什么时候请求选区内容的呢?

这里先科普一下好了。X Window 的每一个选区,是由某个窗口作为所有者持有的,并且在被请求时输出内容。「复制」的时候,内容并不会被存起来放在某处,而是窗口跟 X 服务器说,「我现在是这个选区的所有者了!」至于是哪个,取决于应用程序(及用户的操作)。通常用户明确的「复制」操作(Ctrl-C 快捷键、菜单项等)会使用 CLIPBOARD 选区,而只是选中内容的话,会使用 PRIMARY 选区。我还没有见到有程序使用别的选区的。

如果使用的是 PRIMARY 选区,这个时候,旧的所有者(如果存在的话)会失去选区。在 Vim / GVim 里,可视区域会由「Visual」高亮组变为「VIsualNOS」高亮组(在我的主题里就是变灰了),而多数终端,选中的区域会失去高亮。下一次,有程序想要「粘贴」PRIMARY 选区,就会跟 X 服务器讲,请把 PRIMARY 选区的内容,以某个指定的格式写到指定窗口的指定属性上。然后 X 服务器把请求传给选区所有者,选区所有者就去按要求写属性。当然选区所有者也可能会拒绝请求,比如它拥有的是文本而请求方想要图片。当然后来大家要粘贴的内容比较大了,于是就有了 INCR 机制来一点一点地传数据。

这样的流程,也可以解释,为什么我往 Telegram 里粘贴图片,GIMP 却莫名其妙地挂掉了……

所以啦,在一个程序里复制之后,退出那个程序,你就粘贴不出来东西啦。可这样岂不是很不方便?是啊,所以又有了剪贴板管理器。它们的工作原理我还没有研究,猜想是支持 SAVE_TARGETS 的话,就等着对方退出之前把内容传过来,不支持的话一复制就传过来。

fcitx-clipboard 是这么干的:选区一有变化,它就获取其纯文本格式的内容,符合一定条件的就存起来。所以,当我在 Vim 里用键盘不断进入可视模式选东西进行各种操作时,因为我设置了 clipboard=autoselect 选项,Vim 会不断地通告「我拥有 PRIMARY 选区啦!」「我这边的 PRIMARY 选区又更新啦!」结果就是,fcitx-clipboard 会不断地把我在 Vim 中选中的内容给拿过去。

就那么点数据,本地传来传去当然没啥问题。但是,当我通过 ssh 使用的时候,我发现我在 Vim 里每一次扩大可视区域都如此地艰难。不得已只好关了 autoselect 选项。当时我还以为就选点文本,怎么就这么慢呢,谁曾想到,每一次更新可视区域,fcitx-clipboard 都会把我选中的文本请求一份……

那么好吧,不用 fcitx-clipboard 了。于是问题又回到了原点:怎么通过键盘粘贴 PRIMARY 选区呢?用程序把鼠标移过去点中键是不行的,因为程序不会知道当前光标在哪里。通过 xdotool type 也行,但是这样一个个字地输入,而且还不仅仅是 ASCII,鬼晓得有多少程序跟 Minecraft 一样会处理不过来而丢字?而且,怎么判断当前是否是输入文本的状态也是个问题。所以我还是走输入法这条路了。

其实这事完成并不难,从肥猫的傲娇扩展开始改,照猫画虎地注册热键,然后请求选区,提交文本。可我遇到一个很奇怪的 bug:扩展加载了,但是热键不生效。为了调试这问题,我通过手机 termux ssh 连过来,tmux attach 上,然后用蓝牙键盘对着屏幕里那只由于 fcitx 被 gdb 停下来了因此从电脑收不到键盘事件的 tmux 调试好久,最终发现热键怎么没注册上,才找到配置文件里一处没有被更新到的 tsundere 字样……

这个扩展名叫 fcitx-paste-primary,源码放 GitHub 上了。Arch Linux 用户可以通过 AUR 或者 archlinuxcn 仓库安装 fcitx-paste-primary-git。

对了,差点忘了说,这个扩展「粘贴」的时候,只是把会被粘贴的文本提交给应用程序,程序并不会认为是真的粘贴,所以在一些需要区分的程序里会出现问题。比如 Vim 和 zsh,都会把来自 fcitx-paste-primary 的文本当作用户输入而非粘贴而可能造成问题。

's avatar

恋爱喜剧害死人

小千的一天从开始就很忙碌。

只听见她咚咚踏踏跑上楼,跑到一个卧室门前。

深深地呼气,深深地吸气。

用最大的嗓门喊:“起床了!!!哥哥!!!”

“今天的早餐是小千特制的培根煎饼和鸡蛋,记得吃哦。”她把装着早餐的盘子摆到餐桌上的空位前。

吃完早饭,整理好后,她对着门廊说:“我先走了。”

这就是小千的早晨。


小织是市立高中的二年级生,现在上完一上午课的她,正准备去吃午饭。

她收拾好桌面,抬起头,闯入视野的是她的两个好朋友,各自手里拿着午餐盒。一起吃吗,其中一个问道。

“哈哈……”她露出尴尬的微笑,但没说什么。

两个人互换了一下眼神。第一个人点了点头。

“你们去吃吧。”小织起身说道。

“回见。”点头的人说。

“保重。”另一个人说。

小织抱着午餐盒,慢慢悠悠地朝着社办走去。


课上,每个同学轮流朗读昨天留的作业:我的梦想。

轮到小夏的青梅竹马了,小夏看着他站起来,站得挺直,开始念道:

“我的梦想是成为一名消防员。这样在人们遇到危险的时候,我就能解救他们。

“在他们需要帮助的时候,我就能帮助他们。”

“我在电视上经常看到消防员们,他们舍己为人的精神深深感动了我,所以我也想像他们一样,成为一个消防员。”

……

当轮到小夏的时候,她醒了。小夏坐起来,环顾四周,她发现自己在学校旁公园的长椅上睡着了。

我当时的梦想是什么,是新娘吗?她问自己。

小夏笑了。夕阳在她背后,她一个人走在回家的路上。


小千回到家,发现餐桌上摆着一盘煎饼和鸡蛋。于是她倒掉了。

IceHoney Blog's avatar

GIthub如何省略每次输入验证信息

项目开发中经常会遇到CI自动拉取Github库的情况,我们个人使用的时候会手动输入账户密码信息,但是针对CI来说,我们一般会把敏感的认证信息放在环境变量里。所以针对认证信息的自动输入,需要做一些处理。

环境变量自动替换

针对 Travis CI,我们一般把认证信息放在环境变量里,所以在执行clone其他repo的时候,可以先设置认证信息。

git config --global url."https://${CI_USER_NAME}:${CI_USER_TOKEN}@github.com/".insteadOf "https://github.com/"'

这样设置的话,会自动替换git clone的网址。

URL里面写入认证信息

有时候需要以另外一个身份进行clone repo的时候,会直接在repo的URL前面加上认证信息。

git clone https://${USER_NAME}:${USER_TOKEN}@github.com/{username}/{repo}

这样可以很方便的clone信息,但是认证信息会存在当前库的git config文件里,有泄漏的风险。

netrc设置

netr文件用于存储网站的认证信息,一般位置在~/.netrc。格式是:

machine github.com
login username
password xxxxxxx

总结

总共有这么三种省略认证信息的方式,根据实际情况选择自己方便的一种认证方式即可。

参考:

GitHubでユーザ名・パスワード省略

Kouga's avatar

从 Ghost 0.x 升级到 2.x 的正确姿势

今天花费了一整天在这上面,终于成功了喵!趁着还没遗忘先整理一下正确的升级姿势供参考喵~

系统准备

注意!坑最大的竟然在这里喵!如果你的系统缺少一些组件会导致整个流程非常折腾喵……以下使用 Ubuntu 18.04 系统作为基准喵~

  • 安装必要软件包 apt install sudo build-essential curl (一些VPS登录就是 root 但是偏偏没 sudo 包……
  • 创建 Ghost 安装/维护账户:
sudo adduser {username}
sudo usermod -aG sudo {username}
sudo su {username}
  • 安装 NVM ,这一步很关键,因为 Ghost 的奇葩设计导致它只能在特定版本运行……
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash

升迁流程

  • 使用 NVM 安装 node 6.9.x 版本,nvm install v6.9.5 (Ghost 0.x 只能在该版本下运行

  • 启动 Ghost 0.x node index.js

  • 进入后台管理页面,在 Lab 里导出(Export) 全站 json ,在 Design 里下载主题 Theme

    当然你如果用的官方主题并且只是修改过 Disqus 的话,主题完全可以不用下载,直接去官网下载最新主题并插入修改的代码即可。

  • 做完备份后 Ctrl + C 停掉 Ghost 0.x 版本

  • 新建一个 ghost-xxx 目录,准备安装 Ghost 1.x 版本

    这里才是重点问题所在,Ghost 只能按照 0.x -> 1.x -> 2.x 的顺序升级,而 1.x 和 2.x 依赖的 node 版本竟然不一致(扶额

nvm install lts/carbon
npm i -g ghost-cli@latest
cd ghost-xxx
ghost install --v1 # --db=sqlite3 #如果使用 sqlite 数据库
vim config.production.json
  • 在开启的 vim 中仔细修改 config, 否则各种作死起不来……而且不兼容之前的 config.js 配置……我的配置供参考(密码密钥什么的是没有的喵~ :
{
  "url": "https://kouga.us",
  "server": {
    "socket": {
      "path": "./content/注意如果用 Linux socket,则路径必须在 content 下面否则权限错误",
      "permissions": "0666"
    }
  },
  "database": {
    "client": "sqlite3",
    "connection": {
      "filename": "./content/??????"
    }
  },
  "mail": {
    "transport": "SMTP",
    "options": {
      "service": "???",
      "auth": {
        "user": "???@???.???",
        "pass": "????????????????????????????????"
      }
    }
  },
  "logging": {
    "transports": [
      "file",
      "stdout"
    ]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "升级的时候要注意这个绝对路径!"
  },
  "bootstrap-socket": {
    "port": ???,
    "host": "localhost"
  }
}
  • 使用 ghost run 尝试运行,如果成功,直接去站点后台 /ghost 路径完成初始化向导

  • 初始化完成后,直接在管理页点 Lab -> Import File -> Import ,等待完成

    这一步可能会出现大量警告,注意看警告都是哪些 Post,可能要用 vs code 一类能编辑大文件的工具来修正对应的 json 错误……(扶额),但是其中关于 duplicate content/theme 可以忽略,因为本来就导不进来

  • 导入完成后去 Posts 里看看文章是否正常,如果 OK 则再次进入 Lab 进行一次导出(避免后续升级失败再来一次 v0.x -> v1.x 数据转换……

  • 做完导出后 Ctrl + C 停掉 Ghost 1.x 版本,准备升迁到 Ghost 2.x

nvm install lts/dubnium
npm i -g ghost-cli@latest # yesterday once more
cd ghost-xxx
ghost update # 心惊胆颤的等它完成
ghost run
  • 在新启动的 Ghost 2.x 后台 /ghost/ 下登录,看看 Post 是否还在,主站现在可能是个 500 状态,因为主题还没安装;
  • 进入 Design 重新上传并激活主题
  • 进入 Lab 关掉旧API兼容等等不安全开关
  • 一切就绪后可以 nvm uninstall {version} 清理一下前面安装的多余 node 版本,并且 ghost brust 清理一下 yarn cache.

好,重新开始写作喵!

Phoenix Nemo's avatar

软件工程实践上的一点思考

曾经大学时对于软件工程这类理论课不屑一顾,认为这些课本都是只在大学里讲学而并不实际参与工程的教授写的东西。但是经过这些年从自己开发程序编写代码,到与公司团队同学、兴趣圈的朋友一起开发项目,也积累、总结了一些经验和教训。正巧昨晚在游戏建设里参与了这类讨论,于是记下一些思考免得忘记。

案例 1

命令方块是 Minecraft 里用于执行游戏命令、实现各种触发性或持续性功能的方块。在游戏地图中需要展示一些浮空的名称标签,便是用命令方块生成隐形盔甲架实现的。这些盔甲架参数复杂且需要在地图里很多特定位置生成,负责的同学便在每个生成的位置下面放了重新生成的命令方块,生成的坐标是相对坐标,因此写好标签的命令方块便可被无限复用。

由于盔甲架属于实体,而实体在 Minecraft 中被认为是不可靠的:有无数种可能这实体会被移动或被清除。
因此我的建议是:将这些命令方块全部放到控制室,坐标写成绝对坐标并加上统一标签,便可做到一键生成全部、一键清除全部。

该同学表示:不想写绝对坐标,因为很麻烦。

案例 2

由于游戏玩法的需要,编写了新的插件。几天后按照原计划应当可以准备第一次基本功能测试时,负责开发的同学表示只写了大约 1/4 的功能。进度很慢的原因是 Minecraft 的实现过于糟糕,而 Spigot 和 Paper 等修改版也没有很好封装 API 导致几乎所有的事件都需要手动处理。

接下来的协同开发中该同学又在反复尝试对配置文件中属性类似的部分使用同一个序列化/反序列化方法处理、对不同配置文件中的不同物品记录项也加上了一层包装来使得其能够被一个序列化/反序列化方法处理、在其他一些程序逻辑上也在尝试复用代码减少冗余度。

我说,你先专心把功能快速叠出来,然后再去想优化的事情。
这位同学表示不能接受,他认为代码应该从编写时就是整洁的。

论点:矫枉过正的代码复用

代码复用是很常见的代码结构优化方式。更少的代码冗余可以减少维护的复杂度,也降低出错的可能。

但是在案例 1 中,如此复用代码(放置同样的命令方块)却实际上造成了更多的冗余:如果要修改一个属性,就需要记录整个世界里每一个命令方块的位置,然后一个个去修改它。相反,由于游戏世界地图里的建筑几乎不可能变化(虽然现实需求很少会有这种条件),统一放在控制室、hard code
所有的坐标作为一个大方法调用,却是在这需求前提下的更好的实现方式。如果需要修改属性,可以只在一个地方修改所有的命令方块。

或者说,重复放置命令方块的过程,就是 copy’n’paste 冗余代码的过程。

而案例 2 则更具有代表性。在项目初期,是否应当关注代码质量?
我认为是应当关注的,但是这基于开发者的工程实践经验。优秀的、熟练的开发者应当在代码编写时就能灵活使用各种简单的优化手段减少初期的代码冗余,但是对于在校大学生没有足够的项目经验时,面对紧凑的项目时间安排应当集中更多精力实现功能。此时过分关注代码优化会被分心导致各种问题——例如这位同学编写的代码基本没有能够一次通过所有测试的情况,而且绝大多数的错误都看起来只是粗心,并不是不理解、写不出的问题。

论点:实现,调整,优化

“Make it work, make it right, make it fast.” – Kent Beck

这是很多软件工程推崇的敏捷开发指导方向。在案例 1 中,该同学只做了第一步——复用同样的、带有相对坐标的命令方块(方法)快速实现了所有的功能。但是从后续维护的角度来讲,这样的实现没有 make it right,更不用提 fast。

而在案例 2 中,这位同学将三个阶段在初期就全部揉进去,但是由于工程经验不足,在思考优化方案时花费了过多的精力,也导致了代码精度不够,反复修改也无法顺利通过测试。

从个人经验来看,前期的代码编写应注重功能实现,并在编码能力基础上直接编写清晰的代码结构。功能实现后,再根据需求和测试中的问题「重构」打磨细节、尝试更好的实现方式。这个过程不仅在完善整个程序,对自己的系统架构把握和设计经验也有很大的提升。最后一个阶段,则是针对性的优化少量代码使整个系统更加稳定、高效。

论点:架构的改动

这是一个比较小型的项目。需求和基本功能架构从一开始便已经讨论清楚。后续的调整不大,但是每当有少量的需求修改或架构微调,都导致了很大的代码变动。而按照这位同学的思路,每次改动都要重新思考代码结构,这浪费很多的时间。

从实际工程角度,需求变化并带来架构的微调甚至大改动都可以说是很常见的事情。在前期编码实现阶段如果揉入过多对于代码结构的过多考量,每次改动都可能会使这些思考的时间被浪费。因此,在前期编码时不应为架构考虑消耗过多的时间,而在重构过程中,由于已经完成基本的功能实现,且对已有代码还处在熟悉的热度,可以快速适配需要修改、调整的架构,并基于前期编码时的各种尝试和实验的结论选择最佳的实现方式。

以上是基于近期项目中的讨论,在软件工程层面上的思考。如有缺漏不当之处,欢迎指正。

's avatar

宿舍用小型 UPS 电源与新台灯

去年 11 月,我购买了一部二手的联想手机,把自己的电信米粉卡塞了进去,用来运行 Telegram SMS 和开热点。这部手机其实一切都好,但是有个问题:电池不耐用。因为我们会定时熄灯,导致手机并不能 24 小时充电,因此:

  • 如果一直开热点,第二天就没电了。
  • 如果按需开热点可以解决,但这部手机开热点的过程很麻烦,需要进入多级菜单。

我其实还考虑过使用充电宝,但是:

  • 我的品胜充电宝,如果重新插入电源,那么充电过程就会中止,必须手工再启动一次。
  • 我的紫米充电宝,尽管可以即插即用,但是如果在断电的情况下手机充满电,那么就会自动停止充电。
  • 我的 Anker 充电宝,容量太小了(只有 5000 毫安)。

虽然我理解这些充电宝的设计是有道理的,但显然不能满足我的需求。几个月以后,忍无可忍的我决定试试传说中的那种适合宿舍用路由器的 UPS。

几天以后,UPS 到了。

我选购的这款 UPS 可同时提供 USB、5V 和 9V / 12V 输出(可以通过开关切换 9V / 12V);最大输出电流为 2A(所有端口总和)。

同时,它使用 DC 输入,非常节约空间。

我把它接上了我的手机,让这部 UPS 为我的手机持续供电。效果还是不错的:我同时开着 Telegram SMS、代理软件和热点,也不用担心手机中途断电啦。


不久以后,我们团队在一个省级比赛获得了一等奖,每个人都得到了这样的奖品:飞利浦台灯。

因为这部台灯使用 12V 输入,功耗很低,我便把它接入了我的 UPS。这样,即使熄灯也可以使用啦。

⇧ 旧的『宿舍神灯』。

⇧ 新的飞利浦台灯。从我的实际感觉看,光线还是比那部『宿舍神灯』要舒服的,而且还有四档调光,非常灵活。

总之,很开心获得这么一套装备升级。我可以变得更懒惰了!

20190612 更新:一加 3T

我的联想电池鼓包了,连盖子都盖不上了。考虑到这部联想的坑爹之处,我买了一部二手一加 3T 作为 Telegram SMS 服务器使用。

目前来看,它的电池并不是很坑爹,所以只需要用普通的 2A 电源定时充电即可。

于是这 UPS 的供电目标就只剩下那个台灯了。不过不知道未来还会发生什么…… 😅

Phoenix Nemo's avatar

WireGuard 真香

真是老了跟不上时代了,这么好的东西为什么我现在才开始用??

其实这东西刚出来就在关注了不过确实前段时间才有机会尝试折腾一下。优点很多,也有无数人写过文章介绍,所以就不再多废话。主要看中它的 PtP 特性(服务器之间)和支持漫游(服务器-客户端)。当然目前在梯子方面的表现,即便是优秀的隧道方案,但由于折腾的人多了,面对万里城墙,这谁顶得住哇。

所以本文只讨论 WireGuard 作为访问企业网的隧道方案,算是初步折腾的笔记。

服务器配置

一个基本的 PtP 配置结构 /etc/wireguard/wg0.conf

1
2
3
4
5
6
7
8
9
[Interface]
Address = 10.0.0.1/32
PrivateKey = [CLIENT PRIVATE KEY]

[Peer]

PublicKey = [SERVER PUBLICKEY]
AllowedIPs = 10.0.0.0/24, 10.123.45.0/24, 1234:4567:89ab::/48
Endpoint = [SERVER ENDPOINT]:48574
PersistentKeepalive = 25

生成私钥

1
2
wg genkey > privatekey
chmod 600 privatekey

基于私钥生成本机的公钥

1
wg pubkey < privatekey > publickey

或者一步完成的操作

1
wg genkey | tee privatekey | wg pubkey > publickey

额外生成预共享密钥来进一步增强安全性

1
wg genpsk > preshared

这样服务器之间的互联配置就基本完成了。使用 wg-quick up <config> 来快速启动 WireGuard。

如果要配合客户端使用,则需要配置 NAT。顺便如果客户端没有 IPv6,也可以通过此法来给客户端提供 IPv6 Enablement。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Interface]
Address = 10.200.200.1/24
Address = fd42:42:42::1/64
SaveConfig = true
ListenPort = 51820
PrivateKey = [SERVER PRIVATE KEY]

# note - substitute eth0 in the following lines to match the Internet-facing interface
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

[Peer]

# client foo
PublicKey = [FOO's PUBLIC KEY]
PresharedKey = [PRE-SHARED KEY]
AllowedIPs = 10.200.200.2/32, fd42:42:42::2/128

[Peer]
# client bar
PublicKey = [BAR's PUBLIC KEY]

AllowedIPs = 10.200.200.3/32, fd42:42:42::3/128

在此例中需注意 Allowed IPs 不可 overlap 否则会造成包转发错误。

客户端

与上文中服务器配置相照应的客户端配置示例如下:

1
2
3
4
5
6
7
8
9
10
11
[Interface]
Address = 10.200.200.2/24
Address = fd42:42:42::2/64
PrivateKey = [FOO's PRIVATE KEY]
DNS = 1.1.1.1

[Peer]
PublicKey = [SERVER PUBLICKEY]
PresharedKey = [PRE-SHARED KEY]
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = [SERVER PUBLIC IP ADDRESS]:51820

客户端的 AllowedIPs 如果使用 catch-all 0.0.0.0/0, ::/0 也就会默认转发所有的流量到服务器。该选项实际作用是路由表,控制哪些流量需要经由服务器转发。

配置完毕即可使用 wg-quick up <config> 启动 WireGuard。如果一切顺利,通过路由追踪应该可以看到流量已经交由服务器转发。

总结

由于工作需要,经常合上笔记本动身前往其他地方。在接入传统企业网例如 L2TP/IPSec 甚至 AnyConnect 都无法保证设备下次进入工作状态时可以立即恢复连接。而 WireGuard 在不同网络、不同地域、不同网络中断时间等各种情况下均可在下次进入网络覆盖时立即恢复连接,再也不必担心网络中断恢复时手忙脚乱配置隧道或者不小心泄密啦。

目前唯一的不足,大概就是还没有 Windows 客户端,没有办法推广到非技术部门(虽然影响不到我…

总之,真香.jpg

Reference:

[1] https://wiki.archlinux.org/index.php/WireGuard

IceHoney Blog's avatar

使用WebAssembly编译C++到JS

实际项目开发中遇到了一些已经使用C++实现的功能,需要在新的Web客户端使用。由于主要是数学和算法的计算,没有平台依赖性。所以需要一个成本最低的移植方式,显然WebAssembly是一个非常好的方式。现在官方的编译工具是emscripten

移植方式

从C++编译到JS。官方提供了两种编译方式embindWebIDL Binder。这两种方式,一开始我也很困惑。不过现在也是有所了解了。向大家介绍一下如何选择。如果你的C++项目中有很多高级数据结构要使用,例如vector,map。那推荐使用embind,如果你的项目主要是简单数据类型,例如数字,字符串,bool,都可以简单的映射到JS,并且是用class封装的,那推荐使用WebIDL Binder。

封装处理

由于C++数据结构比JS复杂的多,很多时候没有直接暴露成JS函数。我们需要进行封装,例如C++中的引用调用,可以改变传入的参数的值。但是编译成JS的话,就不会生效。所以这时候我们需要写wrap函数,封装这些引用调用的C++函数,然后再单独写get函数,得到修改的值。

webpack打包

官方的demo是使用src的方式来引入,并且暴露成Module的全局变量。但是现代化的Web项目都是使用webpack打包,并且自动化引入的。所以我们也不想为了WebAssembly搞特殊。接下来就讲述如何配置来引入。首先,使用emscripten输出 mjs 文件,这样才可以作为模块被导入。具体的编译参数可以参照emcc。 首先,wasm格式并不能被webpack正确识别,我们需要添加loader

{
  test: /\.wasm$/,
  type: 'javascript/auto',
  loader: 'file-loader',
}

然后在项目中,分别 import mjs文件和 wasm文件。

import lib from './wasm.mjs';
import libWasm from './wasm.wasm';

const module = lib({
  locateFile(path) {
    if(path.endsWith('.wasm')) {
      return libWasm;
    }
    return path;
  }
});

然后在调用的时候替换掉默认的locateFile函数,即可完美导入到我们的项目中。

总结

编译C++到JS,不仅需要JS的知识,还需要C++知识,我们需要先把所有需要的C++文件,先全部编译到LLVM bitcode(.o 文件)。这里编译C++可以使用GCC的全部编译参数,推荐使用O3参数来优化代码。最后编译到JS文件的时候,需要按照emscripten的规范来书写胶水文件。

参考:

webpack-emscripten-wasm

依云's avatar

T470p 使用N卡运行 Xorg

本文来自依云's Blog,转载请注明。

这么做的原因是:这样 minecraft 帧率高,不卡顿。

  • intel 显卡:帧率低,好像是20fps左右吧。开不了光影
  • optirun:坏了
  • primusrun:帧率高了一些,不多
  • nvidia-xrun:丝般顺滑,只是切换回我之前跑程序的 Xorg 时,发现我的 Awesome 已经没了。一开始是黑屏,经过配置之后倒是能得到 LightDM 的登录画图。另外 nvidia-xrun 无法卸载模块,因为被 Xorg 使用了,需要停止 lightdm。

那么,既然 nvidia-xrun 效率不错,我要是把整个桌面都搬上去呢?经过了一些折腾之后,取得了不错的结果。一个意料之外的好处是,播放视频、网页浏览器里滚动页面时常出现的画面撕裂好了~

当然这样做会费电,降低续航时间。不过既然是 T470p,一开始我就没打算整天带着它到处跑,所以无所谓啦。需要的时候再切回去好了。有个叫 optimus-manager 的软件,看介绍是帮助这么切换的。不过我对一切自动化程度太高的软件都心存疑虑,不确定它到底干了什么,会不会和我其他的配置相冲突。所以以后再看看啦。

最终的配置方案是这样的——

首先,把 bumblebeed.service 关掉并禁用。

然后,Xorg 配置一份,放 /etc/X11/xorg.conf.d/ 下就好。这份配置来自于惠狐的《Archlinux 下 Intel 和 NVIDIA 双显卡 de 折腾笔记》一文。

Section "OutputClass"
    Identifier "intel"
    MatchDriver "i915"
    Driver "modesetting"
EndSection

Section "OutputClass"
    Identifier "nvidia"
    MatchDriver "nvidia-drm"
    Driver "nvidia"
    Option "AllowEmptyInitialConfiguration"
    Option "PrimaryGPU" "yes"
    ModulePath "/usr/lib/nvidia/xorg"
    ModulePath "/usr/lib/xorg/modules"
EndSection

lightdm.conf 里在 [Seat:*] 里加一个 hook 配置,否则会黑屏的:

display-setup-script=/usr/local/bin/lightdm-setup

这个脚本内容如下:

#!/bin/bash -e

xrandr --setprovideroutputsource modesetting NVIDIA-0 || exit 0
xrandr --auto

写了一个 systemd service,用来启用 N 卡。因为默认它是关的。

[Unit]
Description=Switch On nvidia card
ConditionPathExists=/proc/acpi/bbswitch
Before=display-manager.service

[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo ON > /proc/acpi/bbswitch"

[Install]
WantedBy=graphical.target

我之前在 ~/.xprofile 配置了视频的硬件加速,现在得删掉。GM108M [GeForce 940MX] 这个显卡的视频加速没法用的。

设置内核模块的选项 options nvidia_drm modeset=1,不然 xrandr --scale 时结果会不对。

暂时就这些了。


2019年07月20日更新:我又换回 Intel 显卡了。虽然这样性能差一点,滚动、视频时画面有点撕裂,外接屏幕中鼠标会闪,但是它稳定可靠啊!Nvidia 的驱动实在是崩得太闹心了(而且我那卡不支持视频硬解)。

IceHoney Blog's avatar

前端中的e2e测试

对于单页面应用,我们需要由前端来控制每个页面的路由。但是在开发过程中,我们经常要对路由进行调整。每次调整都要手动检查所有页面是否正常显示。这个过程实在是太浪费生命了, 所以我们需要选择一个测试框架来自动完成这个过程,结合CI系统,在每次PR的时候都自动测试一遍,只有通过测试才会进行下一步的review。

Nightwatch

Nightwatch是基于W3C WebDriver API。WebDrive主要是为了满足浏览器的自动化测试需求,把最终的接口统一成HTTP协议。 所以可以统一不同浏览器的自动化测试接口。我们只要安装每个浏览器的WebDriver实现,就可以同一套代码在不同的浏览器中进行测试。老版本的NightWatch需要使用Selenium来管理各个浏览器,但是从1.0版本开始便不需要也不推荐。所以我现在的项目中并没有安装Selenium。

依赖

由于不需要安装Selenium,所以并不需要安装Java依赖。我使用Chrome浏览器来进行自动化测试,所以需要测试环境安装Chrome,注意安装的必须是Chrome,Chromium不可以。 在Travis CI中可以很方便的添加Chrome。还需要安装ChromeDriver依赖来驱动Chrome完成自动化测试。

配置

在Linux测试环境下,基本上都是没有图形化界面的,所以我们在配置Chrome的启动参数中需要加入--headless选项,这样就可以不启动UI。当我们以root权限启动的时候还需要添加--no-sandbox选项,这两个选项基本上是必须的。在我的实际情况下,需要测试不同UA下的展示效果,所以还需要添加--user-agent=Mozilla/5.0 (Macintosh; Test自定义的UA。 下面给出一个配置的例子供大家参考:

// http://nightwatchjs.org/gettingstarted#settings-file

module.exports = {
  output_folder: "tests/e2e/reports",
  custom_assertions_path: ["tests/e2e/custom-assertions"],
  globals_path: "globalsModule.js",
  src_folders: ["tests/e2e/specs/web"]

  webdriver: {
    start_process: true,
    server_path: require("chromedriver").path,
    cli_args: ["--verbose"],
    port: 9515
  },

  test_settings: {
    default: {
      desiredCapabilities: {
        browserName: "chrome",
        javascriptEnabled: true,
        chromeOptions: {
          args: ["--headless", "--no-sandbox"]
        },
        acceptSslCerts: true
      }
    }
  }
};

每个参数的详细含义可以参考官方的说明文档。

总结

在集成Nightwatch的时候,我是卡在了安装浏览器上。在Mac系统上可以正常执行,在CentOS的docker里面却不行。找了半天发现是自己用了Chromium,之前对Chromium能代替Chrome深信不疑。。。 在遇到问题的时候,还是多怀疑,多找找可能性比较好。

Felix Yan's avatar

nspawn.org:简单的 systemd 发行版容器

如果你想要运行一个发行版容器,而又不想被 docker 一类的重量级方案打扰,现在有一个新的简单方案了。

nspawn.org 目前提供了 Arch、CentOS、Debian、Fedora、Ubuntu 的各版本镜像,并可以直接用 systemd-nspawn 的验证机制进行签名验证。

推荐的用法是使用其提供的 “nspawn” 工具。下面以创建一个 Fedora 30 容器为例:

1、获取工具:

$ wget https://raw.githubusercontent.com/nspawn/nspawn/master/nspawn
$ chmod +x nspawn

2、获取 Fedora 30 镜像:

$ sudo ./nspawn init fedora/30/tar

3、启动容器并获取 shell:

$ sudo machinectl start fedora-30-tar
$ sudo machinectl shell fedora-30-tar
Connected to machine fedora-30-tar. Press ^] three times within 1s to exit session.
[root@fedora30 ~]#

一些背景:容器默认的存储路径在 /var/lib/machines/。nspawn.org 的创建者是 shibumi,目前是 Arch Linux Trusted User。所有的镜像使用 mkosi 制作,定义文件均在 GitHub 上。除了 nspawn 容器镜像,这个站点还提供可引导的 GPT-UEFI 镜像。

依云's avatar

系统在解析哪些域名呢?

本文来自依云's Blog,转载请注明。

最近用 Rust 写了个叫 capture-dns 的小程序,实时显示 DNS 查询结果的。配合 ipmarkup 的效果是这样的:

>>> sudo capture-dns lo | ipmarkup
[sudo] lilydjwg 的密码:
github.com -> 52.74.223.119(新加坡Amazon数据中心)
github.com -> 13.229.188.59(新加坡Amazon数据中心)
github.com -> 13.250.177.223(新加坡Amazon数据中心)
live.github.com -> 192.30.253.125(美国弗吉尼亚州阿什本GitHub)
live.github.com -> 192.30.253.124(美国弗吉尼亚州阿什本GitHub)
collector.githubapp.com -> 34.193.248.191(美国弗吉尼亚州阿什本Amazon数据中心)
collector.githubapp.com -> 52.20.29.9(美国弗吉尼亚州阿什本Amazon数据中心)
collector.githubapp.com -> 34.197.57.23(美国弗吉尼亚州阿什本Amazon数据中心)
api.github.com -> 13.250.94.254(美国Amazon数据中心)
api.github.com -> 13.250.168.23(美国Amazon数据中心)
api.github.com -> 54.169.195.247(新加坡Amazon数据中心)
ocsp.digicert.com -> 117.18.237.29(澳大利亚美国MCI通信服务有限公司(韦里孙商业Verizon Business)EdgeCast亚太网络CDN节点)

可以看到本地的软件们都在查询哪些域名,得到的 IP 又是什么。抓取的是应答,所以没得到 IP 结果的不会显示。我抓取的是 lo 网络接口,因为我本地有用 dnsmasq 做缓存。

其实这个程序一开始不是这样子的。群里有人想抓取系统上进行的 DNS 查询的域名。一开始是用 tshark 抓取的,然而它太占用内存了。我粗略看了一下 Python 的 scapy 工具,也用掉了大几十M内存。那么,用 Rust 写一个好了,也顺便练习一下 Rust。

这个程序运行时只有几M的内存占用,CPU 占用也是非常低的。不过它并没有做完全的协议分析,而是假设抓得的包是以太网帧封装的 IPv4 报文封装的 UDP 数据包里包着 DNS 应答报文。所以如果你是在 eth0 上跑 PPPoE 的话,抓 eth0 上的包就不行了,得抓 ppp0 这种了。当然你要是 IPv6 啊 DoH、DoT 啥的就更抓不到了。

后来我用 bcc 的 tcpretrans 脚本查看我这里到哪些地方的 TCP 连接不太通畅,然而经常会看到一些我猜不到是干嘛的 IP。所以就把这个程序改了一下,把域名对应的解析结果显示出来了。

Rust 不仅节省资源,而且开发的体验真的很棒呢,编译成功之后就能按我预期的运行了。也不用担心什么时候遇到个有问题的报文导致程序崩掉,因为写的时候就已经处理好了出错的情况。不像 Python 写的脚本,刚写好,一跑就抛个异常出来,提示我哪里不小心写错了。好不容易调试好了,跑着跑着,遇到意外情况就挂掉了……

依云's avatar

正确的隐藏挂载点的方法

本文来自依云's Blog,转载请注明。

脚本需要挂载文件系统,但是不希望外部看到。正确的做法是:

mount --make-rprivate /

然后该干嘛干嘛。当然如果你不知道在执行之前先调用 unshare 或者等价的系统调用,说明这篇文章不适合你阅读。

错误的做法是在挂载的时候加 --make-private 或者把 / --make-private。这个标志(MS_PRIVATE)的意思是挂载/卸载事件在这里停止传播,而不是这个挂载点的事件是否传播出去。至于为什么需要使用 --make-rprivate(增加了 MS_REC 标志),暂时我还不理解。

这个用法是从 unshare 工具的 strace 结果里挖掘出来的。因为我的目的跟 unshare -m 一样嘛,当然首先想到的是看看它是怎么干的了。你问我为什么不用 unshare -m?你自己写脚本的时候试试看啰?

依云's avatar

迁移系统到 SSD

本文来自依云's Blog,转载请注明。

最近一段时间,不知道是磁盘、缓存相关算法的更新,还是我开的服务太多,又或者是新软件占用内存太高,我的系统越来越卡了,尤其是更新系统的时候(备份系统时也特别卡,然后我用限制内存占用的办法解决了)。我当然知道最主要的原因是因为机械硬盘的处理能力就那么多,于是经过一些了解和计划之后,还是决定换 SSD 了。

刚才查看了一下历史数据。从去年七八月份起,平均内存使用量从2G多升高到了3G多。大概是火狐更占内存了吧。我都尽量减少内容进程数量了……也可能是 PHP / MediaWiki 的锅,因为使用 SQLite 存储时,经常发生错误也是这段时间的事情。不过也可以理解为由于磁盘负载重导致的。算了不管了。

准备工作

当然首先要去买块 SSD 啦。我买的是 LITEON T11 Plus 512,512GB,800块。实际操作系统得到的空间是 477GiB,因为硬盘产业还在沿用1000进制的单位词头。它比我预期的要小不少呢,不过拿在手里感觉比一般同样大小的电路板要重。

拆开我的 T470p,把空闲接口旁边的螺丝下下来,然后 SSD 标签朝外插进去。我也不清楚这个接口叫什么。插进去之后它是翘起来的,难怪要用螺丝固定。然后用下下来的螺丝固定好,再把机器装好,就好了。启动系统,可以看到 /dev/nvme0n1 设备在了~GNOME 磁盘软件不能读取到 SMART 信息,用 smartctl -a /dev/nvme0n1 命令就好了。

设备没问题了,接下来当然是备份系统啦。

开始迁移

备份妥当之后,我就开始格式化 SSD。计划是 EFI 分区 512M,400G 给我的 Arch Linux,然后剩下 76G 左右的空间预留给我的 Win10。

然后这 400G,首先上一层 LUKS 加密,然后格式化为 btrfs 文件系统。其实我想要 btrfs 很久了,快照、去重、压缩都挺棒的。但是听说它的性能比较差,而我已经在受磁盘 I/O 能力不足的苦了,所以到现在有了 SSD,是时候换 btrfs 了!

其实之前 zfs(zfsonlinux)也是候选项,并且已经在工作本上使用过了。然而最近我的 zfs 备份两度出现问题(磁盘掉线之后 zfs 元数据损坏,导致一整个 zfs 文件系统一写就卡住;近期莫名其妙 rsync 跑着跑着就卡在那里不动了,磁盘也没有什么活动),再加上之前遇到的各种大小问题(ARC 被算进内存使用量中;挂载期间一旦磁盘离线就卡死;克隆出来的文件系统无法摆脱原文件系统;不支持 overlayfs;因为是树外模块所以需要专门准备的支持 zfs 的系统来执行安装),以及 TRIM 支持刚刚才加入,我已经停用 zfs 并将其排除考虑范围了。

然后就是规划子卷。参考了 openSUSE 的方案,最终决定分为这么几个子卷:/, /var/cache, /var/tmp, /var/log, /var/lib/lxc/lxc-debian/rootfs, /var/lib/lxc/lxc-centos6/rootfs, /home/lilydjwg, /home/lilydjwg/.cache。主要考虑的是快照。另外我给 /var/log/journal 和 /var/lib/postgres chattr +C 禁用了 CoW。这样也会禁用压缩,不过本来它们基本上就没什么可压缩的。需要排除的有:我的公开第三方源码和各类大文件用的 /ldata 还是放在机械硬盘上、/var/cache/pacman/pkg 缓存不要、/var/lib/pacman.fs 不用单独放连续的文件里了、/home/lilydjwg/.cache 缓存不要、/home/lilydjwg/.debug 这个 perf top 用的目录会有 libc 的硬链接,rsync 时会失败所以就不要了。

最终的同步命令如下:

sudo systemd-run -p MemoryMax=64M --scope \
  rsync -aviHAXKhPS --inplace --delete --exclude='*~' --one-file-system \
  / /mnt/root --exclude=/var/cache/pacman/pkg --exclude=/home/lilydjwg/.cache \
  --exclude=/var/lib/pacman.fs --exclude=/ldata --exclude=/home/lilydjwg/.debug

同步好之后,重启进入 live 系统再同步一次以保证最新数据也同步好了。然后把部分被排除的目录再同步一下:~/.cache/winetricks 这个以后不一定能够下到、~/.cache/sxiv 都是有效缓存(我有清理)而且生成耗 CPU、/var/lib/pacman 这个是被 --one-file-system 排除掉的。

然后是在 /etc/default/grub 里更新内核命令行 cryptdevice=/dev/disk/by-partlabel/ssd:ssd:allow-discards。这个 allow-discards 会轻微地降低安全性,不过在中国没什么用的。更新 /etc/fstab。

然后还有 /boot 要处理。其实就是把内核和 initrd 复制过去,然后重新安装 grub、生成 grub 配置。位于机械硬盘上的旧文件之后再删掉即可。

重启,使用 fallback 版 initrd 进入系统,开始修复各种问题。

首先是更新默认的 initrd。不过在更新它之前,我要修改一下我自己的 hook。之前这个 hook 里只有 partprobe 我解密之后的机械硬盘分区,因为我在它上边又分了 xfs 和 swap 两个区。现在因为 encrypt hook 解密的是 SSD 上的分区,所以这个机械硬盘上的加密分区的解密也要自己做。其实也很简单,给这个加密分区添加一下文件密钥,然后

cryptsetup open --type=luks --key-file=/etc/keys/hdd.luks /dev/disk/by-partlabel/main main

就可以了。不需要输入两次密码。

/ldata 使用 automount 延迟挂载,所以需要写 ldata.mount 和 ldata.automount 两个文件,然后 enable ldata.automount 那个。不知道写在 /etc/fstab 里是不是也行。然后把机械硬盘里的目录结构调整一下,把原来 /ldata 下的东西上移一级,旧的 / 里的其它东西都放到隐藏的 .oldroot 里去好了。

swap 本来我是保留着的,不过发现这样子我会时不时听到机械硬盘启动了。而且因为机械硬盘启动比较费时,所以系统会卡好一会儿(大概有一两秒)……所以我默认就不开 swap 了,但是 resume hook 还是保留,需要的时候打开 swap 就可以休眠了。这个 resume hook 也是我需要在启动的时候就解密机械硬盘上的加密分区的原因。

加了一个每周运行的 fstrim -v / cron 任务。没有使用 fstrim.timer 是因为它会 trim 所有设备。而我可不希望它去 trim 我挂载的机械硬盘上的 loop 设备,会造成大量碎片的。

还有一些小问题要处理。chattr +i /etc/resolv.conf 以避免 DNS 服务器被不知不觉修改了。我有用 dnsmasq 的所以这个文件不用动。我有一个 MediaWiki 实例的文件是使用 overlayfs 的,它现在挂载提示「failed to verify upper root origin」。后来才发现相关目录上有同步到几个 trusted. 开头的、overlayfs 使用的扩展属性。是它还挂载的时候被同步到的,不知道为什么最后一次同步时没有被清除掉。手动使用 setxattr 删除掉就好了。

rsync 还出了另外几个莫名其妙的问题。我在 /usr/local/sbin 下有个最近新加的文件的执行权限消失了,造成使用它的 systemd 服务失败。另外有个最近被删除的配置文件竟然还在。我不是有指定 --delete 选项吗?火狐缓存的网站图标也都没有了,需要访问之后才会重新出现。~/.cache 下有很多 root 所有的空目录,也许是我哪次忘记 --exclude 它然后又中断才加上?

Wine 有几个文件有几十 KiB 大的 user.wine.sd 扩展属性。太大了以至于 btrfs 里放不下,报「No space left on device」错误。我刚看到时还吓一跳,以为是我的 SSD 满了,仔细一看才发现只是扩展属性写不下而已。

我于是又带 --dry-run 参数同步了一次,确定再没有什么需要的东西被落下。这次 rsync 出现这些问题很是奇怪,不过我没有留日志,加上操作的时候其实是有不少修修改改的,所以就不深究了吧。

修好所有发现的问题,再次重启之后,systemctl status 和 systemctl --user status 没有失败项了~撒花 O(∩_∩)O~

后记

现在我的系统超快的!比如启动时间:

>>> systemd-analyze
Startup finished in 9.257s (firmware) + 1.466s (loader) + 15.110s (kernel) + 6.945s (userspace) = 32.780s 
graphical.target reached after 6.945s in userspace

firmware 和 loader 咱管不了。kernel 那儿包含了我输入密码解密,以及解密和探索机械硬盘上的分区,所以花了些时间。userspace 那里你别看花了好几秒,其实大部分时间都是花在联网上了。不依赖网络的服务在差不多一秒的时间内就全部启动好了。

之后我还要更新备份脚本,因为我用了 --one-file-system 而现在它们在不同的子卷上。再写一下每日快照的脚本,就不用一不小心删错文件啥的都要去备份里找了。

关于写入量,smartctl -a /dev/nvme0n1; sleep 300; smartctl -a /dev/nvme0n1 统计了一下,因为我开了 collectd 收集一些系统数据,每分钟大概会写入 60MiB 的数据。算下来,一年要写 20T 左右。这块 SSD 标称的是 280TBW,也就是可以写 280TB 的数据。这么算起来能用十年,所以就这样吧,不用再优化了。顺便说一下,SMART 信息里的「Data Units Written」数据,乘以 512000 之后是字节数。

就这样啦。最后还要说一句:SSD 超快的!

RecursiveG's avatar

Haskell 简易指南

Haskell 简易指南
HY's avatar

Horror game

最近玩了生化2重制版,有点感想就随便写点东西_(:3」∠)_ 生化2全程给我的精神压力都不大,大多被吓到的场景 […]
依云's avatar

使用 cgroups net_cls 来让 docker 走代理

本文来自依云's Blog,转载请注明。

我这里 docker hub 连不上或者连上了访问很慢,根本没法用。本来我常规代理的办法,要么是 proxychains,要么是用 iptables 代理特定的 IP 段。至于 docker 嘛,亚马逊的 IP 段那么多,它用到的域名我也不是很清楚,一点点加好麻烦。作为系统服务,用 proxychains 不仅得修改 systemd 服务配置,而且不知道会不会出什么幺蛾子。最近刚好在某个地方看到这一手,就试试啰。

其实用法很简单的。去 /sys/fs/cgroup/net_cls 下建立个目录,往 net_cls.classid 里写一个整数(支持十六进制的 0x 表示法),然后把 dockerd 的 pid 写到 cgroup.procs 里去。最后用 iptables 代理这部分流量即可。现在都用 443 端口啦,所以只要代理它便好,也避免影响了别的东西:

iptables -t nat -A OUTPUT -p tcp --dport 443 -m cgroup --cgroup 0x110001 -j REDIRECT --to-ports XXX

XXX 是 ss-redir 的端口啦。

注意不要把进程的 pid 往 tasks 文件里写。那里得写的是 task 的 id 而不是 process 的 id,也就是说(用内核的术语来说)是线程的 pid 而不是进程的 tgid(thread group id)。所以非要写 tasks 文件的话,得把 docker 所有的线程的 pid 都写进去才行。真是混乱呢……画个表格好了:

用户态 内核 相关系统调用
pid tgid getpid, kill
tid pid gettid, tgkill
process task group fork, clone without CLONE_THREAD
thread task clone with CLONE_THREAD

另外如果更新过内核的话,那句 iptables 有可能会找不到模块的。(所以更新内核之后还是重启一下以避免尴尬吧。)

依云's avatar

使用 cgroups 限制指定进程的内存使用

本文来自依云's Blog,转载请注明。

最近我的系统有这么个问题:在备份或者系统更新等高 I/O 负载的时候,系统整体性能下降严重,界面经常卡到动不了。经过分析发现此时比平常多了许多磁盘读操作。平常的时候磁盘读操作是很少的,会有大量的缓存命中,反倒是写操作一直都有(因为我本地搭了个监控系统)。啊对,分析用到的磁盘性能数据就是来自于这个监控系统。

所以原因很清楚了:备份和系统更新不仅造成了大量缓存未命中,还占用了本来存放着热数据的缓存,导致常规使用的缓存命中率也急速下降,结果我的机械硬盘就忙不过来了。

那么,要是能够限制这些操作占用的缓存,性能不就能好一点吗?那些新读进来的数据反正是短期内再也用不到了,缓存起来也只是浪费有限的内存空间啊。

研究了一下 /sys/fs/cgroup/memory/memory.stat,看起来 cgroups 内存限制是包含缓存部分的,于是就试试呗。正好 systemd 直接就能设置了:

$ sudo systemd-run -p MemoryMax=512M --scope pacman -Syu

本来我是设置的 256M 限制,结果发现 dkms 编译内核模块的时候超级慢,还用掉了不少 swap……于是分了 512M。效果还是不错的,常规操作偶尔还是卡一卡(毕竟还是有一些 I/O 操作),但比起不限制的时候要少很多。

要注意一点的是,不使用 cgroups v2 的话(Arch Linux 默认),这个命令不能加 --user 以在用户级的 systemd 下跑的。而使用 cgroups v2 的话,lxc 和 docker 都跑不了……

备份也是类似的,而且因为 rsync 自己用不到多少内存,这个效果更好:

$ systemd-run -p MemoryMax=256M --scope ./backup-my-system

终于又一次在半小时内完成了备份 QAQ 之前动不动就一两小时的。

我也不知道为什么这个问题近期才出现,总之现在是缓解了。(接下来有空继续计划换 SSD 硬盘的事情~

Roy Binux's avatar

Zerotier Nat 网关出口 和 iptables 调试

每当看到各类教程中的 iptables 指令,在格式参数组合之下可以实现从防火墙,封禁 IP 端口到 NAT 的各种操作,就如同魔法一般,看不明白,却又感到无比强大。想学,但又好像不得要领,稍微不慎可能就再也连不上了。最近配置 Zerotier 的 Nat 网关的时候,看着

IceHoney Blog's avatar

localStorage互斥锁的使用

JavaScript是单线程语言,所以我们在写代码的时候根本不会遇到互斥锁的问题。但是当用户打开多个Tab页面的时候,这些页面却是共享同一个localStorage的。当我们试图修改localStorage的时候就会遇到竞争问题,如果两个页面同时修改了localStorage,程序的可靠性就无法保证。

多Tab互斥

这个问题查了不少时间,目前有的解决方案:Shared Web Workersfast mutex。由于想尽可能的支持更多浏览器,所以我们选择了后者。

实现localStorage互斥

fast mutex的源码也不是很多,所以读起来也没有很复杂。当执行lock函数的时候,会为一个key存储 _MUTEX_LOCK_X_KEY_MUTEX_LOCK_Y_KEY。以下简称X和Y。 首先存X,然后读Y。如果Y存在说明别人已经拿到互斥锁了,所以重新执行函数。直到获取到互斥锁。如果Y不存在,说明没有人竞争锁。所以往下继续执行存储Y。 但是我们不能保证这段时间没有别的tab来存储X或者Y。所以继续读取X,如果X没有发生变化,说明没有人来竞争锁。我们就可以resolve传入的回调函数了。 如果X发生了变化,说明有人来竞争互斥锁,这时候的函数设置了一个50ms的延迟执行,就是保证检测的足够晚。竞争的tab能够执行完自己的lock函数。50ms之后再去读取Y,如果发现Y没有发生变化,则自己还拥有这个互斥锁,可以顺利执行resolve。否则自己丢失了互斥锁,重新执行lock函数。

总结

fast mutex的实现确实很巧妙,通过添加两个localStorage值和setTimeout完成互斥锁的实现。看到这个项目之前,一直以为想做到localStorage的互斥是不现实的事情。

farseerfc's avatar

東方歌詞翻譯遷移至 sak.uy

最近幾個月在這個博客發了不少歌詞翻譯 似乎有要轉型成音樂博主的趨勢 ,前段時間買了個新域名 sak.uy ,準備專門用來放這些東方歌曲的歌詞翻譯,於是分設了單獨的博客「 Sakuya的音樂盒 」。主博客這邊右側邊欄會有到音樂盒的鏈接。

曾經在這邊的那些歌儘量保持 URL 跳轉過去,新的歌詞翻譯會發到那邊去,還想繼續聽歌的話請繼續訂閱那邊的 RSS 呀。

主博客這邊還是像往常一樣保持記錄生活點滴和技術經驗好了。說道介紹技術, 有人問過我那些日語歌詞上給漢字標註的假名都是我一個個手輸的麼? 一開始是手輸的,後來發現了不錯的自動化方案,於是這裏介紹一下。

首先是 python-furigana

這是個 python 寫的小程序(嚴格說是庫),可以把一段日文轉換成標準的 HTML 形式的 <ruby> 標籤的振假名( 振(ふ) り 仮名(かな) )。 它本身只是個方便的格式化庫,實際工作是用 python-mecab 這個 binding 去查詢 mecab 這個著名的日語語料分析庫。要用它還得配合一些開源的 mecab 詞典,這些在 [archlinuxcn] 都有打好的包了,直接安裝:

$ sudo pacman -Syu python-furigana mecab-git python-mecab mecab-ipadic

裝好之後用法也很直接,甚至沒有 binary 直接調用 python 的 module 就可以:

$ python -m furigana.furigana "振り仮名の例"
<ruby><rb>振</rb><rt>ふ</rt></ruby>り<ruby><rb>仮名</rb><rt>かめい</rt></ruby>の<ruby><rb>例</rb><rt>れい</rt></ruby>

就是提供日語作爲輸入,然後輸出 HTML 形式的 <ruby> 標籤而已。 像上面的例子中出現的錯誤(「振り仮名」完整的一個詞中「仮名」意思是「平仮名」應該發音「がな」而非意爲「假的人名」的「かめい」) 可以看出其實標註的準確率還是有些問題的。嘛日語作爲一個非常依賴上下文判斷的語言, 經常日本人都會搞錯某些漢字的發音,這些也不能強求機械化的算法能 100% 正確實現。 好在單純的詞典匹配也能滿足大部分標註的需要了,用這個標註總體來說 95% 以上的情況都是正確的(歌詞的話正確率低一些,畢竟歌詞中古語啦当て字啦訓読み這些情況很常見)。

把輸出插入我的博客

然後我的博客用 reStructuredText 語法寫,不能直接用 HTML 標籤(雖然我加了 :html: 這個 行內角色(inline role) 但是大量用也不方便)。這個博客一開始用 Pelican 重寫主題的時候 我就實現了個自己的 :ruby: 行內角色(inline role) 用來標發音,於是一段 sed 就能把 python-furigana 的輸出轉換成我用的 rst 語法:

$ which clipboard Co Ci Ct
clipboard: aliased to xclip -selection clipboard
Co: aliased to clipboard -o
Ci: aliased to clipboard -i
Ct () {
    t=$(mktemp /tmp/furigana-XXXX)
    python -m furigana.furigana $(Co) | sed 's@<ruby><rb>@ :ruby:`@g;s@</rb><rt>@|@g;s@</rt></ruby>@` @g' | sponge $t
    cat $t | tee /dev/tty | perl -pe 'chomp if eof' | Ci
}

上面這些 alias 在我的 .bashrc 中。有了這些之後, 我只要把需要標註的日語文本放入剪切版,執行 Ct ,再粘帖結果就好了。

$ echo "振り仮名の例" | Ci
$ Ct
:ruby:`振|ふ` り :ruby:`仮名|かめい` の :ruby:`例|れい`

然後所有那些歌詞上標註的假名都是這樣一句一句標註好之後,再手動校對修改的。

HY's avatar

TwitterImg Downloader

想必不少朋友也和我一样关注了很多画师,定期收图。 Pixiv的图片文件名可以直接反推出图源,直接保存就可以,但 […]
依云's avatar

在 Linux 下整理磁盘碎片

本文来自依云's Blog,转载请注明。

磁盘碎片其实有两种:文件碎了,和空闲空间碎了。使用 FIEMAP 命令可以获取到文件在磁盘(的逻辑地址上)的分布情况。也是 filefrag -v 命令输出的东西。比如我的 pacman.log 就很碎:

Filesystem type is: 58465342
File size of /var/log/pacman.log is 11052443 (2699 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..    2015:  170210423.. 170212438:   2016:
   1:     2016..    2017:  170567879.. 170567880:      2:  170212439:
   2:     2018..    2027:  170569969.. 170569978:     10:  170567881:
   3:     2028..    2030:  170574582.. 170574584:      3:  170569979:
   4:     2031..    2031:  170574631.. 170574631:      1:  170574585:
   5:     2032..    2033:  170592662.. 170592663:      2:  170574632:
....
 123:     2683..    2687:   56903805..  56903809:      5:   56906403:
 124:     2688..    2698:   56903011..  56903021:     11:   56903810: last,eof
/var/log/pacman.log: 125 extents found

整理的办法也很简单,复制一下,基本上就好了。只要剩余空间足够,小文件会变成一整块,大文件也是少数几块。如果非要弄一整块大的,比如我存放 pacman 数据库的那个小文件系统,可以用 fallocate -l 200M pacman.fs2 这样子的命令分配空间,然后把数据 dd 进去(cp 不行,因为它会先截断文件再写入,之前分配的空间就释放掉了)。

介绍完毕,重点来了:怎么找到那些被写得很碎很碎的文件呢?

对每个文件调用 filefrag 肯定太慢了,所以我写了个库和工具 fiemap-rs 直接调用 FIEMAP。它提供两个工具。一个是 fraghist,统计碎片数量分布直方图,用来了解一下某群文件有多碎。另一个是 fragmorethan,用来寻找碎到一定程度的文件。运行起来是这样子的:

/var/log:
# Number of samples = 712
# Min = 1
# Max = 297
#
# Mean = 11.338483146067423
# Standard deviation = 40.138129228003045
# Variance = 1611.0694179238724
#
# Each ∎ is a count of 13
#
  1 ..  31 [ 658 ]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
 31 ..  61 [  11 ]:
 61 ..  91 [   9 ]:
 91 .. 121 [  10 ]:
121 .. 151 [   6 ]:
151 .. 181 [   5 ]:
181 .. 211 [   3 ]:
211 .. 241 [   2 ]:
241 .. 271 [   3 ]:
271 .. 301 [   5 ]:
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 271
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 277
/var/log/journal/00000000000000000000000000000000/system.journal: 274
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 297
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 274

我系统上最碎的两群文件是 journal 日志和 python2-carbon 的数据文件。carbon 优化做得挺不好的,明明是预分配的固定大小文件啊,不知道怎么的就弄得很碎了。部分程序的日志(如 pacman、getmail)和火狐的 SQLite 数据库也挺碎的。后边这些我已经处理掉了所以示例输出只好用 journal 的啦。

找到想要整理的过碎的文件之后,复制一下就好啦:

for f in $(<list); do sudo cp -a $f $f.new; sudo mv $f.new $f; done

啊对了,工具的编译方法是,获取源码并安装 Rust 之后,在项目根目录里 cargo build --release 然后就可以在 target/release 下找到新鲜的可执行文件了~顺便说一下,这东西是支持 Android 的哦。

brainbush's avatar

OpenWrt 编译安装 Nginx rtmp module

参考 https://wonpn.com/2018-03-15-compile-nginx-with-ssl.html

1. 下载 OpenWrt SDK

这里我使用的是斐讯K3,所以下载 bcm53xx 的。

wget https://downloads.openwrt.org/releases/18.06.2/targets/bcm53xx/generic/openwrt-sdk-18.06.2-bcm53xx_gcc-7.3.0_musl_eabi.Linux-x86_64.tar.xz
tar xf openwrt-sdk-18.06.2-bcm53xx_gcc-7.3.0_musl_eabi.Linux-x86_64.tar.xz

2. 下载 Nginx rtmp module

wget https://github.com/arut/nginx-rtmp-module/archive/v1.2.1.zip
unzip v1.2.1.zip

3. 下载nginx并添加设置选项

./scripts/feeds update
./scripts/feeds search nginx
./scripts/feeds install nginx

修改 package/feeds/packages/nginx/Congif.in 添加:

config NGINX_RTMP
         bool
         prompt "Enable RTMP module"
         help
                 Enable RTMP module
         default n

修改 package/feeds/packages/nginx/Makefile ,在213行附近(TARGET_CFLAGS前)添加:

ifeq ($(CONFIG_NGINX_RTMP),y)
  ADDITIONAL_MODULES += --add-module=/path/to/nginx-rtmp-module
endif

4. 修改和编译

make menuconfig
# 在network->Web Servers/Proxies->Nginx->Configuration->开启 Enable SSL module 和 Enable RTMP module 
# (如没有RTMP module请检查是否在Config.in重添加)
make -j4

5. 抱走 Nginx 包

cp bin/packages/arm_cortex-a9/packages/nginx_1.12.2-1_arm_cortex-a9.ipk /where/you/want
摸完了
依云's avatar

docker 里几个基本概念的简单类比

本文来自依云's Blog,转载请注明。

首先说明一下,这是一位 docker 新手对于 docker 的粗浅理解。如有不对还请谅解。我很早之前就尝试过使用 docker,然而由于术语的差异,导致我每次运行东西时都傻乎乎地创建了一个新的容器……现在感觉用法终于是弄对了,所以整理一下,将其类比到 Linux 上的普通软件的概念上。

image 相当于软件分发中的软件(安装)包。 Dockerfile 跟 PKGBUILD 类似,是用于制作一个 image 的打包脚本。用 docker build -t name:tag . 就可以制作。 container(容器) 一个容器就像是一个安装好了的软件包。该软件已经准备好,随时可以运行了。 docker run 「安装」指定的 image。也就是从 image 制作出容器来,顺带着进行首次运行。如果反复使用,会把同一个软件给安装多次。 docker start 就像是「运行」一个已经安装好的软件,容器跑起来了。之前容器的状态(文件的修改)也会生效。 docker ps 列出运行中或者已安装(带 -a 参数)的软件们。前者和 UNIX 命令 ps 类似,后者则没什么相似之处了。 docker exec 在正在运行的软件的环境内执行命令。有点类似于 ssh。 repository 跟 Linux 的包含众多软件的软件源并不一样。这个东西跟软件名类似,用于标识为特定功能的 image 集。发布出来的 repository 名的格式通常是 `owner/name`,跟 GitHub 差不多的。 tag 软件的版本,跟什么 lite、pro、beta 之类区分类似。它并不是用于分类的标签,也不是 git 中对于指定版本的不变的称呼。它更像是 git 的分支在某些情况下的作用,比如 latest tag 就跟 git 仓库的 master 分支一样,总是指向最新的版本。

我经过以上这样的映射之后,docker 理解起来就容易多了,行为也更符合预期。

farseerfc's avatar

用 usbip 轉發 raspberry pi 的 USB 鍵盤鼠標給 Arch Linux 的 PC

惠狐 megumifox 寫了篇 用PulseAudio將電腦的聲音用手機放出來 ,文末提到想知道我怎麼用樹莓派轉發 USB 的,於是寫篇文章記錄一下。

起因

家裏有個裝了 Arch Linux ARM 的樹莓派3B 閒置着,裝了 Arch Linux ARM 偶爾上電更新一下, 不過因爲性能實在不適合做別的事情於是一直在吃灰。某日 給老婆安利幻想萬華鏡和老婆看片 的時候, 老婆不吃安利於是遷怒鍵盤鼠標鍵盤鼠標被長長的 USB 線扯着感覺很難受 ,於是偶發奇想,能不能利用一下樹莓派的多達 4 個 USB 2.0 端口接鼠標鍵盤呢, 這樣鼠標鍵盤就可以跟着樹莓派來回走,不用拖着長長的 USB 線了。

上網搜了一下, Linux 環境有個 usbip 工具正好能做到這個。原理也很直觀, usbip 能把 USB 端口上的數據封裝成 IP 協議通過網絡轉發出去,從而兩個網絡間相互聯通的電腦就可以遠程轉發 USB 了。 設置好的話,就像是一臺 PC 多了幾個位於樹莓派上的 USB 端口,插上樹莓派的 USB 設備統統作爲 PC 的設備。

這篇文章假設有一個裝了 Arch Linux 的 PC ,和一個裝了 Arch Linux ARM 的樹莓派, 並且兩者間能通過網絡互相訪問到。別的發行版上大概也可以這麼做,只是我沒有試過。 usbip 工具似乎普遍被發行版打包了,除此之外需要的也只是 Linux 內核提供好的功能而已。

設置 Arch Linux ARM 的樹莓派端

假設樹莓派上面網絡已經設置妥當,開機插電就能自動聯網。接下來安裝 usbip 工具:

$ sudo pacman -Syu usbip

然後需要記錄一下樹莓派的 IP 地址:

$ ip addr
3: wlan0: ......
inet 192.168.0.117/24 brd 192.168.0.255 scope global noprefixroute wlan0
......

接下來給 udev 添加一個規則,當插入 usb 設備的時候,執行我的腳本 usbipall.sh 把 usb 設備通過 usbip 共享出去:

$ cat /etc/udev/rules.d/usbipall.rules
ACTION=="add", SUBSYSTEM=="usb", RUN+="/usr/bin/bash /usr/local/bin/usbipall.sh"

這個 rules 文件 可以在我的 dotfiles 裏面找到

然後規則調用的 usbipall.sh 我這麼寫的, 文件同樣在我的 dotfiles 裏面

#!/bin/sh
(
allusb=$(usbip list -p -l)
for usb in $allusb
do
    busid=$(echo "$usb" | sed "s|#.*||g;s|busid=||g")
    if [ "$busid" = "1-1.1" ]
    then
        # ignoring usb ethernet
        continue
    fi
    echo "$(date -Iseconds): Exporting $busid"
    usbip bind --busid="$busid"
done
) >>/var/log/usbipall.log 2>&1

這個腳本做了這樣幾件事。

  1. 調用 usbip list --local 列出本地所有 usb 設備。
  2. 針對每個設備
    1. 取出它的 busid
    2. 判斷是不是樹莓派的 USB 以太網卡,不是的話繼續
    3. 通過 usbip bind --busid= 命令把這個 usb 設備導出到網上
  3. 最後把所有輸出記錄到 /var/log/usbipall.log 日誌裏面

樹莓派這邊設置就完成了。從此之後插入的 usb 設備就會統統導出出去。

這裏需要注意一下,啓用了 udev 規則之後,就沒法插鍵盤鼠標到樹莓派上控制它了……我都是從另一端 ssh 上樹莓派操作的。如果有什麼地方設置錯誤,可能需要把樹莓派的 SD 卡拔下來插到電腦上,刪除掉 rules 文件……

仔細檢查設置正確了之後,重新載入 udev 規則,或者重啓樹莓派:

# systemctl restart systemd-udevd

這樣樹莓派這邊就設置好了。

設置 Arch Linux 的 PC 端

同樣假設 PC 這邊也已經聯網。接下來同樣安裝 usbip 工具:

$ sudo pacman -Syu usbip

然後我寫了個小腳本去鏈接樹莓派端, 這個文件 usbiprpi3.sh 也在我的 dotfiles

#!/bin/sh
rpi3="192.168.0.117"

modprobe vhci-hcd

allusb=$(usbip list -p -r $rpi3 | cut -d":" -f1 -s | sed 's|^[ \t]*||;/^$/d')
for busid in $allusb
do
    if [ "$busid" = "1-1.1" ]
    then
        # ignoring usb ethernet
        continue
    fi
    echo "Attaching $busid"
    usbip attach --remote=$rpi3 --busid="$busid"
done

其中腳本第一行填入上面記錄下來的樹莓派的 IP 地址,接下來腳本做了這麼幾件事:

  1. 用 modprobe 確認加載 vhci-hcd 通用虛擬鍵鼠驅動
  2. usbip list --remote= 列出遠程設備上已經導出了的 USB 設備,取出他們的 busid
  3. 對每個設備用 usbip attach 接上該設備

然後就已經準備妥當,接下來是見證奇蹟的時刻:

$ sleep 10; sudo ./usbiprpi3.sh
Attaching 1-1.4.3
Attaching 1-1.4.1

因爲只有一套鍵盤鼠標,所以先 sleep 個 10 秒,在此期間快速把鍵鼠拔下來插到樹莓派的 USB 口上去。 如果對自己手速沒自信也可以把時間設長一點。然後用 root 權限執行 usbiprpi3.sh 。

一切正常的話,先能觀測插上樹莓派的鍵盤鼠標被樹莓派初始化了一下,比如鍵盤燈會亮, 然後這些設備會被導出出去,從而鍵盤燈滅掉,然後 10 秒等待結束後他們被遠程接到了 PC 端, 又會被初始化一下,同時 PC 端這邊會有上述 Attaching 的輸出。然後鍵盤鼠標就能像平常一樣用啦。

使用體驗

因爲就是通過 IP 轉發 USB 嘛,所以就和普通地接 USB 的體驗差不多,當然前提是網絡環境足夠穩定。 在我家間隔 5 米到無線路由器的環境下,基本感覺不到網絡延遲的影響。 通過這種方式聊天上網應該和直接接 USB 設備完全一樣。本文就是在通過樹莓派轉發的前提下用鍵盤打字寫的。

不過如果網絡負載本身就很大的話,可能會一些延遲,比如我開着 OBS 直播打東方的時候,原本就手殘 的我感覺更加手殘了……

試過拿着樹莓派在房間到處走,走到無線信號覆蓋不到的地方, usbip 會斷掉,PC 上的現象就像是 USB 設備被拔下來了……所以如果無線網絡不穩的話,可能需要對上面腳本做個循環?不過那樣可能會用起來很彆扭吧。

以及,上述操作 usbip 是走 TCP 3240 端口,數據包大概完全沒有加密,所以考慮安全性的話, 最好還是在內網環境使用。不過轉念一想,萬一有別人接上了我導出出去的 USB ,也就是截獲我的鍵盤, PC 這邊沒法 attach 設備了,應該馬上會發現吧。我敲打 sudo 之類命令的時候 shell 裏面沒有回顯, 就不會再繼續敲密碼了。而且似乎對攻擊者也沒有什麼好處?要是他 usb attach 到了我的設備上, 我就能控制他的鍵盤了耶~

IceHoney Blog's avatar

亚马逊S3服务简单介绍

最近在做后端的开发,需要一些二进制数据保存在服务器云端。团队决定调查AWS的S3服务是否满足需求,所以就做了一些调查工作。不过也遇到很多坑的地方。所以记录下来,防止以后再遇到。

基本需求

需要云服务有稳定性保证,并且可以批量上传文件。可以设置上传和下载链接的有效期。

AWS试用

AWS提供免费试用,但是注册的时候需要填写信用卡信息。确实有点不安,万一不小心被收费了就不好了。

生成上传凭证

我使用的是AWS的 JavaScript SDK。使用createPresignedPost API可以创建用于上传的凭证。这个凭证是根据用户的AccessId,AccessKey和Policy策略等计算生成的,并没有和AWS服务器直接进行交互。所以不用担心这个接口和AWS直接的流量费用问题。

var params = {
  Bucket: 'bucket',
  Conditions: [
    ['starts-with', '$key', 'path/to/uploads/']
  ]
};
s3.createPresignedPost(params, function(err, data) {
  if (err) {
    console.error('Presigning post data encountered an error', err);
  } else {
    data.Fields.key = 'path/to/uploads/${filename}';
    console.log('The post data is', data);
  }
});

官方提供的例子中,可以使用starts-with的方式来指定上传文件的key必须是以什么开头的,这样就可以指定上传的文件夹。很多文件也可以使用这一个上传凭证来完成上传。

上传Policy构造

AWS提供了一个详细文档说明如何构造合法的Policy:Creating a POST Policy。例如常见的需求就是在上传的时候添加meta信息声明文件的格式或者MD5值。 Policy的Conditions数组里面可以添加["starts-with", "$x-amz-meta-md5checksum", ""]。最后一个参数为空字符串代表可以上传任何数值。

构造POST表单

AWS也有文档说明了如何构造一个上传的表单。这个表单中最重要的是一句注释:The elements after this will be ignored。在file字段之后的所有信息都会被忽略掉,我测试的时候一直把x-amz-meta-md5checksum字段放在file字段之后导致上传一直报错。直到Stack Overflow上面有人解释了才恍然大悟。

构造下载链接

我们使用getSignedUrl API来生成下载链接,下载链接也是根据自己的AccessId和AccessKey生成链接的凭证,也没有和AWS服务器直接进行交互。当请求文件的时候,AWS再计算凭证是否有效。所以后端无需和AWS交互就可以返回客户端有效的AWS下载链接。针对需要返回实际文件的API接口,可以采用返回302的跳转链接来完成需求。示例代码如下:

var params = {Bucket: 'bucket', Key: 'key'};
var url = s3.getSignedUrl('getObject', params);
console.log('The URL is', url);

可能有人会问,这个API也可以用来上传啊。但是这个API接口必须指定key值,这样我们就需要为每个文件来生成一个独立的上传URL。这样太麻烦了。

总结

我们使用pre-sign的方式来生成URL主要是为了对客户端透明。虽然我们可以设置最小权限的IAM User给客户端,但是客户端很容易被逆向拿到敏感数据。这样难免会有风险,所以生成一个单纯的URL供客户端使用一定程度上保证了安全性也减少了客户端的复杂性。毕竟我也不想引入一个AWS的SDK进来。

's avatar

拆解我爸 2010 年装的廉价 PC

2010 年,我爸觉得我家的 P4 台式机太过时了,所以就花 2k 托人组装了一台新 PC。

然而这台 PC 不仅卡顿时常发生,而且非常不稳定,经常死机、蓝屏,期间我还被母上大人认为我是让那台 PC 不稳定的罪魁祸首,因为我安装的那么几款软件,结果我没少被母上大人数落。😒😒😒半年以后,那台机器突然无法开机,甚至不会 POST,然后我爸只好把同一位 JS 叫过来进行维修。然后,JS 把那块烧掉的双敏主板换掉了。

2011 年,我爸给我买了一块 1TB 的希捷移动硬盘,但是在那台机器上带!不!动!,必须连接外置电源才能正常启动。

2013 年,因为母上大人实在不能忍受那台 PC,所以购买了一台戴尔整机(经过我的几次硬件升级,目前还在我家服役中)。而那台廉价 PC 就拿到我爸那边做监控机,结果半年以后硬盘就完蛋了,SMART 状态为不良,0x05 爆表。随后,我舅舅又拿来一块闲置 80 GB 硬盘,在我爸那边做工控设备,直到 2018 年 7 月彻底完蛋,甚至不再 POST 了。然后那台机器就在仓库里又放了半年。

2019 年,我把那台 PC 从我爸仓库搬了回来,决定看看还有没有什么利用价值。

做工廉价的机箱

这个机箱从外面看还像是那么回事,但是轻飘飘的,而且金属部分甚至会划手。

不过我丢掉了这个机箱才想起来忘了拍照了。

做工粗糙,而且接线的地方是用热胶固定的……什么鬼……

机箱内部

散发着寒意而且相当脏乱差的内部。

安装在主板上的 CPU 和 768 MB DDR2 内存。CPU 与风扇之间的硅脂所剩无几的样子,不知道是当年给我爸装机的那 JS 敷衍了事,还是发生了什么黑魔法。

AMD Athlon 7750。这台机器是 2010 年装的,但是据 @qwe7002 说,这片 CPU 在 2008 年就属于比较慢的了。

擦干净的主板,是 ASUS M2N68-AM PLUS。也是低端板子呢。

PHILIPS SPD2213,据说是市场上最便宜的光驱。

大炸弹

嗯,来自惠州高级工厂的产物。这台机器的不稳定,一定程度上就是这个大炸弹搞得鬼了(包括 USB 供电不足的问题)。

顺便这个序列号其实没什么卵用,因为看起来他家所有产品都用这个序列号。

于是我把大炸弹的风扇拆了出来,发现了绿色的奇怪地方。那是做什么用的呢?

原来特么是用来遮盖固定两组线缆的部分!看起来这玩意还是从奇怪的电子垃圾上迫真剪下来的风扇,然后这样凑合固定一下,就装到电源里面了。

结论

这台机器,最后除了两根 SATA 线被我拆了出来,被我原封不动的扔到垃圾桶了。

对于我们这种装机小白,看起来还是买品牌整机比较好。虽然性价比不是那么高,但最起码很靠谱啊。

以及便宜没好货。

Felix Yan's avatar

用脏办法解决 BLE 鼠标重连后指针不动的问题

我的蓝牙鼠标(雷柏 MT750)使用 BLE(Bluetooth Low Energy) 连接笔记本时,时常遇到自动重连后电脑这边认为已连接,而鼠标那边灯自动灭掉,鼠标指针无反应的问题。在网上反复搜索 bluez 相关问题找到了许多类似问题,鼠标类型也集中在罗技、ThinkPad 等 BLE 鼠标上。

偶然间发现每次重连后,如果手动用 bluetoothctl 发一个 “pair” 指令(会超时失败),就能令鼠标正常连接。给 bluez 报了一个 bug 后,我写了下面的简单脚本先绕过问题:

#!/usr/bin/python

import dbus
import dbus.mainloop.glib
from gi.repository import GLib

adapter = "hci0"
device = "xx:xx:xx:xx:xx:xx"
device_path = '/org/bluez/' + adapter + "/dev_" + device.replace(":", "_")

dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
system_bus = dbus.SystemBus()
device_object = system_bus.get_object('org.bluez', device_path)
device_interface = dbus.Interface(device_object, 'org.bluez.Device1')

def device_info(_arg0, event, _arg2):
    if event.get("Connected"):
        print("Mouse connected, attempt to pair...")
        try:
            device_interface.Pair()
        except dbus.exceptions.DBusException:
            pass

system_bus.add_signal_receiver(
    device_info,
    dbus_interface='org.freedesktop.DBus.Properties',
    signal_name="PropertiesChanged",
    arg0='org.bluez.Device1')

GLib.MainLoop().run()

保持在后台运行即可。我自己测试了一段时间,效果非常好。

PS: bluez 上游 bugzilla 堆满了各种没有回答的错误报告,目测不是那么好修的……

依云's avatar

解析 zxinc IPv6 数据库

本文来自依云's Blog,转载请注明。

很久以前,我就开始使用纯真 IP 数据库来进行简单的 IP 归属查询。离线数据库的好处在于快速、可依赖,我可以使用 ipmarkup 程序来快速地给文本中的所有 IP 地址注解。

然后随着 IPv6 的普及,我越来越多地需要查询 IPv6 地址了。每次 whois 或者找 ipip.net 实在是太没效率了,还暴露隐私。于是看到这个IP地址查询网站提供离线数据库下载之后,就也做了一个和 LinuxToy 介绍的这个程序类似的工具。

数据格式部分参考《纯真IP数据库格式详解》以及该网站自带的简略说明。

ipdb 程序库(带命令行)使用截图:

用于在文本中标注 IP 的 ipmarkup 工具链接开头给了。另外有一个 cip 可以根据情况调用 ipdb 或者 QQWry。

ipdb 解析部分只依赖 Python 3.5+,下载及更新部分会依赖我同一仓库中的 myutils 库。

顺便我也优化了一下 QQWry 模块:一是没必要保留一个低效率的版本,二是既然用 mmap 了,就去掉了所有的 seek 操作,目前大概是线程安全的了。这种跳转来跳转去的二进制格式,就算不用 mmap,也可以用 pread 来操作,免得要维护个当前文件位置,还线程不安全。

's avatar

我的 2018

好耶,我终于把 2018 年瞎鸡巴过完了!让我们看看究竟发生了什么吧。

新增项目

大学以后,自由的时间越来越多啦,于是自己糊了一些奇怪的项目:

以及其它一些奇怪的多人合作和为别人糊的东西(小声

非主流氪金(?

没想到曾经作为网易云音乐、Spotify 等串流服务的忠实用户的我,居然在 qwe 前辈的影响下,开始买专辑听了。其中实体专辑占了相当一部分,而且基本上都是捡垃圾

同时终于意识到,听专辑其实是另外一种完全不同的听音乐的方式。一张专辑里面的曲目安排等等,按顺序听下来其实相当有趣的!而串流服务上的那些歌单,却把这些有趣的地方都掩盖了,就相当尴尬了(

以及

毕竟我还是相信那句话,要是真的喜欢的话,就花钱支持他们吧。(ref)

(我知道我的数字收藏还是比大佬们差远了 QAQ

设备的变迁

2018 年初,我给我家安装了网件的 GS308 千兆交换机,还买了块 2TB 的东芝监控盘和硬盘盒,用一部 NUC 搞起了自家的云存储服务。

然后我意识到我其实并不需要那么多存储……直到 2018 年底,我只使用了不到 200GB。毕竟以前很多垃圾文件盒所谓资源塞满了我的移动硬盘什么的,删除它们其实就没啥了。于是大概很长时间都不用担心存储空间不足的问题了。

蛤,你说那种网盘?我现在都几乎不用了(

以及手机的话,我的主力开始转向一部 iPhone 6S Plus 了(虽然是我爸淘汰的,他换了部高级黑色 oppo)。一个原因嘛,就是我跟 macOS 进行互联互通,不至于那么胶水化。以及 iOS 真的是不折腾党的福音啊!

杭州、上海、厦门

今年暑假,我做了件相当作死的事情:一个人去杭州、上海和厦门瞎几把玩。

(TODO:添加相关图片)

结果当然是不尽如人意的:我在杭州的最后一天中暑了,然后在上海的酒店休息了半天,最后潦草的去了趟外滩,隔着河观赏了东方明珠塔;同时中暑的其它后遗症一直持续到我的厦门行程,那是相当尴尬了……

不过其实游玩景点并不是重点。在这期间还是和许多在网络上认识的、从未谋面的人见了面,感觉相当开心!qwe7002、Librazy、TonyPrince、SJoshua、rsqppp、Jack Works、M0xkLurk3r,以及其它最终因故没有与我会面的朋友们,感谢你们的支持!

结果我关于美丽的景色毫无印象,却对与沙雕网友的交流印象深刻

杂项成就

  • 通过了英语四级考试

已知问题

  • 瞎几把花钱导致自己山穷水尽(比如 Bandcamp 上的音乐和各种蜜汁转接头
  • 我用的 MacBook Pro 没有 Touch Bar(因此也没有另外两个雷电 3 接口,导致我的外设设定是相当的 clunky 啊。。后悔死了,但我当时真的糊涂了
  • 对一些底层的事物了解不深
  • 很多东西只会瞎几把做,然后搞得一塌糊涂

2019 年要做点啥?

  • 把 Pomment 项目写到能够发布的程度
  • 去更多的城市旅行,包括但不限于南京 / 福州 / 广州 / 成都等等
  • 停止瞎氪金,买一些重要的东西:
    • Nintendo Switch
    • Hades Canyon
    • 2019 款 15 寸 MacBook Pro
    • 一个还行的 1080p 或 2k 显示器
  • 通过科目二考试
  • 把三次元的其它事情处理好

啊,差不多就这些了。

2019 年,大家要一起努力哦。

Cee's avatar

Oh My 2018

Summary

変わる世界、 変わらない私。

Moments

2 第一次在国外过年,跨年的时候正好在面 Tumblr

3 和郑老板的自由女神像之旅

3 HackNYU First Prize

3 Frank 走了

4 和腾壕、岛娘的日本 LongHash Hackathon

5 买了吉他

5 西海岸旅行

6 开始在 Tumblr 写 iOS

7 Miku Expo 2018

8 搬家

9 换了纽约州驾照

9 秋招开始

10 第一封 Offer,来自 GrubHub

11 Chara Expo USA

11 决定当一名 Googler 了

12 决战 C95(三日)

Input & Output

+ Sony RX100 VI

- iPhone 7 Plus (Jet Black)

+ iPad Pro 12.9-inch (Space Gray)

- iPad Pro 10.5-inch (Silver)

+ Sony WH-1000XM3 (Gray Silver)

- Sony MDR-1000X (Gray Silver)

Numbers

  • 9,567 photos
  • 0 posts (?)
  • 2,853 twitter followers
  • 760 instagram followers
  • 11 books
  • 245 contributions (only on GitHub)

Hall of Fame

The Next

  • 写完 One Clock!
  • 努力找到另一半😘(真丢人,退群吧)

PS:拖这么晚才发的原因是升级了系统 Ruby 挂了…没法生成静态网页

farseerfc's avatar

【聽譯】君さえいなけりゃよかった

君さえいなけりゃよかった 如果你從未出現過該多好
降り出した雨の中で 君に出会った時から 下起雨的那一刻 從遇到你那時起
君がいないということが 当たり前じゃなくなった 身邊沒有你的情況 就已經不再是平常
ああ こんなはずじゃない 啊 不應該是這樣的
ずっと自分勝手にさ 過ごせたはずなのに 明明一直是散漫地過着自己的日子
まるで僕じゃないような僕が さらけ出されてくよ 就像是帶出了不是我的另一面的我

君さえいなけりゃよかった こんな気持ちは知らないから 如果你從未出現過該多好 就不會知道這種心情
やらなくちゃいけないことが 手つかずのまま積もってく 一堆不得不做的事情 堆在手頭越積越多
僕じゃなくてもいいのなら こっちを見て笑わないでよ 如果不是我也可以的話 就別看着我這邊笑啊
大袈裟じゃなくてそれだけで 忘れられなくなるの 甚至那些不重要的事情 都變得難以忘記了

君の適当な話も 全部心に刺さります 你無意間隨口說的話 全都刺在心頭
気にしなけりゃいいのにな 残らずかき集めちゃうの 雖說只要不在意就可以了 卻一句不剩全收集了起來
ああ こんなはずじゃない こんなはずじゃない 啊 不應該是這樣的 不應該是這樣的

君に出会わなきゃよかった こんなに寂しくなるのなら 如果沒遇到過你該多好 就不會變得如此寂寞
君じゃなくてもいいことが もう見つからないの 已經找不到 和你無關也可以的情況了
忘れられないから 君じゃなかったら 無法忘記了 要不是你的話

いっそ見損なってしまうような そんなひとだったらなあ 乾脆變成根本看不起的人 如果是那種人的話
でもそれでも どうせ無理そう 嫌いになれないや 但是即使如此 大概反正也不可能 無法變得討厭

僕がいなくてもいいなら いっそ不幸になってしまえ 如果不是我也可以的話 乾脆變得不幸吧
最後にまた僕の元に 泣きついてくればいい 最後還是會回到我身邊 哭着湊過來的話就可以
君さえいなけりゃよかった こんな気持ちは知らないから 如果沒有你該多好 就不會知道這種心情
やらなくちゃいけないことが 手つかずのまま積もってく 一堆不得不做的事情 堆在手頭越積越多
僕じゃなくてもいいのなら こっちを見て笑わないでよ 如果不是我也可以的話 就別看着我這邊笑啊
大袈裟じゃなくてそれだけで 甚至那些不重要的事情
君のこと 間違いなく 對你 毫無疑問
苦しいほど 好きになっちゃうよ 刻骨銘心地 變得喜歡上了啊

忘れられないから 君じゃなかったら 因爲無法忘記 如果不是你的話
君に出会わなきゃ 僕じゃなかったら 要是沒遇到過你 如果不是我的話
君さえいなけりゃよかった 如果你從未出現過該多好
Phoenix Nemo's avatar

制作 Arch Linux 内存系统启动盘

之前尝试过 Arch Linux in RAM 完全运行在内存中的轻量业务系统,最近在维护一些物理服务器看到没有安装系统的服务器不断重启,想到了可以制作类似的内存系统启动盘,以高效完成系统测试、安装、远程维护等任务。

这时候就要祭出 mkarchiso 大法了。这是自动化制作最新版 Arch Live 镜像的工具集,当然也可用于制作定制化的 Arch 镜像。

准备

首先安装 archiso

1
~> sudo pacman -Syy archiso

它提供了两种配置方案,一种是只包含基本系统的 baseline,一种是可以制作定制 ISO 的 releng。要制作维护用 ISO,当然是复制 releng 配置啦。

1
2
~> cp -r /usr/share/archiso/configs/releng/ archlive
~> cd archlive

定制

整个过程不要太简单。先来了解下各个文件的用途:

  • build.sh - 用于制作镜像的自动化脚本,可以在这里修改一些名称变量或制作过程的逻辑。
  • packages.x86_64 - 一份要安装的包列表,一行一个。
  • pacman.conf - pacman 的配置文件,不用多说了吧。
  • airootfs - Live 系统的 rootfs,除了安装的包之外,其他的定制(以及启动执行脚本等)都在这里。遵循 rootfs 的目录规则。
  • efiboot / syslinux / isolinux 用于设置 BIOS / EFI 启动的配置。

[archlinuxcn] 仓库加入 pacman.conf

1
2
[archlinuxcn]
Server = https://cdn.repo.archlinuxcn.org/$arch

然后修改 packages.x86_64,加入 archlinuxcn-keyring 和其他需要预安装的包:

1
2
3
4
5
archlinuxcn-keyring
htop
iftop
iotop
ipmitool

按需修改即可啦。

要启动为内存系统,需要加启动参数 copytoram

修改文件 syslinux/archiso_pxe.cfgsyslinux/archiso_sys.cfg 文件,在启动参数后加 copytoram,像这样:

1
2
3
4
5
6
7
8
9
10
11
INCLUDE boot/syslinux/archiso_head.cfg

LABEL arch64
TEXT HELP
Boot the Arch Linux (x86_64) live medium.
It allows you to install Arch Linux or perform system maintenance.
ENDTEXT
MENU LABEL Boot Arch Linux (x86_64)
LINUX boot/x86_64/vmlinuz
INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img
APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram

启动时即可将整个 SquashFS 文件复制到内存。如果内存比较小,也可以指定 copytoram_size 来限制 tmpfs 占用内存的最大数量。

同样,也需要修改 efiboot/loader/entries/archiso-x86_64-usb.conf 的启动参数。在 options 行添加

1
options archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram

制作

创建工作目录和输出目录

1
mkdir -p work out

最后一步,只需要以 root 权限执行 ./build.sh 就可以啦。

要看具体执行过程的话,加 -v-h 看所有参数。

完成后,即可在 out 目录得到准备好的 ISO 文件。将其 dd 到 USB 闪存盘,大功告成(‘・ω・’)

Ref:

  1. https://wiki.archlinux.org/index.php/Archiso
  2. https://git.archlinux.org/archiso.git/tree/docs/README.bootparams#n53
farseerfc's avatar

【譯】使用 GNU stow 管理你的點文件

譯註

這篇是翻譯自 Brandon Invergo 的博客的英文文章 Using GNU Stow to manage your dotfiles 。 Brandon Invergo 的博客採用 CC-BY-SA 3.0 授權,因此本文也同樣採用 CC-BY-SA 3.0 ,不同於其它我寫的文章是 CC-BY-NC-SA 4.0 授權。

我自己已經使用此文中介紹的方案管理 我自己的 dotfiles 快 3 年了。最早想採用這樣的管理方案是爲了方便在多臺 Arch Linux 系統之間同步配置, 後來逐漸主力系統也更新換代了一次,又同步到了自己的 vps 上去,目前管理多個 Arch Linux 上都多少都有這套配置。甚至裝好 Arch Linux 添加好用戶最初做的事情就是安裝 …

AprocySanae's avatar

谁在里面

这篇文章完全是虚构的,比如我几乎晚上不加班,也没有会 JOJO 立的同事。毫无疑问我看过冰菓小说,当然日常系推理也并非冰菓的专利。

我的确寻思过写个和千反田类似的女孩放入书中,以读者的身份引出线索。但是构思时候很悲伤地发现我并不熟悉女性的思考方式。

文辞相当市侩,但是毫无疑问这才是我熟悉的生活。

IceHoney Blog's avatar

前端开发地图应用的调研

我司的产品终于发布了新版本,所以忙碌的开发暂时告一段落。最近在做下一期的规划,然后就是要做前端网站来满足用户的需求。由于我们的产品是和地图强相关的,所以也对地图做了很多调研。

基本需求

产品需要在国内和国外使用,费用合理,可以换地图的贴图并且能在地图上绘制各种信息。例如多边形或圆形还有贝塞尔曲线。

MapKit JS

苹果居然也出了网页版本的地图产品MapKit JS,可谓是良心。但是目前还处于Beta版本。如果以后要考虑产品的全平台化,显然不是一个很好的选择。不过苹果的产品可以在国内国外使用不用太担心地图偏移问题,而且可以贴图和绘制图形,但是目前网页版还不能绘制贝塞尔曲线,iOS客户端倒是可以。

MapBox

MapBox是一个地图服务提供商,经过调研MapBox使用最新的WebGL技术来渲染,摆脱了传统的下tile来贴图的方式,渲染速度得到了大大的提升。也能解决国内和国外的地图显示问题,但是画图功能就只有画线和多边形。不过提供了底层的画图层的接口,需要自己写WebGL的shader。这就增加了开发成本。。。

GoogleMap

谷歌地图当然好,但是我最后才说。因为谷歌无法在国内使用,这种情况下我就不得不写两套接口来对应,例如国内高德地图,国外谷歌地图,会大大增加开发成本。而且谷歌地图最近刚升级付费条款,费用是按照请求次数来收,所以用户大量增长之后的开销也会非常大。不过谷歌也不能画贝塞尔曲线。。。

地理坐标系

由于是需要在地图上绘制新的tiles,所以自然就涉及到坐标的转换和计算。如何计算一个经纬度落在哪一张tiles上,以及在不同缩放级别下tiles的正常显示和重绘。这些都是需要自己来实现的。 这里有一个还算有名的官方介绍,并给出了Python的源码。可以参考这个来进行实现。

坐标系转换

每个国家都有自己的坐标系系统,虽然GPS使用的WGS 84标准非常流行,但是这个是美国制定的。每个国家当然都需要根据自己国家需要来定制自己的标准。例如中国就有北京54坐标系,西安80坐标系。北京54和西安80是参心坐标系,大地原点分别在苏联和西安。难以表达高度信息,目前国家正在推广2000国家大地坐标系,这个和WGS84一样是地心坐标系,即以地球质量中心为原点。日本也有自己的平面直角坐标系,我们当然需要各种坐标转换,还好有现成的开源项目proj4js

总结

各个地图服务商都各有优劣,但却没有一款完美的。主要是用途也比较特殊,可能在地图服务上进行二次开发的可能性比较高吧。不过提供地图服务的也没几家可以选择的。。。

Phoenix Nemo's avatar

使用 fs.WriteStream 编写超简单的日志流

虽然 console.log 很好用,但是生产环境需要保存日志的时候就比较蛋疼。暴力 fs.appendFile 会消耗大量的 file handler,因此用 writable stream 来复用 file handler 是更好的选择。

大概是个不能再简单的思路了。先创建一个写入流

1
2
3
const fs = require('fs');

let logStream = fs.createWriteStream('./test.log');

这样便创建了一个文件写入口,需要时直接调用 logStream.write 即可写入数据。
接下来编写一个用于记录日志的函数替代 console.log

1
2
3
function logger (message) {
logStream.write(message);
}

至此基本功能就写完啦。但是太简陋了对不对,还是要再加点装饰。

重写 logger 函数,区分 stdoutstderr

1
2
3
4
5
6
7
8
9
10
11
12
let logInfo = fs.createWriteStream('./stdout.log');
let logError = fs.createWriteStream('./stderr.log');

let Logger = {};

Logger.info = (message) => {
logInfo.write('[INFO] ' + message);
}

Logger.error = (message) => {
logError.write('[ERROR] ' + message);
}

感觉还是少了点什么…日期?

1
2
3
Logger.info = (message) => {
logInfo.write(new Date().toISOString() + ' [INFO] ' + message + '\n');
}

嗯嗯。这就像样了。把代码整合起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs = require('fs');

let logInfo = fs.createWriteStream('./stdout.log');
let logError = fs.createWiteStream('./stderr.log');

let Logger = {};

Logger.info = (message) => {
logInfo.write(new Date().toISOString() + ' [INFO] ' + message + '\n');
}
Logger.error = (message) => {
logError.write(new Date().toISOString() + ' [ERROR] ' + message + '\n');
}

module.exports = Logger;

需要用时

1
Logger.info('This is an information.');

现在看对应的 stdout.log 文件就有相应内容啦。

1
2
~> tail -f stdout.log
2018-11-18T10:52:57.333Z [INFO] This is an information.

不够刺激?

1
2
3
[...Array(10000)].forEach((item, index) => {
Logger.info('Hello! ' + index);
});
1
2
3
4
5
6
7
8
9
10
11
12
~> tail -f stdout.log
...
2018-11-18T10:58:30.661Z [INFO] Hello! 9990
2018-11-18T10:58:30.661Z [INFO] Hello! 9991
2018-11-18T10:58:30.661Z [INFO] Hello! 9992
2018-11-18T10:58:30.661Z [INFO] Hello! 9993
2018-11-18T10:58:30.661Z [INFO] Hello! 9994
2018-11-18T10:58:30.661Z [INFO] Hello! 9995
2018-11-18T10:58:30.661Z [INFO] Hello! 9996
2018-11-18T10:58:30.661Z [INFO] Hello! 9997
2018-11-18T10:58:30.661Z [INFO] Hello! 9998
2018-11-18T10:58:30.661Z [INFO] Hello! 9999

搞定(┌・ω・)┌超简单的吧。

Felix Yan's avatar

为 glibc localedata 添加民国纪年支持

昨天被 @聞其詳 问起 glibc 对日本平成XX年的纪年支持情况(感谢 @farseerfc 老师),发现 glibc localedata 中并未包括民国纪年支持。在查询文档后稍微鼓捣了一下,简单实现了这个功能:

$ LC_TIME=zh_TW.UTF-8 date +%EY
民國107年
$ LC_TIME=zh_TW.UTF-8 date +%EY --date="1912/4/3"
民國元年
$ LC_TIME=zh_TW.UTF-8 date +%EY --date="1913/4/3"
民國2年
$ LC_TIME=zh_TW.UTF-8 date +%EY --date="1911/4/3"
民前1年
$ LC_TIME=zh_TW.UTF-8 date +%EY --date="1900/4/3"
民前12年

修改方法:

编辑 /usr/share/i18n/locales/zh_TW 文件,找到 END LC_TIME,在它之前加入这样三行:

era "+:2:1913//01//01:+*:民國:%EC%Ey年";/
    "+:1:1912//01//01:1912//12//31:民國:%EC元年";/
    "+:1:1911//12//31:-*:民前:%EC%Ey年"

保存后重新 locale-gen 即可。

不足:暂时没有弄明白怎么实现中文数字,不过阿拉伯数字也还过得去……

提交:glibc 的提交方式好像比较麻烦,所以先写篇博客好了。已提交并合并到 glibc。

发散:这个方法当然还可以用来实现各种自定义年号,比如朝鲜的主体纪年同样在 1912 年为元年,另外还有1970 “Unix 元年”、2013(咚咚咚,敲门声

's avatar

面向人气编程

我设计一款软件有个毛病:常常设计一大堆自己根本用不上的无用功能,并美其名曰『为了我的用户』。

但是为什么我常常这样想?为什么我要觉得他们喜欢的东西就是我必须做的?为什么我要追捧他们喜欢的无用功能一大堆的软件?

为了讨好他们。

我从以前就有一种错觉:当我的软件变得有很多人使用,便是黑魔法发生的时候。越来越多的人会对我产生好感,那些 block 了我的人会 unblock 我,我的安全感会得到越来越多的提升,并得到更多的自信。

但是别人的实践却在给我自己打脸。贴吧签到助手等一系列开源软件,都是人气很旺的东西,但为什么最后它们都停止开发了呢?总会发生一些奇怪的事情吧。

其实我这种思想就太矛盾了。毕竟当使用我软件的人越来越多,用户群的质量自然会逐步下降啊。

市面上还有很多软件有很多在实践中没用的功能,但它们其实并不是程序员设计出来的,他们只是在实现既定的计划。那么既然我有机会自己设计和开发一款软件,为什么我就不能做的简单一些呢?

所以在接下来的 Pomment 开发中,我还是放弃那些思想,只开发我自己需要的功能好了,毕竟我这轮子首先还是为我自己服务(

IceHoney Blog's avatar

后端开发的技术总结

说实话,最近前端开发的事情并不是很多。所以自己也慢慢开始做后端开发,逐渐向全沾工程师靠拢。现在的开发工作,分工分明,后端也只需要专注API的请求与返回就好了。

API文档

使用API Blueprint作为API规范,并使用RSpec API Blueprint来自动生成文档。 生成的Markdown文件,可以很方便的转换成网页便于客户端开发。例如这个工具aglio

后端开发

现在采用的开发技术框架是Ruby on Rails。不得不吐嘈一句,Ruby on Rails在日本真是压倒性的流行。Ruby on Rails还是MVC架构,原生支持RESTful API。但我觉得下一代的接口标准应该是facebook推出的GraphQL,不过先阶段RESTful API也足够了。

Models

Models主要是用来描述数据库结构,表与表之间的关系。还可以定义对数据库操作的方法,数据之间复杂的业务逻辑基本都是放在这里。

Views

Views主要是用来定义相应的数据的格式,把数据整合成统一的JSON标准供客户端使用。

Controllers

Controllers是请求的入口,用来定义请求的参数,并接收请求进行参数的过滤处理。然后调用Models里定义的方法完成数据的操作,并调用Views来完成数据的响应。

权限管理

权限管理是一件非常复杂的事情,定义某个接口在什么条件下可以调用,需要检查用户是什么身份。所以推荐是使用现有的成熟框架来进行管理。这里使用pundit来管理各个请求的权限检查。

软删除

在实际的项目中,我们需要进行软删除操作。例如用户加入了某个组织,以组织成员的身份发表了文章。当用户退出这个组织的时候,我们并不能直接删除数据库记录。否则无法显示文章的作者。这时候我们就需要软删除操作,对用户来说这条记录已经消失了。但对开发者来说,这条记录还是需要的。我们也使用框架来解决这个问题,这里采用的是Paranoia

测试

后端开发是偏向于纯逻辑的开发,所以测试非常重要。正常情况下的输入输出非常简单,代码的80%都是需要考虑到各种各样的边界条件。需要考虑到用户的各种不同情况下的操作,给出合适的错误提示。所以需要完善的测试来覆盖所有场景,这里采用的是e2e测试,模拟请求,测试后端服务器的响应输出是否符合预期。采用的测试框架是rspec-rails

参考:

Ruby on Rails Guides

Roy Binux's avatar

少女前线拖尸脚本 和 生成它的可视化工具

最近在玩少女前线,这是一个手机游戏,over。不是,就真的没有什么好讲的嘛,了解的人早有耳闻,不了解的就只要知道这是个手机游戏就好了,嗯。

然后,我会好好地,正常地,氪金地去玩这个游戏吗?不可能的,玩游戏哪有破解它有意思呢。当年破解 Ingress 是因为它用的 HTTPS 通信的,算是本行。百万亚瑟王是因为别人已经逆向好了,我只是写了一些 bot。现在这么办,玩不了了吗?作为一个不会安卓,不会逆向,不会汇编的菜鸡,那我只好上按键精灵了啊。于是乎,我找到了这个: AnkuLua

AnkuLua 是一個專注在自動化的Android App
基本自動化動作有:

  • 抓取螢幕並找尋指定圖案
  • 對找圖結果採取使用者要的動作(例如點擊、抓放(drag and drop)、打字…等等)

最重要的是,它能运行 lua 脚本!虽然我是一个不会安卓,不会逆向,不会汇编的菜鸡,但是我会 lua 啊。

ankulua-vision

不过,在使用过程中发现,找寻指定图案,需要不断截图/裁剪,这样太麻烦了。于是我又用 electron 做了一个可视化的截图资源管理器 ankulua-vision,像这样的:

基本思路就是,一般游戏是由众多 UI 界面组成的,点击某个按钮能跳转到某个界面上去。那么通过截图,标注识别区域,那么程序就能知道游戏现在所处的界面。通过标注按钮区域,那么只需要 goto('battle'),程序就能自动规划从当前界面到 battle 的可行路径,然后点啊点啊就完成需要的操作了。这样一方面不需要自己去裁剪图片了,另一方面通过框架代码,在运行过程中能够有更多的错误检查,自动应对可能出现的各种异常。

理论上,对于点啊点的游戏,是能实现无代码的。即使不能,对于复杂的动作,也可以通过 lua 拓展。

源码在这里:https://github.com/binux/ankulua-vision

你依旧需要在安卓手机或者模拟器中安装 ankulua,然后加载生成的 start.lua 脚本。默认自带了一个简单的循环逻辑,运行后可以直接图形化界面配置运行。当然你也可以通过 lua 脚本拓展,除了 ankulua 本身的 API 可用之外,你也可以使用 stateMachine 这套界面跳转逻辑 API,重用简化步骤。stateMachine 的 API 在 README 中有简略的文档说明。

源码使用 GPLv3 或 MIT 许可证,取决于第一个有效 PR(例如 fix typo 不算),如果第一个 PR 之前有商业化需求或者 PR 作者要求,则 MIT。

少女前线拖尸脚本

WARNING: 任何使用脚本的行为都是官方禁止的,我不对下文所述任何内容以及其后果负责

于是,这里就是 少女前线的拖尸脚本:

https://github.com/binux/binux_github_com/releases/download/gf/shojo.zip

同时它也是一个 ankulua-vision 的项目,你可以通过 ankulua-vision 打开这个项目目录,调整截屏或者按钮位置。

脚本实现的功能

  • 43e, 02, 52n 拖尸
  • 自动重启后勤
  • 自动强化或者分解人形
  • 自动修理

使用方法

  1. 根据 [填坑结束?][失了智]萌新向拖尸教学帖[更新8-1N相关] 一文准备好打手和阵型,一队练级队,二队补给队,52n 还需要 3 队狗粮队。
  2. 解压拷贝脚本到手机中,在 ankulua 中加载 start.lua。
  3. 在启动界面中选择你的两个打手(每轮结束后,两个打手会交换),选择拖尸任务,如果仅自动后勤,选择 null 就好了。

其中 52n 会在战斗中撤退 5, 8 号位 (见 NGA 文 “43e的说明” 展开部分),02 在选择 m4a1 时会撤退 1, 7 号位。

然后开始吧!

WARNING: 任何使用脚本的行为都是官方禁止的,我不对上文所述任何内容以及其后果负责

over

's avatar

2018 XJNU CTF Web Writeup

比较简单的新疆师范大学的 CTF 比赛 http://ctf.xjnu.edu.cn/
Felix Yan's avatar

请不要把 Wifi Dongle 翻译成加密狗

在许多翻译过来的文本里,无线网卡(Wifi Dongle / Wifi Adapter)被翻译成了“加密狗”。最近在微博大火的一篇《俄罗斯特工又蠢了!实名行动暴露自己还卖了300多特工队友…..》里面,又出现了类似的错误,让我不得不想说说这个问题。

先来看看这个例子:

文章描述了几个黑客在目标附近发射伪造热点钓鱼,此处的未翻译单词包括了 Wifi 和 Dongle。根据上下文,带一个无线网卡发射热点应该是正确的意思,而加密狗在这里和上下文没啥关系……

我尝试在搜索引擎里搜索对应词组,很不幸,惨状令人叹息:

就连正规电子产品的用户界面都中了枪:

这个问题的来源,看起来是国内加密狗早年被简单称呼为 Dongle:

Dongle,按照维基百科的解释,是和 Adapter(适配器)很类似的一个词,泛指了一大类连接、转换器。

Wireless/Wifi Dongle = Wifi 适配器,Bluetooth Dongle = 蓝牙适配器,以此类推。所以,在看到类似 USB Dongle 这样模糊名字的时候,请务必联系上下文。如果原文用了 Wifi Dongle 这样比较明确含义的词组,请翻译为 Wifi 适配器,或者无线适配器,不要再翻译成加密狗了。

Felix Yan's avatar

Thunderbolt 3 eGPU Bumblebee 方案尝鲜

最近从公司淘了一块 GTX 1060,淘宝了一个显卡盒折腾 TB3 eGPU 方案。由于笔记本经常带出门,打算采用即插即用的 bumblebee 方案(回家插上盒子用独显,拔下盒子可以带出门,可以正常待机不用重启)。下面记录一下 Arch 上配置的步骤和遇到的坑。

1、准备软件包

# pacman -S bumblebee primus lib32-primus nvidia-dkms lib32-nvidia-utils bolt

2、修改 bumblebee 配置

修改 /etc/bumblebee/xorg.conf.nvidia,在 Section “Device” 中加入:

Option "AllowExternalGpus" "true"

修改 /etc/bumblebee/bumblebee.conf,在 [optirun] 中修改:

Bridge=primus

3、插入 TB 线,验证设备(不建议完全关闭设备验证!)

输入 boltctl 查看设备信息,记下设备的 uuid。

$ boltctl
 ● TUL TBX-550CA
   ├─ type:          peripheral
   ├─ name:          TBX-550CA
   ├─ vendor:        TUL
   ├─ uuid:          00xxxxxx-xxxx-xxxx-ffff-ffffffffffff
   ├─ status:        authorized
   │  ├─ domain:     domain0
   │  └─ authflags:  none
   ├─ authorized:    Fri 28 Sep 2018 08:23:25 AM UTC
   ├─ connected:     Fri 28 Sep 2018 08:23:21 AM UTC
   └─ stored:        Thu 27 Sep 2018 05:22:07 AM UTC
      ├─ policy:     auto
      └─ key:        no

验证设备并设置为自动验证:

$ boltctl enroll --policy=auto 00xxxxxx-xxxx-xxxx-ffff-ffffffffffff

4、启动 bumblebeed

# systemctl start bumblebeed

5、测试、运行程序

$ optirun glxspheres64
Polygons in scene: 62464 (61 spheres * 1024 polys/spheres)
Visual ID of window: 0x13f
Context is Direct
OpenGL Renderer: GeForce GTX 1060 5GB/PCIe/SSE2
……

6、安全删除设备(参考了 jpamills 博客里的脚本)

#!/bin/bash

secs=5
tbt_chain=/sys/bus/thunderbolt/devices/0-0/../../../..

echo "Unplug eGPU script started."
if [ "$(id -u)" != "0" ]; then
        echo "Please run using sudo. Exiting."
        exit 1
fi

systemctl restart bumblebeed
sleep 2
modprobe -r nvidia_modeset
modprobe -r nvidia-uvm
modprobe -r nvidia

if [ -e $tbt_chain/remove ]
then
        echo 1 > $tbt_chain/remove
        echo "Thunderbolt chain removed from PCI tree. Please unplug eGPU now."
        while [ $secs -gt 0 ]; do
                echo -ne "$secs to rescan...\033[0K\r"
                sleep 1
                : $((secs--))
        done
        echo 1 > /sys/bus/pci/rescan
        echo "Rescanned the PCI bus. Completed."
        exit 0
else
        echo "eGPU does not appear to be attached. Exiting."
        exit 1
fi

如果和我一样在盒子上插了外置硬盘,还可以在脚本里加上相应的 umount 语句。删除设备时需要先把使用设备的程序退出。

效果展示

剩下的问题

  • 脚本里可以考虑通过判断 nvidia-smi 列出的 PID 自动杀掉所有还在用卡的进程
  • Vulkan 应用程序暂时无法使用外置卡运行(仍然使用集显),可能需要 bumblebee 提供支持
Phoenix Nemo's avatar

重构 StickerSetBot

关注 Telegraf 有一段时间了。特别是最近 Telegram 上 spammer 猖獗导致 Telegram 对于用户行为限制越来越严格,由此想过写一个简单的 bot 来处理加群请求之类的。

总之原因都是没时间。终于搞定一些事情之后发现之前瞎写的 Telegram 导出贴图 bot 居然备受欢迎…正好 Telegram Bot API 也更新了,来重构吧!

拆分逻辑代码

最头疼的事情首先是当时写这 bot 的时候只顾着考虑各种情况,逻辑像流水一样全部写成一坨。虽然实际不复杂吧但这不是 best practice。于是把每个功能单独拆出来先。

on('command') 的逻辑代码整块移出来作为 handler,然后能够原子化的功能再单独拆分成函数调用。目前的效果虽然还是有不少逻辑层在 handler 里,但是基本达到了比较方便维护的目的。

handler 本来就是拿来写逻辑的啊摔

接下来再清理冗余代码和各种 hard code,加了两个方法让代码看起来更整洁一些。于是就先这样。

迁移框架

好在 Telegraf 和之前用的框架在参数上很多兼容,所以这没有花太多时间。顺便尝试采用了一部分 ES6 的风格,嘛…果然不喜欢。

所以就不要吐槽为什么 ES5 和 ES6 的风格混写了。

之前要一大长串的传参现在只要一个 context 了好方便啊。中间件也好方便啊~

以上。

调试:无尽的 bugfix

并不指望一通大换血之后的代码能一次跑起来…但是没跑起来的原因是我传错了中间件值这不能忍!!为什么一会儿传的是函数本体一会儿传的是函数调用啊摔!!

而且这问题还让我调了两个小时!!!

调通了之后就很舒服了

遇到的坑还有 context 本身不能当 session 用,然而不想再引入 session 中间件于是自己写了个超简陋的内存 session。就是为了多语言支持。因为一觉醒来发现这 bot 语言莫名其妙变中文了(messages 成了全局变量 = =

当然还有 Telegram 自己的坑,比如什么贴纸就是死下载不能然后整个程序就 hang 着了。

一键导出贴纸包

终于!Telegram bot API 添加了 StickerSet 类型。只要有贴纸包名称,就可以获取整个贴纸包的信息。考虑不改变用户习惯的情况下(你哪有什么用户啊可恶)对本身处理贴纸和其他消息的函数做了修改,顺便又拆了俩函数出来(怎么代码越来越多了啊喂!

最后结果就是没有一屏看不到头的函数啦~(你快够

以及加入了用贴纸包链接导出一整组贴纸的功能,算是真正意义上的 StickerSetBot 了。

然后贴纸过多卡死了 Telegram 的 ratelimiting

直接导出单张贴纸

既然功能拆分了那也就方便加更多别的功能啦。比如不新建任务,直接甩过去一张贴纸来获得 PNG 文件~

这只 bot 在这里,源码在这里。欢迎各种玩坏~(记得去发 issue

就酱(,,•﹏•,,)

IceHoney Blog's avatar

跨域请求中的cookies处理

现在的前后端开发已经完全分离,后端服务器和前端服务器分别部署在不同的服务器。同时也对应不同的域名,所以跨域请求领域方面的知识也需要补充。

跨域请求添加header

我们都知道,出于安全考虑,JS是有同源策略限制。所以,我们在对其他域名发起请求的时候需要添加 http header。

    Access-Control-Allow-Origin: *

这个参数的值只能为星号或者具体的网址,星号代表所有网站。

跨域请求添加cookie

JS跨域请求有两个API可以使用,XMLHttpRequestfetchXMLHttpRequest会默认带上cookies,但是fetch默认不会带上。如果需要带上cookies,需要把withCredentials设置为true

跨域请求服务器设置cookie

我们都知道服务器设置cookie是通过set-cookie的 http header来完成。浏览器会读取这个信息设置cookie。但是对于跨与请求,默认是无效的。我们需要再添加一个 http header。

    Access-Control-Allow-Credentials: true

当设置了这个 http header,Access-Control-Allow-Origin就不能设置为星号了,必须指定具体的网址。我们必须指定withCredentialstrue并且Access-Control-Allow-Credentialstrue的时候,服务器返回的set-cookie才会生效。

参考:

Access-Control-Allow-Credentials

CORS

Felix Yan's avatar

Arch Linux 社区中不成文的约定(一)

作为一个年轻和小众的社区(咳咳),Arch Linux 社区中有着许多不成文的约定。本文希望通过稍稍讲解一二,来消除一些新人们常常感到的困惑。由于预感到日后可能还会有更多问题,暂且认为这是系列里的第一篇

  • 修理“坏”包和提醒更新

仓库里一般的包可能有一个或多个维护者,也可能没有维护者,成为“孤儿”。如果坏掉或者过期的包是一个孤儿,申请从仓库里删掉是更有效的方法,这样它可以在 AUR 找到新的主人。发邮件到 aur-general 解释一下情况,并表明自己或者别人想维护它,一般都会很快得到解决。

如果“坏”包有维护者,但他太忙了呢?这种情况下,这个包很可能已经在 Bug Tracker 里泥足深陷。这种时候可以通过帮维护者一个小忙的方式来促进问题的解决。如果有人找到了上游 Bug Report,并且上游已经修复提供了 patch 的话,可以考虑把 patch 贴到 Arch 的 Bug Tracker;如果这件事已经有人做了,可以修改 PKGBUILD 打上 patch,然后把改好的 PKGBUILD 贴上去;如果连 PKGBUILD 都有人准备好了,而且过去了一段时间的话,可以考虑把这份准备好的 srcpkg 或者 diff 直接发邮件给维护者,附上简短的感谢和一个笑脸

同样的道理,如果一个过期包已经被标记过期很久了,也可以准备一个新版的 PKGBUILD 发邮件给维护者。

  • 提供旧版软件包

因为懒得迁移各种各样的原因,你可能希望某些软件包的新旧版本同时提供。在 Arch 的实践中,这种情况被尽量避免了。把无法迁移的古老软件删掉还是提供一个旧版库让古老的软件能用,一定程度上取决于维护者对这个古老的软件有多坚持。

另一个考虑的因素是上游如何看待新旧版本。有的软件新版发布后,旧版就不再维护了,这种情况下 Arch 通常尽力避免成为新的上游。而有的上游则同时维护多个版本,Arch 这边根据其他软件的依赖情况,可能会有选择性地同时维护几个。

  • 第三方 patch 能不能加上

Arch 对 patch 的态度比较保守,在规则里写的是一般只有修复编译和主要功能的上游已经接受的 patch 会考虑。在实践中,有些时候没有这么严格,比如开发者自己写的 patch 一边提交给上游,还没等答复,一边就加到了包里。因为 Arch 打包并没有 Review 过程,实际上加了什么 patch、靠不靠谱就全靠开发者自己掂量了。

一般来说,被上游明确拒绝的功能性 patch 是不怎么会考虑的。修复一个特定问题,尤其是影响比较大的问题的上游有点意见的 patch 有可能会被考虑。修复一个安全问题的 patch 经常会被接受,尤其是已经拿到 CVE 号的。

  • 使用上游二进制 vs 从源码编译

一般常识是发行版们倾向于从源码编译一个软件,理由包括确保二进制真的来自这份源码、尽量使用系统中的动态链接库而不是静态编译一份以满足安全更新和体积方面的考虑、进行必要的修改等。

但是因为太懒一些问题,即使是在 Arch 官方仓库里也直接重新打包了一些上游的二进制。这些问题包括:源代码不开放(nvidia、flash 等)、编译过程中会去下载东西,而且不容易解决(dart、一些 java 软件等)。

  • 文档是否打包

现在互联网十分发达,开发者们查询文档通常都是直接上网搜索。在这样的背景下,是否打包软件包的文档(通常都是开发文档)成为了一个问题。早些时候的包开启文档较多,如果太占体积还会考虑拆一个单独的文档包。后来才增加的新包则很多都没有启用文档,除非被用户要求提供文档才考虑这件事。

's avatar

T-Pot 蜜罐的介绍及使用

T-Pot 蜜罐是德国电信下的一个社区蜜罐项目,是一个基于 Docker 容器的集成了众多针对不同应用蜜罐程序的系统,目前发行的最高版本是 2017 年 11 月 7 日发布的 T-Pot 17.10,根据官方的介绍,每年都会发布一个新的版本。
Dimpurr's avatar

钉子的谜之 SETUP 18 @ Ningen

本文原载于 人间 / Dimpurr Cheny ,前文 钉子的谜之 SETUP (2014) 。

?‍? 简要介绍一下自己,并且谈谈正在做什么?

这里钉子,现役帝都大学生。曾经写写画画做过不少事情,当过宅圈内知名前端博主,设计过几个流行的 WordPress 主题,发起过一点音乐社区相关小项目,都已经成为过去。大一以来,唯一的成就是为了拯救北邮人技术组废部危机,不得不成为偶像,建立了 BYRIO 开源社区。目前在选择遵循自己内心去做游戏设计和编曲,还是顺应他人期待继续在 CASIA 和 MSRA 的 ML 搬砖日常前犹豫不决。

? 你使用的硬件有?

主役 MacBook Pro + Surface Pro ,经常同时携带 (虽然很重) ,前者用于影音处理、工程开发、平面设计、绘图板画画 (Wacom Intuos PTM CTH680) ,后者用于 PDF 阅读和批注、 OneNote 笔记、 Surface Pen 作画、推 Gal 和小游戏 (V-A HALL, FrostPunk) 。 Surface Pro 虽然性能有限,但是在轻薄的前提上能让自己在任何时空基本具备进行任何工作的能力。

宿舍配备 Linksys WRT1900AC v1 用于享受百兆校园网 (IPv6 免流 + 学校网络中心成员特权套餐) ,一块 26’ DELL UltraSharp Monitor U2415 外接屏一般连接 MBP 用于宿舍组团看 Rick and Morty 或少女歌剧、瀑布流展示作画和人体结构参考资料、工作时看论文和文档等,计划接上 Switch Dock 之后用来在宿舍玩舞力全开。配置 MIDIPLUS X6 键盘 + ATH-AD2000X 开放式大耳用于演奏。

随身 Sony XPERIA XZ1 ,佩戴 Moto 360 。 SHURE SE846 耳塞退烧,前端 iBasso DX90 和耳放 Sony PHA3 常年借给同学。 iPad mini 用于音游 (Cytus II, Arcera, Groove Coaster, Dynamix) , Nintendo Switch 用于 Party Game (分手厨房) 和沉迷死喷浪涂 (Splatoon) 。

? 你使用的软件有?

常年 macOS, Windows 和 Linux 跨平台用户,曾经是 OpenSUSE 党。 macOS 下剪贴板历史 Paste 和快速访问 Spotlight 重度用户, Win 对应工具是 Ditto 和 Keypirinha 。所有常用 App 必须选择跨平台解决方案, 必备滴答清单 + SimpleList 。日常 IM Telegram 和 QQ ,偶尔用 HexChat 挂 IRC 。念念不忘的 Mac 独占 App 有 Sketch, GarageBand, Agenda, OmniFocus 和 XLD 。念念不忘的 Win 或者 Unix 独占 App 没有,非要说的话 PC 游戏。

曾经 Sublime Text 2 党,如今 Visual Studio Code 忠实用户,必备插件是 background 自带魔理沙背景 + GitLens 。终端分别 iTerm2 和 Cmder / MobaXTerm 。 macOS 下用 SourceTree 做 Git GUI ,用 MAMP PRO 做服务器测试环境。写作环境 ByWord 和 Typora ,设计主 Sketch 辅 Axure 和 PhotoShop ,偶尔用 Illustrator 描矢量画或者 InDesign 做小册子。剪辑一般用 Final Cut Pro 或者 Premiere 。三维制作 3DS MAX 和 Blender 。

主要用 CLIP STUDIO PAINT 作画,新的拟真铅笔手感非常好。有时候会使用 Krita , Tyson 大大绘制的 Kiki 启动屏幕非常可爱,自带的丰富笔刷和镜像画笔等功能很能激发创作灵感, BYRIO 社区还组织参与过 Krita 中文文档的翻译工作,在此安利。

? 你梦想中的设备是怎么样的?

梦想是巨硬让 Surface Pro 性能、品控和售后再好一点,水果让 MacBook Pro 再便宜一点,索法不要搞 XZ2 这种歪门邪道好好出 XZP 这种全平衡侧面指纹带耳机孔的好手机。离开大学宿舍后可能会对个人工作站的设备有新的愿望,目前暂时并没有什么其他的梦想。

? 以下附图。

Surface Pro 桌面

Surface Pro 开始

MacBook Pro 桌面

MacBook Pro LaunchPad

本文来自 钉子の次元 - Dimpurr - 千里之行,始於足下。 ,原文地址 钉子的谜之 SETUP 18 @ Ningen

kookxiang's avatar

安装 homebridge

说实在的,哪个土豪会只在一台机器上跑一个应用…
多个应用共用一台机器不是很正常吗?

node.js 一升级,homebridge 就挂了 23333333

那么,有没有什么办法能延续 homebridge 的寿命呢?

首先我们把锅烧热,新建一个目录(~/homebridge)
然后再在里面运行 npm init 初始化

接下来就是神奇的时刻了,这时候输入 npm install node@8
然后你就会惊奇地发现 node_modules 下面有个 node…

这时候再通过 npm i -S homebridge 安装 homebridge 和其他组件
这样再系统无论怎么升级都不会挂啦~

然后再在 package.json 添加一段

  "scripts": {
    "start": "homebridge"
  },

最后通过 npm run start 就能正常使用啦~
最后当然是写个 systemd service 调用 npm 启动啦

附上手游玩家氪金的悲惨故事:
< 访问完整版以查看此播放器 > 视频:

's avatar

Fish Shell 精简手册

Fish —— 一个友好的交互式终端
AprocySanae's avatar

恶龙会梦见经济学家吗?(其二)

SLAY MORE, GAIN MORE

AprocySanae's avatar

恶龙会梦见经济学家吗?

经济学就是屠龙术,字面意义上。

— 《高等宏观经济学》 王国历112年修订版 第一页第一行

Phoenix Nemo's avatar

Office Service Router 解决方案:Arch Linux in RAM

一直把自己在办公室的 PC 保持开机用于连回办公区、存取数据工作需求。由于最近办公室所在的写字楼要全馆断电检点,所以诞生了构建一个 Service Router 的想法。

思路

运行在内存里对于 Linux 系统来说是完全可能(而且简单)的事情。

最直接的想法就是使用内核 hook 在启动时复制根分区到内存盘然后挂载内存里的数据作为根分区即可。

设备的话,设置 Power on AC 即可通电自启动。

ramroot

作为一只懒卷,这种简单的事情当然先顺手搜索下啦。然后就发现了几乎完美的解决方案——ramroot

ramroot 通过加入内核 hook 然后自动在内存建立 zram 分区,同步根分区数据再启动。还可以在启动时选择是否启动进内存,正好解决了所有的需求。

实现

硬件选择是一台便宜的 Intel NUC,安装两根 4GB LPDDR3 低压内存和一块 120G 2.5 SSD。虽然说起来其实并不需要 SSD(因为数据全部都在内存里,速度比 SSD 更快)但是毕竟日本多震,还是为数据安全着想。毕竟硬盘坏了的话内存系统也无法启动了。

当然如果有集成 32GB eMMC 的小型 PC 的话也是好的选择。

正常安装完 Arch Linux 系统,安装 openssh 和各种必要的服务程序,修改配置文件,然后安装 ramroot 并执行

1
# ramroot enable

此时先别急着重启,先把不需要的包、缓存等文件(/var/cache)删除,保持最小化的根分区。然后再重启。便可看到加载内核 hook 时的提示是否进入内存系统,默认超时后就会自动复制根分区到内存啦。

由于整个系统是运行在内存中的,所以完全没有等待读盘的时间。整个系统的响应速度非常快。限制是内存不够大的话运行一些业务会比较捉襟见肘,而且这样低功耗、低发热的 SoC 处理性能也只能运行一些轻型任务。

下面是一些 IO 性能测试

1
2
3
4
5
6
7
8
9
10
11
12
# ioping -s 1G /
1 GiB <<< . (ext4 /dev/zram0): request=1 time=1.04 s (warmup)
1 GiB <<< . (ext4 /dev/zram0): request=2 time=1.04 s
1 GiB <<< . (ext4 /dev/zram0): request=3 time=1.04 s
1 GiB <<< . (ext4 /dev/zram0): request=4 time=1.04 s
1 GiB <<< . (ext4 /dev/zram0): request=5 time=1.04 s
1 GiB <<< . (ext4 /dev/zram0): request=6 time=1.04 s ^C

--- / (ext4 /dev/zram0) ioping statistics ---
5 requests completed in 5.18 s, 5 GiB read, 0 iops, 988.4 MiB/s
generated 6 requests in 7.20 s, 6 GiB, 0 iops, 853.8 MiB/s
min/avg/max/mdev = 1.04 s / 1.04 s / 1.04 s / 550.7 us
1
2
3
4
5
6
# ioping -RD /

--- / (ext4 /dev/zram0) ioping statistics ---
530.0 k requests completed in 2.49 s, 2.02 GiB read, 212.9 k iops, 831.7 MiB/s
generated 530.0 k requests in 3.00 s, 2.02 GiB, 176.7 k iops, 690.1 MiB/s
min/avg/max/mdev = 3.44 us / 4.70 us / 69.0 us / 1.39 us

可以看到系统根分区在 zram 里,经过压缩因此 IO 带宽受到了 CPU 处理性能的限制。但是 IOPS 依然高得爆表,对比一下 Intel Optane 900P 的 IOPS 性能:

1
2
3
4
# ioping -RD /
--- / (ext4 /dev/nvme0n1p1) ioping statistics ---
163.1 k requests completed in 3.00 s, 56.5 k iops, 220.8 MiB/s
min/avg/max/mdev = 11 us / 17 us / 114 us / 4 us

炒鸡厉害对不对!

不过需要做永久性修改的话还是要下面的方法之一

  • 重新挂载磁盘(虽然并不麻烦)然后手动修改配置文件
  • 重新挂载磁盘然后 rsync zram 到磁盘(方便但是可能会多一些不必要的东西)
  • 重启进入磁盘系统然后运行修改(需要物理接触)

硬件设置

进入系统 BIOS 设置,开启 Power on AC 或设置 Power Failure 后的操作,选择为 Power On (默认一般是 Last State)。

关闭系统、拔出电源,或意外断电后,再接入电源即可自动开机引导系统。因为数据本身就只在内存中,除了运行中的临时更改会丢失,系统和硬盘本体都是安然无恙的。

再也不担心办公室断电检查啦。

大概就是这样。

Phoenix Nemo's avatar

通过 SSH 修正安装有 GPU 的 HPE Proliant 服务器

由于越来越多的渲染、压制等需求,托供货商的关系搞来一台带有独立显卡的 HPE 服务器。经过几番折腾(包括特别奇怪的 LS26-C14 电源线)麻烦了帮忙托管的数据中心的大兄弟好几回,终于算是上架可以开机了。

登入 iLO,安装许可证,启动 iLO Remote Console,打开电源,一切都很顺利。但是 Console 里显示 Early Initialization… 完成后,突然画面一黑,完全没了动静。

以为 iLO 出了 bug,冷重启好几次都是一样的结果。百思不得其解。

再重启一次。仔细观察了一番发现虽然没了画面,但是 POST Code 还是不断变化的,而且 Virtual Media 指示灯不断在闪烁,说明系统仍在正常运行,只是没有视频输出而已。

因此问题定位在视频输出而非系统硬件。既然这台服务器装了显卡,那么很可能是 PCI-e 初始化后视频输出全部交给显卡处理了。搜索了一下 HPE Community,确实有这样的情况存在。解决方案是通过 BIOS 修改显卡设置为默认集成显卡、备选独立显卡。

尝试在设备初始化阶段进入 BIOS,失败。

联系数据中心远程操作的话,可能要等一段时间。

纠结时随便点开 iLO 的管理页面,突然发现了华点:这货居然支持 SSH。

对啦,HPE 的底层系统几乎都是魔改版 Linux,连他们的 SmartArray 都是 Linux 启动一个 Firefox 浏览器来操作的(X

于是正好在网上搜到一篇通过 SSH 修改 BIOS 视频设置的方法。记录如下。

SSH 进入 iLO

确保 SSH 在 iLO 管理页面中已开启,然后使用 SSH 客户端正常连接:

1
ssh Administrator@10.6.254.121

(ssh 用户名是 Administrator 感觉各种违和)

连接到 Virtual Serial Port

命令很简单:vsp

在 iLO 管理页面重启系统,然后等待初始化完成。如果看到按下 F9 进入 BIOS 设置的提示,不要按下它否则会进入 GUI 模式(于是又去独立显卡了就。

看到 ESC + 9 进入 BIOS Setup Utility 时按下键组合,稍等一会儿应该就可以看到提示符 rbsu>

修改视频设置

命令 SHOW CONFIG VIDEO OPTIONS

显示如下

1
2
3
1|Optional Video Primary, Embedded Video Disabled <=
2|Optional Video Primary, Embedded Video Secondary
3|Embedded Video Primary, Optional Video Secondary

即默认关闭了集成显卡,只用独立显卡(不觉得很蠢吗!

于是修改为第三项,默认使用集成显卡,独立显卡作为备用。

1
SET CONFIG VIDEO OPTIONS 3
1
2
3
1|Optional Video Primary, Embedded Video Disabled
2|Optional Video Primary, Embedded Video Secondary
3|Embedded Video Primary, Optional Video Secondary <=

然后敲 EXIT 退出并重启系统。

安装系统和驱动

至此即可通过 iLO Advanced Console 正常安装操作系统。不过需要注意的是进入操作系统后即便安装了对应的显卡驱动,依然默认使用的是集成显卡。以及 RDP 只能使用软解,无法使用独立显卡加速视频输出。这不影响 Blender 或者 Cinema 4D 等直接操作显卡进行计算的程序,但是会影响直接输出视频到桌面的程序。通过 Teamviewer 则可以强制桌面运行在独立显卡上。

顺便吐槽:Blender 把我的工程材质弄丢了…

IceHoney Blog's avatar

伪随机的种子问题

最近前端的工作不是很多,所以就帮忙写写后端的ruby on rails。在写测试用例的时候,虽然单元测试的数据每次都是随机生成的,但是我们需要根据单元测试来生成API文档。如果测试文档每次都是随机的数据会很难检查每次API更新了什么,所以我们需要在生产测试文档的时候,保证随机数据的稳定性。

Random seed

seed的意义就在于,初始化了随机数生成器。保证了每次随机的结果都一样。例如代码:

@rand = Random.new(1234)

puts @rand.rand

puts @rand.rand(0..1050)

puts @rand.rand(0..1050)

puts @rand.rand

这段代码每次执行都会输出:

0.1915194503788923
674
699
0.2725926052826416

rand range

但是当我们把中间的rand的范围稍微修改:

@rand = Random.new(1234)

puts @rand.rand

puts @rand.rand(10..150)

puts @rand.rand(10..150)

puts @rand.rand

会发现输出发生了变化:

0.1915194503788923
48
63
0.4377277390071145

第一次的输出还是不变,当然2,3两次肯定是变化的,重点是最后一次。我们发现最后一次的随机数也发生了变化。

总结

通过调查ruby源码发现,对于有范围的rand。ruby会不断生成随机数,判断是否落在区间范围内,如果是在范围内就返回,否则继续尝试。默认在0到1之间是百分百命中,所以是随机一次。如果是其他范围,命中次数不一样会导致生成随机数的次数发生变化。所以影响到之后的随机数生成。

参考:

ruby

Random

's avatar

『博客六周年之旅』

7 月 20 日,我乘坐动车从上海虹桥到了厦门北。

qwe7002Librazy 在厦门北站接了我,带着我乘坐厦门地铁到了岛内。

当天晚上,我们和 TonyPrince、SJoshua 和 Lafit 进行了愉快的聚餐。

在 qwe7002 的劝说下,终于尝试了一下海鲜,感觉好棒

第二天和第三天,我们上午在鹭江道星巴克谈笑风生,下午瞎玩,晚上在厦大学生公寓附近继续谈笑风生,同时玩基岩版 Minecraft。

我们陆续游览了世茂海峡大厦、厦门大学和鼓浪屿。芙蓉隧道超有趣的!

qwe7002 带着我和 Librazy 瞎转,去了各种未必能去的景点。真是令人难忘的景色!

由于机票时间等原因,我在 22 号晚上与 qwe7002 和 Librazy 告了别,在 23 号抱着恋恋不舍的心情独自回到了呼和浩特。

这次厦门之旅真是太棒了,特别是有了 qwe7002 和 Librazy 的陪伴。

我们后会有期!

IceHoney Blog's avatar

Web离线的解决方案

最近的工作是做嵌入在IOS程序内部的页面,其中有一个需求就是需要满足在离线的情况下显示页面。当然,现在主流的离线方式是使用Service Worker来完成离线需求。 但是IOS内置的WKWebView并不支持最新的Service Worker(取决于IOS版本)最新版本已经支持,所以不得不想办法来解决。

Application Cache

AppCache是一个过时的技术,但是在IOS下勉强还能用。不过Chrome对这种过时的技术支持不是很好,单个缓存文件最大只支持5MB。而且还有请求的BUG:Accept header on GET request for appcache manifest。触发AppCache是在html标签中添加manifest属性。


  ...

通过manifest.appcache文件来定义需要缓存的文件,不过载入manifest.appcache的页面会被作为Master entries缓存起来。因为AppCache出现的时候还是以静态网站为主,所以并不能缓存请求的Ajax数据。我们需要自己再手动实现缓存所有请求在localStorage,但是localStorage同样也有最大5MB的限制。所以也需要考虑相应的解决方案。不过,我最终采取的方式是通过嵌入iframe来实现触发AppCache,这样做的好处是因为SPA应用是自己来控制路由的,所以导致每个路径都会保存一份Master entries。但是通过iframe的话,我们的Master entries永远只有一份。并且当前页面的所有资源也被顺利缓存,因为SPA无论访问哪个路径返回的都是相同的index.html。由JS来控制路由并加载相应的组件。

这里要补充的一点是,针对https的Application Cache是无法完成跨域请求的,所以请慎重。

WorkBox

Service Worker是现在主流的缓存技术,会帮你缓存所有的静态文件和数据请求。但是对于SPA项目,我们不可能自己手动书写缓存清单。所以还是借助现有的开源解决方案,这里最出名的解决方案是谷歌的Workbox。通过使用Webpack插件和简单的配置,我们便可以做到缓存所有的静态资源和数据。Service Worker有很多种缓存策略可以选择,例如Cache First和NetWork First。但是AppCache每次都会优先使用缓存,然后再去更新最新的文件。所以我们不得不在发生更新的时候去重新加载页面。

总结

最终的解决方案是优先使用Service Worker,当不支持Service Worker的时候再回退到AppCache。但是想要从AppCache升级到Service Worker的时候,必须清除所有AppCache的所有数据。浏览器并没有提供相应的接口,我们目前采用的方式是手动删除AppCache储存的数据库来完成这一需求。

参考:

Using the application cache

HTML5 Offline Application Cache

Application Cache plugin for Webpack

Workbox webpack Plugins

Appcache Facts

HY's avatar

Dark Souls II

魂2基本上玩的快通关了。 虽然魂2的确有不少亮点之处,但是也是知道了为什么魂2被骂得这么惨的原因。魂2整体作为 […]
's avatar

我(曾经)设想过的外置智能大脑

因为一些我不知道的原因,我感觉我情商是负的,在社交等场合常常遇到挫折。

我感觉我的大脑缺乏其它人在情感与社交方面的硬件加速。因此,我一直设想一款外置大脑,可以帮助我解决现实生活中的实际问题,令我的情商提升 -200%。

  • 通过机器学习,预测现实生活中我说出不同话语导致的不同结果,从而让矛盾最小化,生活顺利率和声誉最大化
  • 通过大数据技术:
  • 通过 AI,帮助我自动喷人,对抗那些喷子
  • 通过区块链技术记录下那些欺负我的人
  • 通过机器学习,制订出最好的计划

这样,我的大脑就省去了很多负担,我可以安心的写垃圾 JavaScript 代码了!

(未完待续

's avatar

没用的无懈可击

近年来,我终于意识到为什么我总是不开心,因为我试图让自己无懈可击。

上初中的时候,我所在的班级管理严格,如果出现违反班规的情况,那么就会有惩罚。最常见的就是罚抄课文。

因为我初中的时候就被我同学在暗处捅过很多刀子,向负责罚抄课文的班干部举报我,本来无关紧要的小细节被炒成一件严重的问题(比如忘了关灯、迟到不到一分钟但老师都还没来),作为惩罚,我不得不常常一两点才睡觉。

天真的我以为,只要我做的更好,那么他们就不会惩罚我了。但我并不是计算机那样的东西,我还是偶尔会犯错误,被那些平时笑脸相迎的同学找到把柄,被拿去告状。(其实我们并没有那种告状就有奖励的机制,但也许他们告状的动机是为了看我被惩罚来获得愉悦感?不过我跟他们表面上的关系都不算很好。)

我找老师说他们老告我状,然而我们老师总会说类似『为什么人家不告别人就告你,肯定是你自己有问题』。我很难受但是无语。

于是,我开始试图让自己变得无懈可击,来确保人家根本抓不到我的把柄。有一次我意识到自己要迟到,马路上全是车,还努力往学校跑,差点被一辆疾驰的车撞飞,我恰好闪了过去,侥幸逃过一劫。

但后来我每天很累的努力让自己什么问题都没有,却还是会被同学抓住我的把柄,然后我依然要接受惩罚,抄课文抄到一两点。

最后在母上大人的支持下,我公然拒绝抄课文,最后强行把事件捅到了班主任那边,我们班主任还和母上进行了一番谈话……

就这样,我再也没有抄过课文。

然而我努力让自己无懈可击的心理并没有就这样消除。从各个方面,我害怕别人批评我、害怕我做的有一点不完美、害怕哪一天又有人从很小的细节抓住我的把柄,然后强行上升到某种高度,把我打压下去。于是总是习惯性的把自己搞的很疲惫,直到我有理由感叹『嗯,别人肯定找不到理由抓我把柄』了。

Dimpurr's avatar

如何在 Krita 中创建材质笔刷

Krita 是 KDE 基金会项目下的一款专注于数码图形绘画 (Digital Art) 的跨平台开源软件。

在 Krita 官方讨论区 Krita Tutorial & Resources 板块和官方文档 Resources 页面可以获得许多有用的材质和笔刷。本文主体内容改编翻译自 https://forum.kde.org/viewtopic.php?f=274&t=140349

钉子的知识库: http://note.dimpurr.com/#艺术和动漫绘画学习索引

今天我将教你们如何创建一个酷炫的材质笔刷。如果你需要一些类似 Photoshop 的双重画笔一样的工具,那就是这个没错了。我想这是材质绘制的极佳方式。让我们开始吧:

首先创建一个基本笔刷贴图尺寸的文件(我使用 300*300px)并删除背景。

插入(或者手动绘制)「基础」贴图。这个贴图会决定笔刷贴图的形式、尺寸、比例并仅用于创建选区。随后右键点击该图层,在菜单中选择「选择不透明度 (Select Opaque)」。

image

隐藏这个图层,并创建 5-10 个新图层。

选择某个材质笔刷,并在空白图层上绘画。使用随机的分散/镜像/旋转设置以增加贴图的多样性。对每个图层都这样做。你可以设置分离图层(Isolate layer)或者 Alt + 点击 切换单独绘制每个图层让这个过程舒服一点。

image

现在让我们创建新的动画笔刷材质。点击 +印戳 (+Stamp) 按钮并选择 风格:动态 ,模式 :随机 (当然你也可以是用别的)。

image

这将会使用目前所有的可见图层作为笔刷的贴图。不要使用自动间隙(Auto Spacing),记得为笔刷命名。你需要反复尝试让笔刷变得正常,可以直接在画布上测试你的新笔刷。

image

于是我们有了一个还未经设置的笔刷贴图,接下来进行一系列配置吧。对于方形笔刷贴图,我建议你使用镜像(Mirror)以增加多样性。对圆形笔刷贴图你也可以使用旋转(Rotation)。你也可以调节空隙曲线增加某种动态笔压,我以这种方式让高压力时能画出厚重的线条。

image

别忘了设置你还可以使用滤镜(filters),有时候我会使用非锐化蒙版(Unsharp)、模糊(Blur)、色彩调整曲线(Color Adjustment Curves)。色彩调整曲线(Color Adjustment Curves)在透明通道(Alpha channel )绘制的时候特别有用(当然平常也有效)。

后面是最终效果的一些例子。在我的笔刷包中,我做了许多有用的基本笔刷、材质笔刷并做好了预设。

image

本文来自 钉子の次元 - Dimpurr - 千里之行,始於足下。 ,原文地址 如何在 Krita 中创建材质笔刷

Phoenix Nemo's avatar

在线扩展 LVM root 分区

才不是没东西写了呢

遇到一个奇葩的原因导致 root 分区被占满的。而且还是奇葩的 CentOS,root 分区是 LVM,Hypervisor 里扩展磁盘后无法直接用 resize2fs。

's avatar

与 tcdw 同款的博客主题 Egoist 发布了

我建设自己的个人独立博客已经有近六年时间,但是并没有真正属于自己的博客主题,因为我能力不足,加上当时我还在上中学,使用电脑并不方便。

大学以后,我就设想为自己设计一款专用的博客主题,并且在今年年初完成了这一目标。

虽然我之前制作和修改过一些符合我喜好的博客主题,但这的确是我第一个完全从零开始手写的主题,除了 Normalize.css、highlight.js(样式)和 FontAwesome。

于是,我删除了若干我个人的元素以后,将其以 HTML 模板的形式开源。本博客目前使用的主题,就是这篇博文发布的啦。

项目地址(无耻求 star

发布形式

我决定以 HTML 文件而非某款博客程序专用主题的形式开源,是因为我希望我的主题的定位是一款通用的博客主题,虽然需要被单独适配才能开始使用。

不过,如果你已经给你喜爱的博客程序适配了这款主题,欢迎提交 Pull Request 来加入列表。

IceHoney Blog's avatar

如何封装第三方vue组件

终于算是正式接触SPA的前端工作了,最近在解决很多Vue组件的问题,其中就有一个针对第三方组件的定制化需求,花了不少时间来折腾。

解决v-model绑定

这次封装的就是element的前端vue框架,因为框架本身对IOS兼容性不好,所以需要二次封装。封装的是一个select组件。所以需要数据的双向绑定,官方教程也已经解释了。 v-model 本质上就是绑定一个值和监听相应的事件。 这次本质是封装的一个input元素,所以我们需要手动绑定value和监听事件。


  
  

官方文档也解释了这个内置变量的用法vm-listeners

继承父元素的属性

父元素的属性继承可以使用v-bind="$attrs"来完成。所以完成组建的透明封装只需要加上三个指令。


  
  

虽然知道答案之后很简单,但是探索的过程中还是比较花时间的。希望能帮助大家解决这个问题,如果有什么疏漏之处,也请大家指正。

's avatar

tcdw 与 ECMAScript

其实 tcdw 小时候并没有对计算机特别感兴趣,tcdw 曾经的梦想是当一名建筑设计师。但是,因为在 tcdw 很小的时候家里就有了一台电脑,tcdw 还是玩了很多奇怪的东西,包括 Frontpage 2003。

那时 tcdw 出于好玩的目的,设计了一个又一个版本的个人网站框架,除了内容。 tcdw 还设计了一套自己的『前端样式库』(其实只是在 Frontpage 的 CSS 选项菜单里把所有 HTML 标签都那么定义了一下样式)。

然而 tcdw 发现自己的网站总是缺点什么。好奇的 tcdw 通过右键点击来观察,终于发现了有一种神奇的东西叫 Flash。2006 年, tcdw 的舅舅送给 tcdw 一张 Macromedia MX 全家桶光盘、一张网页素材大全 DVD,还有一本 Flash MX 教材。于是 tcdw 便很开心的开始制作奇怪的 Flash,并嵌入到自己做的网页里。后来 tcdw 又缠着母上大人买了一本《精通 Flash 8》和一张 Flash 8 的盗版 CD。有了滤镜特性,tcdw 做的更来劲了。

2007 年,tcdw 从《小读者》杂志注意到袁日涉拥有自己的环保网站,tcdw 很羡慕,便进去看了看。当 tcdw 看到她在 2006 年的获奖作品《从纸到树的生态研究》里的 Flash 导航菜单,tcdw 很诧异:为什么那个菜单背景可以跟着鼠标移动,而且还会缓动变色?

虽然那本书上已经讲了一些 ActionScript,tcdw 也能照着书上的例子使用 gotoAndPlay 之类的 API 控制播放,但是那两本书讲的非常粗略,因为都是主要讲 Flash 动画制作,而不是 Flash 编程。

后来 tcdw 摸爬滚打的求助度娘,终于发现有一家网站叫闪吧,那边有大量的 Flash 资源。tcdw 如饥似渴的下载了大量的 Flash 源文件,观察它们是如何运作的。这时 tcdw 才发现,原来 ActionScript 可以写的更加复杂!

于是,tcdw 照葫芦画瓢的尝试,凭借小学学的那点英语判断各种关键词的意图,初步建立了编程的概念。而当时 tcdw 也发现了有一款软件叫网页特效制作专家,里面集合了大量的网页特效。因为它们的本质都是 JavaScript,tcdw 也开始靠着复制粘贴奇怪的代码片段,试着拼凑一些不需要 Flash 的网页特效,结果还很成功。不过因为 HTML 4 的表现力太有限了(而且 tcdw 还在用 Frontpage 2003 + IE 6 的坑爹组合),所以 tcdw 的重心依然在 Flash 上。

闪吧投稿的质量也是参差不齐。因为很多投稿人本身对编程也没有太深入的研究,只是用一些意大利面代码完成一些交互性的东西,以至于他们写的代码就有很多逻辑缺陷,而 tcdw 盲目学习他们,对自己的编程思维产生了很多负面影响。

2008 年,tcdw 为杨静翻唱的《雪人》做了一个 Flash MV。那是 tcdw 当时做过的最复杂的 Flash 了,不仅用到了很多 Flash 元件,还使用了 tcdw 学到的 ActionScript 实现了雪花特效以及一个很中二的『拖动光盘到标题文字即可开始播放』的功能(用到了 hitTest 这个 API)。做完以后,得到了父母的赞扬,还在小学的多媒体设备上,向全班同学放了一遍。那是 tcdw 最信心满满的时刻之一了。

闪吧投稿

到了 2010 年,tcdw 感到自己的 Flash 制作已经有了套路,而且有些东西看起来比闪吧上已有的一些源文件(包括粗制滥造的、从同一个教程复制粘贴的)质量还要好。那时,tcdw 做过的最复杂的 Flash 作品是一个交互性比较高的音乐播放器,可以调节进度、音量、播放列表,还有 LRC 展示,以及 没有完工的、基于搜搜的在线搜索音乐并添加到当前播放列表的功能(换成 2018 年应该是基于网易云音乐的吧)。不过遗憾的是,这个播放器在 2010 年的一次 U 盘故障中丢失了。

于是,tcdw 大胆的尝试往闪吧投稿。tcdw 做的一个中看不中用的光影特效、俩鼠标特效和一个 Flash 调色板都审核通过了。 tcdw 在自己的作品介绍里都留下了 QQ 号,然后还真的有不少人加 tcdw 好友,而且大概有 30 多人吧。于是 tcdw 还建了一个 QQ 群,叫『闪客交流』。

每当有人问 tcdw 问题,tcdw 一般出于保持脸面的考虑,会凑合回答一下;tcdw 也遇到过一大堆伸手党,这时 tcdw 就很高冷的说:『闪客教学,收费。』然后他们就走了(没人真的来付费希望 tcdw 教他们

其实 tcdw 也不是没有接纳过伸手党的所谓任务。有一个伸手党希望 tcdw 帮忙做一下他的 Flash 作业, tcdw 当时傻乎乎的同意了。但 tcdw 后来发现他的那个作业还很难做,而 tcdw 还没在人家的 deadline 以前搞定。 tcdw 就想:『我凭什么要为他做作业?』

想到这里, tcdw 便告诉他:『你的作业我不做了。』因为 tcdw 当时的态度也不得当,人家便进 tcdw 的 QQ 空间大骂 tcdw 是骗子云云(当然那些评论都被 tcdw 删除了)。后来吸取了教训,就再也没有承担过那种一上来就求做作业的伸手党的任务。

牛逼的 QQ 空间红人

从闪吧过来加 tcdw QQ 的人,有两位开了很多钻,QQ 空间里面全是 Flash,看起来特别炫酷,还标榜是『blahblah 的腾讯博客』、『网络红人』什么的, tcdw 羡慕死了,为什么 tcdw 的同学开了黄钻,却还在用官方那几套模板?如果做一套很炫酷的 QQ 空间模板,一定会受到周围的人羡慕、尊敬啊!

当时 tcdw 也开通了自己的个人网站,用的是 5944 的免费空间,而且访问地址更新事宜已经在 tcdw 的 QQ 空间公告了好几次(虽然大概没人真的看过)。于是, tcdw 把专门为自己 QQ 空间做的 Flash 传到了自己的免费空间,再在 QQ 空间引用自己做好的 Flash。果然,有很多『萌新』开始加 tcdw 的 QQ 了。

其实 tcdw 当时做的 Flash 并没有什么技术含量:把一些非主流的元素加进去,用 tcdw 小时候学会的 Flash 动画制作方法把它们耦合在一起,再用意大利面的 ActionScript 代码让它们做点什么交互性的事情,最后把 Flash 糊上 QQ 空间就是了。

当时还有一种叫 QCC 的东西非常火爆。其中有一家推出的 QCC 模板非常华丽,功能也很齐全(比如可以读取你的日志列表、显示最近访客等等),收费还记得是 60 元一年、100 元永久。但人家的反逆向工程还是可以的,用 Sothink SWF Decomplier 和 ASV2010 打开,程序都会崩溃。不过后来 tcdw 还是想办法把他们的那个音乐播放器元件结构逆向了出来,并填充了 tcdw 自己写的 ActionScript 代码,实现了一致的功能。

至于空间日志展示等功能, tcdw 也解决了。2010 年的某一期《电脑爱好者》杂志,有篇文章安利了 Chrome 浏览器,表示你可以用里面的开发者工具做很多事情,比如通过录制 Network 动态来下载 FLV 视频。于是 tcdw 下载了 Chrome 浏览器,通过观察 Network 的动态,发掘了很多 tcdw 梦寐以求的 QQ 空间私有 API。然后 tcdw 通过 split 大法解析 JSON,实现了各种付费 QCC 才有的高级功能,吸引了好多人的目光(其实最多也就几十人)。同时 tcdw 发现 Chrome 浏览器在他爸花 2k 装的坑爹 Windows XP 台式机运行非常快,于是 tcdw 便从 IE 6 / 8 用户变成了一名 Chrome 用户,直到今天。

原来 Flash 已经开始被人讨厌了呢

然而好景不长,当 tcdw 在制作功能更高级的 QQ 空间 Flash 时,腾讯突然宣布:为了防止有人通过 Flash 盗取 QQ 密码,从 2011 年 8 月起只允许在白名单列表的网站的 Flash 被插入到 QQ 空间首页。

那还玩个屁啊!只见各种 QCC 卖家和 QQ 空间 FD 模块作者哭天喊地,希望 QQ 空间能够解除这种限制,但是并没有什么用。期间虽然有人发现了一些绕过方法,但是后来都被封堵了。再后来 QQ 空间推出 6.0 版,以前的一大堆套路全失效了。tcdw 也是在那时渐渐跟 tcdw 认识的那群闪客疏远,最后在一两年前的一次 QQ 好友清理中被 tcdw 删除了。

没有 Flash 的 QQ 空间是无聊的。再往后,tcdw 觉得 QQ 空间没什么好玩的,就不再续费黄钻。tcdw 的黄钻等级从 7 级一路下滑到 1 级了。

2011 年,tcdw 恰好通过 ZYH 的 NES 模拟器『ZYH Emulator』知道了百度超级马里奥吧,后来又找到了超吧大水库吧,然后认识了 Jixun。Jixun 菊苣是一个很厉害的人,不仅帮助 tcdw 修改过很多意大利面代码,还让 tcdw 知道了 HTML5、GitHub、响应式页面等很多新奇的东西,让 tcdw 大开眼界。

但在接下来几年里,由于学业原因,tcdw 的 JavaScript 学习就停滞了。虽然期间 tcdw 利用 HTML5 API 做过一些类似 HTML5 音乐播放器之类的东西,但是他的编程思维还是小时候的那套。

吐槽大王与主流 JavaScript

2014 年底,tcdw 在百度免费空间吧 QQ 群看到有人发布了 Hostker 的链接。tcdw 进去一看,发现跟自己熟悉的那种虚拟主机官网很不一样,看上去非常亲和。在示例用户那一栏,tcdw 看到了 卜卜口 的博客,点进去一看,发现样式很新奇,页面加载速度也很快。tcdw 试图查看源文件,却发现只有几个看似单薄的 js 文件。后来通过体验他的 妹 blog,tcdw 第一次拥有了关于 SPA 的概念。

后来,tcdw 在一次偶然的机会认识了 佳佳酱,进而认识了一群有趣的人。tcdw 从他们那里知道了 Node.js,便信心满满的开始编写一些会读取文件的、看起来很正经的东西。然后 tcdw 便发现,自己写的代码不符合他的期望:

var fs = require("fs");
var str;
fs.readFile("1.txt", {encoding: "utf8"}, function(err, txt){
    if (err) throw err;
    console.log("文件读取成功!");
    str = txt;
});
console.log(str);

tcdw 想:我是想先输出『文件读取成功』再输出 1.txt 的内容啊,为什么是先输出 undefined 再输出『文件读取成功』?tcdw 很困惑,他上网搜索答案,知道了这是因为 JavaScript 的异步处理方式导致的,但是一大堆文章都没有给出较好的解决方案。后来,tcdw 便改用了 fs.readFileSync 来读取他的文件,实现了他期望的那种顺序执行。

但后来 tcdw 想使用 superagent 库来读取网络的内容来帮他不断在 SMW Central 的某活动中刷帖,但那个库并没有提供同步方法。于是,tcdw 想到了一个馊主意来实现他期望的同步执行,其实就是 Callback Hell

tcdw 还通过 setTimeout 来延时,并递归调用请求函数,试图实现不间断定时刷帖。但 tcdw 的程序运行了没多久,就因为 Maximum call stack size exceeded 错误崩溃了。那时的 tcdw 想不到更好的办法,就只好在他的 VPS 上设置了一个 cron 任务,每半分钟重新调用一次脚本来发帖。

问题是解决了,但 tcdw 渴望知道究竟该如何玩转 JavaScript 的异步编程,于是通过不断努力,终于搞明白了这些奇怪的事情。

好耶,是 ES2015

直到 2017 年,tcdw 终于结束了高考。tcdw 终于开始系统性的学习 ES2015,不仅学到了很多已经存在几年的新特性,还拥有了新的思维。

同时,tcdw 终于开始大胆尝试接触以前他觉得很复杂、难以搞懂的东西,比如 webpack、rollup 之类的打包工具。与此同时,tcdw 深入的学习了一些更加抽象的知识,比如 DOM 和面向对象思维。总之大学以来的一年是 tcdw 进步最大的一年,算是可喜可贺吧。

总结

由此可见,tcdw 走的这条路其实非常不科学,以至于 tcdw 尽管很小就接触编程,但不仅没有取得骄人的成绩,还跟很多同龄菊苣的水平差距巨大。tcdw 还残留了很多古董的编程思想,以至于很多人看到 tcdw 写的代码都感到有些莫名其妙。

不过那些事情都过去了,而且我距离毕业还有几年。于是对于弥补一些事情来说,还是来得及的吧。

's avatar

评论框

经过两个月的不懈努力,我填了我去年给自己挖的评论框 大坑 的一部分。

目前,我的博客已经开始使用我自己写的评论框了。目前基本完成了后端、访客前端、Telegram 提醒姬和 Disqus 导入工具的开发工作,但是管理面板还没有开工(于是我只有借助 cURL 来管理评论了

在这里要特别感谢一下 qwe7002老宋Jixun 姐姐,他们对 Pomment 的程序设计和实现提出了重要的建议。

总之非常开心用上自己写的评论系统。

AstroProfundis's avatar

于是我终于也换工作了

拖了一个月的博文,再不发就要烂在草稿箱里面了╮( ̄▽ ̄”)╭

阿里内部一直标榜自己是一所大学,同事之间也多以“同学”互称。从2014年4月3日入职实习,到2018年4月9日正式离职,4年了,我算是毕业了吧。

写离职贴向来是阿里内网的一项传统和一道风景(另一道风景是相亲贴),早在决定离职之前的一段时间,就想象过有朝一日我自己的离职贴会写成什么样子,一开始草拟的题目叫《我为什么从阿里离职》,但思来想去我并不像一些遭遇状况离开的同学那样苦大仇深,貌似没有必要玩这种噱头,甚至这篇文章本身也没必要真的发布到内网丢人现眼,就放在这个早已荒草两丈高的博客聊做记录呗。

2014年3月时侯,确定考研无望的我开始找工作,因为早就错过了当年的校招季,其实处境颇有点不尴不尬。先是面试了一两家小公司,互相都没太看上眼,在推上求RT的时候承 @bones7456 抬爱帮忙投了个内推,没两天就接到了阿里的面试电话。经过顺利到我自己都没感觉的面试流程,很快就拿到了 offer, 然后我鸽了在 Google Talk 上聊得很开心的兽兽,无视掉学校里面最后一两个月的课程,买了张卧铺票就跑来杭州了。

除了中间回学校补考高数&办毕业手续,我从14年4月就基本一直住在杭州,必需说,杭州的生活环境真是比北方舒服多了。到6月底我正式毕业,中间不仅没有办离职,甚至还拿着部门特批的笔记本回了学校(正常实习生是只配台式,笔记本是部门工作特性需要经常在工位和值班室来回移动所以特批的)…6月26号早上我直接用实习生工牌刷卡进园区,到4号楼办了第二次入职,主管给我说“这次我就不用来接你了吧”,啊哈哈,当然不用,听完培训我就丢下其他等着各自团队接人的大侠们溜去工位…

然后第二天就跑去千岛湖 Outing 了(乛.乛)

不知不觉过了三年多,部门没变但人来来去去,职责重点也多次变化。中间值了若干个夜班见过了随便几点的西溪园区,亲历了若干次超大型故障并且发了527第一条故障通告,一个人运维了几百台机器的监控系统大半年,徒手写了部署量上百万的监控脚本还搞出来个P2故障至今保持鄙部门记录,带着实习生倒腾出一个跨机房监控被老板拿着截图到故障 Review 上当证据,亲手应急切换过淘宝支付宝交易单元分分钟百万上下,经手和管理几百万的采购项目,和人邮件撕过逼也被人电话撕过逼,被其他部门主管私聊挖过墙角,拿过3.75A也拿过3.25B, 怎么看都是圆满了(

哦对,唯一的遗憾大概是某个再也看不到某个内部群里面老法师们斗法了(

至于为什么要走,怎么说呢,作为毕业直接进了巨头的我,见识了够多的标准化流程和体系化建设之后,慢慢地也开始对大公司的各种弊端产生了厌倦,总觉得如果再继续在这样的环境待几年,不管是技能树还是整个人的心态都会彻底被固化下来,并且很难有进一步突破的空间了。正好最近一年多以来组织架构数次大幅调整,阿里内部整体技术革新的背景下部门方向转型,工作内容里面非技术的内容越来越多、能关心技术的精力也越来越少,整天都埋没在无尽的会议和故障 Review 中,和各个部门各个团队的人在钉钉上电话上会议室里纠缠不清一个故障甚至一个改进措施的责任归属;偶而停下来看一看的时侯,猛然发现自己的发展路线已经不知不觉指向了技术运营和事件管理:这并不是我喜欢的方向,我也很难在这个领域作出漂亮的成果来。

最后那半年多每天的压力都非常大,并且几度出现了轻微的焦虑症状,再加上工作内容愈发混乱,在这里工作已经不能让我感到有趣,我意识到必需作决定了。

于是么,趁着手上几个主要的事情告一段落,我特意放慢了进一步的工作安排,开始准备另谋出路;大公司干了好些年,不如去小公司长长见识也很好啊。然后非常巧合地,在我简历还没改好的时侯,某 @Tiramisu1993 给我说他实习的公司在招人还发了 JD 过来,我上知乎随手一搜居然好(shui)评(jun)如潮,于是就连夜周末赶了份新简历出来投了过去…

反正中间因为我要压到4月之后离职所以大家都不着急地拖拖拉拉几个月的面试不表,春节后调休完上班第一天我把主管拖到小黑屋讲了离职计划,之后就进入了轻松愉快的工(hun)作(chi)交(deng)接(si)阶段…再然后我就变成了 PingCAP 员工了_(:зゝ∠)_

由于这篇文章拖了太久,我已经没有刚离职时侯那种沉重的心情了,所以只好写一些新东家一个月来的感受…

首先,作为一家名符其实的小公司,PingCAP 整个公司的规模和我之前所在的阿里 GOC 这么一个小 BU 相当,比起组织严密、流程翔实的大公司明显地要松散灵活很多,嗯,也就是说知乎上的软文基本属实…往好了说,是氛围轻松、迭代迅速、扁平化管理,往坏了说,是野蛮生长、制度残缺和技术沉淀薄弱,这些对于一个小公司来说都是显而易见甚至可以讲理所当然的,我也很庆幸自己有好几年大公司的经历,让我能直观地意识到这些问题,也就给了我进一步产出价值的更多机会与可能性。

嘛,至少目前来说,有两点让我非常满意目前的工作并且乐在其中:1. 可以100%工作时间放在技术领域,用来写代码读文档而非和人撕逼;2. 非工作时间不会被频繁打断,不用一睁眼睛先摸手机看未读消息

你们看我多容易满足(

Felix Yan's avatar

Linux 发行版:“强迫症患者”们的共识社区

世界上有几百个还在更新的 Linux 发行版。新手常常感叹挑花了眼,换来换去也找不到自己满意的。维护一个发行版需要花费很多时间、精力,为何人们要这样“重复劳动”呢?

  • “强迫症患者”

我小时候追求整齐、秩序,无论是家里的电灯开关还是电脑上的图标,一定要排列的整整齐齐,不惜自己接电线、一个个重命名文件。“我的电脑”、“我的文档”、“网上邻居”,下面的蓝色 e 名字太长,就改叫“上网浏览”吧……然而随着安装了越来越多的软件,这些“秩序”被不断破坏,自己不断妥协。有的程序在我的文档里乱放目录——忍。有的程序会自动下载更新,然后在我代码写到一半的时候弹出来更新提示——忍。有的程序会带各种运行时包安装、替换系统文件导致另外一个程序运行不了——忍。

每次出了大问题的时候,所有人都告诉我:“现在只能重装了。”

2008年的时候,厌倦了折腾各种魔改定制 WinPE 的我首次下定决心安装了 Ubuntu 8.04,从此打开了新世界的大门。

不再需要依赖猜测。手握源代码,就如同掌握了施工的图纸,一切不符合心中秩序的地方都能找到原理。一群志同道合的前辈早已构建了井井有条的目录结构、依赖关系、软件仓库、……一切都显得那么美好。和每个第一次玩 Linux 桌面的折腾狂一样,我花了很多时间试图让 GTK+ 和 Qt 的程序界面一样而且好看,以及折腾 compiz 特效。

然而事情也不是那么完美。当时我在压片组用 x264 压片,采用的方案是使用加上一些特别 filter、并且带 lavf 输入的 x264 命令行。我用 wxPython 做了一些小工具,比如音轨提取、mkv 合成等,但是自动压片脚本需要的 x264 不能用仓库里的版本。我注册了 Launchpad 帐号并创建了一个 PPA,接下去,我花了很长很长时间都没有学会打出一个靠谱的包。别说各种 macro 的使用,就连拆包的部分都让我焦头烂额了。

长久以来折腾的零碎结果——各种经验、配置文件、补丁、翻译,散落在各种网站、论坛、聊天工具,而自己真正想分享出去的成果——软件包,又不知如何下手。我想要为这份秩序做点贡献,怎么办呢?后来群里的大牛们给我了一个简单一些的方法——AUR。

我在 2011 年安装了 Arch,从此找到了贡献之路。

  • 共识社区

使用 Ubuntu 的时候,我有过很多想打包的软件,也曾经用简单的 checkinstall 打出过数个“勉强算是包”的包,但是这个过程一直十分痛苦,以至于我搁置了大量的 TODO。

而在 AUR 里,一个简单的十几二十行普通 bash 语法的 PKGBUILD 文件,就描述完了一个包。字体、游戏、输入法,开发中用到的 python 库,都被我轻松地打成了 Arch 的软件包。2012 年,我申请成为了 Arch 的 Trusted User,将这些包中使用率高的部分加入了官方仓库。在 IRC 和 TU 们的闲聊中,我能感受到或多或少大家都是因为 pacman / PKGBUILD 体系的简洁、维护轻松而走到了一起。

一个人的力量是有限的,我很高兴我找到了这样一群和我类似的“强迫症患者”。开源软件千千万万,自己选择费心费力。有的作者很马虎,有的作者不喜欢条条框框,有的作者对安全更新嗤之以鼻——而发行版的打包者们,则成为了一道防线。我们形成了一些共识,无论是代码里的编译选项还是软件的默认行为,都会尽力向上游作者们争取、提供修改意见,或者直接提交补丁。这样的共识或许不完全和内心的秩序一致,但也已经相当接近了。每人维护一部分的软件包,承担起相应的责任,而其他人就可以坐享这份成果,大家的“强迫症”都得到了满足。

在我看来,这就是一个发行版存在的意义。轻易切换发行版使用的用户们,可能只是不像我们这么傻,这么认真。

近年来每每被问起诸如“Flatpak 会让发行版大一统”、“Docker 会让发行版大一统”、“把 npm 仓库里的包打成 Linux 软件包毫无意义”等等问题时,我时常会感到难以回答。不是没有道理可讲,实在是不知如何讲起。这些都是很有意义的理念和技术,只是它们取代不了形成一个发行版的共识。所谓共识,无非是在取舍时看重哪边更多一点。

Linux 发行版,作为基于共识形成的社区,正是由我这样的“强迫症患者”所组成。

's avatar

如何提出计算机相关问题(更新)

我发现很多计算机相关问题解决速度不够快,或者进度很快夭折的原因,是他们不会合理地提出计算机相关问题,不会合理地尝试解决问题。

有时他们试图在互联网上提问,但因为上述原因,他们往往会被潦草地告知先去阅读《提问的智慧》(或者 RTFM),或者被直接无视。虽然说这里面确实存在着不知感恩的伸手党,但也有很多人只是心急火燎地试图解决问题,因为上述原因走到了无可奈何的地步。

我看到这种场景一次次发生,顿生怜悯之心,决定撰写这篇博文。

先看看软件的官方说明

对于一些常见的错误,软件官方说明一般会以 FAQ(常见问题)的形式给出解决方法,看一看往往很有用。

尝试自行搜索

其实很多计算机问题,你往往不是第一个遇到的(特别是开源软件,一些问题往往可以在它们的 GitHub issue 中看到)。网络上的解决方案,也许比你询问别人得到的更加准确。

不要认为别人知道的跟你知道的一样

「这个 APP 为什么在我手机上闪退啊?」

这个问题看起来很简单,但是对于别人来说很难:你的手机是什么品牌的?配置如何?Android 版本是哪个?系统被厂商魔改程度如何?等等。

因此,提出计算机问题(特别是在网络社区提问),一定要考虑下这个问题,并提供必要的信息(你的系统版本等等)。

相信软件给出的提示

「你的计算机尚未安装 Java,请访问 https://www.java.com 下载 Java。」

嗯,软件已经给出了足够的建议,但是小明直接把这个提示截屏下来,去群里问如何解决这个问题。Geek 们会感觉不可思议:软件都提示你去下载 Java 了啊!小明听说以后,就去下载 Java,问题解决了。

小明为什么会无视软件提示,其实很大的一个原因就是很多用户对软件提示的盲目不信任。有时系统提示「请与系统管理员联系」,但是这是我家的电脑,系统管理员是谁啊?同时,某些国产软件通过恐吓等方式诱使用户去添加启动项等等,而用户反感这种流氓软件,不信任这些软件提示。

其实来自负责任的大厂的软件、「民间」的很多免费软件和开源软件的提示都很负责,照着它们去做其实是解决问题的第一途径。

先了解这是什么

看到一个陌生的格式,你只知道这是图片或者音频,你有转换成熟悉的格式的想法。请控制住你的这种想法,先去了解这个陌生格式到底是什么、它与其它格式有什么不同。

说到这一点,我必须提一下我的一个失败例子:很久以前,我拿到 m4a 格式的音乐,我甚至没有去了解 m4a 是什么,就直接寻求把 m4a 转换成 mp3 的方法。那时哪怕我查一下 m4a 与 mp3 的区别,也许就会改变我的想法,直接去下个好一点的音乐播放器,避免浪费大量转换格式消耗的时间,还毫无意义地降低音质。

合理提供日志

小明想用 MultiMC 启动器启动 Minecraft,结果启动不成功,软件弹出一个窗口,显示了出现的错误详情。于是,小明就把错误窗口截图,在群里提问。但是,由于他的窗口并没有显示足够完整的日志,问题无法继续解决。

也许他看不懂这些日志里到底说明了什么,便觉得屏幕上显示出来的就够了。这种情况下,最好把当时运行时出现的完整错误日志发上去,并说明你想做什么,然后又发生了什么。

顺便提醒一下,大段日志请截图,哪怕是拍照也可以。总之不要直接将文本贴到论坛上,甚至是聊天窗口里!被这样刷屏是很不爽的!

IceHoney Blog's avatar

JavaScript开发中的性能优化

最近工作比较清闲,5月又换了一个工作环境。总结下最近看的有关JavaScript性能的知识,并记下来供未来的自己参考。希望自己能不忘初心,继续成长!

使用requestAnimationFrame来更新动画

使用requestAnimationFrame把动画渲染交给浏览器,可以保证渲染保持在60FPS。应该避免使用setTimeout或者setInterval来实现动画,因为计时器和事件队列会消耗更多的资源,还会导致无用的页面重新绘制。

使用Web Workers来进行复杂的运算

可以把单纯的计算放在Web Workers中,这样不影响主页面的渲染和流畅,例如加密和矩阵变换的运算就可以完全放在Web Workers中来减少主线程的执行时间,避免浏览器渲染堵塞。

避免微优化JavaScript

举个最简单的例子,我曾经在一本书上看到过一个提升程序效率的例子。大致是这样的:

var array = [1, 2, 3, ... 100];

for( var i = 0; i < array.length; i++) {
  console.log(array[i]);
}

这里的array.length获取数组的长度,每次都要计算数组长度会消耗不少时间,优化之后的代码是这样的:

var array = [1, 2, 3, ... 100];
var len = array.length
for( var i =0; i < len; i++) {
  console.log(array[i]);
}

通过缓存数组的长度来保证只计算一次。这就是典型的微优化,说实话这段代码我一直在用,直到最近读了其他的书才发现,现在的JS解释器早就优化了对数组长度的计算。无论是读取一次还是一万次消耗的时间都没有太大差别。微优化应该是JS引擎需要做的事情,我们不应该在实际开发中在这方面耗费过多的时间。

滑动事件优化

当用户监听滑动事件并执行相关动画操作的时候,记得使用window.requestAnimationFrame来优化动画的执行。

function onScroll (evt) {
  // Prevent multiple rAF callbacks.
  if (scheduledAnimationFrame)
    return;

  scheduledAnimationFrame = true;
  requestAnimationFrame(readAndUpdatePage);
}

function readAndUpdatePage () {
  // do something
  requestAnimationFrame(readAndUpdatePage)
}

window.addEventListener('scroll', onScroll);

参考:

Debounce Your Input Handlers

Optimize JavaScript Execution

Phoenix Nemo's avatar

在 Linux 服务器配置 LACP 与 VLAN

存储服务器不想放在 OVH 了。所以自己来托管一台机器,顺便折腾下 2x1Gbps 组 LACP Bonding。

's avatar

提升古诗词背诵及脸部记忆能力

古诗词背诵与脸部记忆,对 tcdw 来说一直是个老大难的事情。

如果说在初一的时候,tcdw 还能在母上大人的呵斥下勉强背诵《木兰辞》,在高三时 tcdw 已经什么古诗词都背不下来了。

那些古诗词,tcdw 翻来覆去的试图记忆很多遍,但是一合上书,那古诗词瞬间从他的脑海中蒸发,一打开书又神奇的回来了。

tcdw 也记不住别人的脸部。tcdw 所在的专业人数就不算多,每当遇到集体活动(比如升旗),tcdw 就不知所措了,因为他永远没法自己找到他们的位置。于是,在类似活动,tcdw 只好四处随意走动,直到被同学们发现。

其实 tcdw 的记忆力本身大概没问题。地点、网址、事件什么的,他都有很好的记忆力。但是面对古诗词与别人的脸,tcdw 就抓瞎了。

tcdw 好希望通过记忆地点、网址、事件的渠道记住古诗词与别人的脸。比如,当我和最好的朋友在一起聊天,他说了一句古诗词,让我感到甜蜜,产生了深刻的印象;各个地点的建筑物,看起来就像我需要记忆的人脸一样,我便可以产生印象了。

然而现实是残酷的,这应该只是天方夜谭。

tcdw 急迫希望记忆他很难记住的,却很好的记住了其它一些奇怪的东西。于是 tcdw 也常常被批评:『背诵这么简单的事情,你多做不了啊』。好吧,那你为什么历史学的那么好,但是化学学的一塌糊涂呢?

那些对于别人来说很简单的事情,然而 tcdw 很难做到,这种体验也许是别人难以理解的吧。

's avatar

一袋凉皮引发的『血案』

我们都知道 XZ Premium 是防水溅的,而 tcdw 有时也会直接用水冲洗一下他的 XZ Premium,还在同学面前炫耀一番。

在一个天气还凑合的星期日下午,tcdw 回到了学校,舍友给 tcdw 所在寝室每个人都买了一袋凉皮,每袋凉皮都有附带辣椒油。

这些凉皮是装在塑料袋里的,包括辣椒油和蔬菜包。

tcdw 准备干掉这些凉皮,便把塑料袋打开,把凉皮倒进了饭盒。但是,辣椒油也是在塑料袋里的。

为了把辣椒油倒出来,tcdw 拿出剪刀,把塑料袋剪开了一个口,但没想到口剪得太大了,里面的辣椒油洒到了 tcdw 的桌子上。

tcdw 很慌张,他的手机被洒了,抽屉里的东西也被洒了。他慌里慌张的拿出纸巾、洗洁精和抹布,努力进行清理。

tcdw 的 XZ Premium 上全是辣椒油,为了清理的更干净一些,他把他的手机用洗洁精洗了一遍。

在报销了 1/4 瓶洗洁精和一大堆纸巾以后,终于把辣椒油清理干净了,味道也闻不到了。


就在前几天,tcdw 跟 qwe7002 聊天,谈到了副卡的事情,于是 tcdw 被安利买了一张米粉卡。

第二天,tcdw 的米粉卡到货了,他兴高采烈地给 XZ Premium 装上了米粉卡,然后上网冲浪。回去以后,他感到手机上全是汗渍,便在水龙头下又冲洗了一遍。

晚上,tcdw 绝望的发现,他的 XZ Premium 出现了奇怪的亮斑。

tcdw 瑟瑟发抖,赶紧跟 Telegram 群里的大佬们讨论,最后得出结论,大概是防水盖没弄好,或者防水胶出问题了。

于是,tcdw 只好把他的 XZ Premium 寄到了北京,开始返厂维修之路。

在此期间,tcdw 的舍友借给 tcdw 一部 vivo Y29L。这部手机小巧美观,内置了 Google 框架,但是 RAM 只有 1G,连正在播放的音乐播放器都有可能会自杀,而且系统是 Android 4.4 的。然而没办法啊,有舍友愿意借 tcdw 手机已经很不错了。

对 tcdw 来说,真是自作自受。

为了你的生活和谐,请确保在第一步做的事情就很靠谱,这样以后可能的不靠谱的事情就没机会做了(

5 月 13 日更新

经过一个月的漫长等待,tcdw 终于拿到了修好的 XZ Premium。于是,除了底部的磨损,这看起来就是部全新的机器了,因为整个屏幕和外框都被换掉了。

tcdw 终于把上个月买的 XZ Premium 贴膜和保护套用上了。但是这钢化膜居然左右两侧怎么也贴合不住,于是就有了奇怪的两边。。先这样吧 🌚

总之手机最后修好了,还是可喜可贺的。

's avatar

我写的一言轮播脚本(2018 更新)

看到很多 ACG 博客都用上了一言,但我觉得在同一个页面只显示一条内容未免有些单调,所以想让它定时轮播,而且采用较好的切换效果。

我后来在 WNJXYK 上看到了我想要的轮播效果,但那是基于 jQuery 制作的。

因为对于我这种讨厌为一个小功能放上大型前端库的人来说不能忍,所以我用原生 JS 写了一个。

2018 年,为了获得更佳的性能和稳定性,我把这个脚本进行了重写,从 JavaScript 动画更改为 CSS 渐变,并改掉了一些不科学的地方。

下面是实际效果:

如果你在使用 RSS 阅读器,请访问原文查看效果。

HTML 部分

<span id="hitokoto"></span>

JavaScript 部分

(function() {
    var server = 'https://api.lwl12.com/hitokoto/v1';
    var target = document.getElementById('hitokoto');
    var fadeDur = 1;
    var waitDur = 10;
    var errorMsg = '加载出现了问题!';
    /* -=-=-=-=-=-=-=-=-=-=-=- 你的配置部分结束 -=-=-=-=-=-=-=-=-=-=-=- */
    var first = true;
    var present = '';
    target.style.opacity = 0;
    target.style.transition = 'opacity ' + fadeDur + 's';
    function loadData() {
        var connect = new XMLHttpRequest();
        connect.open('GET', server, true);
        connect.onload = function() {
            if (connect.status >= 200 && connect.status < 400) {
                present = connect.responseText;
                if (first) {
                    first = false;
                    target.textContent = present;
                    fadeIn();
                } else {
                    fadeOut();
                }
            } else {
                  target.style.opacity = 1;
                  target.textContent = errorMsg;
                  setTimeout(loadData, (fadeDur + waitDur) * 1000);
            }
        }
        connect.onerror = function() {
            target.textContent = errorMsg;
            setTimeout(loadData, (fadeDur + waitDur) * 1000);
        }
        connect.send();
    }
    function fadeOut() {
        target.style.opacity = 0;
        setTimeout(function() {
            target.textContent = present;
            fadeIn();
        }, fadeDur * 1000);
    }
    function fadeIn() {
        target.style.opacity = 1;
        setTimeout(loadData, (fadeDur + waitDur) * 1000);
    }
    loadData();
})();
IceHoney Blog's avatar

使用letsencrypt为自己的网站加密

好吧,这次博客的更新稍微有点迟到了。因为自己各种忙着考驾照什么的,不过新的一年又要有新的开始了。最近买了VPS也就开始折腾起来了,讲讲如何用letsencrypt来给自己的网站颁发不要钱的证书,而且最近还支持通配符的二级域名了,想怎么开子网站都行!!!

dehydrated

浏览器的证书签名的步骤实在是很麻烦,所以有位热心的学生做了一个自动化脚本,可以自动生成脚本并且可以和其他脚本一起使用。达到一键签名证书。

letsencrypt-cloudflare-hook

我把自己的DNS服务托管到cloudflare上,还可以使用cloudflare提供免费的CDN服务。最重要的一点是cloudflare有API接口。利用这个接口就可以自动完成DNS-01 challenge。简单的来说,就是通过添加一条DNS记录来证明自己对这个域名的所有权,才能给这个域名颁发证书。

安装过程

知道了这两个工具之后,我们就可以开始配置证书了。具体的流程都写在项目的文档里面了。我这里主要是讲述下遇到的坑,首先我自己的服务器环境是CentOS7,使用的是默认的python2的环境。需要安装cryptography的库才能顺利完成编译。

sudo yum install gcc libffi-devel python-devel openssl-devel

其次,配置环境最好保存在dehydrated/config里,这样就没必要每次都设置环境变量。

echo "export CF_EMAIL=user@example.com" >> config
echo "export CF_KEY=K9uX2HyUjeWg5AhAb" >> config
echo "export CF_DEBUG=true" >> config

CF_KEY就是cloudflare里面的Global API Key,替换一下就好了。因为我们要配置的域名包含所有的二级域名,所以需要新建一个dehydrated/domains.txt文件,并写入:

icehoney.me *.icehoney.me

这样就会对主域名和所有二级域名生效。

之后再执行命令,./dehydrated -c -t dns-01 -k 'hooks/cloudflare/hook.py'。如果没有问题就可以看到成功生成的日志。

参考:

dehydrated

letsencrypt-cloudflare-hook

Failed to install Python Cryptography package with PIP and setup.py Ask Question

's avatar

土豆评论

2018 年 5 月更新:它开发完成了。


我想了想,决定捡起这个被我在去年弃掉的坑,并重新设计、编写相关代码,确保它有着更加科学的设计。撸起袖子加油干!

背景

Disqus 在中国大陆已经不能用很久了,好气啊。

使用 Disqus 的那些博客,往往会考虑换成多说、ISSO、原生或者一些小众评论框服务,或者直接拿 Github issues 托管评论,也有人(比如我)想:来我博客的人应该都会翻墙吧。

直到有一天,某人问我:

你博客的评论是怎么回事儿呀?好像自己没有看见回复按钮。

看来必须找个替代服务了,要不然我的一些(并不经常翻墙的)朋友没法往我的博客发布评论啊。

多说

多说就算了吧。

  • 抛开国产服务的通用问题不谈,首先它的 HTTPS 支持实在太不正经(博客一定会出现 Mixed Content),还要自己搭个 HTTPS 反代服务器来实现小绿锁。
  • 它的评论提醒也不正经到爆,除了浮窗以外似乎没有别的方法查看评论动态,有个邮件提醒也是摆设。
  • 只能通过社交网络登录,授权会过期,而且还会很烦人地提醒你去延期。
  • 多说带来的垃圾评论比原生 Wordpress 还多,太烦人。
  • 网站界面水平大概是 Disqus 的零头。

更新:多说下线了。

小众评论框

这也是我的考虑之一,但我后来放弃了。

  • 友言:看上去很久没有更新了,而且 UI 也没比多说好到哪去。更重要的是,它不支持 HTTPS
  • LiveRe:看上去是个很棒的评论服务,而且完整支持 HTTPS,然而免费版功能少到可怜,甚至不支持评论导出。网站界面不是韩文就是别扭的中文,想看看专业版有哪些功能,介绍只有韩文版本。英文界面也比这样强啊!
  • 萌评论:功能比 LiveRe 还少,而且看上去极不可靠。虽然我知道偷揉是个很棒的前端开发者,但至少目前没法考虑。

至于 GitHub issues?首先你要有一个 GitHub 帐号,然后还要知道如何使用 GitHub issues ...... 对于我这种所谓的技术宅问题是不大,但是对于圈子以外的朋友,门槛比翻墙用 Disqus 还高。

为了让我看起来是个真正的菊苣,我打算自己写一个(对我来说)最好的评论框服务!

于是 Pomment (Powerful Comment) 的故事就开始了。

Pomment 的计划特点?

  • 易于安装、更新
  • 采用多个 SQLite 数据库存储数据
  • 提供多种管理 API
  • 提供邮件提醒、webhook 支持
  • 前后端分离
  • ……

至于什么时候做好,也许是个谜(

kookxiang's avatar

在 Ryzen 平台上安装 macOS High Sierra

首先简单扔下目前电脑配置吧:

CPU:AMD Ryzen 5 1600X Processor

主板:ASUS PRIME B350M-A

内存:G.SKILL Ripjaws V Series 16GB (2 x 8GB) DDR4 Memory

硬盘:Samsung MZVLW256HEHP (PM961) 256GB SSD

硬盘:SanDisk 250GB Ultra 3D NAND SATA3 SSD

显卡:ASUS Strix GEFORCE GTX 970

基本配置就是这些,当然,SanDisk 的 SSD 是为了这次安装黑苹果系统新买的。因为不想影响原有的 Windows 的分区结构。

首先第一步是下载最新的 High Sierra 镜像,并写入到 U 盘(我用的是 SanDisk CZ80,现在最新升级款是 CZ880)。

< 访问完整版以查看此播放器 >

其实装黑苹果并不是没有苹果设备,相反我手边就有一台 MacBook Pro。黑苹果最诱惑的地方在于它的性能,比如我的 Ryzen 如果拿来日常开发的话构建编译速度绝对比 Intel 的移动 CPU 好得多。

而且,装 High Sierra 首先你需要一台 Mac。

先下载最新的系统镜像:打开 App Store,搜索 High Sierra;等到下载完成后不要点击继续,直接 cmd + Q 退出安装程序。

首先将 U 盘改成 GPT 格式并格式化成 HFS+:

然后打开终端,使用命令将安装程序写入 U 盘:

sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --applicationpath /Applications/Install\ macOS\ High\ Sierra.app  --volume /Volumes/kookxiang/

U 盘写好了以后不要急着推出,下载 Ryzen 内核补丁 解压

用 Finder 打开 U 盘,按下 cmd + shift + . 显示隐藏文件。

复制压缩包中的 Ryzen_Kernel_10.13/PreInstall/Prelinkedkernel/prelinkedkernel 文件 到下面的两个目录覆盖:

  • /Volumes/Install macOS High Sierra/.IABootFiles/
  • /Volumes/Install macOS High Sierra/System/Library/PrelinkedKernels/

顺便将 PreInstallPostInstall 两个文件夹放进 U 盘备用。

下载 Clover 配置工具 运行,找到左侧的 Mount EFI,挂载 U 盘的 EFI 分区,使用 Ryzen_High_Sierra_10.13_EFI.zip 解压覆盖。

确定你的 BIOS 没有开启奇怪的配置,或者直接重置一下,关闭 CSM 模块,这样 Clover 和 Windows 10 开机画面分辨率可以变成 native 的。开启 EHCI 和 XHCI Hands-off,确保 USB 能够正常工作。串口并口这种 20 年前的端口顺便就关了吧,就算开了你也没有对应设备的… HPET 建议打开,由于早期 Ryzen 官方超频软件依赖这个功能我就提前打开了。

需要注意的是直到 Clover 安装完成前硬盘都是没办法启动的,建议在 BIOS 中选择首选启动方式为 U 盘,使用 U 盘中的 Clover 启动安装程序或安装好的系统。

如果你像我这样比较慌,可以把 M.2 SSD 拆了,拔掉其他硬盘的 SATA 线,避免手滑一时爽。

接下来就可以使用 U 盘启动了,在 Clover 画面中选择 U 盘,稍等片刻就可以进入安装界面。如果出现禁止符号,别慌,这是玄学问题,洗洗脸重启几次就好了。

进入安装界面以后,点击上面的实用工具 - 磁盘工具。分区并格式化这里应该就不用说了,但是需要记住磁盘的名字,比如我这里用的 Hackintosh,中间不要空格。完成后退出回到主界面,接下来就跟白苹果一样一路下一步了。

安装完成后会自动重启,但是不要进入系统!

还记得我们的 PreInstall 和 PostInstall 吗?选择从安装 U 盘启动,打开终端,执行 /Volumes/Image\ Volume/PreInstall/pre,填写上一步中的磁盘名称。如果磁盘名称有空格或者其他特殊字符的,自己改脚本吧…

这一步的主要目的是复制补丁文件并替换系统内核为 Ryzen 修改版。

如果没有报错,重启电脑,选择从 Hackintosh 启动,会开始走进度条,其实这才是真正的系统安装过程。

等待进度条走完后再次选择从安装盘启动,打开终端,执行 /Volumes/Image\ Volume/PostInstall/post 替换内核。

替换好后重启,选择从 Hackintosh 盘启动,配置系统并创建账号。
这里不推荐绑定 iCloud,进了系统在绑都不迟。

系统部分基本就 OK 了,接下来需要安装 Clover,可以下载之前提到的 Clover 配置工具,同时挂载 U 盘和系统盘的 EFI 分区,将 U 盘的 EFI 文件夹复制到系统盘上,出现覆盖提示时选择”合并“。

接下来可以删除一些安装程序用的兼容组件,推出 U 盘,打开 /Volumes/EFI/EFI/Clover/kexts/Other 文件夹,删除 DummyUSBXHCIPCI.kextDummyUSBEHCIPCI.kextGenericUSBXHCI.kext 三个补丁。

为了让 Ryzen 在 macOS 下全速运行,使用 Clover 配置工具 打开 clover.plist,在启动参数中增加 busratio=xx,其中 xx 为倍频。例如我这块 1600X 日常超频到了 3.8G,那么这里就写成 busratio=38,因为 Ryzen 超频基本都是超倍频,外频默认都是 100MHz,如果你改了外频的自己慢慢算吧。

GTX 970 驱动还是比较简单的,首先到 Clover 配置工具 中点击安装驱动,点击安装 EmuVariableUefi,到 NVIDIA 下载 最新的 Web Driver 安装即可。

由于苹果本身配的显卡就是 ATI 的,所以 A 卡用户基本可以免驱动跑,有问题再搜吧。

确认基本 OK 后,重启,这回终于可以脱离 U 盘开机了,确认各功能都正常就告一段落了。

最后晒图:

Phoenix Nemo's avatar

迁移 DokuWiki 到 BookStackApp

Wiki 这么反人类的语法是怎么存在这么久的???????

总之受不了 Wiki 语法的可维护性了。什么?这玩意儿有维护性???

以及万年不更新的各种插件。系统升级后 PHP 7 不兼容,一看还是 swiftmail 的问题。生气。

正好有需求要整合一套知识库平台,搜索了一下 Confluence 的 alternative,发现了 BookStackAppPhabricator

前者适合个人或开源社区使用,后者则是一整套企业协作解决方案。对于我的需求来讲,BookStackApp 就足够啦。

页面数据

DokuWiki 并不使用数据库,因此没有一个通用的中间件来实现数据格式转换。而 DokuWiki 的语法非常奇葩——比如,它的一级标题是 ====== 这样 ======,六级标题才是 = 这样 =,正好和一般的 Wikitext 倒置。图片、内链等的表达方式也相当愚蠢,这些问题使我在思考迁移方案的第一个小时内就放弃了直接从源码转移的途径。

顺便,还有另外一个问题——本来为了使 Wiki 易于编写,这 DokuWiki 还安装了 Markdown 插件。因此部分页面中混杂着 Markdown 语法,更增加了源码处理的复杂度。

综合来看,最通用的数据格式,就是最终渲染出来的 XHTML 了。

图片

DokuWiki 的图片存储策略也是非常的奇特。由于它没有数据库,因此为了保持图片与页面的对应,它将图片存储在每个页面同样的路径下,并通过执行 PHP 的方式获取(扶额。

更甚者!!!

外链的图片,也是通过 /lib/exe/fetch.php 带参数来获取!!

我 的 天 哪。

因此既然在页面数据的考量中决定了使用最终渲染输出的 XHTML 来处理数据格式,图片也需要特殊的下载和归档技巧。这将需要使用 sanitize-html 提供的 transformer 方法来实现。

逻辑实现

一开始尝试了一些 Site Exporter 插件,但遗憾的是并没有什么真正能派上用场。甚至一些暴力递归下载所有页面和资源的脚本的表现也非常糟糕。

但是根据 DokuWiki 的官方 Tips,它可以将文章内容单纯导出 XHTML,只需要加上 ?do=export_xhtmlbody 参数即可。这就方便了,因为这样只需要一个完整的页面列表就可以了。随便找一个可以输出子命名空间的插件,新建一个页面用于从根命名空间展开就 OK 啦。

请求这个列表页面的 XHTML body 输出,使用 cheerio 遍历所有的 a 标签,就获得了所有要导出的页面地址。分别再去请求这些页面的 XHTML body 输出,做如下处理:

  1. 跟踪所有的 img 标签,下载图片文件并按预定义的路径规则和文件名归档。
  2. sanitize-html 清除所有不必要的标签、样式、id 和 class。
  3. sanitize-html 按预定义的路径规则更新所有 aimg 标签属性。

看代码

后来发现 DokuWiki 的性能不足以支撑异步请求的速度,额外加上了 sleep 模块来控制请求频率(扶额。

脚本执行完后,将图片目录移动到 BookStackApp 的对应位置,便可以直接读取所有的 HTML 文件来导入数据啦。

用了这么久,才发现原来还有比 raw HTML 更难以维护的数据格式啊…(望天。

HY's avatar

Android Apps

安卓手机用久了,手头也积累了一些解决不少使用上痛点的好应用。今天就来推荐一下这些应用吧。  去你大爷的内置浏览 […]
灰灰's avatar

使用HTTP Alternative Services实现免备案

由于众所周知的原因,国内的主机都是封闭80端口需要备案才能开启的。 这对不能备案的域名(如moe)以及不想备案 […]
HY's avatar

Souls-Like Games

最近因为玩了血源以后一发不可收拾的喜欢上了魂类游戏,血源7周目通关之后紧接着开始打黑魂3(刚好HB月包里送了) […]
kookxiang's avatar

国家队 ED1 发售了吗

没有完整版的《トリカゴ》听,我要死了

发了 magnet:?xt=urn:btih:0E60761122A15F7AECE58E9BF61B653553785110

< 访问完整版以查看此播放器 >

Dimpurr's avatar

校内应用个性化数据年报项目全程吐槽纪实 – 长单页面前端工程与 ECharts 图表可视化

这篇文章以全程实录的方式,记述了我在接到任务之后,从初期策划和出设计稿、组织文案撰写,再到前端工程开发、处理应用统计数据、利用 ECharts 绘制可视化图表,到最后部署上线的完整过程,以及在此期间内心满满的槽点。也许你可以从本文窥见一个完整有趣的项目流程,或者了解我在项目过程中收获到的经验;或者你发现了其中我犯的错误或不足,也请不吝赐教。

本文来自 钉子の次元 - Dimpurr - 千里之行,始於足下。 ,原文地址 校内应用个性化数据年报项目全程吐槽纪实 – 长单页面前端工程与 ECharts 图表可视化

灰灰's avatar

Nginx with TLSv1.3

开发历时近2年,在上个星期,OpenSSL终于发布了v1.1.1的第一个预览版,这也是支持 TLSv1.3 的 OpenSSL 的第一个 Release 。
虽然 TLS1.3 的标准还未修改完成,但绝大部分的浏览器(Chrome)都已经内置了实验性TLS1.3的选项。
下面记录一下 Nginx built with OpenSSL 1.1.1 的过程吧

使用系统: Debian Stretch

编译准备

依赖安装,本人习惯性使用 clang 而非 gcc ,所以把 clang 装上了,不喜请忽略

sudo apt install build-essential clang-3.9 -y

Nginx 在 1.13 才加入了 TLS1.3 的支持,编译时要注意获取 1.13 以上的版本

OpenSSL 1.1.1 pre1 兼容的 TLS1.3 版本为 draft-23

    • 更新: openssl-1.1.1-pre2 发布
    • 2018-09-11更新: 目前 OpenSSL1.1.1 正式版已经发布了,Chrome 预计在 Chrome 70 ( 1个月后发布 stable 版 ) 正式支持正式版的 Tls1.3 ,目前 Chrome 69 Flags 中可选择版本为 Draft 23 ( 对应 OpenSSL 1.1.1 pre2 ) 以及 Draft 28 版 ( 对应 OpenSSL 1.1.1 pre8 )   ,目前已经可以做好切换正式版的准备了
wget http://nginx.org/download/nginx-1.13.9.tar.gz
wget https://www.openssl.org/source/openssl-1.1.1-pre2.tar.gz
tar zxvf nginx-1.13.9.tar.gz
tar zxvf openssl-1.1.1-pre2.tar.gz

编译

OpenSSL 1.1.1 默认开启 tls1.3 ,所以只需要在编译时追加 --with-openssl 即可

cd nginx-1.13.9
COMPILER=clang-3.9 CXX=clang++-3.9 CC=clang-3.9 ./configure \
  --with-openssl=../openssl-1.1.1-pre2 \
  # 其他编译开关
make
sudo make install

安装完成后的 Nginx 为 built with OpenSSL 1.1.1 就是支持 TLS1.3 的了

设定 TLS 1.3

TLS1.3中新增了5种加密套件,分别为

  • TLS13-AES-256-GCM-SHA384
  • TLS13-CHACHA20-POLY1305-SHA256
  • TLS13-AES-128-GCM-SHA256
  • TLS13-AES-128-CCM-8-SHA256
  • TLS13-AES-128-CCM-SHA256

Nginx 设定很简单,只需要

  1. ssl_protocols中新增TLSv1.3
  2. ssl_ciphers最前面加入以上5种加密套件

使用 Mozilla 提供的 SSL Configuration Generator 生成并修改即可。

最终看上去的配置是这样的

server {
    #...
    ssl_certificate /path/to/signed_cert_plus_intermediates;
    ssl_certificate_key /path/to/private_key;
    #...
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS;
   #...
}

启动 Nginx 无错误,然后在浏览器端还需要做一些工作。

要启用 TLS1.3 draft-23 至少需要 Chrome 65 以上( Chrome 64 以下支持的是 TLS1.3 draft-18)

在 chrome://flags/#tls13-variant 中选择 Enabled (Draft 23)并重启浏览器

访问网站时即可看到 TLS1.3 生效

 

喵~

IceHoney Blog's avatar

浏览器的渲染性能

不知不觉,2018年的春节也要来临了。今年只是元旦回家了,春节并不打算回家。回家曾经是一件美好的事情,不知从何时开始,却是那么的揪心。最近也有读很多关于性能优化和底层的前端知识。不想就此停滞,只能不断前进。

浏览器渲染

我们都知道,现在主流显示器的频率是60Hz,也就是1秒要刷新60次。这样才能保持画面的流畅,特别是玩游戏的时候,我们非常在意帧数。前端开发也是一样,所以我们每一项操作都最好在10毫秒之内完成,否则会产生所谓的卡吨现象,影响用户体验。

渲染过程

浏览器的渲染主要是分为5个步骤,我们需要了解这些知识才能编写性能更好的代码。

  1. JavaScript 我们经常使用JS来实现一些复杂的视觉效果,数据排序,DOM操作等等。
  2. 样式计算 此过程是根据样式匹配选择器来计算哪些元素应用哪些CSS规则的过程。不过浏览器会对常用的选择器进行性能优化,例如类选择器。
  3. 布局 在知道每个元素的应用规则之后,浏览器开始计算所需要的空间大小以及其处在屏幕的位置。网页的布局中,一个元素的变化会影响到其他元素的位置。例如Body的宽度变窄之后其子元素的宽度也都会发生变化。
  4. 绘制 绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
  5. 合成 由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。

JS / CSS > 样式 > 布局 > 绘制 > 合成

如果修改了元素的布局属性,也就是改变了元素的几何属性(例如宽度,高度)。那么浏览器就必须检查所有元素,然后重新排版页面。任何受影响的部分都要重新绘制,再重新合成。

JS / CSS > 样式 > 绘制 > 合成

如果之修改了元素的绘制属性,例如背景图片或者文字颜色,并不会对其他元素的布局造成影响。浏览器会跳过布局,但仍然执行绘制。

JS / CSS > 样式 > 合成

目前既不要绘制也不要布局的属性只有transform属性和opacity属性。所以在实现CSS动画的时候,优先使用这两个属性。

如何查询CSS属性触发上面3个流程的哪一个,可以去CSS Triggers 查询。

参考:

Rendering Performance

Roy Binux's avatar

2018 新的冒险

真的又是好久没有写 blog 了。

年纪大了,记忆力下降,没有学习新东西的动力,也没精力折腾新的技术,新的领域了。每天就是看看斗鱼,打打游戏就过去了,现在的理想就是早点退休,当条咸鱼就好了。

2017 年主要给公司开发了一套基于 electron (chromium) 的页面渲染后端,可以保证抓取时和用户浏览器中看到的保持一致。同时这个服务器端的浏览器,可以通过 websocket 连接用户浏览器,双向同步页面内容变化,录下用户操作,在抓取时进行重放。这些功能我真的很想做给 pyspider,但是确实不方便。眼见着 pyspider stars 过万,而我却渐渐没有精力去维护了。我的希望是以后从现在的公司离职之后能有2-6个月全职开发 pyspider,算是这几年项目荒废的补偿吧。

公司终于把伦敦办公室关闭了,我也随着搬到了美国(湾区)。随便写一点美国的感受吧:

  • 加州税真高,比英国还高,英国人家好歹有免费医保啊
  • 美国真的是物资极大的丰富,真的可以理解为什么很多中国人来了就想要留下来,小富即安
    • 地广人稀,使得超市都是 super 起步的,这样会让选择非常多,卖的量都是加大号的
    • 充足的停车场,汽车出行不用担心不方便停车
    • 汽车让生活半径极大扩大,湾区各种中餐半小时车程都能到达,而半小时车程也不过是正常通勤所花的时间
    • 各种服务比起英国齐全多了,而且周六日不休
    • apartment 社区大都自带 365 天 7 * 24 开放恒温游泳池,健身房等设施(即使大冬天根本没有人去用,水也是恒温并更新的)
  • 非实时记账,很多场合真的需要使用支票,需要通过账单付费;因为是后付费,需要 SSN 查询你的信用记录。真的很不方便。
  • 租房好贵,宽带好贵,手机卡好贵,小费好贵

总体来说,英国更接近国内的政府+生活模式,而美国是只要你花钱,什么都有,不花钱,滚蛋。反正 L1 签证也就 3 年,也不能跳槽,而且就美国这个 H1B 抽奖 + 绿卡排队,比起英国来简直就是地狱模式。趁着这几年,在美国多玩一玩吧。9酱。

Phoenix Nemo's avatar

制作 Arch Linux 系统模板镜像

阿里云镜像制作踩坑记。

此文章主要记录按照阿里云 Customized Linux 制作 VPC 镜像的过程。一些部分也可用作制作其他平台镜像的参考。

当然记录的原因主要是 Arch 上的 cloud-init 打死无法在阿里云上修改 root 密码,就很气。

IceHoney Blog's avatar

JavaScript中的数据类型

隔了一个月,我又来发博客了。最近的工作老是在写CSS和HTML。但是我更想学习JS啊!我一直都觉得HTML和CSS是属于设计范畴的,而JS才是真正属于工程师的逻辑范畴。 况且最近Github上有一个神奇的项目Screenshot-to-code-in-Keras可以把截图直接生成HTML代码,我觉得只是单纯的从PSD翻译成页面的工作迟早要被淘汰。

最近在读You Don't Know JS这本书。书上讲解了很多关于JS的细节知识,对于深入了解JS有很大帮助。所以想在读的过程中把一些觉得有意思的东西记下来,便于以后复习。

类型

JavaScript中有七种内置类型:

  1. 空值(null)
  2. 未定义(undefined)
  3. 布尔值(boolean)
  4. 数字(number)
  5. 字符串(string)
  6. 对象(object)
  7. 符号(symbol, ES6新增)

除了对象之外,通称基本类型。

JavaScript中的设计BUG

typeof null === "object"; // true

正确的返回结果应该是null。但这个BUG由来已久,修复反而会出问题。所以我们需要使用复合条件来判断:

(!a && typeof a === "object")

接下来是NaN的问题:

var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true
NaN === NaN // false

很显然"foo"不是NaN,但显然它也不是数字。这个BUG也存在很久了,在ES6时代,我们可以使用Number.isNaN来解决。 ES6之前的polyfill是:

if (!Number.isNaN) {
  Number.isNaN = function(n) {
    return (
      typeof n === "number" &&
      window.isNaN(n)
    );
  };
}

并且NaN是JS中唯一一个不等于自身的值。

值和类型

JavaScript中的变量是没有类型的,只有值才有。变量可以随时持有任何类型的值。

undefined 和 undeclared. 变量在未持有的时候为undefined, 此时typeof 返回 undefined. 大多数开发者倾向于将 undefined 等同于 undeclared(未声明),但在 JavaScript 中它们完全是两回事。已在作用域中声明但还没有赋值的变量,是 undefined 的。相反,还没有在作用域中声明过的变量,是 undeclared 的。

var a;
a; // undefined
b; // ReferenceError: b is not defined
typeof a; // "undefined"
typeof b; // "undefined"

虽然b 是一个 undeclared 变量,但 typeof b 并没有报错。这是因为 typeof 有一个特殊的安全防范机制。防止因为未定义导致程序终止运行。

值和引用

在许多编程语言中,赋值和参数传递可以通过值复制(value-copy)或者引用复制(reference-copy)来完成,这取决与我们使用什么语法。但是JavaScript 对值和引用的赋值 / 传递在语法上没有区别,完全根据值的类型来决定。

简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值/传递,包括null 、 undefined 、字符串、数字、布尔和 ES6 中的 symbol 。

复合值(compound value)——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/传递。由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。

var a
var b
a; //
b; //
= [1,2,3];
= a;
[1,2,3]
[1,2,3]
// 然后
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]

b=[4,5,6] 并不影响 a 指向值 [1,2,3] ,除非 b 不是指向数组的引用,而是指向 a 的指针,但在 JavaScript 中不存在这种情况!

参考:

You Don't Know JS

Kouga's avatar

三题

“呃……”我拿着铅笔的手又停了下来,“这样好像不也太对啊……”

总之又是个深夜了,但是眼前这简单的三个题目仍旧让我无法进行下去。虽然不是什么很困难的题目,但是总觉得如果继续写下去就是矛盾重重的结果,那样就会变成无论谁都无法接受的展开了,可能又会引爆一到两个群体也说不定。

好在深夜里也是有好处的,比如电力供应高峰过去了,现在就算全功率计算也不会出现高昂的电费账单了。而且由于大部分人类都进入梦乡,此时网络带宽也空闲不少,只有一些Bot在网络上游走,试图压榨它们那优先级并不高的线路带宽。

唔,这个时间适合来验证那个问题了:据说此时抛硬币的话,会出现连续结果都是正面的情况,而且无解。虽然这个听上去就是个都市传说,不过验证一下并不会像打破三题那样造成更大的破坏性,正好可以换下负载让过热的大脑轻松一下。

于是,随手拿起一枚硬币,放在拇指指甲上,弹起——叮~ 硬币划破空气传来了清脆的声音,啪!的接住它,嗯,这次是正面,不过这是刚开始而已,再来——叮~啪!正面 * 2,嗯,因为独立事件互相不会干扰,所以继续吧……正面……正面……正面……正面……正……

连续的正面开始挑战我的理智了……这不应该的……本该随机发生的事件现在突然开始变得连续而统一,这不可能,随机性质的事件为什么变得像理所当然的一次又一次的重复着同一个结果,就像随机数女神已经睡着了一样……啊啊啊……为什么会这样呢……明明想要放松一下大脑的,结果这会儿因为分析这件事情又开始满负荷运转了……

时间悄无声息的流逝着,而我却仍旧在执著的想要打破这个可怕的循环,抛起,接住,正面,抛起,接住,正面……直到第一缕阳光倾泻下来,电力系统开始由蓄电向发电切换的一瞬间,抛起硬币的手臂因为这个波动导致力矩有了一点偏差,而修正机构没来得及在硬币脱手前补正,随着硬币再次飞起,落下,啪!这次终于不再是正面了!欣喜的我流下了两行过热溢出的冷却液,却再也无法计算这是怎么回事了,因为今天的工作又要开始了。

“编号SX-8012的那个机器人又在试图挑战三定理,还好它自己又陷入死循环了,竟然在不停抛硬币,哈哈哈”

's avatar

我的 2017

好耶,我的 2017 结束了!

不可思议的

  • 我用着一部没有 root 的 Xperia XZ Premium,居然很开心
  • 我用上了曾经被我黑来黑去的 MacBook Pro,闭嘴了
  • 我家里的 Windows 台式机配置更平衡了,仅仅因为我增加了 RAM 和 SSD,不过比较残念的是主板上四个 SATA 口都是 SATA2 的其实有一个是 SATA3。M.2 SSD?我好穷啊
  • 买了一大堆奇怪的外设,其中一部分是为我 MacBook Pro 的妥协(Type-C),把我自己炸的不要不要的
  • 对三次元音乐大幅恢复兴趣
  • 突然变得通俗、接地气
  • 身份又增加了半吊子设计师
  • 我与我的舍友用着同样的手机、同样的 Type-C 转接头、同样的 U 盘
  • 在正版软件上砸了不少钱,基本实现了软件完全正版化
  • 本博客在 2017 年还活着
  • 解散了所有自己管理的 Telegram 群组,因为我真的不适合做社区管理员(

三次元的

  • 我度过了人生中最后一段高中时光,进入大学
  • 我有了很多三次元小伙伴
  • 第一次与网上的 dalao 面基
  • 做事被迫变得很麻利
  • 我开始主动寻求改变自己内心很奇怪的一面。也许以前我的三次元生活确实很惨,但它们都过去了,就试图从现在开始变得更好

Geeky 的

  • 开始第三次学习 JavaScript,阅读了《你不知道的 JavaScript》等书籍。曾经受某专家毒害停留在 IE6 时代的我终于用上了 ES7,还开始使用 gulp,eslint 这些看起来很高大上的东西。它们真的好好用啊,减少了很多不必要的付出
  • 开始尝试使用 PowerShell,主要是不希望继续拘泥于 Linux 的命令行
  • 第一次提交自己的程序到 NPM,分别是 Typecho Markdown 导出工具OneAnime.js
  • 用上了 qwe7002SilverBlog,当了一回小白鼠,并为他提出了很多建议,同时受到了其思想熏陶(
  • 系统分工更明确:家里的台式机跑着 Windows 10,我的 Macbook Pro 当然跑着 macOS,从闲鱼收的 二手 NUC 跑着 Debian testing,Orange Pi One 吃灰开始

瑟瑟发抖的

Flash Player 要在 2020 年 停止支持 了。虽然是在意料之中,知道它已经过时了,不过还是感觉很惋惜。如果不是小时候被舅舅安利 Flash 6,接触 ActionScript,也许我永远不会跳进码农这个大坑呢。

荣耀的

我被评为 SMW Central 2017 年 12 月的 Member of The Month。虽然这真的只是我的业余爱好,但这样还是很开心的。

总之

希望我的 2018 会更棒!

brainbush's avatar

咸鱼2017

嗯,2017年就这样过去了。当了一年咸鱼……


今年只剁手了个27UD68,别的支出好像都拿去买书和给学校垫钱了的样子。

脚踏实地,勿忘初心。

和三年前想法差不多呢……(反正目标好像没变)

还是单身

Cee's avatar

Oh My 2017

Summary

合縁奇縁 一期一会

Moments

1 在箱根,第一次泡温泉

2 在杭州见了 61,聊了独立开发

3 渡鸦被百度收购

3 有学上了

4工藤去了趟香港

4 香港回来的第二天骑摩拜摔断了腿

5 深圳,@Swift Conf,当了次主持

5 辞职回家

6 美国面签

7 补牙

8 ✈️ 去大美帝

9 拿到了新泽西驾照

10 万圣节

11 北美音游群聚会

12 无尽的作业和大作业

12 暴雪

12 决战 C93

Input & Output

+ iPad Pro 10.5-inch (Silver)

- Sony Z5 Dual (E6683)

+ Sony WI-1000X (Black)

+ iPhone X (Space Gray)

- iPhone 7 Plus (Jet Black)

Numbers

  • 7,481 photos
  • 4 posts
  • 2,535 twitter followers
  • 632 instagram followers
  • 6 books
  • 178 contributions (only on GitHub)
  • Maybe… almost wrote PHP & Java in this year

Hall of Fame

The Next

  • 找到一个好点的 Intern
  • 多写一点 Side Project(17 年基本没怎么写)
  • 努力找到另一半😘
Phoenix Nemo's avatar

自托管的在线协作翻译平台 Weblate

起因:Transifex 这货闭源一段时间后突然开始抢钱了。

正巧一堆开源项目需要一个在线协作的翻译平台,于是测试了几个比较知名的开源程序。一遍折腾下来,发现 Weblate 可以最大化满足要求。顺便提一句,Weblate 也是有 hosted 付费服务的,但是在预算内的源字符串等限制依旧太多,所以选择使用他们的源码来搭建一套。

以及:我讨厌 Docker。

Dimpurr's avatar

BigDataGumi 一期小记 – 浅尝爬虫数据抓取和简单可视化分析 (PySpider)

这篇文章记录了利用 bgm.tv 动画条目和评分数据进行数据统计分析的的初步进展,包括设计网页爬虫抓取数据、利用可视化分析工具尝试获取有价值信息的实际操作部分,还可以了解到数据分析部分领域的概貌。

本文来自 钉子の次元 - Dimpurr - 千里之行,始於足下。 ,原文地址 BigDataGumi 一期小记 – 浅尝爬虫数据抓取和简单可视化分析 (PySpider)

via these people and places