hugo是一个静态网站生成器, 类似hexo

本文假设读者已经了解静态页面的含义, 也接触过Jekyll, octopress或hexo.

目录

简述

与hexo类似, hugo提供了命令行工具

hugo  # 编译生成静态文件
hugo help # 获取帮助信息
hugo new site # 初始化网站
hugo new post/*** # 创建文章
hugo server # 启动服务器

在万事俱备后, 添加新博文的组合拳基本上是

hugo new post/blog_name.md  # 新建博文到content/post/目录下
hugo server -D              # 启动服务器预览
hugo                        # 编译

然后将更新后的public目录上传到pages或服务器上

第一次配置

安装

在windows下, 可以借助choco

choco install hugo -confirm

在linux下,

go get -v github.com/gohugoio/hugo

初始化网站

假设博客目录是/data/blog

cd /data/
hugo new site blog
cd blog

以下操作默认都在/data/blog目录下

安装主题

装好hugo后, 必须安装主题, 才能开始编译
下方任选一个即可

git clone https://github.com/kakawait/hugo-tranquilpeak-theme.git themes/tranquilpeak
git clone https://github.com/vjeantet/hugo-theme-docdock.git themes/docdock

配置主题

以tranquilpeak为例, 拷贝参考配置文件到博客根目录下

cp themes/tranquilpeak/exampleSite/config.toml .

然后编辑该配置文件, 将博客名称, 作者等信息改成自己

如果配置文件里有themeDir配置, 记得删除
另外, 配置文件中theme这项要改为主题名称, 比如theme = "tranquilpeak" (其实是主题所在目录的名称, 如果你是在themes目录下直接执行git clone而没有指定存放目录的话, 就应该是hugo-tranquikpeak-theme)

测试配置效果

hugo new post/first.md
echo "## Content for Test" >> content/post/first.md
hugo server -D

打开浏览器localhost:1313 查看效果

编辑博文

hugo new post/第二篇博文啦.md

默认此命令生成的文件, 头部有3个metadata

  • title
  • date
  • draft

对应的就是文章标题, 生成日期和是否草稿啦. 草稿文章只有hugo server -D指令才会编译和显示

可以编辑archetypes/default.md修改新文章模板 比如添加

categories: [],
tags: []

这样就更容易加标签和分类了

部署

编译生成的静态文件存放在public目录下, 有两种选择将其部署到网上

  • 利用代码托管网站的pages服务
  • 将其放入web服务器资源文件目录下

利用pages服务对读者来说应该是轻车熟路了, 下面讲自行维护web服务的情况. 当然前提是你有一台自己的vps或服务器

计划是这样的

  • 本地机器不参与编译环节, 也不维护public目录, 只维护content目录和一些配置文件, 比如config.toml, archtypes/default.md等
  • 本地编辑源文件后上传到服务器
  • 服务器自动编译, 将public拷贝到web服务器对应目录下, 或者hugo指定静态文件存放目录到web服务器对应目录下

上面提到的是常规做法, 实际上hugo集成了web服务器, 因此可以无需借助nginx或apache.

这里直接贴上守护程序的配置, 应该看得出做啥的吧

[program:hugo]
command=hugo server --baseURL=http://blogDomain --port=80 --bind=0.0.0.0  --disableLiveReload
directory=/data/blog
autostart=true
autorestart=true
user=root
log_stderr=true
redirect_stderr=true
stdout_logfile=/var/log/hugo.log

[program:hugo_draft]
command=hugo server -wD dev --baseURL=http://blogDomain_draft --port=8080 --bind=0.0.0.0
directory=/data/blog
autostart=true
autorestart=true
user=root
log_stderr=true
redirect_stderr=true
stdout_logfile=/var/log/hugo_draft.log

draft域名无需公网解析, 本地hosts即可

配合编辑器的sftp插件, 简直美滋滋

其实既然是本地编辑上传, 就无需维持一个draft状态和对应的服务器了. 只要上传动作是手动的, 那么默认上传的内容都是可以发表的即可.

使用nginx

当然, 开启一个hugo占用了80端口, 如果服务器还部署了其他服务, 就不能采用这种方式, 而是直接使用nginx代理public目录

比如nginx配置

server {
    server_name blog.grunmin.tech;
    listen 443 http2 ssl;
    ssl on; 
    ssl_certificate /path/to/cer;
    ssl_certificate_key /path/to/key;

    root /data/blog/public; 
}

那么, 要实现上传文件时实时更新public目录怎么办?

可以借助这个工具Watch来实现文件变更时自动编译

go get github.com/eaburns/watch
cd /data/blog
${GOPATH}/bin/watch -x public/ -t ${GOPATH}/bin/hugo

然后再将这条命令加入守护即可

从hexo中迁移

从hexo迁移到hugo有几点需要变更

  1. hexo的md文件使用yaml格式, hugo则是toml
  2. hexo中date字段不带时区, hugo带时区
  3. hugo中的隔断符严格要求是<!--more-->, hexo中的隔段符则可能是<!-- more -->

whatever, 下面附上迁移脚本, 改编自从 Hexo 迁移到 Hugo

// yaml_to_toml.js
const readline = require('readline');
const fs = require('fs');
const os = require('os');
const moment = require('moment-timezone');  // 需要通过 npm install moment-timezone 来安装


const timezone = 'Asia/Shanghai';  // 时区
const src = 'y';  // hexo .md 文件源目录
const target = 't';  // 目标文件目录


// 开始转换
readDir();


// 遍历目录
function readDir() {
    // read all files in src
    fs.readdir(src, function(err, files) {
        files.map((filename) => {
            // get the file extension
            const extension = filename.substr(filename.lastIndexOf('.', filename.length));
            if (extension === '.md') {
              readFile(`${filename}`);
            }
        });
    });

}


function readFile(filename) {
  fs.readFile(`${src}/${filename}`, { encoding: 'utf8' }, function(err, data) {
      if (err) {
          return console.log('err: ', err);
      }

      const content = data.split('---');
      let headContent = null;
      let bodyContent = null;
      if(content[0].length){
        headContent = content[0]
        bodyContent = content.slice(2).join('---')
      } else {
        headContent = content[1]
        bodyContent = content.slice(2).join('---')
      }
      const head = headContent.split('\n');
      // console.log('head: ', head);

      let newHead = head.map((item, index) => {
        // console.log('slpitHead: ', slpitHead(item, index, head));
        return slpitHead(item, index, head);
      });
      newHead = newHead.filter((item) => {return item;});
      const newBody = bodyContent.split('\n').map(item => replaceSummaryTag(item)).join(os.EOL)
      // console.log('newHead: ', newHead);
      const newContent = `---${os.EOL}${newHead.join(os.EOL)}${os.EOL}${os.EOL}---${os.EOL}${newBody}`;
      const newFileName = filename.split('-').slice(3).join('-')
      fs.writeFile(`${target}/${newFileName}`, newContent, {
          encoding: 'utf8'
      }, function(err) {
          if (err) {
            throw err;
          }
          console.log(`${newFileName}  生成成功!`);
      });
  });
}

function replaceSummaryTag(item){
  if (item.indexOf('<!-- more -->') === 0){
    return '<!--more-->'
  }
  return item
}

function slpitHead(item, index, head) {
  // title
  if (item.indexOf('title:') !== -1) {
    if(item.split('"').length === 3){
      return `title: ${item.split('title:')[1].trim()}`;
    }
    return `title: "${item.split('title:')[1].trim()}"`;
  }

  // date
  if (item.indexOf('date:') !== -1) {
    return `date: ${(moment.tz(item.split('date:')[1], timezone)).format()}`;
  }

  // tags
  if (item.indexOf('tags:') !== -1) {
    // console.log('tags...');
    const tags = [];
    if(item.length >5){
      const c = item.split(':')[1].split('').filter(o=>o !== ' ').join('');
      if(c){
        tags.push(item.split(':')[1].split('').filter(o=>o !== ' ').join(''))
      }
    }
    for (let i=index+1; i<head.length; i++) {
      if (head[i].indexOf('-') !== -1) {
        // console.log('head[i].split('-')[1]: ', head[i].split('-')[1]);
        tags.push(head[i].split('-')[1].trim());
      } else {
        break;
      }
    }
    // console.log('tags: ', tags);
    return `tags: ${JSON.stringify(tags)}`;
  }

  // categories
  if (item.indexOf('categories:') !== -1) {
    const categories = [];
    if(item.length >11){
      const c = item.split(':')[1].split('').filter(o=>o !== ' ').join('');
      if(c){
        categories.push(item.split(':')[1].split('').filter(o=>o !== ' ').join(''))
      }
    }
    for (let i=index+1; i<head.length; i++) {
      if (head[i].indexOf('-') !== -1) {
        categories.push(head[i].split('-')[1].trim());
      } else {
        break;
      }
    }
    // console.log('categories: ', categories);
    return `categories: ${JSON.stringify(categories)}`;
  }

  return false;
}

其他问题

备份

如果选择了pages托管, 那么只需记得将源码也上传一份即可, 如果是放在自己服务器, 可以跑一个定时任务, 如果当天有变更, 则通过git push到某个仓库进行备份

比如在博客目录下添加一个备份脚本backup.sh, 内容是

git add .
git commit -m "update in `date +'%Y-%m-%d %H:%M:%S'`"
git push origin master

然后添加一个计划任务

0 0 * * * cd /data/blog && sh backup.sh

不过, 如果是通过git的方式备份, 添加的主题仓库必须作为submodule, 如果不是则要先删除, 等初始化后重新下载

git submodule add https://github.com/kakawait/hugo-tranquilpeak-theme.git themes/tranquilpeak

语法高亮

语法高亮与主题的关系大一些, 以tranquilpeak主题为例. 该主题默认使用highlight.js做语法高亮. highlight.js脚本只包含一部分语法高亮的规则, 某些语言被独立出去了, 比如go, 就必须额外导入go.min.js文件, 完整文件路径像这样

https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/languages/go.min.js

有几种方式可以导入该文件, 第一是修改该主题下目录下的layout/partials/script.html文件, 第二是拷贝一份修改版到博客根目录下的layout目录. 不过tranquilpeak提供了一个配置参数, 可以很方便的导入该文件.

方法就是修改config.toml文件, 找到[[params.customJS]]标签, 在下方添加如下一行即可

customJS = ["https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/languages/go.min.js"]