非插件生成jekyll的 sitemap

网上搜到的jekyll生成网站地图 sitemap 大多是使用插件的,而我懒得研究插件,按照 sitemap 格式生成也并不复杂,使用 {% for post in site.posts %}{% for page in site.pages %} 遍历,自个儿生成即可。

一、sitemap

sitemap.xml文件是严格按照xml语言编写的网站地图,用来引导搜索蜘蛛对本站点文章等内容的索引,它是由google提出来的概念。

sitemap的格式如下:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc>https://blog.kelu.org/tech/2021/12/25/git-config-list.html</loc>
        <lastmod>2021-12-25T00:00:00+08:00</lastmod>
        <changefreq>monthly</changefreq>
        <priority>0.5</priority>
    </url>
</urlset>

语法简单。

  • 首尾格式

    <?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    ...
    </urlset>
    
  • <loc></loc>

    必填。格式为: http://www.xxx.com/xxx,此网址应以协议开始(例如:http)并以斜线结尾。此值应少于 2048 个字符。

  • <lastmod>

    可选标签 标签含义:该文件上次修改的日期。此日期应采用 W3C Datetime 格式。

    如果需要的话,此格式允许省略时间部分,而仅使用 YYYY-MM-DD。

  • <changefreq>

    可选标签 标签含义:页面可能发生更改的频率。此值为搜索引擎提供一般性信息,与搜索引擎抓取页面的频率不完全相关。有效值为:

    always 
    hourly 
    daily 
    weekly 
    monthly 
    yearly 
    never
    
  • <priority>

    可选标签 有效值范围从 0.0 到 1.0。标签含义: 告诉搜索引擎您认为您的那个网页最重要,从而它们对您页面的抓取可以按照您最喜欢的方式进 行排序。

  • changefreq则是指内容更新的频率。

有了这些设置,等于告诉搜索引擎机器人,网站的更新情况如何,以及希望搜索引擎优先收录哪些内容。

二、创建文件

  1. jekyll 环境变量设置为 production

    参考:https://jekyllrb.com/news/2016/10/06/jekyll-3-3-is-here/#3-siteurl-is-set-by-the-development-server

    我是容器启动的,所以增加类似的变量:

    services:
      blog:
        command: jekyll serve
        image: jekyll/jekyll:latest
        container_name: blog
        restart: always
        volumes:
          - ./:/srv/jekyll
        environment:
          JEKYLL_UID: 0
          JEKYLL_GID: 0
          JEKYLL_ENV: production
        ports:
          - '4000:4000'
    
  2. 配置文件_config.yml增加域名信息。

    参考: https://jekyllrb.com/docs/variables/

    例如我的:

    url: https://blog.kelu.org
    
  3. 在目录下创建 sitemap.xml 文件。

    以下是我根据自己个人情况的写法,供参考。

    一些变量和表达式可以参考:

    ---
    ---
    <?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
        {% for post in site.posts %}
        <url>
            <loc>{{ site.url }}{{ post.url | remove: 'index.html' }}</loc> {% if post.sitemap.lastmod %}
            <lastmod>{{ post.sitemap.lastmod | date: "%Y-%m-%d" }}</lastmod> {% elsif post.date %}
            <lastmod>{{ post.date | date_to_xmlschema }}</lastmod> {% else %}
            <lastmod>{{ site.time | date_to_xmlschema }}</lastmod> {% endif %}
            <changefreq>weekly</changefreq>
            <priority>0.7</priority>
        </url> {% endfor %}
        {% for page in site.pages %} {% if page.layout != nil %} {% if page.layout != 'redirect' %}
        <url>
            <loc>{{ site.url }}{{ page.url | remove: 'index.html' }}</loc> {% if page.sitemap.lastmod %}
            <lastmod>{{ page.sitemap.lastmod | date: "%Y-%m-%d" }}</lastmod> {% elsif page.date %}
            <lastmod>{{ page.date | date_to_xmlschema }}</lastmod> {% else %}
            <lastmod>{{ site.time | date_to_xmlschema }}</lastmod> {% endif %}
            <changefreq>weekly</changefreq> {% assign uri_array = page.url | remove: 'index.html' | split: "/" %}{% assign cata1 = uri_array.last | slice: 0,4 %} {% assign cata2 = uri_array.last | slice: -4,4 %} {% if page.url == '/' %}
            <priority>1</priority> {% elsif uri_array.size > 2 %}
            <priority>0.2</priority> {% elsif cata1 == 'page' %}
            <priority>0.1</priority> {% elsif cata2 == 'html' %}
            <priority>1</priority> {% else %}
            <priority>0.3</priority> {% endif %}
        </url> {% endif %} {% endif %} {% endfor %}
    </urlset>
    

三、生成

jekyll serve

参考链接


升级 ubuntu 18.04 的内核

机器比较古老,不好折腾太大重装,就升级一下内核吧。

  • 查内核版本

    image-20211027153625168

     apt list | grep linux-generic*
    
  • 升级

    apt-get install linux-generic-hwe-18.04-edge
    
  • 重启

    reboot
    
  • 确认已升级

    image-20211027155519501


confd 模板语法

本文是 confd 在 github 上的文档的翻译和此文的摘抄,该文档详细介绍了confd模板的语法和结构。另外,最后我列出一些我个人常用的表达式,方便未来复用。

一、模板源

模板源以TOML编写并已 .toml 作为后缀的来定义的。默认情况下,模板源存储在/etc/confd/conf.d 目录下。模板源使用Go的库: text/template.。

必要参数

  • dest (字符串) - 目标文件。
  • keys (字符串数组) - 键数组。
  • src (字符串) - 配置模板的相对路径 。

可选参数

  • gid (int) - 应该拥有该文件的gid。默认为有效的gid。
  • mode (字符串) - 文件的权限模式。
  • uid (int) - 应该拥有该文件的uid。默认为有效的uid。
  • reload_cmd (字符串) - 重新加载配置的命令。
  • check_cmd (字符串)- 检查配置的命令。
  • prefix (字符串) - 键前缀的字符串。

[template]
src = "nginx.conf.tmpl"
dest = "/etc/nginx/nginx.conf"
uid = 0
gid = 0
mode = "0644"
keys = [
  "/nginx",
]
check_cmd = "/usr/sbin/nginx -t -c {{.src}}"
reload_cmd = "/usr/sbin/service nginx restart"

二、模板函数

map

创建接口和字符串的键值映射

{{$endpoint := map "name" "elasticsearch" "private_port" 9200 "public_port" 443}}

name: {{index $endpoint "name"}}
private-port: {{index $endpoint "private_port"}}
public-port: {{index $endpoint "public_port"}}

如果您是子模板并且想要向其传递多个值,则特别有用。

base

path.Base函数的别名 。

{{with get "/key"}}
    key: {{base .Key}}
    value: {{.Value}}
{{end}}

exists

判断键是否存在。如果找不到键,则返回false。

{{if exists "/key"}}
    value: {{getv "/key"}}
{{end}}

get

返回键与其键匹配的键值对。如果未找到键,则返回错误。

{{with get "/key"}}
    key: {{.Key}}
    value: {{.Value}}
{{end}}

gets

返回与其key匹配所有键值对,如果未找到键,则返回错误。

{{range gets "/*"}}
    key: {{.Key}}
    value: {{.Value}}
{{end}}

getv

返回与其键或可选的默认值匹配的字符串,如果未找到键且未给出默认值,则返回错误。

value: {{getv "/key"}}

getv默认值

value: {{getv "/key" "default_value"}}

getvs

返回与其键匹配所有值的字符串,如果未找到密钥,则返回错误。

{{range getvs "/*"}}
    value: {{.}}
{{end}}

getenv

返回在os.Getenv 中检索由键命名的环境变量的值。如果变量不存在,该值将为空。(可选)您可以提供一个默认值,如果该键不存在,将返回该值。

export HOSTNAME=`hostname`
hostname: {{getenv "HOSTNAME"}}

getenv默认值

ipaddr: {{getenv "HOST_IP" "127.0.0.1"}}

datetime

time.Now的别名

Generated by confd {{datetime}}
输出:
Generated by confd 2015-01-23 13:34:56.093250283 -0800 PST
Generated by confd {{datetime.Format "Jan 2, 2006 at 3:04pm (MST)"}}
输出:
Generated by confd Jan 23, 2015 at 1:34pm (EST)

更多用法,请参阅官方时间用法

split

包装器 strings.Split。分隔输入的字符串并返回一个子字符串切片。

{{ $url := split (getv "/deis/service") ":" }}
    host: {{index $url 0}}
    port: {{index $url 1}}

toUpper

strings.ToUpper的 别名 返回大写字符串。

key: {{toUpper "value"}}

toLower

strings.ToLower的 别名 。返回小写字符串。

key: {{toLower "Value"}}

json

返回map[string]interface{}形式的json值。

lookupSRV

net.LookupSRV 包装器 。通过组合net.SRV结构的所有字段按字母顺序对SRV记录进行排序,以减少不必要的配置重新加载。

{{range lookupSRV "mail" "tcp" "example.com"}}
  target: {{.Target}}
  port: {{.Port}}
  priority: {{.Priority}}
  weight: {{.Weight}}
{{end}}

etcd添加键值

etcdctl set /services/zookeeper/host1 '{"Id":"host1", "IP":"192.168.10.11"}'
etcdctl set /services/zookeeper/host2 '{"Id":"host2", "IP":"192.168.10.12"}'

创建模板源

[template]
src = "services.conf.tmpl"
dest = "/tmp/services.conf"
keys = [
  "/services/zookeeper/"
]

创建模板

{{range gets "/services/zookeeper/*"}}
{{$data := json .Value}}
  id: {{$data.Id}}
  ip: {{$data.IP}}
{{end}}

map遍历

一旦解析了JSON,就可以使用普通的Go模板函数遍历它 index

更高级的结构,如下所示:

{
  "animals": [
    {"type": "dog", "name": "Fido"},
    {"type": "cat", "name": "Misse"}
  ]
}

它可以像这样遍历:

{{$data := json (getv "/test/data/")}}
type: {{ (index $data.animals 1).type }}
name: {{ (index $data.animals 1).name }}
{{range $data.animals}}
{{.name}}
{{end}}

jsonArray

从接口返回json数组,例如: [“a”, “b”, “c”]`。

{{range jsonArray (getv "/services/data/")}}
	val: {{.}}
{{end}}

ls

返回匹配路径的所有子键,字符串等。如果找不到路径,则返回空列表。

{{range ls "/deis/services"}}
   value: {{.}}
{{end}}

lsdir

返回匹配路径的所有子键,字符串等。注意它只返回也有子键的子键。如果找不到路径,则返回空列表。

{{range lsdir "/deis/services"}}
   value: {{.}}
{{end}}

dir

返回制定键的父目录。

{{with dir "/services/data/url"}}
	dir: {{.}}
{{end}}

join

strings.Join 函数的别名 。

{{$services := getvs "/services/elasticsearch/*"}}
services: {{join $services ","}}

replace

strings.place 函数的别名 。

{{$backend := getv "/services/backend/nginx"}}
backend = {{replace $backend "-" "_" -1}}

lookupIP

net.LookupIP 函数的包装器 。包装器还按字母顺序排序IP地址。这一点至关重要,因为在动态环境中,DNS服务器通常会混淆链接到域名的地址。这将导致不必要的配置重新加载。

{{range lookupIP "some.host.local"}}
    server {{.}};
{{end}}

atoi

strconv.Atoi 函数的别名 。

{{seq 1 (atoi (getv "/count"))}}

三、我的例子

  1. 需要end包裹的关键字

    • range
    • with
  2. 获取环境变量

    {{$hostname := getenv "KELU_HOSTNAME"}}
    hostname="{{$hostname}}"
    
  3. 取值with

    {{with get (printf "/confd/%s/config" $hostname)}}
    {{ with get $node }}
    
  4. 循环range

    {{range $key, $dir := lsdir "/confd"}}
    	{{ $node := printf "/confd/%s/config" $dir }} # 字符串拼接+赋值
       
    	{{ with get $node }}
    		{{ $data := json .Value }}
    	{{end}}
    {{end}}
    
  5. 对比 eq/ne

    {{if $data.isTrue }}
    {{if ne $data.name "kelu"}}
    
    eq
    	Returns the boolean truth of arg1 == arg2
    ne
    	Returns the boolean truth of arg1 != arg2
    lt
    	Returns the boolean truth of arg1 < arg2
    le
    	Returns the boolean truth of arg1 <= arg2
    gt
    	Returns the boolean truth of arg1 > arg2
    ge
    	Returns the boolean truth of arg1 >= arg2
    
  6. 字符串拼接printf

    {{ $config := (printf "/haproxy/%s/config" $haproxy)}}
    
  7. 解析 json

    {{ with get $node }}{{ $data := json .Value }}
    
  8. 数组长度len

    {{$dir := lsdir "/confd"}}
    {{$count := len $dir}}
    
    and
    	Returns the boolean AND of its arguments by returning the
    	first empty argument or the last argument, that is,
    	"and x y" behaves as "if x then y else x". All the
    	arguments are evaluated.
    call
    	Returns the result of calling the first argument, which
    	must be a function, with the remaining arguments as parameters.
    	Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
    	Y is a func-valued field, map entry, or the like.
    	The first argument must be the result of an evaluation
    	that yields a value of function type (as distinct from
    	a predefined function such as print). The function must
    	return either one or two result values, the second of which
    	is of type error. If the arguments don't match the function
    	or the returned error value is non-nil, execution stops.
    html
    	Returns the escaped HTML equivalent of the textual
    	representation of its arguments. This function is unavailable
    	in html/template, with a few exceptions.
    index
    	Returns the result of indexing its first argument by the
    	following arguments. Thus "index x 1 2 3" is, in Go syntax,
    	x[1][2][3]. Each indexed item must be a map, slice, or array.
    slice
    	slice returns the result of slicing its first argument by the
    	remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2],
    	while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3"
    	is x[1:2:3]. The first argument must be a string, slice, or array.
    js
    	Returns the escaped JavaScript equivalent of the textual
    	representation of its arguments.
    len
    	Returns the integer length of its argument.
    not
    	Returns the boolean negation of its single argument.
    or
    	Returns the boolean OR of its arguments by returning the
    	first non-empty argument or the last argument, that is,
    	"or x y" behaves as "if x then x else y". All the
    	arguments are evaluated.
    print
    	An alias for fmt.Sprint
    printf
    	An alias for fmt.Sprintf
    println
    	An alias for fmt.Sprintln
    urlquery
    	Returns the escaped value of the textual representation of
    	its arguments in a form suitable for embedding in a URL query.
    	This function is unavailable in html/template, with a few
    	exceptions.
    
  9. 获取字符串最后一个单词base

    {{base $dir}}
    
  10. 反释义

原文:

image-20211020164157259

在模板中要在外部包裹{{}}再加双引号:

image-20211020164303072

  1. 数组解析jsonArray

    etcd中的数据为:/confd/local/ip/static ["1.2.3.4","5.6.7.8"]

    {{range jsonArray (getv "/confd/local/ip/static")}}allow {{.}};{{end}}
    
  2. 字符串拆分为数组 spilt

    etcd中的数据为:/a/b/c/d {"target":"a,b,c,d"}

    {{ $backend := split $data.target "," }}
    {{ range $backend }}
    ...
    {{ end }}
    

参考资料


在 macOS 上配置 SSH 隧道并创建 SOCKS 代理

创建 SOCKS 代理是一种便捷的方式,可以将所有流量通过 SSH 隧道转发。以下是我在 macOS 上配置 SOCKS 代理的步骤。

创建 SOCKS 代理

  1. 打开终端: 首先,打开终端应用。

  2. 运行 SSH 命令: 输入以下命令,创建一个 SOCKS 代理:

    ssh -D 1080 user@1.2.3.4
    

    user 替换为用户名。这条命令会在本地的 1080 端口创建一个 SOCKS 代理。

配置应用程序使用 SOCKS 代理

接下来,我需要配置支持 SOCKS 代理的应用程序(如浏览器):

  1. 打开系统偏好设置: 点击苹果菜单,选择 系统偏好设置

  2. 选择网络: 在网络设置中,选择当前使用的网络连接(例如 Wi-Fi),然后点击 高级

  3. 配置代理: 在 代理 选项卡中,勾选 SOCKS 代理,并在代理服务器框中输入 127.0.0.1 和端口 1080

  4. 保存设置: 点击 ,然后 应用 保存更改。

持续运行 SSH 隧道

如果希望 SSH 隧道在后台持续运行,可以使用以下命令:

ssh -D 1080 -N user@1.2.3.4

在这个命令中,-N 参数表示不执行任何远程命令,只保持连接,这样可以让 SOCKS 代理在后台持续运行。这样,我就可以在需要时安全地转发流量。

高阶玩法

如果不想在 macOS 的系统网络代理设置中使用 SOCKS 代理,以下是几种更灵活的方式,暂时只做记录。这里假设我们想仅代理Citrix viewer这个软件。

方法 1: 使用 proxychains 工具

proxychains 是一个工具,可以强制指定的应用程序通过代理(如 SOCKS5)进行网络连接。

步骤:

  1. 安装 proxychains-ng
    • 在 macOS 上安装 `proxychains-ng:
      brew install proxychains-ng
      
  2. 配置 proxychains-ng:
    • 编辑 proxychains 的配置文件,通常在 /usr/local/etc/proxychains.conf
    • 在文件末尾,添加你的 SSH 隧道的 SOCKS 代理地址,例如:
      socks5  127.0.0.1 1080
      
  3. 使用 proxychains 启动 Citrix Viewer:
    • 在 SSH 隧道开启的情况下,使用 proxychains 来启动 Citrix Viewer,让它通过隧道访问网络:
      proxychains4 /Applications/Citrix\ Workspace.app/Contents/MacOS/Citrix\ Viewer
      
    • 这样,Citrix Viewer 的所有流量都会通过 SSH SOCKS 代理,而不影响 macOS 的全局代理设置。

方法 2: 使用 sshuttle 来转发流量

sshuttle 是一种简单的 VPN 解决方案,可以将指定的流量通过 SSH 隧道路由。不同于系统级 VPN,它允许我们控制流量的范围和转发规则。

步骤:

  1. 安装 sshuttle:
    • 在 macOS 上,使用 Homebrew 安装 sshuttle
      brew install sshuttle
      
  2. 启动 sshuttle:
    • 使用 sshuttle 来启动隧道,并将指定的流量通过 SSH 隧道。

      如果希望 sshuttle 在后台运行,可以添加 -D 参数

      sshuttle -r user@1.2.3.4 10.0.0.0/8
      sshuttle -r user@1.2.3.4 1.2.3.5/32 9.0.0.0/8 10.0.0.0/8 -D
      
  3. 验证代理是否生效

    要验证代理是否生效,你可以通过 ping 或 curl 测试是否成功通过代理。例如:

    ping 1.2.3.4
    

方法 3: 使用 Proxifier

Proxifier 是 macOS 上的一款 GUI 工具,允许为特定的应用程序(如 Citrix Viewer)单独设置代理,而不影响系统的其他应用程序。不过要注意的是这是一个付费软件。