此前我用的是 Z-blog 系统,这个系统上手简单,不过仅支持在网页在线编写文章。这种方式弊端是比较死板,如果将来需要把文章移动到别的地方会很麻烦,而如果是用 MarkDown 来写文章就不会有这个麻烦了。本文就详细记录这次博客系统迁移的细节,包含 Linux 系统配置、Nginx 的配置、hexo 的配置和 deploy 脚本等内容。

首先是,这次又把 VPS 的 Server 硬盘重置了,每次重置后都需要重新设置配置一遍 Linux 系统(我的 VPS 是 Ubuntu 16.04),所以这次记录一下配置的过程,也就是各种配置命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
adduser liutao # add user, with root user
usermod -aG sudo liutao # add sudo permission, with root user
su - liutao # switch to user liutao

# update repository info
sudo apt-get update
sudo apt-get upgrade

# configure shadowsocks
sudo apt install python-pip
sudo pip install shadowsocks
sudo mkdir /etc/shadowsocks

# create config file
sudo vim /etc/shadowsocks/ss.json
{
"server":"0.0.0.0",
"server_port":22222,
"local_port":1080,
"password":"your pass word",
"timeout":1000,
"method":"aes-256-cfb"
}

# add service configuration info,.
sudo vim /etc/systemd/system/shadowsocks.service
[Unit]
Description=Shadowsocks Server
After=network.target

[Service]
ExecStart=/usr/local/bin/ssserver -c /etc/shadowsocks/ss.json
Restart=on-abort

[Install]
WantedBy=multi-user.target

# start shadowsocks service
sudo systemctl start shadowsocks

# start shadowsocks on startup
sudo systemctl enable shadowsocks

# install linuxbrew
sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)"

# install zsh
sudo apt-get install zsh

# install oh my zsh, this dependends on zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
vim .zshrc

# install Nginx, MySQL, Php
wget http://soft.vpser.net/lnmp/lnmp1.5-full.tar.gz
gunzip lnmp1.5-full.tar.gz
tar xf lnmp1.5-full.tar
cd lnmp1.5-full
sudo ./install.sh lnmp

# install word-press
wget https://wordpress.org/latest.zip
unzip latest.zip -d wordpress

# set root to be/home/liutao/wordpress
sudo vim /usr/local/nginx/conf/nginx.conf
sudo nginx -s reload

# 然后本地浏览器打开 https://tao93.top/ 开始设置 WordPress

# create database named wordpress
mysql -u root -p
> create database wordpress

# 然后在浏览器中填入刚刚的 database 名字,及其他信息
# 在 server 中的 WordPress 根目录中创建 wp-config.php 并填入浏览器页面显示的内容

# 找到 nginx 使用的 config 文件
sudo nginx -t

# 为了解决一些权限问题,www 是 nginx 配置文件中声明的 web server 的 user name
# 这样就把 group 改为了 www,并且 group 用户都有 write 的权限
sudo chgrp -R www <root_dir_of_wordpress>
sudo chmod -R g+w <root_dir_of_wordpress>

# 另外还在 <root_dir_of_wordpress> 中的 wp-config.php 文件中插入了 define('FS_METHOD','direct'); 这样一行,不知道是不是必要的

以上就是最终配置到 WordPress 的过程。之所以配置 WordPress,是因为我查到 WordPress 可以使用 Markdown,另外 WordPress 自带评论功能,插件和主题也非常丰富,所以就先使用 WordPress。结果发现 WordPress 的 Markdown 还需要安装插件才能支持,另外支持也不是很好,比如代码块会把比较长的行转行显示(即不是可以左右滑动),代码行号与高亮都没要。

所以,我还是痛下决心,换回到 hexo。hexo 比较简单,本地安装 hexo 后,在空目录中执行 hexo init 即可生成一堆相关文件,其中的 public 用来存放从当前 theme 和 markdown 文件生成的 static web 文件;而 source 目录用来存放 markdown 文件。简单使用步骤:

1
2
3
4
5
6
# in hexo root directory
hexo g # generate static web files

hexo server # start local web server, then browse http://localhost:4000 to preview web pages

# copy files in public to server's directory, therefore updated pages can be browsed at https://tao93.top/

以上就是简单的用法,不过实际上我做了一些额外工作。例如为了避免 markdown 文件意外丢失,我们需要把它们备份到 server 上,server 和 local 同时丢失的可能性极小。另外,markdown 中插入图片时,可以先把图片放在本地,然后 markdown 中指向图片的本地路径,这样生成的 web 文件会把图片拷贝到 public 目录中某处,然后 html 文件引用 public 目录中的 image,这时的引用其实就是一个 url 了。但是我不是这样做的,我是先把要插入的图片拷贝到 Server 的 public 目录某处,这样就会得到一个指向次图片的 url,然后再 markdown 中直接使用这个 url。这样的话,只要 server 的图片库保持稳定,markdown 文件就不用和本地图片库绑定在一起。同样的,server 的图片库也需要备份到 local,以确保安全。下面就开始介绍以上两点。

首先是本地 source 目录需要备份到 server 上,我的方式是本地 source 目录使用 git 来追踪,然后 server 上简历一个 bare repository source.git,这样本地的每次 commit 都 push 到 server 上的 source.git,就可以作为备份了。相应的本地的 public 目录也要 push 到 server 上的 public.git 这样一个 bare repository,然后在 server 上的 public 中,从 public.git 这个 remote git pull 一下,这样 server 中的 public 就有了最新的 web 文件。

接下来是图片上传到 server 中,为了方便,我写了一个 Python 脚本,用来把本地图片上传到 server,或者把剪贴板的图片 (便于复制图片的情景) 上传到 server,然后将对应的 url 粘贴到剪贴板,这样的话,只需要执行这个 Python 脚本,就可以上传本地图片或者剪贴板上的图片,并把得到的 url 复制到剪贴板,然后就可以直接粘贴到 markdown 中。以下是这个 upload_pic.py 脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from PIL import ImageGrab, Image
import sys
import datetime
import os

now = datetime.datetime.now()

# store image files in date format directory path in server
date_path = now.strftime('%Y/%m/%d')

# use timestamp as image file name in server
f_name = str(int(now.timestamp()))

if len(sys.argv) == 1:
# get picture in clipboard
im = ImageGrab.grabclipboard()
# check whether it's a picture
if isinstance(im, Image.Image):

# file name in server
f_name = str(int(datetime.datetime.now().timestamp())) + '.png'

# save in local
im.save(f_name)

# the url of the image
url = 'http://tao93.top/images/' + date_path + '/' + f_name

# copy the url to clipboard
os.system("echo '%s' | pbcopy" % url)
print('url has been copied')
print('copying...')
dest_path = '/home/liutao/hexo_blog/public/images/' + date_path

# mkdir in server and copy the image
os.system("ssh liutao@tao93.top 'mkdir -p " + dest_path + "'")
os.system('scp ' + f_name + ' liutao@tao93.top:' + dest_path)
print('copy finished')

# remove local image file
os.remove(f_name)
else:
print('clipboard content is not a image!')

elif len(sys.argv) == 2:
if os.path.isfile(sys.argv[1]):
# file name in server
f_name = str(int(datetime.datetime.now().timestamp()))
dot_loc = sys.argv[1].rfind('.')
if dot_loc >= 0:
f_name += sys.argv[1][dot_loc:] # append the file extension

url = 'http://tao93.top/images/' + date_path + '/' + f_name
os.system("echo '%s' | pbcopy" % url)
print('url has been copied')
print('copying...')
dest_path = '/home/liutao/hexo_blog/public/images/' + date_path
os.system("ssh liutao@tao93.top 'mkdir -p " + dest_path + "'")
os.system('scp ' + sys.argv[1] + ' liutao@tao93.top:' + dest_path + '/' + f_name)
print('copy finished')
else:
print(sys.argv[1] + ' is not a file!')
else:
print('too much arguments')

为了简化步骤,我写了一个 deploy.sh 脚本放在本地 hexo blog 根目录中,这样只需要在 source 目录中编辑 markdown 文件,然后 commit,再调用 deploy.sh 脚本即可。此脚本把其他要做的事情都做了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/bin/bash

# into source
cd source

# make sure it's commited in source directory
status=$(git status -s)
if [ -z "$status" ]; then
# git push in source
git push
else
echo 'not commited in source directory!'
exit 1
fi

# back into parent directory
cd ..

# generate static web files
hexo g

# git push in local public directory
cd public
git add .
git commit -m 'update web files'
git push
cd ..

# git pull in server's public directory
ssh liutao@tao93.top 'cd /home/liutao/hexo_blog/public; git pull'

# git commit in server's images directory
ssh liutao@tao93.top 'cd /home/liutao/hexo_blog/public/images; git add .; git commit -m "add images"'

# git pull in local's images directory
cd images
git pull
cd ..

echo '========='
echo 'Finished!'
echo '========='

这样上面的脚本就把生成 web 文件,拷贝 public 中的 web 文件拷贝到 server,把 server 中的 image 目录中的信息更新到 local 的 images 目录这样几个事情。

附:

如果要本地 git repo 没有 commit,remote repo 有 commit,现在需要本地的 master 分支 track remote 的 master 分支:

1
2
3
4
5
6
7
8
# 先添加 origin,设置好 url
git remote add origin liutao@tao93.top:/home/liutao/hexo_blog/public/images/.git

# 然后 fetch 一下 远程的状态
git fetch

# 此时如果 git pull --set-upstream-to 是不行的,会说本地 master 分支不存在,而需要下面这样才行
git checkout master