快速搭建Hugo博客(GitHub+Vercel+Hugo)

想搭建一个Hugo博客有一阵子了,最后按照塔塔的介绍搭建的。https://mantyke.icu/2021/hugo-build-blog/ 略有改动。操作于MacOS平台

基本概念 who is who

Hugo是什么

博客(或者说网页),有两种,静态和动态。本质区别在于是否有后端。我想到的比喻类似于:

静态网页类似于博物馆,不管你(访客)来不来,展品(网页内容)都在那里并且永远一成不变

动态网页类似于餐厅,你(访客)来了,后厨(服务器)才开始根据你的点单,从后厨的冰箱(数据库)拿出材料来制作菜品(网页内容);这个形式也允许你(访客)与后厨(服务器)有更多互动

动态博客的例子是Wordpress,也就是这篇文本所在的博客,静态博客的例子就是Hugo了。Hugo提供了一种简单的博客书写方式,博客的内容大体分为了三部分:

  • 博文内容:博文采用了markdown语法,每篇博客就是一个markdown文件(.md尾缀)
  • 博客设置:诸多设置都在config文件内,包括博客的名字、作者简介、网页宽度、等等等等大量可以个性化(也可以不个性化)的设置
  • 博客主题:预先写好的主题,可以从网上下载放在theme文件夹,在config里更换,轻松换皮

而Hugo这个程序所做的,就是把主题、设置、和内容编制在一起,制作一个网页。对,Hugo所搭建的博客,本质而言就是一个html页面。

GitHub和Vercel又是什么

上文提到,Hugo把主题、设置、内容编制起来制作网页。如果你在本地安装hugo,那么网页就可以在本地浏览了。如果想要分享网页让整个互联网浏览,那么仅仅在本地生成网页是不够的,我们需要一个时时刻刻联网的服务器来储存和提供这个页面。再打个比方:

你发明了一种好吃的蛋糕烤法。你先在家(本地,你的电脑上)试了试:你把面糊(博客内容)倒进模具(博客主题)并设置烘培温度(博客设置),送进烤箱(Hugo),烤箱(Hugo)帮你制作好了蛋糕(网页)。

但是别的朋友没能吃到你的蛋糕。于是,你把面糊+磨具+温度一起送到了一个大仓库(GitHub),又邀请了一个帮手(Vercel)帮你用她的烤箱(Hugo)制作了蛋糕并放在集市(互联网)上。这样一来,人人都能吃到你的蛋糕了。

在此之后,如果你要更改面糊(添加新内容/更改旧内容内容)或者磨具(更换theme)或者温度(更改设置),只需要把新的“面糊+磨具+温度”组合上传到Github,你的帮手Vercel就会用新的配方制作并更新网页了。

所以我们需要一个GitHub库房来储存我们的博客,还需要一个链接在GitHub上的Vercel账号来托管我们的网页。

域名是什么

域名是博客的地址。服务器一般只会有一个IP地址可供访问,但是IP地址很难看又难记,不如用个性化的网址。如果不购买自己的域名,按照这篇文章的步骤进行,Vercel会自动分配一个域名,域名可以事后再改,不用着急先准备好。

域名设置需要两步:

  • 购买一个域名(去Godaddy或者google sites上挑选购买)
  • 告诉全世界,这个域名属于你的博客 (设置DNS的Record,这一步在Vercel里)

准备工作

Step 1.a: 注册Github免费账户

需要非中国大陆邮箱。GitHub现在的注册流程变长了,最后会问是否是企业用户,选择个人免费账户即可。

Step 1.b: 安装Homebrew (OsX平台的环境管理,可以理解为App Store for 程序).

打开terminal (可以cmd+space调出快捷搜索,输入terminal打开)。复制粘贴这个命令并敲击回车运行,这会在你的Mac上安装Homebrew,后续我们会用Homebrew安装Hugo

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

整个过程可能稍微长一点(机器会安装相关组件),虽然也有更快的办法直接安装Hugo,但是我觉得homebrew一旦装好还是很有用的,耐心等一会儿

Step 1.c 安装Hugo

我们用Homebrew安装Hugo,在terminal里输入

brew install hugo

连接本地-GitHub-Vercel

Step 2. 本地-GitHub

首先,我们需要让Github与本地机器建立一个安全的联系。这个链接的办法就是制作SSH key pair。至于什么是SSH key,这篇wiki讲的很好 SSH密钥。简单而言就是:

我们生成一对钥匙:公开public和私密private,私密的钥匙我们在本地私密保存,公开的钥匙交给GitHub。这样将来向Github传输信息就不用每次都登陆了,因为GitHub认证了这台电脑的主人就是你。

  • 制作密钥的具体步骤可以完全一步步复制官网 官网docs:制作密钥
    • 官网也提到,请注意,过程中会询问Enter a file in which to save the key和Enter passphrase,这两步直接按enter跳过。这样一来key会保存在默认位置,方便直接使用后续的代码。不要passphrase,是因为GitHub Desktop目前不支持。

Step 3. Github-Vercel

这一部分完全按照塔塔的攻略里“#搭建博客”这一段进行,写得比我清楚明白多了。小球飞鱼的Hugo教程。教程里没有提及域名设置,我写在了下一步。

这步结束之后你可以登录GitHub网页,发现你的repository里多了一个hugo文件夹。这就是你的博客文件夹了!

如果你输入你的域名,就会发现已经有一个示例网页了!(激动人心)

Step 4. 设置域名 (optional)

没有购买域名可以跳过这步。

Vercel里有一步会询问这个project使用什么域名。可以发现已经默认填好了一个随机的Vercel域名。如果此时你已经有了自己的域名,可以填写自己的域名(比如cronopio.space)。Vercel会告诉你如何把域名交给Vercel托管:一般需要更改DNS server和A record。

这是为啥?你购买域名只是得到了这个网址(比如我的cronopio.space),你需要DNS server不断地帮你把通向cronopio.space的访问正确地指向网站的服务器IP。而现在我们用的是Vercel的服务器,所以需要指向Vercel。这一步没法在Vercel里设置(因为Vercel并不知道你是不是真的拥有这个网址),只能去你的域名管理网站设置。

具体步骤根据你的域名购买方而不同,比如我的域名在Bluehost,我需要在Bluehost进入Settings-Domains-DNS servers,把默认的server改成Vercel提供的两个地址。

如果Vercel提示还需要更改A record,那么我们继续在域名购买方的设置里删除原本的名为@的record,用Vercel提供的信息增加新的A record

Step 5. GitHub克隆到本地

下载并打开Github Desktop这个App。登陆账号,在Let’s get started页面选择你GitHub里的hugo这个repository,右下角克隆到本地。选择一个本地位置存放这个文件夹(想好放在什么位置,以后更改位置比较麻烦)。这样你就得到了一个本地文件夹。

你所做的所有本地更改,都会显示在GitHub Desktop的左边栏。点击commit会把本地更改提交,再点击push会把本地更改同步到GitHub仓库里。Push之后Vercel会自动发现你的更改并更新博客。记得commit后要Push才可以。

现在你可以试试看本地生成博客。在我们的博客文件夹打开terminal。有两种方法:

第一种方法是,在Finder里找到博客所在文件夹,右键菜单最下面会有New terminal from here

第二种方法是使用cd命令:

cd <替换成你的blog路径>

比如我的博客在Desktop/myblog/blog,我就输入 cd Desktop/myblog/blog。你可以活用几个命令来navigate:

  • cd是change directory的缩写,cd 可以更换目前的文件目录位置
  • cd .. 可以回到上一层目录
  • ls 可以现实当前文件夹内都有什么

在博客目录打开terminal后,我们试着让本地的Hugo帮我们制作网页预览:

hugo server

如果失败的话,说明当前目录并不是博客根目录。如果成功的话,terminal里会显示success,并且给你一个本地网址。在浏览器打开,这就是你的博客当前的样子。还记得烤蛋糕的比喻吗?这就是你家的烤箱(本地Hugo用本地文件)烘培出的蛋糕。

现在任何本地的更改都可以实时地显示在这个网页上。等看够了,就在terminal里按control-c关闭Hugo server。

安装主题,修改设置

像塔塔的攻略一样,我也先安装了MemE这个主题。此处我用了terminal安装。在我们的博客文件夹打开terminal

cd <替换成你的blog路径,同上>
git init
git submodule add --depth 1 https://github.com/reuixiy/hugo-theme-meme.git themes/meme
rm config.toml && cp themes/meme/config-examples/en/config.toml config.toml

主题安装好了,现在再试一次terminal里输入 hugo server 就会发现网页换主题了!

下面我们试着更改一下博客的设置,用文字编辑器打开config.toml,更改网站名字、作者名字、作者email,等等。

MemE主题提供了一套中文的config,更方便理解每一行都是干什么的。

修改之后,进入GitHub Desktop,左下角commit changes,再在右边点push。就更新到GitHub库房了。这时候我们在浏览器输入我们的域名,就能看到博客更新主题和设置了。

发送第一篇博客

首先,在我们的博客文件夹打开terminal,输入

hugo new ”posts/第一篇博客.md“

这个命令在content/posts文件夹里成生了新的一个.md文件。

打开发现Hugo已经帮我们写好了一部分,包括标题,发表日期,是否为草稿,等等。Hugo生成的文件含有的这个台头帮助hugo整理文章,让我们的博客井井有条。如果想要加上tag和category,也可以在抬头这部分输入。具体方法参见塔塔的教程。

如果希望以后的新博客.md文件都自动含有tag和category,可以在archetypes文件夹下更改default.md,把tag和category写进去。

现在我们可以在这里写第一篇博客了!保存后,进入GitHub Desktop,再次commit并push,第一篇博文就显示在博客上了!

庆祝网站顺利搬迁

折腾了一天,终于搬迁完成了。虽然用了All-in-one migration这个简单的WP插件,还是遇到了许多问题,慢慢补充。

All-in-one 备份转移

这个插件很方便使用,安装启用之后可以给全站(包括插件和插件设置)做备份,下载后上传到新的WP即可。非常简单。

但是有一些小问题,免费版并不支持上传大文件。可以通过更改WP设置的办法允许大文件上传。可是就算这样,导入时也会显示失败。在网上找了一圈,发现还是需要插件解锁才能解决大文件传输问题。最终解决方法是(在新的WP)卸载all-in-one插件,下载安装一个extension,再安装all-in-one插件。感谢这个 stackoverflow上的答案

我遇到的另外一个问题是,网站太大了,卡在插件可以处理的512兆上线左右。最后解决方法是用sweep插件删除了一些不用的表格和缓存,并且手动删除了一些不在文章中的媒体文件,最终压缩到了490兆。如果超过512兆的WP网站似乎并不能用all-in-one迁移。令人头疼。

安装AP

安装过程遵照这个ubuntu官网教程,一步步走下来安装apache和建立mysql数据库,并将wp与数据库捆绑。

教程写的很清楚,但是没有指出一些操作的原理和道理。对于网络和网络安全不清楚的用户而言也并不算是最友好……

导入后的的密码设置

all-in-one插件覆盖新的WP,是整个数据库覆盖,就连用户也覆盖了。所以导入后立即需要使用原网站的用户名密码重新登陆。这里面有一个问题,如果不记得原网站的用户名密码,就无法登陆了,而且因为设置还没有结束,此时并无法找回密码,头疼。

找回用户名的办法是登陆进 mysql里打印表格

找回密码的办法比较奇葩,需要用新密码生成md5之后注入进wordpress的数据库里。

好在官方有一个教程 官方教程 ,一步步顺下来就可以搞定。

但是就算有教程,一步步具体操作也很麻烦,我觉得知道怎么修改教程里的代码也需要懂一点sql原理。

修改url

最后这个纯属是我犯傻,在domain记录更改之前先把wp里的url改了,结果就是新WP网站登录会被跳转到旧网站的入口,没法登入新网站了。这个问题解决还是需要去改数据库,大概办法根据这个 wp教程

最后

最后,需要更改domain的DNS记录,我按照这个方法 另一份教程 从原domain的DNS服务里改写了A record,先删掉就record再加入新的,整个过程需要一阵子,需要等待(而且会多次失败。。。)

最后的最后

最后还想设置ngix把两个域名挂在同一个服务器上,但是折腾完这一堆,我已经几乎累死了。幸好网站可以访问并且可以编辑(是可以编辑了吧?)。今天先到这里。

Wordle 提示器

心血来潮写的Wordle提示器,思路大概如下

  • 把谜题中已经暴露的信息转换为正则表达式
    • 绿色=固定字母,黄色=含有字母&位置错误,黑色=不含字母
  • 从词表(*)里筛选出符合规范的词,即所有的“可能答案”
  • 计算所有可能答案的mutual information,找到之中mutual information最大的(几个)词(**)
    • 这里使用了简单的计数法:绿色=2分,黄色=1分,黑色=0分

注:

词表:我用了github上的这个词表derekchuank/high-frequency-vocabulary。经嘟友@[email protected] 提醒,其实wordle有dump出的词表,词库大约2.3k,允许输入大约10.6k,来源自这个reddit讨论串 a_note_on_wordles_word_list/。可以自行替换。

答案表:reddit的讨论指出每期问题的答案是人工挑选的而非随机抽取,这意味着信息分布与默认的平均分布不符。Well,既然答案表有2.3k词,就算是人工挑选也足够多词了。

有趣的发现:

  • 从词表上来看,最好的“起始词”并不是adieu而是rates, aries, cares, lanes这几个,因为u这个原因出现次数其实不如辅音r和s高
  • 就算用词表作弊,也挺需要运气的,比如Wordle 243,最后需要从4个合法词里随机尝试,我的这个策略的步数期望值是4.

代码大概这样

import numpy as np
import re

# # create 5-letter word list
# with open("30k.txt") as file:
#     for line in file:
#     	w = line.rstrip()
#     	if len(w)==5:
#     		with open('5letterwords.txt', 'a') as fnew:
#     			fnew.write(w+'\n')


def info_score(target,current,matchscore=2,containscore=1):
	"""
    compute information score
    target: target word
    current: current try
    change the scores to optimize the searching process
    """
	s = 0
	for i in range(5):
		if current[i]==target[i]:
			s+=matchscore
		elif current[i] in target:
			s+=containscore
	return s



def main():
	# run hinter

	# get word list
	with open("5letterwords.txt") as f: 
		words=[line.strip() for line in f]

	# initialize masks
	letters_contain = []
	pattern = ["[^0]"]*5
	done = [False]*5

	while not all(done):

		# new inpt from user
		w = input("Please enter your first try word: ")
		m = input("Please enter your result (black=0,yellow=1,green=2): ")

		# loop through new input
		for i in range(5):
			if m[i]=='2':
				# correct, remove regax mask and mark as done
				pattern[i]=w[i]
				done[i]=True
			elif m[i]=='1':
				# semi-correct, add to contain list and mask this index
				letters_contain+=w[i]
				pattern[i]=pattern[i][:-1]+w[i]+']'
			elif m[i]=='0':
				# wrong guess, add to all regax mask
				for j in range(5):
					if not done[j]:
						pattern[j]=pattern[j][:-1]+w[i]+']'
		# exit if done
		if all(done): 
			print("You solved it. Bye.")
			exit()
		# apply regax and contain list
		words_new = [wd for wd in words if 
				re.match('^'+("").join(pattern)+'$',wd) 
				is not None and all([l in wd for l in letters_contain])]
		# exit if funny problem happened
		if not words_new:
			print("out of words, maybe something wrong?")
			exit()
		# compute similarity matrix
		sim_matrix =[[info_score(x,y) for y in words_new] for x in words_new]
		# zip and sort and print results
		mutural_info_sums = list(zip(words_new, [sum(x) for x in sim_matrix]))
		best_guess = [x[0] for x in sorted(mutural_info_sums, key=lambda x: x[1],
			reverse=True)[:5]]
		print("Best next guesses are "+(", ").join(best_guess)+".")


if __name__ == "__main__":
    main()

效果大概是这样

答案是robin

答案是slime:

答案是abbey:

答案是aloft: 很奇怪这个词不在30k list里