openresty安装篇

环境版本

OS:CentOS Linux release 7.1.1503 (Core)
openresty:1.9.15.1

安装准备

除非你要修改源码,如果只是运行官方的包,官方建议优先考虑预编译包,centos 系统支持如下:

版本号 支持的体系结构
5.x x86_64, i386
6.x x86_64, i386
7.x x86_64

配置yum源

你可以在你的 CentOS 系统中添加 openresty 资源库,这样就可以方便的安装我们的包,以后也可以更新(通过 yum update 命令)。添加资源库,你只用创建一个名为 /etc/yum.repos.d/OpenResty.repo 的文件,内容如下:

[openresty]
name=Official OpenResty Repository
baseurl=https://copr-be.cloud.fedoraproject.org/results/openresty/openresty/epel-$releasever-$basearch/
skip_if_unavailable=True
gpgcheck=1
gpgkey=https://copr-be.cloud.fedoraproject.org/results/openresty/openresty/pubkey.gpg
enabled=1
enabled_metadata=1

显示支持的包

sudo yum –disablerepo=”*” –enablerepo=”openresty” list available

Loaded plugins: fastestmirror
openresty                                         | 3.5 kB     00:00     
openresty/7/x86_64/primary_db                       |  32 kB   00:01     
Determining fastest mirrors
Available Packages
openresty.x86_64                         1.9.15.1-16.el7.centos openresty
openresty-debug.x86_64                   1.9.15.1-7.el7.centos  openresty
openresty-debug-debuginfo.x86_64         1.9.15.1-7.el7.centos  openresty
openresty-debuginfo.x86_64               1.9.15.1-16.el7.centos openresty
openresty-doc.noarch                     1.9.15.1-16.el7.centos openresty
openresty-openssl.x86_64                 1.0.2h-3.el7.centos    openresty
openresty-openssl-debug.x86_64           1.0.2h-4.el7.centos    openresty
openresty-openssl-debug-debuginfo.x86_64 1.0.2h-4.el7.centos    openresty
openresty-openssl-debug-devel.x86_64     1.0.2h-4.el7.centos    openresty
openresty-openssl-debuginfo.x86_64       1.0.2h-3.el7.centos    openresty
openresty-openssl-devel.x86_64           1.0.2h-3.el7.centos    openresty
openresty-resty.noarch                   1.9.15.1-16.el7.centos openresty
openresty-valgrind.x86_64                1.9.15.1-6.el7.centos  openresty
openresty-valgrind-debuginfo.x86_64      1.9.15.1-6.el7.centos  openresty
perl-Lemplate.noarch                     0.07-3.el7.centos      openresty
perl-Test-Nginx.noarch                   0.25-3.el7.centos      openresty

安装

关于openresty的rpm包的介绍见这里:http://openresty.org/cn/rpm-packages.html

按照文档的说明,我们暂时只需要安装openresty,openresty-resty,openresty-doc三个包就可以了

yum install openresty
yum install openresty-resty
yum install openresty-doc

运行

sudo /sbin/service openresty start

stop, restart, 和 reload 这些指令也是支持的。

默认网站实例是加载在/usr/local/openresty/nginx/ 目录下的,如果要改变成自定义的目录使用 -p 选项:

sudo openresty -p /opt/my-fancy-app/

这个命令实测,是需要手动创建目录的,记得要先停止openresty。

其他问题

防火墙

在centos7下面,防火墙由iptables变更为firewalld,测试时需要增加相应的80端口,或者暂时关闭防火墙。

配置firewalld 使用firewall-cmd,默认需要安装:

yum install firewalld firewalld-config

查看当前开放zone,端口,服务:

firewall-cmd --get-active-zones
firewall-cmd --zone=public --list-ports
firewall-cmd --zone=public --list-service

增加80端口:

firewall-cmd --zone=public --add-port=80/tcp

自启动

添加openresty的开机自启动:

chkconfig --add openresty
chkconfig openresty on

由于centos已经用systemD代替了systemV,系统不再有rc-local服务了,所以我们也不能简单添加rc.local了(网上流传的给增加运行权限的,在本机验证不成功)。标准的做法就是创建新的任务,通过chkconfig来启动了。

因为firewallD的规则是不能保存的,所以我们要放到开机自启动脚本中。

libreswan libevent错误

安装libreswan时遇到如下的错误:

/data/l2tp/l2tp/libreswan-3.17/programs/pluto/state.c:800: undefined reference to event_free'
server.o: In function
init_event_base’:
/data/l2tp/l2tp/libreswan-3.17/programs/pluto/server.c:582: undefined reference to evthread_make_base_notifiable'
server.o: In function
pluto_event_new’:
/data/l2tp/l2tp/libreswan-3.17/programs/pluto/server.c:442: undefined reference to event_new'
server.o: In function
pluto_event_free’:
/data/l2tp/l2tp/libreswan-3.17/programs/pluto/server.c:588: undefined reference to event_free'
/data/l2tp/l2tp/libreswan-3.17/programs/pluto/server.c:589: undefined reference to
event_free’
/data/l2tp/l2tp/libreswan-3.17/programs/pluto/server.c:590: undefined reference to event_free'
/data/l2tp/l2tp/libreswan-3.17/programs/pluto/server.c:591: undefined reference to
event_free’
timer.o: In function delete_pluto_event':
/data/l2tp/l2tp/libreswan-3.17/programs/pluto/timer.c:814: undefined reference to
event_free’
ikev2_parent.o:/data/l2tp/l2tp/libreswan-3.17/programs/pluto/ikev2_parent.c:2704: more undefined references to event_free' follow
/usr/local/lib/libevent_pthreads.so: undefined reference to
evthread_set_condition_callbacks’
/usr/local/lib/libevent_pthreads.so: undefined reference to event_mm_malloc_'
/usr/local/lib/libevent_pthreads.so: undefined reference to
event_mm_free_’
/usr/local/lib/libevent_pthreads.so: undefined reference to evthread_set_id_callback'
/usr/local/lib/libevent_pthreads.so: undefined reference to
evthread_set_lock_callbacks’
collect2: ld returned 1 exit status
make[3]: *** [pluto] Error 1
make[3]: Leaving directory /data/l2tp/l2tp/libreswan-3.17/OBJ.linux.x86_64/programs/pluto'
make[2]: *** [local-base] Error 2
make[2]: Leaving directory
/data/l2tp/l2tp/libreswan-3.17/programs/pluto’
make[1]: *** [all] Error 2
make[1]: Leaving directory `/data/l2tp/l2tp/libreswan-3.17/programs’
make: *** [all] Error 2
libreswan-3.17 install failed.

问题分析:
1. 检查libreswan的版本和代码包的正确性,去官网下载比对,确认下载是最新的代码。
2. 查看日志,根据event_free关键词,判断出问题的应该是libevent库,所以确认libevent,发现已经按照说明成功下载了libevent2了。
3. 继续查找日志细节:
—> Package libevent2.x86_64 0:2.0.21-2.el6 will be installed
–> Processing Conflict: libevent2-devel-2.0.21-2.el6.x86_64 conflicts libevent-devel
–> Finished Dependency Resolution
Error: libevent2-devel conflicts with libevent-devel-1.4.13-4.el6.x8664
从上面的提示看到是libevent2跟系统默认自带的libevent冲突了

解决:
yum remove libevent-devel-1.4.13-4.el6.x86_64

yum install libevent2-devel

strongswan charon logging 配置

系统:ubuntu 14.04

strongswan 配置文件路径 /usr/local/etc/

默认的strongwan的日志是打印在syslog 里面的。为了便于管理和分析,我们希望日志能打印在专门的文件里。而strongswan 自带的charon守护服务就默认包含了charon-logging功能,我们只要开启此功能即可。

相关的配置参考如下:
https://wiki.strongswan.org/projects/strongswan/wiki/LoggerConfiguration

git 403 Forbidden

现象

error: The requested URL returned error: 403 Forbidden while accessing https://github.com/xxx/xxx.git/info/refs

fatal: HTTP request failed

分析

错误是在一个脚本中操作git时的错误提示,看错误提示初步判断是权限问题,继续google之,发现 : http://stackoverflow.com/questions/19722521/error-the-requested-url-returned-error-403-forbidden-while-accessing 。

解决

参照帖子的思路:

  1. 将https改为ssh访问,直接修改repo对应的隐藏文件.git/config 文件中的url即可。
  2. Generating an SSH key

再次运行脚本,执行成功。

hugo生成文章中不能正确显示中文字数的问题定位

问题发现

在刚切换到hugo博客的时候就发现文章的字数统计是错误的,但是因为当时也没发现有额外的副作用,就暂时懒得理了,毕竟这个只是写作工具,web前端也不是本人长项。

今天在使用多说分享文章的时候发现部分文章分享失败,于是根据分享生成的URL中的数据初步判断是获取文章content时出错了。插件获取了超长的content内容,导致接口调用失败。

问题定位

1. 多说

我们看多说插件中的相关代码:

data-content="{{ .Summary }}" 

多说只是使用了模版中的Summary变量值。

2. Summary

关于Summary变量,我们看Hugo的解释

Hugo-defined: automatic summary split

By default, Hugo automatically takes the first 70 words of your content as its summary and stores it into the .Summary variable, which you may use in your templates.

Pros: Automatic, no additional work on your part.
Cons: All HTML tags are stripped from the summary, and the first 70 words, whether they belong to a heading or to different paragraphs, are all lumped into one paragraph. Some people like it, but some people don't.

从以上的内容看到Hugo只是默认最多取70个词(words),所以这里很明显是取70个词时,实际取出了远远多于70个的内容,导致上面的url出错。

3. 长度错误

从上面我们初步判断是长度的问题。初步查看模版的代码,我们知道长度在模版中使用的是WordCount变量。于是我们查看hugo中WordCount相关的代码hugolib/page.go:

if p.isCJKLanguage {
        p.WordCount = 0
        for _, word := range p.PlainWords() {
            runeCount := utf8.RuneCountInString(word)
            if len(word) == runeCount {
                p.WordCount++
            } else {
                p.WordCount += runeCount
            }
        }
    } else {
        p.WordCount = len(p.PlainWords())
    }

从上面isCJKLanguage的变量名我们就能猜到是判断非英文的路径。简单看下代码,Hugo是有考虑CJKLanguage的问题的,也就是说可能是这个分支没有执行。

4. isCJKLanguage

我们搜索工程,找到isCJKLanguage:https://github.com/spf13/hugo/blob/2c5e4f7640e71d2a193a74e6c41109ec40bc0222/docs/content/content/front-matter.md 中的介绍,于是手动在出错的markdown文件中添加此变量,再次重新执行hugo,发现对应生成的网页中的字数统计的值正确了,分享也不出错了,说明就是字数的统计导致了系统错误。

问题验证了,但是因为博客的文章都是用工具自动从wordpress中生成的,不可能手动一个一个修改,修改工具也显得很死板,最好的方法是从Hugo这侧来找方案。

5. hugo配置文件

顺着上面的思路,开始搜索相关的关键字,发现hugo的配置中还真有相关的配置hasCJKLanguage:https://github.com/spf13/hugo/blob/b7efbdc12f0a96639b445f7920b6477d88beb744/docs/content/overview/configuration.md ,喜出望外,赶紧添加到config文件中重新编译文件,编译后发现并没有生效。于是再次查看源码hugolib/page.go:

if isCJKLanguage != nil {
        p.isCJKLanguage = *isCJKLanguage
    } else if viper.GetBool("HasCJKLanguage") {
        if cjk.Match(p.rawContent) {
            p.isCJKLanguage = true
        } else {
            p.isCJKLanguage = false
        }
    }

从上面的代码看isCJKLanguage 是由HasCJKLanguage开关控制的。

6. HasCJKLanguage

继续看代码,我们看到工程中只有一个地方有设置commands/hugo.go:

viper.SetDefault("HasCJKLanguage", false)

于是尝试修改本地的此行代码,重新编译Hugo。再次用hugo生成所有文章,发现所有文章的长度都正确了,甚是高兴。

再次检查今天发现的分享出错的文章,都全部正常了,舒服!

go编译中出现的link signal killed问题

在vps中编译hugo代码时出现:
tool/linux_amd64/link: signal: killed

经过搜索查到类似的场景:
https://github.com/beego/wetalk/issues/32

不同的是这个是beego,而我编译的是hugo,所以初步断定应该是跟go的环境有关,而不是hugo代码。所以验证了帖子中关于内存的说法,发现重启后,果然正常。

小米路由器R1D刷机手记

## update
2016.12.16:由于文章中的360 网盘的资源挂了,重新更新了tomato的刷机包的路径

缘由

小米路由器R1D已经使用了快一年,一直想刷机,主要因为:
* 后门问题,请搜索知乎“小米路由器 劫持”,虽然严格讲天朝人民是没有什么隐私,但被人强奸总是很郁闷的。
* 用了半年后,经常会掉网,很快就自动恢复,有时候莫名其妙手机就掉网开始走移动流量,很郁闷。

小米路由器R1D

小米路由器R1D是一款基于博通bcm4709( c )和OpenWRT深度定制的智能路由器。
小米路由器

准备工作

  • 了解基本的linux 常用命令,ssh,root权限等。
  • usb一个,用来制作usb刷机工具。
  • 备份你的拨号账号密码。
  • 一个备用的路由器,刷机有风险,一旦出现变砖,备用路由器至少可以让你继续使用网络,不至于影响正常的网络使用。
  • 一米的短网线,用来直连路由器,当然理论上wifi也是可以的,但是某些rom在进入特定的刷机工具状态时wifi是不可用或者启动有延迟的,有线则方便很多。

刷机的ROM备选

小米路由器是深度定制版的OpenWRT,网上大概搜了一下,可选的有OpenWRT原生系统,DD-WRT和Tomato三种系统可以尝试。

OpenWRT

ROM

首先从官方的硬件支持列表我们并没有看到R1D的支持:https://wiki.openwrt.org/toh/start 。其次从小米论坛和google也没有找到明确支持的rom。

刷机

巧妇难为无米之炊。
结果:放弃

DD-WRT

ROM

从官方的支持列表中:http://dd-wrt.com/wiki/index.php/Supported_Devices ,我们并没有看到R1D的支持ROM。但是我们可以找到非官方的rom,例如:http://bbs.xiaomi.cn/t-10691878 。

刷机

刷机流程参见:http://www.right.com.cn/forum/thread-143121-1-1.html ,本人尝试多次,均在第6步失败,初步判断是tftp server没有正常启动。

分析原因:原博是在0.4.58的rom上做的验证,本人尝试0.4.58版的USB和web页面升级的方式,均失败。web直接校验失败,USB刷机后,路由器无法正常启动,于是只能采用的最新开发版来验证。猜想是小米硬件做了升级限制或者在软件层面有专门对这个漏洞做修复。

结果:失败。如果有最近刷机成功的兄弟,也望留言指教。

Tomato

ROM

AdvancedTomato是一套基于Tomato的路由器管理界面,其核心是tomato。这个是tomato对应R1D的下载路径:

1. http://tomato.groov.pl/download/K26ARM

2. https://bitbucket.org/tsynik/tomato-arm/downloads

本次刷机实际使用的是这个:http://bbs.xiaomi.cn/t-11522784 ,因为有人验证过,并且支持中文,相对风险会小些,实际效果有待日后进一步观察。

说明:

因为文章里面的360的网盘挂了,特将rom重新发布:

链接: http://pan.baidu.com/s/1o8TRooE 密码: qe4j

因为本人验证时已经是很久以前的事情了,所以建议看到的朋友在上面的路径下载最新的版本尝试。

刷机

  1. 升级到最新开发版 :http://miwifi.com/miwifi_download.html
  2. 开ssh,http://www1.miwifi.com/miwifi_open.html
  3. ssh 192.168.31.1,ifconfig得到网卡MAC地址(或者刷机之前在web页面记录下MAC)
  4. 下载tomato固件,见前面的rom部分内容 。
  5. 下载tomato刷机包
  6. 打开CFEEdit.exe,通过菜单打开cfe.bin,修改其中的MAC地址为你的小米路由器的MAC地址,改好后覆盖。
  7. 将cfe.bin、backup.sh、flash-cfe.sh这三个文件放入U盘,插入路由U口。
  8. ssh登录路由器,登陆的账户名和密码参见 http://www1.miwifi.com/miwifi_open.html 。执行:
    nvram set boot_wait=on
    nvram set wait_time=10
    nvram commit
    进入U盘目录:cd /extdisks/sdb1 ,此路径可能会不同,请自行确认。执行:
    ./backup.sh
    ./flash-cfe.sh
    先备份系统文件,在必要时可以手动恢复。
  9. 电脑有线ip设置为192.168.1.1段,通过有线连接局域网端口。
  10. 重启路由,浏览器打开192.168.1.1,刷入tomato固件即可。

刷回小米系统

  1. 将miwifi-stock.bin和小米官网下载的小米路由器最新版U盘刷机包(改名我miwifi.bin)放到U盘中
  2. ssh登录小米路由器,执行命令:
    mtd-write2 /mnt/U盘/miwifi-stock.bin linux
  3. 按复位键,重新接电源,等黄灯闪烁后松开复位键
  4. 几分钟后黄灯常亮,拔下U盘
  5. 浏览器登录小米路由器,通过web页面更新最新开发版。

结果:成功。
启示:按照CFE的思路,理论上也可以来刷DD-WRT,但已经折腾一天了,改日有机会再试吧。

AdvancedTomato 基本安全配置

  1. 修改界面语言,修改管理员名称和密码:系统管理 - 管理员访问
  2. 设置宽带拨号,DHCP,wifi的SSID和密码:基础设置 - 网络
    注意:
    WAN设置:类别为PPPoE,不要勾选“使用DHCP”。
    LAN:默认的DHCP是有问题的,要设置正确的值,客户端才能正确使用外网。
    无线网络:设置WPA/WPA2密码。
  3. 如果为了进一步的安全,建议开启无线过滤:基础设置 - 无线过滤 - 允许以下客户端。手动添加需要wifi访问权限的设备,避免有人搞小动作

AdvancedTomato 服务配置

  1. USB&NAS
    通过设置,我们可以外接USB硬盘了,这个对某些人似乎有点用。
  2. 文件共享
    删除默认的共享,设置你要共享的目录,记得设置身份验证。这样你就可以在局域网使用路由器的1T硬盘了
  3. DLNA服务器
    如果你想在bt下完电影后,直接通过电脑或者手机观看,就要设置这个。设置一下媒体文件的目录,设置完后,DLNA客户端就可以直接在局域网发现媒体资源了。如果需要ios的客户端,你可以在APP store搜索Arkuda。
  4. BitTorrent客户端
    开启后,你可以通过路由器页面给路由器下发bt任务了,它会下载到你指定的路径,如果这个路径支持DLNA,那么下载完,你就可以直接观看大片了。
  5. DDNS+端口转发
    如果你要在远程通过互联网来玩bt,那么就需要继续设置DDNS+端口转发。

最后

如果你是其他型号的路由器,只要是刷相同类型的rom,流程基本相似,希望有帮到大家,也欢迎大家交流。
最后,有图有真相:
AdvancedTomato

刷完后,感觉整个世界都平静了。

其他参考

http://blog.icece.tw/Xiaomi-R1D-1TB-Flash-Tomato

补充:

  1. 关于发热的问题,我觉得原版其实就发热,我自己尝试的135的版本,刷机后感觉网速和发热并没有改善,期望新尝试的朋友继续反馈

IPSec生成证书

在很多安全服务中,我们都需要签名的安全证书。

注意 如果不生成证书,后面配置部分的配置中用 pubkey 认证的 conn 都不能用,甚至不能保留在配置中。
每一个完整的 ssl 证书都有一个公钥和一个私钥,它们可以在一起也可以分开放(当然如果你要在网络上传输,肯定只能用公钥)。公钥是在网络上传输的,而私钥是藏好用来和接收到的公钥配对的(因此私钥里也有整个公钥,用来配对)。

生成 CA 证书

生成一个私钥:

ipsec pki --gen --outform pem > ca.pem

没什么好解释的,–outform 一共有三个格式可选,但是另外两个是 der 和 pgp…

基于这个私钥自己签一个 CA 证书:

ipsec pki --self --in ca.pem --dn "C=CN, O=strongSwan, CN=strongSwan CA" --ca --outform pem > ca.cert.pem

这里 –self 表示自签证书,–in 是输入的私钥,–dn 是判别名,–ca 表示生成 CA,其它同上。这里需要解释下判别名:

C 表示国家名,同样还有 ST 州/省名,L 地区名,STREET(全大写) 街道名。
O 表示组织名。
CN 为通用名。
具体见微软的文档:Distinguished Names

生成服务器证书

同样生成私钥:

ipsec pki --gen --outform pem > server.pem

用我们刚才自签的 CA 证书给自己发一个服务器证书:

ipsec pki --pub --in server.pem | ipsec pki --issue --cacert ca.cert.pem \
--cakey ca.pem --dn "C=CN, O=strongSwan, CN=forum.suse.org.cn" \
--san="forum.suse.org.cn" --flag serverAuth --flag ikeIntermediate \
--outform pem > server.cert.pem

这条命令的意思解释下:

ipsec pki –pub –in server.pem
是从我们刚生成的私钥里把公钥提取出来,然后用公钥去参与后面的服务器证书签发(这个是 VPN 连接时候要用的,你不想把私钥也给它吧?那样跟没签证书一样…)。

–issue, –cacert 和 –cakey 就是表明要用刚才自签的 CA 证书来签这个服务器证书。

–dn, –san,–flag 是一些客户端方面的特殊要求:

iOS 客户端要求 CN 也就是通用名必须是你的服务器的 URL 或 IP 地址;
Windows 7 不但要求了上面,还要求必须显式说明这个服务器证书的用途(用于与服务器进行认证),–flag serverAuth;
非 iOS 的 Mac OS X 要求了“IP 安全网络密钥互换居间(IP Security IKE Intermediate)”这种增强型密钥用法(EKU),–flag ikdeIntermediate;
Android 和 iOS 都要求服务器别名(serverAltName)就是服务器的 URL 或 IP 地址,–san。

生成客户端证书

依然是生成私钥:

ipsec pki --gen --outform pem > client.pem

然后用刚才自签的 CA 证书来签客户端证书:

ipsec pki --pub --in client.pem | ipsec pki --issue --cacert caCert.pem \
--cakey caKey.pem --dn "C=CN, O=strongSwan, CN=client" \
--outform pem > client.cert.pem

这时命令行会提示你输入两遍密码,这个就是你的客户端证书密码。

看懂了服务器的,客户端的也就不难理解了。除了没有那一堆特殊要求别的都一样。

客户端证书可以每个客户端签一个,也可以让它们公用一个。是否多签看用途,一般用于区分设备(计费是不用这样的,是用账户来区分的)。

生成 pkcs12 证书(可选)

你可能还想生成一个可以直接导入的 pkcs12 证书(用于手机,诺基亚没这东西还不行):

openssl pkcs12 -export -inkey client.pem -in client.cert.pem -name "client" \
-certfile ca.cert.pem -caname "strongSwan CA" -out client.cert.p12

安装证书

cp -r ca.cert.pem /etc/ipsec.d/cacerts/
cp -r server.cert.pem /etc/ipsec.d/certs/
cp -r server.pem /etc/ipsec.d/private/
cp -r client.cert.pem /etc/ipsec.d/certs/
cp -r client.pem /etc/ipsec.d/private/

CA 证书、客户证书(两个)和 .p12 证书用 FTP 复制出来给客户端用。有几种 Android 配置还需要服务器证书(server.cert.pem)。

docker中的”layers”指的是什么东西

在很多的docker的说明中,我们都能看到“layer”的身影,那么到底什么是”layer“?

参见http://docker-doc.readthedocs.org/en/latest/terms/layer.html中的说明:

When Docker mounts the rootfs, it starts read-only, as in a traditional Linux boot, but then, instead of changing the file system to read-write mode, it takes advantage of a union mount to add a read-write file system over the read-only file system. In fact there may be multiple read-only file systems stacked on top of each other. We think of each one of these file systems as a layer.

简单翻译如下:
当docker加载rootfs时,和传统的linux系统中一样,它是只读的,但是随后,不是将当前文件系统变成可读写,而是利用union mount添加一个可读写的文件系统在当前的只读系统上。实际上将会有很多的只读文件系统叠加在彼此之上。我们将这里的每一层文件系统称之为”layer”。

参考阅读:
https://docs.docker.com/engine/understanding-docker/#how-does-a-docker-image-work
https://www.ctl.io/developers/blog/post/caching-docker-images/

通过wordpress钩子接口实现新增修改删除文章后执行相应动作

wordpress hook介绍

hook是wordpress实现功能扩展的重要手段,详细的介绍:
Plugin API

hook主要分两类:actions 和filters。
对应的注册函数分别为:add_action()和add_filter()。

目前支持的hook类型参见:http://adambrown.info/p/wp_hooks

例子

在模板的基础函数文件中:
/data/wp/wordpress/wp-content/themes/premium-style/functions.php

新增以下代码:

function publish_post_extra($post_ID){
    // 1. 初始化
    $ch = curl_init();
    // 2. 设置选项,包括URL
    curl_setopt($ch, CURLOPT_URL, "https://xxx" . $post_ID);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    // 3. 执行
    curl_exec($ch);
}
add_action('publish_post', 'publish_post_extra');
add_action('deleted_post', 'publish_post_extra');

以上的代码实现动作发生时,发送一个特定的通知url。

debian7系统安装docker

起始

最近开始做docker相关的学习和技术准备,准备将目前的一些基础服务,通过docker做同构或者异构分布式调整,提升运维效率和系统稳定性。

安装

我的系统:

Linux chicago1 3.2.0-4-amd64 #1 SMP Debian 3.2.41-2 x86_64 GNU/Linux

Docker要求Kernel 3.8+,幸运的是, wheezy-backports 目前有Kernel 3.16 , 该版本正式支持Docker。

升级系统

  • 从wheezy-backports安装内核

在文件 /etc/apt/sources.list中添加如下行

deb http://http.debian.net/debian wheezy-backports main

然后安装linux-image-amd64包 (注意使用 -t wheezy-backports)

sudo apt-get update
sudo apt-get install -t wheezy-backports linux-image-amd64
  • 重启你的系统。对于Debian来说使用新内核是必要的。

安装Docker

使用get.docker.com 的脚本:

curl -sSL https://get.docker.com/ | sh

卸载

为了卸载Docker包:

$ sudo apt-get purge lxc-docker

为了永久卸载Docker及其依赖包,你应该这样:

$ sudo apt-get autoremove --purge lxc-docker

命令将不会移除镜像,容器,数据卷,或者用户创建的配置文件。如果你希望删除所有镜像,容器,数据卷 ,运行如下命令:

$ rm -rf /var/lib/docker

你必须手动删除用户创建的配置项。

一键迁移wordpress到hugo

网站已经迁移了一个多月,一直想写下,但是因为一个issue的问题,一直不爽快:
the tag format
一直想等待解决再来写。

周末抽空过了一遍代码,代码中是用spyc来生成yaml的。改成[xx, x] 的格式还是要费点神的,而且破坏了原来格式的简单优美,什么意思?看下两种格式的生成就明白了,前者是不用考虑长度的迭代模式,后者要考虑长度,因为要在结尾加后括号’]’,这样逻辑就不是简单流式的了。为了确认是否值得修改,确认了官方的文档:
yaml官方文档
发现2种格式都是支持的,但是hugo的问题是:
文档的tags和categories本质来讲,都是列表数组,没有道理两种采用不同的格式,详情:
the tag format advice
显然关闭的人并没有理解我的意思,但是还是有其他的开发者明白了我的意思,做了回复,总之此问题目前确认是属于hugo的“不优雅”。不打算勉强,为了修复而修复,静待hugo的变化吧。

hugo

hugo是一个能把markdown转化成静态html网站的工具。

wordpress-to-hugo-exporter

说了这么多,言归正传,一键切换主要是使用这个插件:
wordpress-to-hugo-exporter
这个插件功能是通过提取wp的页面,将所有的可见页面转换成markdown文档。

生成md文件

支持两种方式:
1. 在wp插件页面通过按钮执行
2. 在后台命令行通过cli方式执行:

php hugo-export-cli.php

前者的文件会导出到用户客户端,后者生成的.zip在/tmp目录下。

后继的工作

这个插件只是将wp的内容转化成markdown文档,要创建hugo网站,你需要:

  • 找一个你喜欢的theme,从hugo官网就能找到
  • 根据theme的指导,配置相应的config文件,每一个theme的config会有略微差异
  • 将之前的markdown打包文件根据你自己配置的目录层次解压到hugo的content目录下的对应路径中。
    如果以上你都做对了,你应该已经可以看到你的hugo网站了。
    相关参考:
    一步一步教你用hugo搭建博客

中文支持的PR

拿到插件时,是不支持中文路径的,含有中文路径的文章,都是404。这个对大陆用户来说很不方便的,因为有很多人的文章都是有中文路径的。于是提交了对应的PR:
fix the unicode url issue and markdown format error
作者很勤快,很快就合入了主干,所以现在大家可以直接使用主干的代码,就是支持中文了。

selinux相关的permission-denied问题

起因

最近将系统更新到了centos7,由于centos7 采用systemctl 替代了原有server相关的服务管理接口。当使用

systemctl start nginx.service

时,系统提示出现错误,请运行:

systemctl status nginx.service -l

-l 表示显示完整的提示日志,否则会出现提示日志缩略的问题。运行status指令时,出现下面日志:

[root@server]# systemctl status nginx.service -l
nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled)
   Active: failed (Result: exit-code) since Fri 2016-01-08 11:56:09 EST; 30min ago
  Process: 704 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=1/FAILURE)
  Process: 698 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)

Jan 08 11:56:09 chicago1 nginx[704]: nginx: [emerg] BIO_new_file("/xxx/xxx.crt") failed (SSL: error:0200100D:system library:fopen:Permission denied:fopen('/xxx/xxx.crt','r') error:2006D002:BIO routines:BIO_new_file:system lib)
Jan 08 11:56:09 chicago1 nginx[704]: nginx: configuration file /etc/nginx/nginx.conf test failed
Jan 08 11:56:09 chicago1 systemd[1]: nginx.service: control process exited, code=exited status=1
Jan 08 11:56:09 chicago1 systemd[1]: Failed to start The nginx HTTP and reverse proxy server.
Jan 08 11:56:09 chicago1 systemd[1]: Unit nginx.service entered failed state.

分析

因为看到’Permission denied’,所以先检查文件权限,ll指令查看,文件的权限是没有问题。想起的了之前遇到的部署nginx时,部署在自定义目录下不行,部署在nginx默认安装目录’/usr/share/nginx/html’ 下就可以的问题。当时也是直接ls查看文件属性,两边的文件权限和归属都是相同的。

今天再次遇到这个问题是,觉得两次应该是同一个问题,所以有必要彻搞清楚,因为是权限问题,所以还是要从权限策略方面入手,于是想到selinux,在之前的系统中默认都是关闭的,但在新系统中我们并没有关闭。于是确认下:

[root@server]# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      28

于是我们查看下selinux策略配置:

ls -lrtZ /usr/share/nginx/html

显示如下:

-rw-r--r--. root root system_u:object_r:usr_t:s0

而我们自己的目录:

-rw-r--r--. root root unconfined_u:object_r:default_t:s0

解决

于是修改策略:

chcon -R -u system_u /xxx/
chcon -R -t usr_t /xxx/

重新执行,发现还是失败,最终确认,需要重启后,才能生效。

正则学习笔记

简介

如果做文本处理,日志运维,数据分析,爬虫等相关工作的同学,一定离不开的一个工具就是正则表达式。正则可以从一个完整的字符串中查找或者提取到具有相同“模式”的字符串子串。

什么是正则表达式

正则就是描述文本规则的代码,例如: *.doc 表示所有以doc后缀结束的文件名,这就是一个简单的正则。

基本模式

正则 含义
[abc] A single character of: a, b or c
[^abc] Any single character except: a, b, or c
[a-z] Any single character in the range a-z
[a-zA-Z] Any single character in the range a-z or A-Z
^ Start of line
$ End of line
\A Start of string
\z End of string
. Any single character
\s Any whitespace character
\S Any non-whitespace character
\d Any digit
\D Any non-digit
\w Any word character (letter, number, underscore)
\W Any non-word character
\b Any word boundary
(…) Capture everything enclosed
(a/b) a or b
a? Zero or one of a
a* Zero or more of a
a+ One or more of a
a{3} Exactly 3 of a
a{3,} 3 or more of a
a{3,6} Between 3 and 6 of a

元字符

元字符是正则中的具有独立完整意义的字符,类似变成语言中的保留字,只不过保留字一般是多字符的。

代码/语法 说明
. 匹配除换行符以外的任意字符
w 匹配字母或数字或下划线或汉字
s 匹配任意的空白符
d 匹配数字
b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束

例子说明:
元字符 ^(和数字6在同一个键位上的符号)和 $ 都匹配一个位置,这和 \b 有点类似。^ 匹配你要用来查找的字符串的开头,$ 匹配结尾。这两个代码在验证输入的内容时非常有用,比如一个网站如果要求你填写的 QQ 号必须为 5 位到 12 位数字时,可以使用:^\d{5,12}$ 。
这里的 {5,12} 和前面介绍过的 {2} 是类似的,只不过 {2} 匹配只能不多不少重复 2 次,{5,12} 则是重复的次数不能少于 5 次,不能多于 12 次,否则都不匹配。
因为使用了 ^ 和 $,所以输入的整个字符串都要用来和 \d{5,12} 来匹配,也就是说整个输入必须是 5 到 12 个数字,因此如果输入的QQ号能匹配这个正则表达式的话,那就符合要求了。

字符转义

如果你想查找元字符本身的话,比如你查找 . 或者 *,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\ 来取消这些字符的特殊意义。因此,你应该使用 \. 和 \* 。当然,要查找 \ 本身,你也得用 \\。
例如:unibetter\.com 匹配 unibetter.com,C:\\Windows 匹配 C:\Windows。

重复

常用的表示符

代码/语法 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

字符类

要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?
很简单,你只需要在方括号里列出它们就行了,像 [aeiou] 就匹配任何一个英文元音字母,[.?!] 匹配标点符号 (. 或 ? 或 !)。
我们也可以轻松地指定一个字符范围,像 [0-9] 代表的含意与 \d 就是完全一致的:一位数字;同理 [a-z0-9A-Z_] 也完全等同于 \w(如果只考虑英文的话)。

下面是一个更复杂的表达式:

\(?0\d{2}[)-]?\d{8}  

英文括号 ( 和 ) 也是元字符,后面的分组节里会提到,所以在这里需要使用转义。

这个表达式可以匹配几种格式的电话号码,像 (010)88886666,或 022-22334455,或 02912345678 等。我们对它进行一些分析吧:
首先是一个转义字符 (,它能出现 0 次或 1 次(?),然后是一个 0,后面跟着 2 个数字 (\d{2}),然后是)或 – 或 空格 中的一个,它出现 1 次或不出现(?),最后是 8 个数(\d{8})。

分支条件

正则1|正则2:从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

分组

我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。

(\d{1,3}.){3}\d{1,3} 是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3} 匹配 1 到 3 位的数字,(\d{1,3}.){3} 匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复 3 次,最后再加上一个一到三位的数字 (\d{1,3})。

不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:

((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)  

理解这个表达式的关键是理解 2[0-4]\d|25[0-5]|[01]?\d\d?,具体含义参看后面的注释章节

反义

有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义:

代码/语法 说明
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了 x 以外的任意字符
[^aeiou] 匹配除了 aeiou 这几个字母以外的任意字符

例子:
\S+ 匹配不包含空白符的字符串。
\<a[^>]+> 匹配用尖括号括起来的以 a 开头的字符串。

后向引用

使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。

呃……其实,组号分配还不像我刚说得那么简单:
分组0对应整个正则表达式
实际上组号分配过程是要从左向右扫描两遍的:第一遍只给未命名组分配,第二遍只给命名组分配--因此所有命名组的组号都大于未命名的组号
你可以使用(?:exp)这样的语法来剥夺一个分组对组号分配的参与权.

后向引用用于重复搜索前面某个分组匹配的文本。例如,\1 代表分组 1 匹配的文本。难以理解?请看示例:

\b(\w+)\b\s+\1\b 可以用来匹配重复的单词,像 go go, 或者 kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字 (\b(\w+)\b),这个单词会被捕获到编号为 1 的分组中,然后是 1 个或几个空白符 (\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)。

你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?\\w+) (或者把尖括号换成 ‘ 也行:(?’Word’\w+)),这样就把 \w+ 的组名指定为 Word了。要反向引用这个分组捕获的内容,你可以使用 \k\,所以上一个例子也可以写成这样:\b(?\\w+)\b\s+\k\\b。

使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:

常用分组语法

分类 代码/语法 说明
捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
(?< name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp)
(?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言 (?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

我们已经讨论了前两种语法。第三个 (?:exp) 不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面,也不会拥有组号。“我为什么会想要这样做?”——好问题,你觉得为什么呢?

零宽断言

接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像 \b , ^ , $ 那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。最好还是拿例子来说明吧:

断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。

(?=exp) 也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式 exp。比如 \b\w+(?=ing\b),匹配以 ing 结尾的单词的前面部分(除了ing以外的部分),如查找 I’m singing while you’re dancing. 时,它会匹配 sing 和 danc。

(?<=exp) 也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如 (?<=\bre)\w+\b 会匹配以 re 开头的单词的后半部分(除了re以外的部分),例如在查找 reading a book 时,它匹配 ading。

假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})+\b,用它对 1234567890 进行查找时结果是 234567890。

下面这个例子同时使用了这两种断言:

(?<=\s)\d+(?=\s)

匹配以空白符间隔的数字 (再次强调,不包括这些空白符)。

负向零宽断言

前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词–它里面出现了字母 q,但是 q 后面跟的不是字母 u,我们可以尝试这样:

\b\w*q[^u]\w*\b 匹配包含后面不是字母 u 的字母 q 的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像 Iraq,Benq,这个表达式就会出错。这是因为 [^u] 总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的 [^u] 将会匹配 q 后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的 \w*\b 将会匹配下一个单词,于是 \b\w*q[^u]\w*\b 就能匹配整个 Iraq fighting。负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b。

零宽度负预测先行断言 (?!exp),断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d) 匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。

同理,我们可以用 (?<!exp),零宽度负回顾后发断言来断言此位置的前面不能匹配表达式 exp:(?<![a-z])\d{7} 匹配前面不是小写字母的七位数字。

请详细分析表达式 (?<=<(\w+)>).*(?=<\/\1>),这个表达式最能表现零宽断言的真正用途。

一个更复杂的例子:
(?<=<(\w+)>).*(?=<\/\1>) 匹配不包含属性的简单 HTML 标签内里的内容。(?<=<(\w+)>) 指定了这样的前缀:被尖括号括起来的单词(比如可能是 < xxx> ),然后是 “.*” (任意的字符串),最后是一个后缀 (?=<\/\1>)。注意后缀里的 \/,它用到了前面提过的字符转义;\1 则是一个反向引用,引用的正是捕获的第一组,前面的 (\w+) 匹配的内容,这样如果前缀实际上是 < xxx> 的话,后缀就是 < /xxx> 了。整个表达式匹配的是 < xxx> 和 < /xxx> 之间的内容(再次提醒,不包括前缀和后缀本身)。

注释

小括号的另一种用途是通过语法 (?#comment) 来包含注释。例如:
2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)

要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以前面的一个表达式写成这样:

(?<=    # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签)
)       # 前缀结束
.*      # 匹配任意文本
(?=     # 断言要匹配的文本的后缀
<\/\1>  # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
)       # 后缀结束

贪婪与懒惰匹配

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以 a 开始,以 b 结束的字符串。如果用它来搜索 aabab 的话,它会匹配整个字符串 aabab。这被称为贪婪匹配。

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号 ? 。这样 .*? 就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:

a.*?b 匹配最短的,以 a 开始,以 b 结束的字符串。如果把它应用于 aabab的话,它会匹配 aab(第一到第三个字符)和 ab(第四到第五个字符)。

为什么第一个匹配是 aab(第一到第三个字符)而不是 ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。

懒惰限定符

代码/语法 说明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

处理选项

正则支持设置匹配的选项,比如忽略大小写,忽略空白,单行模式,多行模式等

其他语法

代码/语法 说明
\a 报警字符(打印它的效果是电脑嘀一声)
\b 通常是单词分界位置,但如果在字符类里使用代表退格
\t 制表符,Tab
\r 回车
\v 竖向制表符
\f 换页符
\n 换行符
\e Escape
\0nn ASCII代码中八进制代码为nn的字符
\xnn ASCII代码中十六进制代码为nn的字符
\unnnn Unicode代码中十六进制代码为nnnn的字符
\cN ASCII控制字符。比如\cC代表Ctrl+C
\A 字符串开头(类似^,但不受处理多行选项的影响)
\Z 字符串结尾或行尾(不受处理多行选项的影响)
\z 字符串结尾(类似$,但不受处理多行选项的影响)
\G 当前搜索的开头
\p{name} Unicode中命名为name的字符类,例如\p{IsGreek}
(?>exp) 贪婪子表达式
(?< x>-< y>exp) 平衡组
(?im-nsx:exp) 在子表达式exp中改变处理选项
(?im-nsx) 为表达式后面的部分改变处理选项
(?(exp)yes/no) 把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no
(?(exp)yes) 同上,只是使用空表达式作为no
(?(name)yes/no) 如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no
(?(name)yes) 同上,只是使用空表达式作为no

在线测试

以上这么多知识,边学边练是很有必要的。这里推荐两个有趣的在线网站:
http://rubular.com/
https://www.debuggex.com/

参考

http://blog.jobbole.com/96708/

通过源码将git升级到最新版

因为go-get下载hugo失败,需要升级git到最新版本。

下载最新git代码

wget -O git-master.zip https://github.com/git/git/archive/master.zip

编译

unzip git-master.zip
cd git-master

yum install openssl-devel curl-devel expat-devel perl-ExtUtils-MakeMaker gettext gettext-libs gettext-devel asciidoc xmlto docbook2X

ln -s /usr/bin/db2x_docbook2texi /usr/bin/docbook2x-texi

错误

libgit.a(utf8.o): In function `reencode_string_iconv':
/usr/local/git/utf8.c:463: undefined reference to `libiconv'
libgit.a(utf8.o): In function `reencode_string_len':
/usr/local/git/utf8.c:502: undefined reference to `libiconv_open'
/usr/local/git/utf8.c:521: undefined reference to `libiconv_close'
/usr/local/git/utf8.c:515: undefined reference to `libiconv_open'
collect2: ld returned 1 exit status
make: *** [git-credential-store] Error 1

从错误提示看,是缺少libiconv。

安装libiconv

  1. 下载编译

    cd /usr/local
    wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz
    tar -zxvf libiconv-1.14.tar.gz
    cd libiconv-1.14
    ./configure –prefix=/usr/local/libiconv && make && make install

  2. 创建一个软链接到/usr/lib

    ln -s /usr/local/lib/libiconv.so /usr/lib
    ln -s /usr/local/lib/libiconv.so.2 /usr/lib

  3. 然后回到git目录继续编译

    cd /usr/local/git
    make configure
    ./configure –prefix=/usr/local –with-iconv=/usr/local/libiconv
    make
    make install

  4. 此时,git应该编译安装成功了,测试如下:

    $ git –version
    git version 2.5.0

centos6下go get卡停问题

go get可以通过选项,直接下载所有依赖项,非常方便。所以在安装hugo时,我们也通过go get 下载源码:

go get -u -v github.com/spf13/hugo 

下载时突然卡住,没有任何反应和提示,日志停留在:

Fetching https://gopkg.in/fsnotify.v1?go-get=1
Parsing meta tags from https://gopkg.in/fsnotify.v1?go-get=1 (status code 200)
get "gopkg.in/fsnotify.v1": found meta tag main.metaImport{Prefix:"gopkg.in/fsnotify.v1", VCS:"git", RepoRoot:"https://gopkg.in/fsnotify.v1"} at https://gopkg.in/fsnotify.v1?go-get=1
gopkg.in/fsnotify.v1 (download) 

根据缺失的库逐一排查,后来根据缺失 gopkg.in/yaml.v1 为线索,网上说是原因git版本太低,需>= 1.7.9.5,而Centos6.7 自带的git 是1.7.1的。

升级到git最新版后,go get 成功!

一步一步教你用hugo搭建博客

Hugo 是一个轻量级的静态网站生成工具,是基于GO语言的模版技术开发而成,因为最近在学习go,就花了时间研究了下,一研究就喜欢上了。
再加上最新wordpress版本有严重的问题,在文章发表后或者再次编辑时,编辑框会丢失所有的格式,这个让使用Markdown的人无法接受。

安装hugo

Hugo官方主页:HUGO
hugo托管在github上,我们可以直接二进制安装也可以源码安装。
这里我们演示源码安装。

  1. 源码安装
    在go里面我们可以直接通过get安装:
    go get -u -v github.com/spf13/hugo
    或者直接git下载
    git clone https://github.com/spf13/hugo.git

  2. 编译
    go build -o hugo main.go
    mv hugo $GOPATH/bin
    终端查看是否成功,mac下可能出现路径没找到的问题,要重新开终端
    $ hugo version
    Hugo Static Site Generator v0.16-DEV BuildDate: 2015-12-14T16:07:24+08:00

生成静态站点

  1. 创建网站
    我们先创建一个空网站.
    $ hugo new site localhost
    $ tree
    .
    ├── archetypes
    ├── config.toml
    ├── content
    ├── data
    ├── layouts
    ├── public
    ├── static
    └── themes  

默认情况下这些目录都是空的,直接运行的话会有ERROR提示

    ERROR: 2015/12/14   =============================================================
    ERROR: 2015/12/14 Your rendered home page is blank: /index.html is zero-length
    ERROR: 2015/12/14  * Did you specify a theme on the command-line or in your
    ERROR: 2015/12/14    "config.toml" file?  (Current theme: "")
    ERROR: 2015/12/14  * For more debugging information, run "hugo -v"
    ERROR: 2015/12/14 =============================================================

看提示说是没有指定theme导致,我们需要下载一个theme。

  1. 安装theme
    我们可以从hugo的网站下载自己喜欢的theme
    $ cd themes
    $ git clone https://github.com/spf13/hyde.git

  2. 测试框架
    安装完theme后,我们体验下效果,使用 hugo server就可以起一个http server,默认监听在1313端口,如果没有在config中配置theme,就要指定theme。

    $ hugo server -t hyde
    0 draft content
    0 future content
    0 pages created
    0 paginator pages created
    0 tags created
    0 topics created
    in 24 ms
    Watching for changes in /Users/alex/run/localhost/{data,content,layouts,static,themes}
    Serving pages from memory
    Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
    Press Ctrl+C to stop            

hyde 界面

  1. 发表文章
    hugo里面写文章其实就是写markdown文档了。写好文档,hugo会给你自动转成html静态文件。我们通过Hugo创建一个md文档。
    $ hugo new first.md
    /Users/alex/run/localhost/content/first.md created
    运行时在网站根目录下运行,创建的文件默认创建在content目录下。
    +++
    date = "2015-12-15T22:35:22+08:00"
    draft = true
    title = "first"
    
    +++

我们从内容看默认创建的是草稿类型,需要将draft改为true才能看到页面。正常情况下我们会通过Mou或者github编辑文档,只要文件头符合hugo的规范就可以。
第一篇文章

调试部署

  1. 调试
    在开发的过程中,我们需要不断的修改验证,所以hugo支持LiveReload功能,用户修改后,可以实时看到效果。执行hugo server命令时加上-w选项,hugo就可以自动检测本地站点文件的变更。
    $ hugo server -w -t hyde
    注意:在使用server命令时,hugo并没有在public目录下产生相应的静态页面。

  2. 部署
    部署时,我们需要生成静态页面文件,然后就可以随便部署在自己的空间上了。转化时,一个hugo命令就搞定:

    $ hugo -t hyde
    0 draft content
    0 future content
    1 pages created
    0 paginator pages created
    0 topics created
    0 tags created
    in 38 ms

我们看到有一个页面生成了,默认在public目录,实际一起生成的还有其他文件:

    $ ls
    404.html                first
    apple-touch-icon-144-precomposed.png    index.html
    css                 index.xml
    favicon.png             sitemap.xml

把这些文件放到你的空间,你就可以看见你的页面和theme了。

到这里,我们已经有了一个基本的能创建文章并且显示的网站了。

have fun!

记一次移动端frame注入事件

起因

在用手机调试网站效果时,偶尔发现底部出现广告,于是就有了以下的内容。

界面表现


停留一会后自动消失

代码表现

通过提取,我们可以拿到广告出现时和消失后的代码。
出现广告时页面被插入的frame代码
<iframe src="http://i.dreamfull.cn/api/my.jsp?sid=320418129&amp;pn=_QWERJAD_274192119_320418129_1_&amp;sd=api.dreamfull.cn#_maerd_dnegel_=1" style="display: none; border: 0px; width: 0px; height: 0px;"></iframe><div style="display: block; visibility: visible; overflow: hidden; width: 375px; height: 56.25px; margin: 0px; padding: 0px; border: 0px; box-sizing: border-box; z-index: 2147483647; position: fixed; bottom: 0px; left: 0px;"><div style="position: relative; z-index: 0;"><a style="width: 100%; height: 59px; display: none; text-decoration: none; -webkit-box-align: center; color: rgb(0, 0, 0); overflow: hidden; line-height: 59px; font-size: 10px;"></a><script src="http://c3.moogos.com/js/_jssdk.js?aid=s4b7b3f9" type="text/javascript" async="async"></script><div style="left: 0px; font-size: 0px; z-index: 2147483583; position: fixed; bottom: 0px; display: block; width: 100%; height: 59px;" csstext="display:block;left:0;font-size:0;z-index:2147483583;position:fixed;bottom:0;display:block;width:100%;height:59px;"><iframe style="border: 1px; bottom: 0px; display: block; width: 375px; height: 56.25px;" frameborder="0" scrolling="no" border="0" src="http://api.moogos.com/js/index.html?_ts=1450470254933&amp;info=%7B%22domain%22:%22www.goodmemory.cc%22,%22urls%22:%22www.goodmemory.cc/%25E9%2580%259A%25E8%25BF%2587event-hook%25E5%25B0%2586github%25E8%2587%25AA%25E5%258A%25A8%25E9%2583%25A8%25E7%25BD%25B2%25E8%2587%25B3hugo%25E7%25BD%2591%25E7%25AB%2599/%22,%22adslot%22:%22s4b7b3f9%22,%22version%22:%7B%22major%22:2,%22minor%22:26%7D,%22prod%22:1,%22adParentId%22:%22moogos_s4b7b3f9%22,%22inittime%22:1450470254923,%22duration%22:10,%22appId%22:%2202eddf5b%22%7D"></iframe><div style="position:absolute; width:30px; height:30px;top:0;right:0;z-index:2147483584;background:rgba(255,255,255,0);" onclick="var p = this.parentNode;p.parentNode.removeChild(p);p.setAttribute(&quot;close&quot;, &quot;0&quot;);var b = document.getElementById(&quot;blankDivs4b7b3f9&quot;);b &amp;&amp; b.parentNode.removeChild(b);"><i style="position:absolute; width:1px; height:18px; background:#000; top:6px;left:6px; -ms-transform:rotate(45deg); -webkit-transform:rotate(45deg); -moz-transform:rotate(45deg); transform:rotate(45deg); left:15px;"></i><i style="position:absolute; width:1px; height:18px; background:#000; top:6px;left:6px; -ms-transform:rotate(-45deg); -webkit-transform:rotate(-45deg); -moz-transform:rotate(-45deg); transform:rotate(-45deg); left:15px;"></i><i style="position:absolute; width:24px; height:24px; top:3px; left:3px; background:rgba(255,255,255,0.4); border-radius:50%;"></i></div></div><div style="position: absolute; top: -20px; right: -10px; z-index: 99; width: 50px; height: 60px; overflow: hidden; display: block; left: auto !important; background: url(&quot;http://api.dreamfull.cn/s/images/none.png&quot;) repeat transparent;"></div></div><div style="position: absolute; cursor: pointer; bottom: 0px; right: 0px; z-index: 100; width: 35px; height: 20px; overflow: hidden; display: block; left: auto !important; background: url(&quot;http://api.dreamfull.cn/s/images/logo_mini.gif&quot;) repeat transparent;"></div><div style="position: absolute; cursor: pointer; top: 0px; right: 0px; z-index: 100; width: 35px; height: 20px; overflow: hidden; display: block; left: auto !important; background: url(&quot;http://api.dreamfull.cn/s/images/none.png&quot;) repeat transparent;"></div><div style="position: absolute; cursor: pointer; top: 0px; right: 0px; z-index: 100; display: none; width: 86px; height: 20px; overflow: hidden; left: auto !important; background: url(&quot;http://api.dreamfull.cn/s/images/close_long.gif&quot;) repeat transparent;"></div></div>
广告自动消失后的代码变成:
<img src="http://rcv.moogos.com/rtsdk?type=show&amp;version=2.26&amp;urls=www.goodmemory.cc/%E9%80%9A%E8%BF%87event-hook%E5%B0%86github%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2%E8%87%B3hugo%E7%BD%91%E7%AB%99/&amp;adslot=s4b7b3f9&amp;_ts=1450467804078" style="display:none;">

分析

通过以上的信息,我们得到如下信息:
* http://dreamfull.cn 是广告平台方,根据域名信息查询:


* 广告投放方为http://moogos.com
* 广告代码样本
https://hiproz.github.io/goodmemory.cc/blog/images/2015/12/frame1.txt
https://hiproz.github.io/goodmemory.cc/blog/images/2015/12/frame2.txt
* 其中的js脚本:http://c3.moogos.com/js/_jssdk.js?aid=s4b7b3f9,搜索这个脚本中的”.com”和”.png”能看到更多细节。
为了避免被清理,做了备份,方便后面举证:
https://hiproz.github.io/goodmemory.cc/blog/images/2015/12/jssdk.js-bak
* 搜索了以下,有很多dreamfull.cn的案例,多和联通有关:

安全

因为注入的原理不是在服务器修改源代码的,可能是在运营商的路由环节,或者我们使用的所谓智能路由器,或者第三方动态加载时被劫持,所以很难从根源上消除,目前能想到的就是先把https做了,还有就是加载安全js插件,用户加载时动态触发检测,不过这个只是计划,目前本人能时间上和能力还做不到。

最后

以上做了这么多细致的工作,是为了拿到更多的证据,方便更多的人去投诉和举报,创造健康的网络环境。

通过event-hook将Github自动部署至Hugo网站

WHAT

hugo是一个轻量高效的博客系统,很适合个人博客。使用hugo,我们只要写作完markdown文档,就可以利用hugo工具,自动生成网页,
变成我们的网站。

我们理想的步骤:

  • 在github上写完markdown文章
  • 提交完后,数秒后就看见了我们的网站页面
  • 在github上修改完网站的配置文件,数秒后我们的网站就变化和更新了。

好爽!

流程

为了完成上面的效果,我们大概分为几步:

  1. 设置github的webservice hook。当完成一篇新的文章或者修改旧的文章后,github就会向目标网站发webservice hook消息。
  2. 目标网站收到消息后git pull,解析消息特征,更新相关的文档,最后调用hugo
  3. hugo将markdown文章转化成html静态页面
  4. 将html页面部署到目标web服务器

HOW

详细请移步:
https://github.com/hiproz/hugo-sync

分分钟就搞定了,一劳永逸!

have fun!