分类 技术 下的文章

快速配置 HTTPS

已经是快到 2017 年,很多网站居然还不支持 HTTPS 访问。要知道 Chrome 可是要开始将 HTTP 站点标记为“不安全”了。那么这一篇就说一下如何最快速地将一个站点配置为 HTTPS 访问。

注意:一旦将你的网站配置为 HTTPS 访问,引用的所有脚本、图片、样式表等都必须是有效的 HTTPS 资源,否则页面将被标记为不安全。

首先,我们使用的组合是这样的:DNSPod + Let's Encrypt 服务 + nginx。也就是说在本文的例子中,域名 NS 服务使用的是 DNSPod,证书是由 Let's Encrypt 颁发的,服务器使用 nginx。

关于 nginx 的版本,建议是卸载服务器系统源中的版本,从 nginx 官方源安装,具体操作步骤参考 nginx 的官方文档。需要注意的是,nginx 在 Ubuntu 14.04 上不支持 HTTP2,具体原因和解决方法参考这里

第一件事情:在服务器上配置好你的域名,最简单的 nginx 配置如下。

server {
    server_name example.com;
    listen 80;
    rewrite ^(.*) https://example.com$1 permanent;
}

server {
    server_name example.com;
    listen 443 http2;
    ssl on;
    include /etc/nginx/ssl_params;
    ssl_certificate /home/user/example/keys/ssl/example.com.fullchain.cer;
    ssl_certificate_key /home/user/example/keys/ssl/example.com.key;

    include /etc/nginx/hsts_headers;

    location / {
        return 204;
    }
}

以上配置文件示例中,我们让 nginx 在 80 端口监听 HTTP,在 443 端口监听 HTTPS(并且使用 HTTP2),并将 HTTP 请求转向 HTTPS 地址。最终使用的证书和密钥存放在 /home/user/example/keys/ssl/ 目录中,这个路径你可以任意指定,只要 nginx 有权限读取对应文件就行。

此外,配置中还有两个 include 的文件 /etc/nginx/ssl_params/etc/nginx/hsts_headers。后者只是添加了一些 HSTS 头,内容如下。

add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;

关于 HSTS 头有什么作用,以及为什么要添加这些响应头,请参考维基百科的 HSTS 页面

/etc/nginx/ssl_params 这个文件是一些 SSL 相关的配置,一个简单的例子如下。

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_dhparam /etc/nginx/dhparams.pem; # See https://weakdh.org/sysadmin.html for more details
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_prefer_server_ciphers   on;
ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA";

其中 /etc/nginx/dhparams.pem 文件的生成以及 SSL ciphers 的选择请参考 Guide to Deploying Diffie-Hellman for TLS

编辑好配置文件后,可以按一般习惯存放在 /etc/nginx/sites-available/example.com,然后软链接到 /etc/nginx/sites-enabled/example.com。暂时先不要重启 nginx 服务,因为我们还没有申请证书,重启是肯定会失败的。

第二件事情就是去 DNSPod 将你的域名解析配置好,并生成 API token。域名解析应该不用多说,API token 是在通过 DNSPod 的 API 来操作 NS 记录时授权使用的,可以认为就是应用程序密码。关于如何新建一个 API token,请参考 DNSPod 的官方文档。这里个人建议是一台服务器上只需要申请一个 API token,名称要描述清楚 token 的用途和使用的机器,方便日后查询和管理。token 生成后只显示一次,这时候需要复制下来,供下一步使用。

第三步就是去 Let's Encrypt 申请证书了,我们使用的是 acme.sh 工具。关于这个工具的优点有很多,我个人选择它的原因大概是:

  • 安装、使用、卸载都非常简单
  • 对于一个域名只需要手动操作一次,之后续期和重启服务器都是自动的

自动续期是很重要的一点,因为 Let's Encrypt 颁发的都是 90 天有效的短期证书。

从零开始使用 acme.sh 的步骤如下。

# 首先安装这个工具
curl https://get.acme.sh | sh

# 安装路径在 ~/.acme.sh
# 安装好之后看一看命令是否可用,如果有问题,请手动修改你的 shell 配置文件

which acme.sh
# acme.sh: aliased to /home/user/.acme.sh/acme.sh

# 创建我们最终存放证书的路径
mkdir -p /home/user/example/keys/ssl

# 设置你的 DNSPod API token
# 注意:多数 shell 会记录你的操作历史,安全起见,操作完毕后请手动删除 ~/.zsh_history 或 ~/.bash_history 中对应的内容
export DP_Id="1234"
export DP_Key="sADDsdasdgdsf"

# 申请证书
# acme.sh 会自动调用 DNSPod API 去设置证书申请期间需要使用的 NS 记录,并会等待 120 秒让记录生效
acme.sh --issue --dns dns_dp -d example.com

申请完毕之后,acme.sh 会打印出所有相关文件的绝对路径。但是不要急着去使用这些文件,我们还要将它们安装到指定的位置。

说点多余的,其实这一步不是必须的,你完全可以直接使用这些文件。但是这些文件都存放在 ~/.acme.sh 中,之后 acme.sh 升级时,内部目录结构可能会改变,造成你的配置文件失效;并且通过额外的安装步骤,acme.sh 可以在续期后帮你自动重启 nginx 服务。后一点是很重要的,因为 nginx 只是在启动时读取证书和密钥,在续期后如果不重启,它还是使用旧的证书,你可能直到旧证书失效才会发现。

安装证书的步骤很简单。

acme.sh --installcert -d example.com --keypath /home/user/example/keys/ssl/example.key --fullchainpath /home/user/example/keys/ssl/example.com.fullchain.cer --reloadcmd "sudo /usr/sbin/service nginx reload"

只是将证书和密钥安装到指定的路径中,并且 reload nginx 配置。

需要注意的是,如果你的 sudo 命令需要密码,这里可能会有麻烦。但是也不要就因此直接用 root 用户来运行 acme.sh。你可以参考这篇文章,将当前用户配置为不需要输入 sudo 密码,或者配置为运行指定命令时不需要密码。

安装完证书后就可以访问你的网站了,按照前面的 nginx 配置,网站会返回 204 空响应,你可以通过 Chrome 开发工具的 Security 面板来检查网站的 HTTPS 证书。建议同时通过 SSL Server Test 来检查一下,根据检测结果来修正 SSL 相关设置,尽量达到 A 级或 A+。

最后,如果你想确认 acme.sh 会不会帮你网站的证书做自动续期,运行 crontab -l 来查看当前用户的定时任务,你应当会看到 acme.sh 自动添加的条目。这个任务会每天运行一次,如果一个域名的证书已经颁发超过 60 天,acme.sh 就会帮你自动续期。

本文中使用的例子是 DNSPod,但 acme.sh 还支持很多其他的 NS 服务提供商,具体请参考官方文档。

附一篇 nginx 相关配置的文章:本博客 Nginx 配置之安全篇 | JerryQu 的小站

PyQt5 使用 QSettings

虽然写的是 PyQt5 中遇到的问题,PyQt4 中也可能会遇到类似问题。

中秋假期在家继续改进之前写的小工具,第一件事把设置数据的存取从直接读写 yaml 文件改成了使用 QSettings。什么是 QSettings?摘一段 Qt 文档的翻译。

QSettings 类提供平台无关的持久化应用程序设置。

用户通常期待应用程序在不同会话中保留其设置(窗口大小和位置、设置项等等)。这些信息在 Windows 上经常存储在注册表中,在 Mac OS X 和 iOS 上则保存在 plist 文件中。在 Unix 系统上,由于缺少标准,许多应用程序(包括 KDE 程序)使用 INI 文本文件(来存储设置)。

QSettings 是围绕这些技术提供的一个抽象层,让你使用一种可移植的方式来存储和恢复应用程序设置。它同时支持自定义存储格式。

- 阅读剩余部分 -

Qt 中的窗口大小

本文中所说的窗口都是指 decorated top-level widget,对于简单程序来说通常就是 QMainWindow 的子类对象。

之前玩 Qt 的时候一直以为 QMainWindow 的大小就是 .geometry() 报告的大小(也就是 .width().height()),结果今天发现其实不对的。

起因是我想将一个窗口放在整个桌面的右下角。那么这里有几个问题。

首先 Windows 平台默认下面是任务栏,那么我们肯定不希望窗口的内容被任务栏挡住(或者挡住任务栏),所以这里需要使用 QDesktopWidget.availableGeometry() 来获取可用的屏幕。在 Windows 平台上,这个方法计算除了任务栏之外的屏幕区域;在 Mac 上会计算除了 Dock 栏和菜单栏的屏幕区域。(什么?Linux?抱歉我已经不在桌面上使用 Linux)

那么可能我们就会写出这样的代码

class MyWindow(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        desktop_geopmetry = QtGui.qApp.desktop().availableGeometry()
        self.move(
            desktop_geometry.width() - self.width(),
            desktop_geometry.height() - self.height()
        )

那么显然我们就是 too young。因为根据 Qt 文档.geometry() 这一类的方法返回窗口 widget 区域(相对应地,.width().height() 都是返回窗口 widget 的宽高),而真实的窗口还带有操作系统窗口管理器附加的装饰,例如标题栏和额外的边框。看下图就懂了(图片来自 Qt 文档,链接在本段头部)。

geometry.png

好吧,那么我们现在改成这样就可以了吧?

class MyWindow(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        desktop_geopmetry = QtGui.qApp.desktop().availableGeometry()
        self_geometry = self.frameGeometry()
        self.move(
            desktop_geometry.width() - self_geometry.width(),
            desktop_geometry.height() - self_geometry.height()
        )

那么显然我们又是 too simple。因为我测试了发现,如果这个窗口还没显示(__init__ 里显然还没显示),那么 .frameGeometry() 这类方法的返回结果其实跟 .geometry() 这一类是一样的。换句话说,只有窗口被至少显示过一次,窗口管理器才有机会去装饰它,这样 Qt 才能知道最终窗口的实际占用区域大小。

那么我们只要在首次 showEvent 信号被触发的时候来获取窗口大小,然后据此移动窗口就可以了(当然如果你想每次显示都移动一下我也没话说 =,=)。

class MyWindow(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.__first_show = True

    def showEvent(self, evt):
        evt.accept()
        if self.__first_show:
            desktop_geopmetry = QtGui.qApp.desktop().availableGeometry()
            self_geometry = self.frameGeometry()
            self.move(
                desktop_geometry.width() - self_geometry.width(),
                desktop_geometry.height() - self_geometry.height()
            )
            self.__first_show = False

写了一个键盘计数器

起因是有个朋友说想写一个小工具看看自己每天敲了多少次键盘,当时因为很忙就直接回复用 pyHook。后来没忍住,自己业余时间写了个图形界面的工具,用了几天感觉还可以了,放出一个 exe 下载。

项目在 Microcore/KeyCounter,下载在项目的 release 里,目前只支持 Windows。主要原理就是

  • 用 pyHook 监听键盘按键被按下的事件
  • 创建一个屏幕大小的窗口,背景设置为透明,在上面绘制数字
  • 每次按键事件,更新数字并且重绘窗口上的文字
  • 创建一个托盘图标,通过菜单来重置计数、调整文字透明度和退出

代码写得很简单,基本就是去调用各种 Windows API,不过设置透明度的窗口是用 MFC 做的。

中间还发现三个问题。第一是如果你监听 KeyDown 事件,那么按住一个键一段时间后,这个事件就会不停触发(后来想了一下这是合理的);二是如果你监听 KeyUp 事件,那么基本上可以断定你会遇到 pyHook 的头号 bug,目前还没有修复,而我也不想直接修改它的源码;最后一个就是,我尝试监听鼠标键按下的事件,发现会对鼠标指针的移动速度造成很大影响(我是速度调得比较高并且去掉指针加速的,如果你是老年人速度那么可以尝试搞一搞),就是鼠标移动快一点的话,比如跨屏快速定位,指针酒会变得一卡一卡的。最终我放弃了鼠标键计数,专心做键盘计数。

想要验证签名的话请使用这个公钥 0A662175C3997E84A6FA879499321BCE08B738B8,关于如何验证签名请参考 Making and verifying signatures

最后提一下一个 Simon Brunning 写的一个很有用的 Windows 系统托盘图标类,以及一个实用的量化生活工具(其中包含了记录键盘活动,并能进行历史搜索和统计等操作)selfspy

[速记] Python 使用网络代理

在测试 Telegram 机器人的时候遇到了网络问题(由于众所周知的原因,Telegram 的服务器在国内大部分地区无法访问),于是需要使用代理。我本机上是有一个运行在 1080 端口的 socks5 代理,那么速记一下如何使用。

urllib2

python-telegram-bot 是直接使用的 urllib2,所以我直接让 urllib2 全局使用代理。需要安装 PySocks

pip install PySocks
# 会安装 socks 和 sockshandler 两个模块

使用:

import socks
import urllib2
from sockshandler import SocksiPyHandler
opener = urllib2.build_opener(
    SocksiPyHandler(socks.SOCKS5, address, proxyport=proxyport, **kwargs)  # 额外参数
)
urllib2.install_opener(opener)

requests

让 requests 使用代理就简单多了。requests 本身支持 HTTP_PROXYHTTPS_PROXY 两个环境变量(通过 os.environ 获取)。按照官方说法,要使用全局代理的话也可以直接设置 ALL_PROXY 这个环境变量,不过我测试没有成功。

那么,要让 requests 全局使用代理的话,只要在调用前设置 os.environ['HTTP_PROXY']os.environ['HTTPS_PROXY'] 就可以了,格式是类似 protocol://user:passwd@address:port

更多代码实例可以参考 《Python:使用代理proxy爬虫》,但是注意一点:使用爬虫恶意抓取网站内容不仅是不道德行为,更有可能因违反站点的 ToS 而被封禁帐号/IP,甚至起诉。

设置树莓派使用外置硬盘启动

整件事情是这样的:某一天我尝试在树莓派上安装一个 vim 的编辑器插件 YouCompleteMe,需要从源码编译。编译这个比较耗费内存,可能我分配的 SWAP 不够大(1024MB),导致内存不足,然后 sshd 就挂了。嗯,并且已经过去了一个小时我也没法连接上去。于是我就拔电源重启了,这下可好,SD 卡彻底启动不了了,估计是文件系统损坏之类的。上网搜了一下这种现象似乎非常常见,只要在断电时 SD 卡还在写入就可能出现。于是我就想到要把系统装在外置硬盘上。

- 阅读剩余部分 -

12月近况

11月和12月似乎并没有什么重要的事情发生的样子。那么以下是并不重要的流水账。

  • 黑五 Steam 打折,趁机入手了几个之前玩过盗版的游戏.
  • 从 12月13日 起,本站开始使用 HTTP2 了(托管在别处的子域名除外)。
  • 之前因为网站迁移到了国内,所以 Telegram 机器人一直没去弄了(网络原因)。最近清理了一下之前的代码,没什么卯月的功能都去掉了,增加了一个消息推送功能,也是目前唯一的功能。
  • 买了一个诺基亚 5110 的 LCD 屏幕用在树莓派上,然而对比度太低,加上屏幕接触不良、液晶显示速度慢等等问题,现在已经不在使用中。
  • 买了一个 SSD1306 驱动的 OLED 单色屏幕,对比度高,SPI 接口速度还不错,使用中。
  • 买了本 Flask 的书(好久不买实体书了),希望能一边看一边做点实用的东西出来。
  • Mac 上有些应用突然打不开(可能是上次 MAS 证书过期问题导致的),这其中包括 Dash 2;然而 Dash 2 的 MAS 页面已经被替换成了 Dash 3,内购解锁完整版的价格是 163 软妹币。因为我之前通过 MAS 购买了 Dash 2 的原因,给作者发邮件后拿到了优惠码,现在已经换成 Dash 3 了。
  • DNSPod 在 12月18日 解决了Let's Encrypt 的支持问题,之后如果可行的话所有子域名都会强制开启 HTTPS。
  • 前段时间作息有点不太规律,近期计划调整好。
  • 好像很久没画画了,记得不要荒废了自己的数位板。
  • Google Analytics 拖慢页面加载速度,加上很多用户都会开启 Adblock,其实并没有什么卯月,打算年底前找个替代品。
  • Gravatar 拖慢页面加载速度,目前换成了 typcn 的镜像,先观察一段时间。

那么额外说明一下该如何启用 HTTP2 吧(Ubuntu 系统)。首先卸载已经安装的所有 nginx

然后导入 Nginx 官方签名的 key:

wget -c http://nginx.org/keys/nginx_signing.key
cat nginx_signing.key | sudo apt-key add -

其次,在 /etc/apt/source.list 中加入 Nginx 官方源:

deb http://nginx.org/packages/mainline/debian/ codename nginx
deb-src http://nginx.org/packages/mainline/debian/ codename nginx

其中 codename 替换成你自己所使用 Ubuntu 版本的代码名称。
接下来 apt-get update 更新一下软件包列表,然后重新安装 nginx

这时候 nginx 应已经是 1.9 以上的版本了。在配置文件中修改启用 HTTP2:

server {
    listen 443 http2;
    ssl on;
    ...
}

然后重启 nginx 就好了。

开启了之后感觉飞快的样子,页面打开时间比之前减少一半以上,不知道是不是我的错觉。

使用 Gogs 搭建自己的 Git 服务器

作为个人私有云的一环(虽然是搭建在公共服务器上),一直都很想搭建一个类似 GitHub 的服务,但 GitLab 太复杂了(说句不好听的,GL 就是 Git server 中的 WordPress),别的又相当简陋,于是一直以来都只有围观的份。最近发现上半年关注的 Gogs 经过几个版本迭代,已经达到基本可用的状态了。于是搭建了一个自己的服务,用来镜像自己分散在各个服务商处的代码,以及开新坑的时候托管用。这里把安装的过程和一些需要注意的地方记录一下。

- 阅读剩余部分 -