使用 Certbot 更新 Let's Encrypt 证书

Let's Encrypt 证书即将过期时会给你发送邮件,这个还是比较贴心的。这样也就不会陷入证书过期却没发现的尴尬境地(Let's Encrypt 的证书只有 90 天的有效期)。

2826714bc9645e4b9828433b8e674800.png

以前我写过使用 Certbot 这个工具申请证书的文章,而同样使用这个工具更新证书只需要一行命令:

certbot renew --post-hook "service nginx reload"  

certbot 这个脚本的位置呀名称啥的自己看着改,对于我来说是 ./certbot-auto。加了个钩子可以让它在证书更新更新完毕后重载 Nginx 配置来更新证书。

b2134999636066c29c9e93a40bd88a57.png

不想每次都登上去更新的也可以把上面那行脚本加入 crontab(crontab -e),让它每个月执行一次:

# 这里用绝对路径,保险一点
0 0 1 * * /home/xxx/certbot-auto renew --post-hook "service nginx reload" >/dev/null 2>&1  

P.S. Xshell 管理远程机子比起单纯的终端来还是很方便的,而且最近也对 Home/School 发放免费许可了。

参考链接:Renewing certificates - Certbot User Guide

阅读全文→

PHP 远程文件下载的进度条实现

PHP 实现远程下载文件到服务端并不是什么新鲜玩意,用 cURLfile_get_contentsfopen 等都能够轻易实现。

但是这几种常规的方法都是在一个线程内下载文件,等文件下载完毕以后才能返回 HTTP 响应。所造成的结果就是用户在页面上点击「下载到服务器」按钮后,会看到空白页和加载的小菊花转啊转,转好久之后才出现「下载成功」的页面。

当然,我上面所举例的情况是只使用纯粹的表单 POST 发送请求的情况。现在的话就算再不济也一般会使用 ajax 发送请求,然后在前台放个加载动画,等收到下载成功的回应之后再进行下一步操作。

但是!即使是去掉了恶心的且需要等待的空白页,这样做还是对用户体验有不好的影响。没有具体的下载进度,只有一个一直转呀转的菊花图,估计挺多用户都无法坐和放宽吧(至少对于我来说是这样的)

而我一个 PHP 项目的一键更新系统正好需要重构,遂研究了如何在 PHP 作为后端时显示远程文件下载进度条,并捣鼓出了个像样的解决方案,在这里分享一下。

阅读全文→

耳机插头修理技巧

群里大佬 pan爷低价出了我一个耳机插头附近断线的 ATH-ES55,这已经是挺久以前的事了。当时我去某宝上买了个耳机插头,刮掉耳机线的漆包草草焊上去就完事了(好歹是挺不错的耳机你就这样对待啊喂)。然而最近耳机插头接触不良了,我就寻思着重新焊下,在这里记录一下需要注意的地方。

虽然修耳机这种事我在小学的时候就鼓捣过了,但总归当时还是不专业,也没啥像样的工具。而且最近听的好歹也算是 HiFi 耳机,不能再用之前那种乡巴佬方法了(笑)。在网上搜了一些,学到了不少,在此记录一下。

下面几乎都是盗图,侵删w

3.5mm 耳机插头接线图

82f3a4e9e890e1d4ab91352def12f33a.png

阅读全文→

Laravel 框架下的插件机制实现(一)—— 构建插件系统

插件,即 Plug-in(又有外挂、Extension、Addon 等叫法),是一类特定的功能模块,通过和应用程序的互动,用来替应用程序增加一些所需要的特定的功能。插件的特点是:

  • 当你需要它的时候激活它,不需要它的时候禁用/删除它;
  • 无论是激活还是禁用都不影响系统核心模块的运行。

也就是说插件是一种非侵入式的模块化设计,实现了核心程序与插件程序的松散耦合。上面的介绍部分摘自 中文 WikiPedia

虽然现在网上有很多 PHP 的插件机制的实现(当然我指的是英文内容,中文关于 PHP 插件机制的搜出来现在还是那么点破文章),譬如 FoolCode/Plugin,或者 WordPress 的插件实现。

不过我今天想介绍的是如何使用 Laravel 的服务容器、事件机制等功能来实现一个比较优雅的插件机制。

Greatly inspired by Flarum,在此致谢 ヾ(´ω゚`)

阅读全文→

实现点击按钮加载 Gist 代码块

GitHub Gist 应该也是很多人都在用的功能了,我也喜欢把一些大段的代码块贴上去而不是直接写在博文里。

但是 Gist 的 embed js 是在页面加载的时候就直接开始加载了的,所以或多或少还是对网页的性能有所影响。受上次搞的「点击加载 Disqus」启发,我也准备写一个点击加载 Gist。

最开始我尝试了传统方法,即动态创建一个 <script> 标签并添加到文档流中。然而却得到了一个这个错误:

Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.  

看来动态加载的 js 文件中是不允许写文档流的。而不巧的是 Gist 提供的 embed js 正是使用 document.write 来实现的。

在网上找了一下,虽然也有些利用 iframe 的解决方法,譬如:Dynamic Gist Embedding,但都感觉不咋好(嫌麻烦)

后来,我在 SegmentFault 的动态预览 Gist 的功能上发现了,Gist 竟然能够提供 JSONP 式的回调!

阅读全文→

使用 ADB Shell 刷入 Recovery

众所周知 MTK(联发科)的大部分机器都是没有 fastboot 模式的,也就意味着不能进行普通的依赖于 fastboot 系列命令的线刷模式。普通来讲这也没啥特别大的影响,毕竟刷 recovery 可以直接系统里拿到 root 后用工具刷,救砖有 MTK 专用的线刷工具(这玩意的驱动坑的不行)

但是,在一些神秘的状况下,我有可能会被没有 fastboot 的情况下坑到。

这次我想刷的是「朵唯 iSuper S1」,用的是 MTK6589 芯片组。按照惯例刷机之前首先要把 Recovery 换掉以刷入第三方 ROM 包。照理来说这块是没有问题的,但是不知道为什么,市面上的 Root 工具能够让手机拿到 Root 权限,但是在手机上安装权限管理应用时均告失败,也是迷的不行。

明明 Root 权限都已经拿到了却刷不了 Rec,这也太吃瘪了吧!然而没有 MTK 机子也不能用 fastboot 刷 rec,专门的线刷工具又太难用。。就在这时,我突然想到了一个命令 —— dd

阅读全文→

使用 Nginx 反代并缓存动态内容

因为着实没想到一个皮肤站竟然会有这么大的并发(好吧其实也不大,QPS 大概 50 左右),所以我之前 开发/部署 Blessing Skin Server 的时候是完全没考虑到缓存优化的。直到后来出现了 神秘的高并发(QPS 上 200)导致皮肤站宕机我才意识到缓存的重要性。

我到现在也还是搞不懂那到底是正常流量还是 CC 攻击。。难道正常用户会每秒请求 20+ 次 Json Profile 吗?又不是天天退/进游戏玩 emotion

虽然当时我就花了一点时间给 Json Profile 和皮肤预览之类的地方 加上了缓存,但是由于这些缓存方案都是在 Laravel 层之上的,所以框架的性能依旧是硬伤。试了下 Stone 之类的优化措施也没什么卵效果,再加上我学业也忙了起来,这个问题就被搁置了。

正好最近国庆放假(虽然只有四天),我就抽了点时间出来研究了一下如何给我的皮肤站套一层缓存 ( ・_ゝ・)

阅读全文→

劳资也是开得起 Minecraft 光影的男人辣!

这次国庆去电脑城摸了块 GTX750(原先的 9800GT 实在是烂得不行了)和一块新硬盘回来,克隆完硬盘后就迫不及待地给 MC 打上了 GLSL Shader Mod。玩了一会后我的表情是这样的:emotion1

不得不说上了光影之后的 MC 根本不是一个游戏啊

95e7cf170b0d4f5fbe87e97360faacb9.png

阅读全文→

String.prototype.endsWith() 的浏览器兼容性问题

String.prototype.endsWith() 这个方法是 ECMA6 新加入的,而我当初随手 Google 了一下,也没怎么看就直接用上去了,直到我今天在一台破手机上测试的时候发现有个用到这个方法的值神秘地变成了 undefined ( ´_ゝ`)

我记得上次好像也是这样随手搜索导致使用了不兼容的函数 ( -д-) 论面向搜索引擎编程的坏处

不过我们还是可以通过扩展 String 的原型来实现在不支持的浏览器上使用这个方法:

/**
 * 代码来自 MDN
 * 
 * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
 */

if (!String.prototype.endsWith) {  
  String.prototype.endsWith = function(searchString, position) {
      var subjectString = this.toString();
      if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
        position = subjectString.length;
      }
      position -= searchString.length;
      var lastIndex = subjectString.lastIndexOf(searchString, position);
      return lastIndex !== -1 &
阅读全文→