老姜讲技术

优秀是一种习惯!

--亚里士多德

使用nikola制作静态博客

为什么是静态博客?

折腾博客很久了,尝试过CSDN、博客园等第三方托管,尝试过WordPress自己维护,运维WordPress时也是关闭了Comment功能,其实我只是需要一个写博客的地方,能够记录我的技术研究过程,分享给大家作参考,静态页面完全够用了,这可以让我专注于内容,而不是折腾各种插件、主题等等。

为什么是Nikola?

MarkDown是大家比较熟知的标记语言,简单方便著称,但是MarkDown有多种实现,各自都有不同的扩展,整体扩展能力偏弱,reStructuredText(简称rst)也是一种标记语言,相比于MarkDown有更丰富的表达能力,相比于LaTex使用更加简单,reStructuredText的解析实现Sphinx是Python实现,与一众Python Web框架容易结合。

因此,笔者选择了ReStructuredText作为写作语言。

调研了Nikola,Pelican,Jekyll等静态博客生成器,Nikola优先支持rst,其他功能可以满足基本需求,因此选择了Nikola。 几种静态博客生成器都很优秀,感兴趣的可以自己尝试。

开始上手-安装与初始化

Nikola是基于Python语言实现的,首先创建Python VENV环境,并安装nikola及相关依赖。

官方使用文档

# 创建工程目录
mkdir blog
cd blog

# 创建虚拟环境,并激活
python3 -m venv venv
source venv/bin/activate

# 安装 nikola
pip install nikola
# 安装本地调试服务需要的依赖
pip install aiohttp watchdog

使用nikola命令初始化项目

cd ..
nikola init blog

上述命令会交互式提示输入一些基本信息,包括博客的名称、语言、时区等等。

命令执行成功之后会生成一个conf.py文件,是所有的配置信息,后续需要一些自定义的配置都是这里修改。

修改一些基本配置

一些初始化向导会提示填写的配置,如果需要也可以自己修改

# Data about this site
BLOG_AUTHOR = "姜福泉"  # (translatable)
BLOG_TITLE = "老姜讲技术"  # (translatable)
# This is the main URL for your site. It will be used
# in a prominent link. Don't forget the protocol (http/https)!
SITE_URL = "https://blog.jiangfuquan.com/"
# This is the URL where Nikola's output will be deployed.
# If not set, defaults to SITE_URL
# BASE_URL = "https://blog.jiangfuquan.com/"
BLOG_EMAIL = "jiangfuquan@hotmail.com"
BLOG_DESCRIPTION = "老姜讲技术-技术博客"  # (translatable)

# What is the default language?
DEFAULT_LANG = "zh_cn"

设置导航菜单,archive,categories,tags,是自动生成的,about需要自己创建页面,参考 创建页面

NAVIGATION_LINKS = {
    DEFAULT_LANG: (
        ("/", "首页"),
        ("/archive/", "文章归档"),
        ("/categories/", "分类"),
        ("/tags/", "标签"),
        # ("/rss.xml", "RSS 源"),
        ("/pages/about/", "关于"),

    ),
}

设置archive,categories,tags,页面生成规则

TAG_PATH = "tags"
CATEGORY_PATH = "categories"
CREATE_MONTHLY_ARCHIVE = True
CREATE_ARCHIVE_NAVIGATION = True
ARCHIVE_PATH = "archive"
ARCHIVE_FILENAME = "index.html"

设置license(参考 给你的博客添加CC许可协议 ),footer,logo等: 其中我的footer还加入了友盟统计(cnzz)的统计脚本,可以掌握你的博客的访问量。

LOGO_URL = '/images/logo_small.jpg'

# A HTML fragment describing the license, for the sidebar.
# (translatable)
LICENSE = """
    <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/">
        <img alt="知识共享许可协议" style="border-width:0"
             src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png"/>
    </a>
    <br/>
    本作品采用
    <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/">
        知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议
    </a>
    进行许可。
    <br/>
<!--
    <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/">
        <img alt="Creative Commons License" style="border-width:0"
             src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png"/>
    </a>
    <br/>
-->
    This work is licensed under a
    <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/">
        Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License
    </a>.
"""

# I recommend using the Creative Commons' wizard:
# https://creativecommons.org/choose/
# LICENSE = """
# <a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">
# <img alt="Creative Commons License BY-NC-SA"
# style="border-width:0; margin-bottom:12px;"
# src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png"></a>"""

# A small copyright notice for the page footer (in HTML).
# (translatable)
CONTENT_FOOTER = """
{license}
<br/><br/>
Contents &copy; {date}         <a href="mailto:{email}">{author}</a> - Powered by         <a href="https://getnikola.com" rel="nofollow">Nikola</a>
<script type="text/javascript">document.write(unescape("%3Cspan id='cnzz_stat_icon_1278825800'%3E%3C/span%3E%3Cscript src='https://v1.cnzz.com/z_stat.php%3Fid%3D1278825800%26show%3Dpic1' type='text/javascript'%3E%3C/script%3E"));</script>
"""

# Things that will be passed to CONTENT_FOOTER.format().  This is done
# for translatability, as dicts are not formattable.  Nikola will
# intelligently format the setting properly.
# The setting takes a dict. The keys are languages. The values are
# tuples of tuples of positional arguments and dicts of keyword arguments
# to format().  For example, {'en': (('Hello'), {'target': 'World'})}
# results in CONTENT_FOOTER['en'].format('Hello', target='World').
# If you need to use the literal braces '{' and '}' in your footer text, use
# '{{' and '}}' to escape them (str.format is used)
# WARNING: If you do not use multiple languages with CONTENT_FOOTER, this
#          still needs to be a dict of this format.  (it can be empty if you
#          do not need formatting)
# (translatable)
CONTENT_FOOTER_FORMATS = {
    DEFAULT_LANG: (
        (),
        {
            "email": BLOG_EMAIL,
            "author": BLOG_AUTHOR,
            "date": '2020-%d' % time.gmtime().tm_year,
            "license": LICENSE
        }
    )
}

给博客添加百度搜索框,这里si参数可以起到过滤站点的作用,但是对于我这小博客来说,结果页广告比博客多,通过给关键词加入" site:blog.jiangfuquan.com"居然可以没有广告,所以在提交之前,通过js修改一下搜索词。

SEARCH_FORM = """
<script type="text/javascript">
function baidu_submit()
  {
    let wd = document.getElementById("baidu_wd").value
    let site = " site:blog.jiangfuquan.com"
    if (!wd.endsWith(site)) {
        document.getElementById("baidu_wd").value += site
    }
    document.getElementById("baidu_form").submit();
  }
</script>
<form id="baidu_form" action="http://www.baidu.com/s" method="GET" target="_blank" submit=>
        <!-- img src="http://www.baidu.com/img/baidu_jgylogo3.gif" alt="百度Logo" / -->
        <input id="baidu_wd" type="text" name="wd" size="" />
        <input type="submit" onclick="baidu_submit()" value="百度一下" />
        <input type="hidden" name="ie" value="utf-8" />
        <input type="hidden" name="tn" value="jiangfuquan" />
        <input type="hidden" name="si" value="blog.jiangfuquan.com" />
        <input type="hidden" name="ct" value="2097152" />
    </form>
"""

创建页面

nikola提供好用的命令行,帮你创建页面和博客。

nikola new_page

根据提示输入页面名称即可。

创建博客

同样使用命令行工具:

nikola new_post

根据提示输入名称,可以自己修改一些元数据,如category,tags,title,date等。

本地预览与部署

本地编译输出到output文件夹。

nikola build

本地编译加预览可以用:

nikola auto

线上部署只需要nginx即可,我使用了https复杂一些,证书申请参考 docker下使用letsencrypt申请证书

server {
    listen       80;
    listen       443 ssl;

    # ssl on;

    server_name  blog.jiangfuquan.com;

    #charset koi8-r;
    access_log  /var/log/nginx/blog.access.log  main;
    error_log /var/log/nginx/blog.error.log;

    ssl_certificate /etc/letsencrypt/live/blog.jiangfuquan.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.jiangfuquan.com/privkey.pem;

    ssl_session_timeout 5m;
    ssl_protocols TLSv1.1 TLSv1.2;
    # ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
    ssl_ciphers HIGH:!ADH:!MD5;
    ssl_prefer_server_ciphers on;

    #301转移
    #if ($server_port = 80) {
    #       return 301 https://$server_name$request_uri;
    #}
    if ( $scheme = http ){
        return 301 https://$server_name$request_uri;
    }

    location ^~ /.well-known/acme-challenge/ {
        alias /var/www/challenges/;
        # try_files $uri =404;
    }

    location / {
        root   /var/www/html;
        index index.html index.htm;
        autoindex off;
    }

     error_page  404              /404.html;

     # redirect server error pages to the static page /50x.html
     #
     error_page   500 502 503 504  /50x.html;
     location = /50x.html {
         root   /var/www/html;
     }

     # deny access to .htaccess files, if Apache's document root
     # concurs with nginx's one
     #
     location ~ /\.ht {
         deny  all;
     }
}
version: '3.1'

services:
  nginx:
    image: nginx:1.21.3
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
      - $PWD/nginx/conf:/etc/nginx/conf.d
      - $PWD/nginx/logs:/var/log/nginx
      - $PWD/output:/var/www/html
      - $PWD/cert/challenges:/var/www/challenges
      - $PWD/cert/letsencrypt:/etc/letsencrypt