Logseq 第一次试用记录以及发布 Logseq 到网页

早之前在使用 [[Obsidian]] 的时候就有了解过 [[Logseq]] 但一直没有找到机会去尝试一下,毕竟我从 [[WizNote]] 迁移到 Obsidian 之后使用一直没有遇到任何问题,毕竟 Obsidian 够简单,也足够扩展性,并且基于 Markdown 文件的笔记是我过去一直在使用的方式。但现在让我有尝试使用一下 Logseq 的契机是因为在以前我都是使用 Vault 存放所有的笔记,然后使用其中一个 Blog 目录存放我想发布的内容,这样每一次我想发布一个具体的文章的时候就可以直接将文件移动到 Blog/_posts 目录之下,然后 Git 提交即可,但这个发布存在的问题便是其中的特殊双向链接 [[]] 会在页面上有些违和,而我看到 Logseq 可以将页面直接以 HTML 方式发布,并且也可以非常好的处理双向链接的问题,于是想来试一试。

Logseq 官方的文档 便是一个非常好的例子,展示界面几乎和用户的编辑页面是一致的,在线浏览也没有任何卡顿的情况。

Logseq 和 Obsidian 存在的区别

block

上手体验的第一个非常大的区别就在于 Logseq 有 block 的概念,这个概念可能是从 Roam Research 中借用而来,不像 Obsidian 整个文档就是一个 markdown 文件,所以每一行文字其实还是段落,但是 Logseq 中每一次 “Enter” 都创建了一个 block。Logseq 中使用 ((uuid)) 的语法来引用 block。有了 block 的概念之后就会发现其实文档内容的最小单元变成了 block,而在 Obsidian 中我能使用的最小单元也无非是通过 # 来划分出来的页面的段落。这可能是 Logseq 更加灵活的地方,但因为目前我还没有想好怎么使用这个 block 所以之后有了具体的使用场景再来分享。

command

Obsidian 中的 Command 和 Logseq 中的 Command 在使用起来还有一些区别,我设定了 Ctrl + P 来调用 Obsidian 中的 Command Palette,而这其中的命令大部分是对整个文档,或者部分内容进行的调整。而在 Logseq 中,在任何页面中使用 / 都可以进行响应的插入,/ 更像是 Notion 的方式,通过 / 来快捷调用复杂的输入操作,比如插入页面引用、块引用、标题、图片等等。

但我个人觉得在 / 中提示输入标题有些累赘,我个人的习惯一般不会在一篇文章中使用三级标题以上的子标题,那这也就意味着我只会使用 1~3 个 #,那么在纯 markdown 文档中,输入 # 要远比先输入 / 然后搜索对应的标题,选中要来的快,即使是输入三个 # 也会比 / 快。

/ 让我觉得最重要的就应该是页面引用和块引用了,通过模糊搜索,在笔记和笔记之间建立关系使用 / 就大大简化的。而在 Obsidian 中我就只能使用 [[ 来进行页面的关联。

vim mode

Obsidian 让我直接上手的一个非常重要的原因就是开箱支持的 Vim 模式,我几乎没有想就启用了,并且一直使用到现在,这无非只是让我在终端的笔记转移到了 Obsidian,并且我熟悉而这个快捷键,命令都可以直接使用,这也让我在终端,IntelliJ IDEA,浏览器 Vimium 达成了统一。而 Logseq 开箱就是所见即所得的界面,当然普通使用起来是没有问题的,但用起来就慢慢地发现有些别扭,比如在上一行插入,或者快速跳转到页尾,以前非常熟悉的 O 或者 G 突然没有了就有些陌生。

然后再看到 Logseq 的 Feature Request ,大家对 Vim-mode 的讨论,要求支持还是挺多,不过截止目前还用不了。

分享

就像上面提到的一样,让我试用 Logseq 最重要的一个原因就是 Logseq 生成在线文档的能力,因为我之前使用 Jekyll 的文档分享部分 Obsidian Vault 中的内容就没有购买 Obsidian Publish 服务,因为一来我觉得 Jekyll 够用,然后 Obsidian Publish 服务的页面最初的时候访问优点慢,并且早鸟价 8$ 一个月的价格也有些高。但后来在使用的过程中还是会有一些些的不便,毕竟双向链接如果没有特殊的处理会显得有些奇怪。

Tag 使用

在 Logseq 中使用 #tag 新建 tag,当点击 tag 的时候会创建新的页面,而在 Obsidian 中 #tag 就只是页面的一个元数据,标签。在 Logseq 中可以点击 tag 页面来查看所有该标签下的页面和块,而在 Obsidian 中我通常是使用搜索。

Logseq 中特殊的语法

Logseq 中使用 key:: value 格式来对页面或块进行属性设置,页面的属性在页面的第一个块定义,作为 frontmatter,块属性可以在任何块中定义。

Logseq 中的 Properties 的两大作用:

  • Query,可以通过查询语法来将带有 property 的内容检索出来
  • 定义 Page/Block 的通用属性,比如想要有一个模板,当记录一本书的时候自动会有相应的属性

Obsidian 搭配 Logseq 使用

在上面的使用过程中,我一直使用 Git 来追踪 Logseq 界面中修改了文档之后在原始文档中的体现,大体来看如果没有使用 Logseq 自身的特殊语法,大致还是兼容 markdown 语法的。

所以我想到了如果将 Logseq 仓库存放在 Obsidian Vault 中,那么其实和我之前使用 Jekyll 发布内容的方式就是一致的了,我只需要把需要发布的内容移动到 Logseq 文件夹下即可。比如说我的 Logseq 仓库叫做 notes,那么他下面会有一个 pages 文件夹,存放的都是 Logseq 中的笔记原始文件。那么我将整个 notes 作为我 Obsidian Vault 的一部分,那么我既可以在 Obsidian 中编辑这些笔记,也可以用 Logseq 打开这个子文件夹来编辑。我只需要使用 Logseq 兼容的语法,那么每一次提交,然后推送到 GitHub 之后,就可以利用 GitHub Actions 自动发布。

Obsidian Vault 是一个独立的仓库,然后使用 git submodule 将 Logseq 仓库添加到其中,还可以利用版本控制来管理。

将 Logseq 发布到 GitHub Pages

这里使用 pengx17 的 Logseq-publish 的 GitHub Action,在个人仓库下创建 .github/workflows/main.yml 文件,根据自己的情况填入配置:

name: CI

# Controls when the workflow will run
on:
  push:
    branches: [master]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2
      - name: Logseq Publish
        uses: pengx17/logseq-publish@main
      - name: add a nojekyll file
        run: touch $GITHUB_WORKSPACE/www/.nojekyll
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          branch: gh-pages # The branch the action should deploy to.
          folder: www # The folder the action should deploy.
          clean: true
          single-commit: true

然后每一次 push 之后会自动触发 build,将静态 HTML 文件发布到 gh-pages 分支中。然后在 GitHub 仓库设置界面 Pages 中设置域名即可。

我发布的内容 https://notes.einverne.info


2022-01-03 logseq , notes , backlink , markdown

Bash 中的 Parameter Expansion

Parameter Expansion 是一个用来描述命令行中部分参数被展开(内容被替换)的术语。在大部分的场景中,被展开的参数通常会带有 $ 符号,在一些特定的场景中,额外的花括号(curly braces)也是必须的。

比如:

echo "'$USER', '$USERs', '${USER}s'"
'testuser', '', 'testusers'

上面的例子展示了基本的 parameter expansions(PE) 是什么,第二个 PE 结果是一个空字符串,那是因为参数 USERs 是空的。其实 s 不是参数的一部分,但是因为 bash 无法分辨这一点,所以我们需要使用 {} 来限定参数的边界(前后)。

Parameter Expansion 也可以让我们去修改会被展开的字符串,这个操作会使得一些修改变得非常方便:

for file in *.JPG *.jepg
do mv -- "$file" "${file%.*}.jpg"
done

上述的代码会重命名所有扩展名为 .JPG.jepg 的 JPEG 文件到 .jpg 扩展名。然后 ${file%.*} 表达式则会截取 file 文件从开头到最后一个 . 的内容。

其他的一些 PE tricks

  • ${parameter:-word}, Use Default Value 如果当 parameter 未设置或为null时,使用默认值 word,否则直接使用 parameter 的值
  • ${parameter:=word}Assign Default Value 赋值,当 parameter 未设置或为 null 时,word 会被赋值给 parameter,然后 parameter 的值会被展开
  • ${parameter:+word}Use Alternate Value 如果 parameter 是 null 或者 未设置,那么结果没有任何被替换,如果 parameter 有值,则会被替换成 word
  • ${parameter:offset:length} Substring Expansion,使用 offset 和 length 限定的字串展开,序号从0开始。
  • ${#parameter} 使用 parameter 的长度展开,如果 parameter 是一个数组名,则展开其中元素个数
  • ${parameter#pattern} pattern 会从 parameter 值开始匹配,最短的 match 会从 parameter 中被删除然后剩余的被展开
  • ${parameter##pattern}## 后面的 pattern 会把最长的 match 删掉
  • ${parameter%pattern},pattern 从 parameter 后往前匹配,最短的匹配被删除,剩余部分展开
  • ${parameter%%pattern},从后往前,最长的 match 会被删除
  • ${parameter/pat/string},parameter 值中的第一个出现的 pat 会被替换为 string
  • ${parameter//pat/string}, 每一个出现的 pat 都会被替换
  • ${parameter/#pat/string}
  • ${parameter/%pat/string}

reference


2021-12-29 linux , bash

不丢失数据 降级 Android 应用版本

之前一次不消息把 Google Play Store 中的自动更新启用了,之后一个夜里把所有应用都更新了,不过有些应用本来就不想升级的,比如网易云音乐(有一些低版本没有广告,没有乱七八糟的直播什么的),微信。所以想着能不能在不丢失数据的情况下降级应用。简单的搜索了一下果然可以。

这里需要使用到 adb 命令,不同的系统直接安装即可,我现在在 Linux 下之前就已经安装过。

macOS 下:

brew cask install android-platform-tools

执行:

adb devices

查看是否连接,如果出现了设备 ID,则表明连接成功了。

然后准备好特定版本的 apk。

adb push wechat_7.0.0.apk /sdcard/Download/wechat_7.0.0.apk

然后进入下一步:

adb shell

进入系统的 shell 环境。

pm install -d -r /sdcard/Download/wechat_7.0.0.apk

说明:

  • -d 表示运行降级安装
  • -r 表示保存数据重新安装现有应用

如果无法安装报错:

255|OnePlus7Pro:/ $ pm install -d -r /sdcard/Download/wechat_7.0.0.apk                                                                                                                                                                       
avc:  denied  { read } for  scontext=u:r:system_server:s0 tcontext=u:object_r:fuse:s0 tclass=file permissive=0
System server has no access to read file context u:object_r:fuse:s0 (from path /sdcard/Download/wechat_7.0.0.apk, context u:r:system_server:s0)
Error: Unable to open file: /sdcard/Download/wechat_7.0.0.apk
Consider using a file under /data/local/tmp/
Error: Can't open file: /sdcard/Download/wechat_7.0.0.apk

Exception occurred while executing 'install':
java.lang.IllegalArgumentException: Error: Can't open file: /sdcard/Download/wechat_7.0.0.apk
	at com.android.server.pm.PackageManagerShellCommand.setParamsSize(PackageManagerShellCommand.java:520)
	at com.android.server.pm.PackageManagerShellCommand.doRunInstall(PackageManagerShellCommand.java:1283)
	at com.android.server.pm.PackageManagerShellCommand.runInstall(PackageManagerShellCommand.java:1249)
	at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:185)
	at android.os.BasicShellCommandHandler.exec(BasicShellCommandHandler.java:98)
	at android.os.ShellCommand.exec(ShellCommand.java:44)
	at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:22322)
	at android.os.Binder.shellCommand(Binder.java:940)
	at android.os.Binder.onTransact(Binder.java:824)
	at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:4644)
	at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:4515)
	at android.os.Binder.execTransactInternal(Binder.java:1170)
	at android.os.Binder.execTransact(Binder.java:1134)

那么需要将 apk 移动到 /data/local/tmp/:

mv /sdcard/Download/wechat_7.0.0.apk /data/local/tmp/

然后再安装:

pm install -d -r /data/local/tmp/wechat_7.0.0.apk

如果还不行,那么可以保留数据卸载应用然后重新安装:

pm uninstall -k com.tencent.mm
pm install -d /data/local/tmp/wechat_7.0.0.apk

2021-12-23 android , apk , adb

使用 k3s 和 WireGuard 网络部署 Kubernetes 集群

在今年的黑色星期五入手了几个不同 VPS 提供上的机器,包括之前的 A400,以及 HostHatch,手上的机器可以用来学习搭建一个 Kubernetes。

在之前的文章中已经介绍过netmaker,这是一个可以用来管理 [[WireGuard]] 网络的工具,这篇文章就简单介绍一下如何在不同的云服务器提供商的主机之间,利用 WireGuard 构建的局域网,并使用 k3s 来搭建一个简单的 Kubernetes 集群。

概念介绍

Netmaker

Netmaker 是一个开源的、基于 WireGuard 网络的组网工具,可以非常方便的构建 WireGuard 网络。

WireGuard

[[WireGuard]] 是一个已经合并到 Linux 内核的轻量级 VPN 协议,可以在不同的主机中建立点对点通信隧道。

K3s

[[k3s]] 是 Rancher Lab 发布的一款轻量级的 Kubernetes 发行版。

Kubernetes

Kubernetes 是一个用于管理容器的开源运维平台,非常易于扩展。通常简称 k8s。

工具选择

因为我搭建 k8s 只是为了学习,不是为了应用于生产,所以不直接使用 Kubernetes,而是选择更轻量的 K3s,也可以在性能没有那么好的 VPS 上运行。

Kubernetes 安全性的问题则通过 WireGuard 网络解决,WireGuard 可以在不同主机节点之间建立加密隧道,可以让节点和节点之间的通信都只经过这条加密隧道,这样即使我的不同的主机在不同的地点,可以保证之间的通信是可靠的。但是存在一个问题便是,如果只是只有几台机器,那么完全可以通过手工的方式来管理 WireGuard 网络,但如果机器很多则管理起来会非常麻烦,所以这里使用 Netmaker,可以只通过简单命令快速构建一个私有局域网。

这里不会使用 Netmaker 更加只能的一些特性,比如 DNS,storage,或者 High Availability(高可用),只简单的借助其基本的组网特性。

设置

在这个演示的过程中,我使用两台 RackNerd 的 1核2G VPS(分别叫做 RN1,RN2)作为 worker 节点 ,以及一台 2核4G 的 A400 机器作为 k3s master 节点,然后有一台另外的机器安装 Netmaker 管理端。

  • 操作系统:机器都安装 Ubuntu 20.04
  • 节点机器都已经安装 apt install wireguard-tools
  • Netmaker 节点安装了 Docker 以及 docker-compose,并且保证 80, 8081, 50051 端口是开放的

Netmaker 安装 WireGuard 设置

第一步首先需要通过 Netmaker 构建一个节点与节点之前的安全网络,用于之后的通信。首先在 Netmaker 节点的机器上安装 Netmaker。

教程可以参考之前的 Netmaker 文章,为了方便起见可以选择一键安装脚本:

sudo wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh | bash

如果熟悉 docker-compose 可以下载 docker-compose.yml 自行修改配置后启动。

启动之后可以根据脚本的提示获取 Netmaker 登录后台的地址。在 UI 界面中创建用户,并登录。

在界面中创建一个 Network 叫做 k3s,然后在选项中配置私有网络地址的范围是 10.11.11.0/24

这一步完成之后,就可以将不同的主机节点添加到这个网络中,首先点击 Access Keys,选择 k3s 网络,然后创建 Key,Key 的名字重要,可以随意,然后给这个 Key 100 次使用机会。然后就会获得一个安装脚本,通常是 curl 开头的。

然后在去其他节点中依次安装,将其添加到 Netmaker 网络中。

确保节点已经安装 wireguard-tools 包,然后使用 root 账户:

su -
# 粘贴安装脚本
curl ...
# 完成安装后执行 wg show 查看状态是否正常
wg show

wg show 命令会显示网络接口,如果节点添加成功,可以继续在其他节点中依次执行上述步骤,直到把所有节点都添加到网络中。然后其 Netmaker 后台,点开 k3s 网络可以查看到网络中添加进来的主机节点,以及其私有 IP 地址。一般 Netmaker 会根据主机的 Hostname 来在界面中展示,可以点击修改来重命名网络中的任何节点名字。

K3s 安装

假设 master 节点安装在 A400 机器,worker 节点使用两台 RackNerd 节点。

那么首先登录 A400 机器

# 切换到 root
su -
# 查看 IP
ip a

可以看到结果中会有一个 nm-k3s 的网络接口,其中显示的 IP 地址就是 WireGuard 的地址,假设是 10.11.11.4,然后需要将此 IP 地址替换下面命令中的 IP 地址。

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC=”server --node-ip 10.11.11.4 --node-external-ip 10.11.11.4 --flannel-iface nm-k3s” sh -

等待安装完成,可以使用如下命令查看状态:

systemctl status k3s
kubectl get nodes
kubectl get pods --all-namespaces

一旦确认状态都没有问题,所有的 pods 都正常运行,可以开始部署 worker 节点。首先从 master 节点获取 node key:

cat /var/lib/rancher/k3s/server/node-token

然后在每一个 worker 主机上执行:

# 切换到 root
su -
# 查看 IP
ip a

获取机器的私有 IP 地址,用来替换下面命令中的 10.11.11.x。然后下面命令中的 MASTER 需要替换成 k3s master 机器的 IP,如果是上面的例子就是 10.11.11.4,将 MASTER 修改为 4。 然后将 <TOKEN VAL> 替换成上面 cat /var/lib/rancher/k3s/server/node-token 命令输出的结果。

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="agent --server https://10.11.11.MASTER:6443 --token <TOKEN VAL> --node-ip 10.11.11.X --node-external-ip 10.11.11.X --flannel-iface nm-k3s" sh -

替换上面的命令中的部分,完成执行,使用如下命令查看状态:

systemctl status k3s-agent

然后依次在两台 worker 机器中执行。

然后到 master 节点执行:

sudo kubectl get nodes
sudo kubectl get pods --all-namespaces -o wide

显示如下:

测试

创建一个 pingtest.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pingtest
  namespace: pingtest
spec:
  selector:
    matchLabels:
      app: pingtest
  replicas: 4
  template:
    metadata:
      labels:
        app: pingtest
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - pingtest
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: busybox
        image: busybox
        command: ["/bin/sh", "-ec", "sleep 1000"]

然后执行:

kubectl create namespace pingtest
kubectl apply -f pingtest.yml
kubectl get pods -n pingtest -o wide

执行完成之后,可以看到三个节点中有三个运行的 pods,因为上面指定了 replicas 是 4,所以会有一个在 pending 中。

进入一个运行的节点,执行 ping:

kubectl exec -it pingtest-588df6f488-zzcrp -n pingtest -- sh

然后执行 ping 其他节点的操作。

Nginx test

创建一个 nginx.yml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: ClusterIP

然后执行:

kubectl create namespace nginx
kubectl apply -f nginx.yml -n nginx

然后

kubectl exec -it pingtest-588df6f488-zzcrp -n pingtest -- sh

在其中可以访问其他 worker 节点中的 nginx index.html

reference


2021-12-13 kubernetes , k3s , netmaker , wireguard

使用 Netmaker 快速组建 WireGuard mesh 网络

前段时间在逛 LET 看到有人发帖询问在 VPS 上安装了什么,论坛中的 @Galeej 提到,自己将所有的 VPS 通过 Tailscale 私有网络连接,对其他 VPS 的操作都通过一台新加坡的 relay 服务器转发(并且设置其他 VPS 的网络连接只允许特定的 IP),而所有需要暴露到公开互联网上的端口都隐藏在 Cloudflare 之后,我看到这样的配置之后发现这样的 VPS 配置非常的安全,公开互联网上没有暴露任何可以攻击的端口,所有的服务都在 WireGuard 内网中。

然后在之后的讨论中 @Ouji 又提出,他将用 Netmaker 来替换 Tailscale,然后我就调研了一下 Netmaker 发现这是 Tailscale 的开源版本,不仅提供了非常友好的配置管理界面,还能够提供自定的网络段(我使用 Tailscale 不舒服的一点就是分配的内网 IP 都是随机的)。所以下面就简单的介绍一下 Netmaker 的使用,并简单的演示一下创建一个点对点 mesh 网络。

什么是 Netmaker

Netmaker 是一个开源的、基于 [[WireGuard]] 的网络(overlay network) 控制工具,可以非常快速的用来组建 WireGuard 网络。

如果你有两台连接互联网的设备,那么 Netmaker 可以组建一个安全的网络,并打通一个安全的隧道提供给两台机器通信。而如果你有数千台机器分布在不同的地区,不同的数据中心,不同的网络中,那么 Netmaker 也可以组建一个网络来提供给这些不同的节点通信。

如果熟悉 AWS,那么 Netmaker 就像 VPC 一样,不过 Netmaker 可以应用在任意的机器中。从 Netmaker 网络中的机器来看,同一个网络中的机器尽管在世界各地,但其相互通信就像是在同一个局域网中一样。

Netmaker 和其他一些产品非常相似,比如 [[Tailscale]], [[ZeroTier]],[[Nebula]] 但不同于这些产品的是,Netmaker 连接更快,更加灵活。Netmaker 使用 [[WireGuard]] 所以更快,Netmaker 中的节点不管是服务端还是Agent都完全可配置,所以提供了更大的灵活性。

Netmaker 优于 [[Tailscale]] 的地方还在于 ,Netmaker 不需要 Google, Microsoft 或者 GitHub 账号。Netmaker 可以认为是一个可以自行托管的 Tailscale。

Netmaker 工作原理

Netmaker 依赖于 WireGuard 来在机器间创建隧道(tunnel), Netmaker 通过管理不同机器的 WireGuard 来创建网络。简单来说,Netmaker 实现 Client/Server 架构:

  • the admin server 管理端,称为 Netmaker,管理网络的界面
  • the agent,客户端,称为 Netclient,客户端通过 gRPC 与服务端通信

作为 Network 管理端,你可以通过管理端来创建网络,管理连接的设备。服务端会保存所有网络和设备的配置信息,这些信息会被 netclient (agent) 来获取使用。

客户端(netclient) 是一个二进制文件,netclient 会在节点被添加到网络中的时候安装到不同的机器中,netclient 可以在大多数系统中运行,不管是虚拟机,物理机,或者 IoT 设备都可以运行 netclient。netclient 会连接服务端,通过服务端的配置来自动管理 WireGuard,动态更新 Peers。通过不断向网络中添加节点的方式,可以创建一个动态的,完全可以配置的虚拟网络。

Netmaker server 不会路由网络流量,否则这个网络就变成了一个中心辐射模型(hub-and-spoke model),这会使得中心服务器变成瓶颈,并且拖慢网络。相反,Network 会告诉网络中的节点他们之间可以相互直接通信,这被称为 full mesh network(网状网络),这会让节点和节点的连接更快。即使管理端宕机,只要现存的节点没有变化,那么这个网络依然可以正常工作。

应用场景 Use Cases

Netmaker 有非常多的应用场景,事实上,现在可能就已经在使用了。

用例:

  • 自动创建 WireGuard mesh network
  • 在云环境和数据中心之间创建 flat, secure 网络
  • 给 IoT 设备提供更安全的网络访问
  • 增强家庭,或办公网络的安全性
  • 在现存网络上增加一层加密
  • 安全的 site-to-site 连接
  • 管理 cryptocurrency proof-of-stake 机器
  • 创建动态的安全的 Kubernetes underlay network

更多的用例也可以参考官网

架构

Netmaker 的架构图:

概念

熟悉一下 Netmaker 中常常被提到的概念,有助于理解。

WireGuard

[[WireGuard]] 相对比较新,但非常强大,WireGuard 被加入到了 Linux kernel。WireGuard 可以非常简单快速地在设备之间创建加密通道。从 WireGuard 官网的介绍中可以看到,“WireGuard 可以被认为是工业界最安全,最方便使用,最简单的 VPN 解决方案”。

之前的解决方案,比如 OpenVPN,或者 IPSec 都被认为又重又复杂,并且性能也不是很好。所有现存的 VPN tunneling 方案都会导致网络延迟增大。WireGuard 是第一个实现了几乎接近有线连接网络速度的 VPN,可以看到 WireGuard 对现有网络连接几乎没有影响。随着 WireGuard 的发布,没有任何理由去使用其他隧道加密技术了。

Mesh Network

当提到 mesh network (网状网络)的时候通常也会说 「full mesh」。一个 full mesh network 指的是网络中的任何节点都可以相互直接连接。

比如在路由器后面的家庭网络,所有的电脑都会通过私有局域网地址相互连接。

Mesh network 通常会和 hub-and-spoke (中心辐射) 网络放到一起对比,中心辐射的网络中,一个节点必须通过 relay server 才能和另外一个节点进行通信。

在一些场景中,你可以需要部分的 mesh network,网络中只有部分设备需要相互直接通信,而其他设备都需要将流量转发给一个 relay/gateway 。Netmaker 在某些时候也可以实现这类模型。在第一张图片中,这个设置就是一个部分的 mesh network,因为节点A-D 是网状网络,而其他的客户端通过 gateway 连接。

Mesh networks 通常比其他拓扑的网络更快,但通常设置也会更加复杂。WireGuard 提供了在设备之间创建加密隧道的方法,但是它不提供设置完整网络的方法。这是 Netmaker 诞生的理由。

Netmaker

Netmaker 是一个建立在 WireGuard 上的平台,允许用户在设备之间构建 mesh networks。Netmaker 可以根据需要创建完全的、或部分的 mesh network。

当我们提及 Netmaker 整体的时候,通常指的是 Netmaker 以及 netclient, 以及其他辅助的服务,比如 CoreDNS,rqlite 和 UI 服务。

从终端用户来看,通常会和 Netmaker UI 交互,或会直接在终端节点中直接运行脚本。而其他部分都会在后台默默地执行。

Netmaker 做了非常多的配置相关的工作,简化了用户的配置。包括了 WireGuard 的端口,endpoints( 终端) , public IPs(公网IP),keys(密钥) 和 peers(节点)。Netmaker 尽可能多地抽象了网络管理,只需简单的在界面点击创建网络,然后点击将计算机添加到网络。也就是说,每一个机器都是不同的,可能需要不同的配置。这就是为什么,Netmaker 使用一套默认设置,则网格内的所有内容都是完全可配置的。

SystemD

SystemD 是一个被 Linux 广泛使用的系统服务器管理器。尽管没有被所有发行版采用,但是不管如何,它已经成为了 Linux 世界的事实标准。非 Linux 可能没有 systemd,而 Linux/Unix 发行版有可代替的 system service managers。

Netmaker 的 netclient (客户端) 会控制节点上的网络,可以通过命令行运行,或者通过作为 system 守护进程(daemon),在 Linux 上会默认以 daemon 运行(依赖于 systemd)。

从 0.8 版本开始,支持了 macOS 和 Windows,在这些操作系统中,netclient 使用 LaunchD 来启动 netclient 守护进程。

Ingress Gateways

在 Netmaker 网络中的任何节点都可以成为 Ingress Gateway,Ingress Gateway 可以接受 Netmaker(WireGuard)网络外部的流量。

Egress Gateways

Egress Gateway 出口网关,允许将内部网络流量转发到外部指定 IP。

Netmaker 安装

硬件要求

服务器:

  • 一台可用的 VPS(最好比较干净,没有占用端口,否则需要根据自己的需要自行调整),不推荐 Oracle Cloud 的机器,网络接口有问题
  • 公开的 IP 地址
  • 至少 1GB RAM,1CPU(4GB RAM,2 CPU 生产环境)
  • 2GB+ 存储
  • Ubuntu 20.04

软件要求

域名:

  • 一个可用的域名(可选)
  • 可以操作管理 DNS 服务(53端口)
  • 保证 443(tcp), 53(tcp udp), 51821-518XX(udp) 端口可用
    • 443 端口,Dashboard,REST API 和 gRPC
    • 53 端口,CoreDNS
    • 51821-518XX,WireGuard,每一个网络需要一个端口,起始端口会使用 51821,可以根据自己的网络端数量需要设定端口范围
    • 允许防火墙 sudo ufw allow proto tcp from any to any port 443 && sudo ufw allow 53/udp && sudo ufw allow 53/tcp && sudo ufw allow 51821:51830/udp

一键安装

如果想使用自己的域名,比如 dashboard.netmaker.example.com 这样,可以参考官网。这里为了演示方便,就使用一键脚本。

Netmaker 官方已经提供了一个 Docker 镜像,并且也提供了安装脚本:

sudo wget -qO - https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh | bash

如果没有使用自己的域名,一键脚本会使用一个 nip.io 的泛域名解析映射工具根据 IP 自动获取一个域名,用来访问后台地址。

首次登录后台会要求设定用户名和密码。登录后台之后,左侧的 Networks、 Nodes、 Access Keys 是比较重要的菜单。

在 Network 选项中选择创建 Network,设定 IP 段范围,然后在其他机器上依次安装客户端,添加节点到网络中即可。

在添加到之后网络之后,可以运行 sudo wg show 查看信息:

❯ sudo wg show
interface: nm-k3s
  public key: PGeGQKOlJt4zZJX2axf15dRsWvs6QaFRF/j/fJUfnjw=
  private key: (hidden)
  listening port: 51821

peer: Cjbp/IeTEFgPEJpOldjkaleUvlNjqg+y75hiI/Sq61Q=
  endpoint: 140.XXX.XXX.XXX:51821
  allowed ips: 10.10.11.8/32
  latest handshake: 2 seconds ago
  transfer: 6.74 KiB received, 1.88 KiB sent
  persistent keepalive: every 20 seconds

通常会看到 interface 和 peer 信息。

手动安装

如果需要手动安装,也不是特别麻烦,从官网下载 docker-compose.yml 文件:

wget -O docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.contained.yml
sed -i 's/NETMAKER_BASE_DOMAIN/<your base domain>/g' docker-compose.yml
sed -i 's/SERVER_PUBLIC_IP/<your server ip>/g' docker-compose.yml
sed -i 's/COREDNS_IP/<default interface ip>/g' docker-compose.yml

生成唯一的 master key:

tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo ''
sed -i 's/REPLACE_MASTER_KEY/<your generated key>/g' docker-compose.yml

配置 Caddy

wget -O /root/Caddyfile https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile

sed -i 's/NETMAKER_BASE_DOMAIN/<your base domain>/g' /root/Caddyfile
sed -i 's/YOUR_EMAIL/<your email>/g' /root/Caddyfile

然后启动:

sudo docker-compose up -d

然后可以访问 dashboard.nm.example.com 后台。

需要注意的是,如果使用自己的域名需要添加一个泛域名 A 记录(wildcard A record),比如想要后台访问地址是 dashboard.nm.example.com 那么需要添加 *.nm.example.com

Caddy 会创建三个子域名:

  • dashboard.nm.example.com
  • api.nm.example.com
  • grpc.nm.example.com

netclient 使用

netclient 是一个简单的 CLI,用于创建 WireGuard 配置和接口。netclient 可以管理任意私有网络。

macOS 安装

首先安装依赖:

brew install wireguard-tools

外延

Nebula 是另外一个选择,同样基于 WireGuard。

更多资料可以查看 Gravitl 官网:https://gravitl.com/resources

reference


2021-12-10 netmaker , wireguard , mesh-network , linux , vpn , network , tunnel , secure

使用 Ansible Roles 结构化并复用 playbook

之前简单的了解过一下 Ansible,但没怎么具体使用起来,这两天因为要管理的机器多了起来,所以又把 Ansible 学了起来。这篇文章就主要了解一下 Ansible Roles 的使用。

之前的文章简单的知道在 Ansible 中可以使用 playbook 来组织一系列的任务。但如果要复用这些任务,并且更加模块化的花,那就离不开 Ansible Roles。

Role 用来解决的问题

之前的文章中也说过可以使用 playbook 来管理一系列的任务,但随着使用 playbook 就不可以免的膨胀,可能会出现上百行的 playbook,那为了复用和结构化地组织 playbook, Ansible 在 1.2 版本引入了 Roles 的概念。

  • 层次化、结构化组织 playbook
  • 复用任务

Roles

Ansible 中的 Roles 是 Ansible 的另一个重要的概念,通过 Roles 可以通过文件结构自动加载相关的 vars, files, tasks, handlers, 或者其他 Ansible 组件。这样说可能比较抽象,可以理解成通过在文件系统上的文件分类,可以自动让 Ansible Roles 去加载相关的内容。一旦通过 Roles 组织了内容就可以非常简单地复用和分享给其他人。

Role directory structure

Ansible Role 定义了一个目录结构,包括了8大类标准的结构,一个 Role 必须包含至少其中一个文件夹,其他没有使用的文件夹可以省略:

# playbooks
site.yml
webservers.yml
fooservers.yml
roles/
    common/
        tasks/
        handlers/
        library/
        files/
        templates/
        vars/
        defaults/
        meta/
    webservers/
        tasks/
        defaults/
        meta/

默认情况下 Ansible 会自动寻找每一个目录下的 main.yml 文件(main.yaml 或者 main)。

  • tasks/main.yml,role 需要执行的主要任务
  • handlers/main.yml,可能会被使用的 handlers,可以由该 role 使用,也可以被 role 之外的其他任务使用
  • library/my_module.py modules
  • defaults/main.yml 默认变量
  • vars/main.yml role 的其他变量
  • files/main.yml files that the role deploys,role 需要使用的文件
  • templates/main.yml templates that the role deploys
  • meta/main.yml metadata,角色依赖

Storing and finding roles

默认情况下 Ansible 会在下面两个位置寻找 Roles:

  • 相对于 playbook 的目录 roles
  • /etc/ansible/roles

也可以通过 roles_path 的方式指定 Role 的位置。更多可以参考 Configuring Ansible

ansible.cfg 中定义:

roles_path    = /etc/ansible/roles:/usr/share/ansible/roles

或者也可以直接指定具体的 path:

---
- hosts: webservers
  roles:
    - role: '/path/to/my/roles/common'

使用 Roles

可以通过三种方式使用 Roles:

  • 在 play 层级使用 roles 选项,最常用的方式
  • 在 tasks 级别使用 include_role,可以动态使用
  • 在 tasks 级别使用 import_role,静态使用

Using roles at the play level

在 playbook 中,可以这样使用role:

- hosts: webserver
 roles:
   - common 
   - webserver

可以传递参数:

---
- hosts: webservers
  roles:
    - common
    - role: foo_app_instance
      vars:
        dir: '/opt/a'
        app_port: 5000
      tags: typeA
    - role: foo_app_instance
      vars:
        dir: '/opt/b'
        app_port: 5001
      tags: typeB

也可以向 roles 传递参数,例如:

- hosts: webserver
 roles:
   - common
   - { role: foo_app_instance, dir:'/opt/a',port:5000}
   - { role: foo_app_instance, dir:'/opt/b',port:5001}

甚至也可以条件式地使用roles,例如:

- hosts:webserver
 roles:
   - { role: some_role, when: "ansible_so_family == 'RedHat" }

Including roles: dynamic reuse

include_role 会按照定义的顺序执行,如果之前有定义其他的任务,会先执行其他任务。

---
- hosts: webservers
  tasks:
    - name: Print a message
      ansible.builtin.debug:
        msg: "this task runs before the example role"

    - name: Include the example role
      include_role:
        name: example

    - name: Print a message
      ansible.builtin.debug:
        msg: "this task runs after the example role"

Importing roles: static reuse

行为和上面的一样。

---
- hosts: webservers
  tasks:
    - name: Print a message
      ansible.builtin.debug:
        msg: "before we run our role"

    - name: Import the example role
      import_role:
        name: example

    - name: Print a message
      ansible.builtin.debug:
        msg: "after we ran our role"

使用 ansible-galaxy 创建 role

可以使用 ansible-galaxy role init role_name 来创建 role,这个命令会创建一个目录结构。

  • 创建以roles命名的目录
  • 在roles目录中分别创建以各角色命名的目录,如webserver等
  • 在每个角色命名的目录中分别创建files、handlers、meta、tasks、templates和vars目录;用不到的目录可以创建为空目录,也可以不创建

使用 ansible-galaxy 创建的 role 会有一些初始化的设定,在 meta/main.yml 中可以看到基础的 galaxy_info 配置,包括了作者信息,协议等等。

role内各目录中可应用的文件

  • task目录:至少应该包含一个为main.yml的文件,其定义了此角色的任务列表;此文件可以使用include包含其它的位于此目录中的task文件;
  • file目录:存放由copy或script等模板块调用的文件;
  • template目录:template模块会自动在此目录中寻找jinja2模板文件;
  • handlers目录:此目录中应当包含一个main.yml文件,用于定义此角色用到的各handlers,在handler中使用include包含的其它的handlers文件也应该位于此目录中;
  • vars目录:应当包含一个main.yml文件,用于定义此角色用到的变量
  • meta目录:应当包含一个main.yml文件,用于定义此角色的特殊设定及其依赖关系;ansible1.3及其以后的版本才支持;
  • default目录:应当包含一个main.yml文件,用于为当前角色设定默认变量时使用此目录;

通过 ansible-galaxy 认识 Roles

ansible-galaxy list  # 列出已经安装的galaxy
ansible-galaxy install geerlingguy.redis  # 安装一个galaxy role
ansible-galaxy remove geerlingguy.redis  # 删除一个galaxy role

reference


2021-11-10 ansible , ansible-playbook , ansible-role , linux , python

自行搭建 ZeroTier Network Controller 组件虚拟局域网

之前的一篇文章简单的通过内网穿透,异地组网的概念介绍过 ZeroTier,过去几年里面几台设备也一直陆陆续续地在使用着,虽然中间也短暂切换成 frp,也尝试过 [[Tailscale]],但 ZeroTier 一直是候选方案中排名在前的。

ZeroTier 官方默认免费的方案可以支持最多 50 台设备的连接,我陆陆续续也用不超过 20 台。所以使用官方提供的基本服务是丝毫没有任何问题的。但就是本着折腾的态度,也是学习一下 ZeroTier network controller 相关的内容,记录一下如何自建 ZeroTier network controller。

概念介绍

ZeroTier

ZeroTier 是一个虚拟组网工具,他可以让设备和设备之间通过互联网的帮助,就像是在局域网(LAN)之间通信一样。通过 安装 ZeroTier One 客户端,并加入一个 16 位数字的 ZeroTier 网络就能实现。

ZeroTier network controller

ZeroTier 网络是通过 ZeroTier network controller 来完成配置的。用户既可以使用 ZeroTier 官方 提供的 network controller,也可以使用自己搭建的独立网络控制器(standalone network controller)。

如何设置自己独立的网络控制器就是这篇文章的重点。

ztncui

ztncui 是一个开源的 ZeroTier 网络控制中心的用户界面。

代码地址:https://github.com/key-networks/ztncui

ZeroTier 网络实现原理

ZeroTier 的网络 Controller 是一个控制节点,该节点会通过 roots (根节点)来发现彼此。可以和 DNS 根服务器类比。

ZeroTier 的 Network ID 由两部分组成:节点 ID + 其他字符。

每一个 ZeroTier 节点(Nodes)会通过 Network ID 的前10位来发现 network controller,然后通过 networking controller 来发现局域网中的其他节点。

然后每一个节点可以通过 zerotier-cli peers 来查看匹配网络中的节点。

如果 network controller 离线了,那些已经建立连接的节点会保持连线,但是无法再往网络中添加新的节点。

如何搭建独立的ZeroTier网络控制器

上文提及的 ztncui 就是一个开源的 ZeroTier 网络控制器界面,通过他可以快速搭建自己的 ZeroTier 网络控制器,这里我们使用 Docker 镜像快速搭建。如果需要手工搭建,可以直接参考官网

本文使用的镜像是:

这里直接贴出 docker-compose.yml 文件:

version: '3.3'

services:
  ztncui:
    container_name: ztncui
    image: keynetworks/ztncui
    restart: always
    volumes:
      - ~/ztncui/ztncui:/opt/key-networks/ztncui/etc
      - ~/ztncui/zt1:/var/lib/zerotier-one
    environment:
      - NODE_ENV=${NODE_ENV}
      - HTTP_PORT=${HTTP_PORT}
      - HTTP_ALL_INTERFACES=yes
      - ZTNCUI_PASSWD=${ZTNCUI_PASSWORD}
      - MYADDR=${MYADDR}
    ports:
      - '3443:3443'
      - '3180:3180'

说明:

  • HTTP_PORT:后台端口
  • ZTNCUI_PASSWD:后台默认密码

然后在同级目录新建文件 .env:

# more https://github.com/key-networks/ztncui-aio
NODE_ENV=production
HTTP_PORT=3443
ZTNCUI_PASSWD=
MYADDR=your.ip

后续更新会在 dockerfile

独立网络控制器的优劣

优点

自建 ZeroTier network controller 可以提升节点建立连接的稳定性,同时也解除了官网的设备连接数限制。

但 network controller 自身并不能提升节点和节点之间的连接速度。

缺点

一旦使用了自建的 ZeroTier 网络,便需要一定精力去维护 network controller 的稳定性。

并且如果 network controller 挂掉可能无法再新增加节点。不过新增节点的操作也不是高频操作,对我个人使用而言问题不大。

外延

除了 ztncui 这一个用户界面,还有一些在逐渐发展的,可以根据自己需要挑选:

reference


2021-11-04 zerotier , linux , networking

究竟什么是「内卷」

过去的这一年里,不断的有人在我耳边说起一个词「内卷」,但是我让说这个词的人解释一下自己所说的这个词的含义,却没有人能够用一句话解释清楚这个词。所以在我的笔记中就一直留着这一个词条,让我不断的去思考「内卷」一词的含义。一般来说,我在没有弄明白某一个概念之前,不会在日常生活中反复的去使用。但周围反复在用的人实在太多了,所以整理一下过去我听到的,以及看到的。

内卷的几个表达场景

我周围的人通常在这几个场景中会用到「内卷」这个词:

  • 在大多数的语境中,我们会说互联网行业的竞争,以及在这个竞争下的程序员为主体的人形成了内卷的趋势,表现形式就是加班越来越严重
  • 我也听到有人形容《鱿鱼游戏》说是反应韩国的内卷
  • 同样在学习,比如高考的竞争中也有「内卷」的趋势

但究竟什么是内卷,如果是恶性竞争,那么我们为什么要发明一个词来表达重复的含义?

什么是内卷

那接下来我们就从最原本的含义来探究一下「内卷」。

字典上的定义

内卷对应的英文单词是 involution。involution 在 American Heritage English 上的解释:

  • the state of being involved 被卷入的状态
  • intricacy, 复杂性,复杂化的状态和过程
  • 在医学上:
    • A decrease in size of an organ, as of the uterus following childbirth.(器官在大小上的萎缩,例如生育之后女性子宫的萎缩。)
    • A progressive decline or degeneration of normal physiological functioning occurring as a result of the aging process.(因年龄增长而出现的身体正常功能的持续衰退或退化。)
  • 在植物学上:
    • Having the margins rolled inward,边缘内卷,形容植物枝叶内卷的特性

英文单词 involution 有内卷,退化的含义。

哲学定义

在哲学领域,内卷化一词最早被提及是 18 世纪德国哲学家 [[康德]] 的《批判力批判》,康德将 involution 和 Entwicklung(演化)进行了区分。

他在《批判力批判》中将「内卷化」定义成与「演化」相对的概念,「演化」形容事物从无到有、从低级到高级的发展过程,而「内卷化」则指的是事物在原有的基础之上,不断向内发展,不断复杂化的过程,这个过程中,并没有新事物诞生。1

人类学家定义

美国人类学家 Goldennweiser(Alexander Aleksandrovich Goldenweiser) 将内卷化定义成在某一个状态下,无法稳定,也无法转变为新的状态,只能不断内部运动,变得复杂的文化模式。2

农业领域

美国人类学家 Clifford Geertz ,在《农业内卷化——印度尼西亚的生态变化过程》(Agricultural Involution: The Processes of Ecological Change in Indonesia)中,借用 Goldenweiser 内卷化的概念研究爪哇岛的水稻农业,提出了 agricultural involution(农业化内卷)的概念。他认为水稻种植造成了社会复杂性,但没有促成技术或政治的变革,这个过程被他称为「内卷化」,Geertz 的使用,使得「内卷化」一词在人类学家和社会学家之间广为流传。

Geertz 在书中提到,1830 年以后爪哇受到了两方面的限制:

  • 增长的人口压力
  • 阻碍本地经济作物种植和商业部门发展的帝国主义

面对土地短缺采用了共同分担贫穷的模式,引起工业变革的失败3

国内学者的研究

而在国内对「内卷」一词的引用则是在黄宗智的《长江三角洲小农家庭与乡村发展》中,作者把内卷化这一概念用于中国经济发展与社会变迁的研究,他把通过在有限的土地上投入大量的劳动力来获得总产量增长的方式,即边际效益递减的方式,将一种没有发展的增长称为「内卷化」。

内卷化含义的扩大

但是在「内卷化」一词进入公众领域之后,其含义发生了巨大变化,在网络上,随着传播,内卷一词的语义发生了偏差。人们往往用其来指代恶性竞争、过度竞争,底层淘汰等现象。

总结

总结上面的这些定义,尽管在哲学、社会学等等不同领域中,不同学者都对内卷化展开了研究,但是对内卷化都有各自不同的定义,这些定义不尽相同,目前也没有一个统一的定义。

但是能看到一些统一性的地方:

  • 复杂化,相同的一件事情不断发展越来越复杂
  • 不断发展,却不会产生新的事物、思想

对于社会整体而言,内卷是一个停滞不前,无法转变的状态,虽然看着再不断发展,但是无法产生新的事物。而对于社会中的个体而言,内卷则使得个体投入更多,但却收获相同的结果,在边际效应上退化,对个体而言不管如何努力、改变都无法促进变革。

造成内卷的原因

信息不对称

在这里我无法再进一步探究内卷的原因,我也无法得知为什么整个社会会掉进这样的陷阱,我知道只有当市场上的人能够充分的去发明新的事物,能够自由的表达自己的观点,即使这个事物的发明可能带来灾难,即使这个观点可能是错误的,但只有这样的自由,才能不让社会陷入一潭死水,只有自由的讨论才能使得真理在辩论中脱颖而出。在信息公开透明、可以自由交流的社会中,信息的流动会弥补信息的不对称,从而使得社会中的个体能够根据自己所获得的信息做出决策,从而提高生产效率,从而发展出新的理论。就像[[密尔]]在[[论自由]]一书中所说,迫使意见不能被表达这是对整个人类的掠夺。

内卷化的误读

在我们日常生活中所讨论的内卷一词早已超出了其原本的含义,甚至在某一些地方的使用是错误的,只掩盖了原来的一些问题。很多人所说的「内卷」,其实并不是「内卷」。

就像之前看到的豆瓣短文

有人将职场学历要求越来越高视为内卷。事实上,这并不是内卷。例如一个工程师、银行经理的招聘要求从本科到硕士,从硕士到名校硕士,这其实是社会对岗位人才的要求越来越高,而不是内卷。 一个单位加班越来越严重,只要加班是有效的,这也不叫内卷,这属于劳动剥削。 将资本、风投偏爱低技术含量产业(例如共享单车、社区团购、滴滴这种的),而非原研药、精密机床、机器人这些高技术产业视为内卷。其实这也不是内卷。热钱真正偏爱的,是收益周期短的服务行业,“共享经济”、滴滴、美团、拼多多、游戏产业……这些看上去是互联网科技产业,其实它们是互联网加持的服务业,它们提供的是交通、配送、游戏娱乐、餐饮、购物等等服务。资本热钱真正偏爱的收益周期短、好讲故事的服务业。而这些生活服务产业又都是大众化服务,所以我们经常会在媒体宣传上、身边生活中看到这些服务产品(例如共享单车、美团骑手、游戏广告等),于是就会形成了资本只喜欢这种产业的错觉。实际上,高技术产业——造精密机床、造飞机、造机器人……这些技术产业都有它们自己的体系,只是它们离我们的生活较远,我们不太了解而已。所以这也算不上内卷。这属于是风投资本倾向于收益周期短、曝光率较高的服务业。如果真要靠投资共享单车那点钱去研究飞机、造芯片,那国家怎么可能发展呢?高科技企业自有其发展的政策、金融、市场体系。 将所有的加班都视为「内卷」这实际上掩盖了劳动法保障不健全的问题。假如劳动法保障够刚性,当暗示加班成为一种人人鄙视的违法行为时,加班生态是绝对可以好转的。而乱用内卷概念,则会把责任归于员工身上,反而忽略了法律制度、企业的问题,这就掩盖了真实的问题所在。

正确的使用「内卷」一词,才能让我们有针对性的解决问题,而不是像现在这样任其随意被滥用,从而掩盖真正能够被解决的问题。

reference

  1. 康德.判断力批判·第 2 版[M].北京:人民出版社,2002. 

  2. McGee, R. Jon; Warms, Richard L. Critical Contributions to Anthropology. Theory in Social and Cultural Anthropology: An Encyclopedia. SAGE Publications. 2013-08-28. ISBN 9781506314617 

  3. Cohen, Jeffrey H.; Dannhaeuser, Norbert. Involution and modernisation. Economic Development: An Anthropological Approach. Rowman Altamira. 2002-04-23. ISBN 9780759116696 


2021-11-03 thinking , involution

zinit 作者删库事件以及后续代替方案

前两天在新机器上使用我的 dotfiles 配置的时候,本来会自动安装 zinit,并进行一些初始化配置,但突然发现卡在了 zinit 配置拉取的过程中,还以为 GitHub 权限配置的问题,但仔细看了一下发现作者把整个仓库,以及个人页面都给删除了。 https://github.com/zdharma/zinit 这个仓库显示 404,我还以为产生了错觉,因为刚刚从 Google 点击跳转过来,Google 的结果还在,但自己一搜就发现原来真的是作者本人把仓库删除了。

所以也没有办法,除了我本地的一份缓存,最近一次提交还是 6 月份,所以只能搜索一下看看还有没有人有最新的备份,然后就看到了 GitHub 上之前贡献者新建的社区维护的仓库。把我 dotfiles 中的地址替换成该仓库目前暂时没有遇到任何问题。

另外要注意如果用到了如下的插件也要响应地替换:

zdharma/zinit   -> zdharma-continuum/zinit
zdharma/fast-syntax-highlighting   -> zdharma-continuum/fast-syntax-highlighting
zdharma/history-search-multi-word -> zdharma-continuum/history-search-multi-word

我个人也备份了一份代码 https://github.com/einverne/zinit 有兴趣可以看看。

不过我个人还是建议切换到社区维护的版本上。

一点感想

我不对作者的行为做任何评价,因为我并不清楚发生了什么,但是无疑这种删库的行为已经伤害了曾经的使用者,以及曾经贡献过代码的开发者。代码容易恢复,当作者仓库的 wiki 内容已经只能从 Google Cache 中恢复了,这无疑会对使用者造成一些困扰。

从这件事情延伸到生活中,以及这两天刚刚发生的 [[Notability]] 买断制更改为订阅模式造成的恶劣舆论影响,让我不经去思考,在如今这样的严重依赖数字化的生活中保持安定。在过去的经历中,已经让我渐渐地养成习惯,尽量去使用自由软件(能够获取源码),尽量去使用跨平台能导出可使用数据的软件(比如 Obsidian 即使再用不了,我还可以用任何编辑器去编辑我的笔记),如果有离线可用的,绝不用在线服务(Obsidian 相较于 Notion,Notion 开始就不在我的备选方案)。虽然已经这样的做法已经渐渐地让我不会再受到服务关闭的影响,但于此同时我需要考虑的东西就变得多了,数据安全问题,数据备份的问题,这只是涉及数字资产。

但生活中比数字资产重要多的东西也非常多,要做好任何重要的东西可能丢失的备份策略,如果丢失身份证呢,如果在旅行的过程中丢失了护照呢,或者手机失窃了呢? 去备份任何你生活需要依赖的东西,不要将手机和身份证放到一起,不要将银行卡和任何证件放到一起,去备份你生活中产生的任何个人的数据

reference


2021-10-31 zinit , github , repository

升级 Gogs(Docker) 从 0.11.91 到 0.12.3

很早之前在 QNAP 上就已经安装过老版本的 Gogs,一路升级到 0.11.91 之后很久没有更新,看了一下用的镜像还是 2020 年 2 月份的,看到 Gogs 也已经迭代了好几个版本,正好这一次做迁移,把 Gogs 从 QNAP 迁移到 VPS 上,随便想着也升级一下 Gogs 的版本。

因为之前使用 Docker 安装的,所以迁移的步骤也比较简单,两个部分数据,一部分是 MySQL 数据库,mysqldump 迁移导入即可,另一部分是写的磁盘持久化部分,tar 打包,scp 或 rsync 传输也比较快。

修改配置文件

Gogs 升级到 0.12.x 的时候官方有一些配置发生了变化,我的所有配置文件都在 ~/gogs 文件夹下,所以我需要修改:

vi ~/gogs/gogs/conf/app.ini

然后修改其中的配置。官方的 0.12.0 的 changelog 已经写的非常清楚了,将这些修改都更改了。

❯ cat ~/gogs/gogs/conf/app.ini
BRAND_NAME = Gogs
RUN_USER = git
RUN_MODE = dev

[database]
TYPE  = mysql
HOST     = db_host:3306
NAME     = gogs
USER     = gogs
PASSWORD   = BTxax
SSL_MODE = disable
PATH     = data/gogs.db

[repository]
ROOT = /data/git/gogs-repositories

[server]
DOMAIN           = git.example.com
HTTP_PORT        = 3000
EXTERNAL_URL     = https://git.example.com
DISABLE_SSH      = false
SSH_PORT         = 10022
START_SSH_SERVER = false
OFFLINE_MODE     = false

[mailer]
ENABLED = false

[service]
REQUIRE_EMAIL_CONFIRMATION = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION   = false
ENABLE_CAPTCHA         = true
REQUIRE_SIGNIN_VIEW    = false

[picture]
DISABLE_GRAVATAR        = false
ENABLE_FEDERATED_AVATAR = false

[session]
PROVIDER = file

[log]
MODE      = file
LEVEL     = Info
ROOT_PATH = /app/gogs/log

[security]
INSTALL_LOCK = true
SECRET_KEY   = Mj

可以大致参考我的,但不是每一个选项都要一致,最好自行查看每个选项的含义

cp app.ini app.ini.bak
sed -i \
  -e 's/APP_NAME/BRAND_NAME/g' \
  -e 's/ROOT_URL/EXTERNAL_URL/g' \
  -e 's/LANDING_PAGE/LANDING_URL/g' \
  -e 's/DB_TYPE/TYPE/g' \
  -e 's/PASSWD/PASSWORD/g' \
  -e 's/REVERSE_PROXY_AUTHENTICATION_USER/REVERSE_PROXY_AUTHENTICATION_HEADER/g' \
  -e 's/\[mailer\]/\[email\]/g' \
  -e 's/\[service\]/\[auth\]/g' \
  -e 's/ACTIVE_CODE_LIVE_MINUTES/ACTIVATE_CODE_LIVES/g' \
  -e 's/RESET_PASSWD_CODE_LIVE_MINUTES/RESET_PASSWORD_CODE_LIVES/g' \
  -e 's/ENABLE_CAPTCHA/ENABLE_REGISTRATION_CAPTCHA/g' \
  -e 's/ENABLE_NOTIFY_MAIL/ENABLE_EMAIL_NOTIFICATION/g' \
  -e 's/GC_INTERVAL_TIME/GC_INTERVAL/g' \
  -e 's/SESSION_LIFE_TIME/MAX_LIFE_TIME/g' \
  app.ini

使用命令 sed 替换。1

修改 Docker Compose 配置

然后在新的 VPS 上使用 docker-compose:

version: "3"
services:
  gogs:
    image: gogs/gogs:0.12.3
    container_name: gogs
    restart: always
    volumes:
      - ~/gogs:/data
    ports:
      - "10022:22"
    environment:
      VIRTUAL_HOST: git.example.com
      VIRTUAL_PORT: 3000
      LETSENCRYPT_HOST: git.example.com
      LETSENCRYPT_EMAIL: admin@example.info

networks:
  default:
    external:
      name: nginx-proxy

因为我使用 Nginx Proxy 做反向代理,如果需要可以去除掉。

然后直接 docker-compose up -d 启动即可。

这个时候我遇到一些问题。查看日志

less ~/gogs/gogs/log/gogs.log
2021/10/30 07:35:18 [ INFO] Gogs 0.12.3
2021/10/30 07:35:18 [FATAL] [...o/gogs/internal/route/install.go:75 GlobalInit()] Failed to initialize ORM engine: auto migrate "LFSObject": Error 1071: Specified key was too long; max key length is 767 bytes

会发现报错,这个错误 GitHub issue 上面也有人报错,之前因为迁移,没有来得及查看,后来仔细查看 Gogs 其他日志:

less ~/gogs/gogs/log/gorm.log

发现 gorm 日志中在创建 lfs_object 表的时候发生了错误。

2021/10/30 07:33:49 [log] [gogs.io/gogs/internal/db/db.go:166] Error 1071: Specified key was too long; max key length is 767 bytes
2021/10/30 07:33:49 [sql] [gogs.io/gogs/internal/db/db.go:166] [823.087µs] CREATE TABLE `lfs_object` (`repo_id` bigint,`oid` varchar(255),`size` bigint NOT NULL,`storage` varchar(255) NOT NULL,`created_at` DATETIME NOT NULL , PRIMARY KEY (`repo_id`,`oid`)) ENGINE=InnoDB [] (0 rows affected)

结合之前在 changelog 中看到的,升级到 0.12.x 之后 Gogs 会自动创建这张表,而创建失败了自然就无法启动报 502 错误了。

看这个错误 Error 1071,一看就是 MySQL 的错误。

Error 1071: Specified key was too long; max key length is 767 bytes

我的机器上使用的是 MariaDB,然后 gogs 数据库默认使用的是 utf8mb4_general_ci collation,默认情况下索引长度会有问题,所以将数据库的默认 collation 改成 utf8_general_ci 即可。

使用 phpmyadmin 修改 collation

登录 phpmyadmin 选中数据库 gogs,然后在 Operations 最下面可以看到 Collation 设置,直接修改保存即可。

使用命令行修改 collation

ALTER DATABASE <database_name> CHARACTER SET utf8 COLLATE utf8_general_ci;

2021-10-30 gogs , gitlab , github , git , docker , linux , mysql

电子书

最近文章

  • Logseq 第一次试用记录以及发布 Logseq 到网页 早之前在使用 [[Obsidian]] 的时候就有了解过 [[Logseq]] 但一直没有找到机会去尝试一下,毕竟我从 [[WizNote]] 迁移到 Obsidian 之后使用一直没有遇到任何问题,毕竟 Obsidian 够简单,也足够扩展性,并且基于 Markdown 文件的笔记是我过去一直在使用的方式。但现在让我有尝试使用一下 Logseq 的契机是因为在以前我都是使用 Vault 存放所有的笔记,然后使用其中一个 Blog 目录存放我想发布的内容,这样每一次我想发布一个具体的文章的时候就可以直接将文件移动到 Blog/_posts 目录之下,然后 Git 提交即可,但这个发布存在的问题便是其中的特殊双向链接 [[]] 会在页面上有些违和,而我看到 Logseq 可以将页面直接以 HTML 方式发布,并且也可以非常好的处理双向链接的问题,于是想来试一试。
  • Bash 中的 Parameter Expansion Parameter Expansion 是一个用来描述命令行中部分参数被展开(内容被替换)的术语。在大部分的场景中,被展开的参数通常会带有 $ 符号,在一些特定的场景中,额外的花括号(curly braces)也是必须的。
  • 不丢失数据 降级 Android 应用版本 之前一次不消息把 Google Play Store 中的自动更新启用了,之后一个夜里把所有应用都更新了,不过有些应用本来就不想升级的,比如网易云音乐(有一些低版本没有广告,没有乱七八糟的直播什么的),微信。所以想着能不能在不丢失数据的情况下降级应用。简单的搜索了一下果然可以。
  • 使用 k3s 和 WireGuard 网络部署 Kubernetes 集群 在今年的黑色星期五入手了几个不同 VPS 提供上的机器,包括之前的 A400,以及 HostHatch,手上的机器可以用来学习搭建一个 Kubernetes。
  • 使用 Netmaker 快速组建 WireGuard mesh 网络 前段时间在逛 LET 看到有人发帖询问在 VPS 上安装了什么,论坛中的 @Galeej 提到,自己将所有的 VPS 通过 Tailscale 私有网络连接,对其他 VPS 的操作都通过一台新加坡的 relay 服务器转发(并且设置其他 VPS 的网络连接只允许特定的 IP),而所有需要暴露到公开互联网上的端口都隐藏在 Cloudflare 之后,我看到这样的配置之后发现这样的 VPS 配置非常的安全,公开互联网上没有暴露任何可以攻击的端口,所有的服务都在 WireGuard 内网中。