前情提要

已经在群晖部署了Emby+Aria2,但是番剧一直是手动下载,比较麻烦。想着能不能订阅自动下载之类的,翻了一遍Github看到了BGmi这个仓库,尝试下来体验还算不错。

部署

由于是在群晖部署,自然能用docker就用docker,BGmi也贴心提供了docker镜像。镜像除了BGmi本体之外,还包含了一个Transmission,如果没有安装Aria2的姥爷们,也可以直接使用Transmission。

ok,那么下面开始正式部署:

  1. 打开群晖的docker-注册表,搜索codysk/bgmi-all-in-one,下载并启动。
    下载
    启动
  2. 选择高级设置,进行相关配置
    高级设置
  3. 配置应用开机自启
    开机自启
  4. 配置文件映射,这里有两个文件夹需要映射,
    • 首先是/bgmi这是所有数据文件以及配置文件所在的地方,映射到群晖方便管理。
    • 其次是将Emby的资源文件夹映射到docker内,需要注意的是,由于BGmi的配置项中下载路径只有一个,所以这里的路径要与Aria访问的路径保持一致。
      文件映射
  5. 配置端口映射,这里也有两个端口需要映射:
    • 80端口:这是BGmi的web管理界面端口,可以映射到自己想要的端口上去。
    • 9091端口:这是内置的Transmission端口,这里我们用Aria2,所以可以自动,也可以干脆删除。
      端口映射
  6. 配置环境变量,添加一项环境变量BGMI_SOURCE,代表默认的数据源类型,这里可以填写bangumi_moe/mikan_project/dmhy,我们使用蜜柑计划作为我们默认的数据源。
    环境变量
    完成docker配置之后,就可以一路启动啦!

BGmi配置

部署完成后,进入到docker的bash,进行BGmi的配置。
这里需要用到bgmi config命令,下面先来介绍一下用法:

1
2
3
4
5
bgmi config # 查看当前各项设置的默认值.
bgmi config KEY # 查看某项设置的默认值
bgmi config KEY value # 修改某项设置
# example:
bgmi config ARIA2_RPC_TOKEN 'token:xxxxx'

我们需要修改的配置项如下:

1
2
3
4
bgmi config SAVE_PATH "/bangumi" #下载路径,这个路径要确保Aria2能够访问到
bgmi config DOWNLOAD_DELEGATE "aria2-rpc" #设置下载工具
bgmi config ARIA2_RPC_URL "http://172.17.0.1:6800/rpc" #ARIA2 RPC URL,这里由于我们使用的是docker,所以填写宿主机ip 172.17.0.1,具体可以根据情况更改
bgmi config ARIA2_RPC_TOKEN "token:xxx" #这里填写Aria2的token,如果没有则留空,或者不做修改

订阅

配置完成后,则可以开始进行番剧的订阅。 下面,我们以最近大火的《石蒜反冲》为例(乐

  1. 先前往蜜柑,得到番剧名称“莉可丽丝”
    莉可丽丝
  2. 订阅番剧,使用BGmi的命令行进行订阅:
    1
    2
    3
    4
    5
    bgmi add "莉可丽丝" 
    # 这里可以跟多个参数同时订阅多部番剧,如:
    bgmi add "莉可丽丝" "鬼灭之刃" "Love Live! Superstar!!"
    # 要注意的是,此处订阅默认是从最新一集开始的,如果想要从头开始下载,需要加上 --episode 0
    bgmi add "莉可丽丝" --episode 0

筛选

由于一部番剧下有不同的字幕组/清晰度/语言,所以在下载前需要设置番剧的筛选规则。

1
2
3
4
5
bgmi list # 列出目前订阅的番剧
bgmi fetch "莉可丽丝"
# include和exclude会忽略大小写。`720p`和`720P`的效果是相同的
bgmi filter "莉可丽丝" --subtitle "喵萌奶茶屋" --include 1080P --exclude "繁体"
bgmi fetch "莉可丽丝"

你可以按照自己的喜好,直到fetch到的列表是你所想要的内容。

下载

运行下面的命令更新番剧列表并且下载番剧,如果一切顺利的话,下载任务就会发送到Aria2啦! 前往SAVE_PATH查看吧!

1
bgmi update --download

定时任务

2022.8.2:发现docker里自带了10分钟一次的crontab,群晖的定时任务似乎没有什么必要了(点点点

《笨蛋白茶》

先前的内容,可以不用看

既然已经完全了解如何下载番剧,那我们自然可以用任务来让BGmi定时运行来自动下载订阅的番剧啦!

  1. 进入docker创建更新脚本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 进入docker容器
    > docker exec -it codysk-bgmi-all-in-one1 /bin/bash
    # 创建更新脚本
    [email protected] < cd /bgmi/conf && touch cron-update.sh
    [email protected] < vi cron-update.sh
    # 脚本内容
    ---- cron-update.sh -----
    #!/bin/sh
    echo "subscribe list:"
    bgmi list
    bgmi update --download
    ---- cron-update.sh -----
    # 更改文件权限
    [email protected] < chmod a+rwx ./cron-update.sh
    # 退出docker容器
    [email protected] < exit
    > exit
  2. 建立定时任务
    • 打开群晖 控制面板->任务计划,选择 新增->计划的任务->用户定义的脚本
      新增->计划的任务->用户定义的脚本
    • 配置任务
      账号选择root
      调度时间
    • 填写执行脚本
      1
      docker exec codysk-bgmi-all-in-one1 /bin/bash -c "/bgmi/conf/cron-update.sh"
      执行脚本

ok!这样就可以每天自动下载番剧,而不用去关心字幕组什么时候更新啦!

参考链接

  1. BGmi
  2. bgmi-docker-all-in-one

以上!まいど~


0. V8介绍

官方文档这么介绍:V8是谷歌开源高性能JavaScript和WebAssembly引擎,用C++编写。它主要用在Chrome和Node.js中,等等。

简单的来说,我们知道,JavaScript是解释型的语言,需要逐行解释执行

V8则是一种C++开发的JavaScript解释器,它将JavaScript编译成可执行代码,即机器码。

1. V8工作流程概述

JavaScript是解释型的语言,解释型的语言先天就有执行效率上的不足。为此,V8引擎同时采用了解释执行和编译执行这两种方式,也就是在解释执行的同时进行编译,这种方式称为JIT (Just in Time) 即时编译

解释执行:V8在执行JavaScript源码时,会先通过解析器Parser将源码解析成AST,解释器Ignition会将AST转化为字节码,一边解释一遍执行。

即时编译:Ignition同时会记录某一代码片段的执行次数,如果执行次数超过了某个阈值,这段代码便会被标记为热点代码(HotSpot),同时将运行信息反馈给优化编译器TurboFan,会将这部分热点代码的字节码优化并编译,生成机器码更高效地运行。

graph LR
  源代码 --Parser--> AST
  AST --Ignition--> 字节码
  字节码 --Ignition--> 逐行解释为机器码执行
  字节码 -.Ignition高频执行.-> HotSpot
  HotSpot --TurboFan--> 编译为机器码执行

1.1 Parser 解析器 生成语法抽象树(AST)

生成AST中的一个优化是惰性解析(Lazy Parsing),因为源码在执行前如果全部完全解析的话,不仅执行时间过长,而且会消耗更多的内存。
惰性解析就是指如果遇到并不是立即执行的函数,只会对其进行预解析(Pre-Parser),当函数被调用时,才会对其完全解析。
预解析时,只会验证函数的语法是否有效、解析函数声明以及确定函数作用域,并不会生成AST,这项工作由Pre-Parser预解析器完成。

1.2 Ignition 解释器 生成字节码及解释执行

Ignition 是 V8 的解释器,负责生成和执行字节码。字节码与平台无关,也就是说无论 X86 架构或 ARM 架构,同一份 JavaScript 源码生成的字节码序列是相同的,解释器逐条读取字节码并执行。

字节码是机器码的抽象,可以看作是小型的构建块。相比机器码,字节码不仅占用内存少,而且生成字节码的时间很快,提升了启动速度。

1.3 Turbofan 优化编译器 对部分代码直接编译执行

Turbofan 是 V8 的优化编译器,它使 JavaScript 执行的更快,但比Ignition需要更多的编译时间,所以 V8 只对热点代码(HotCode)使用 Turbofan。

JavaScript 代码先由 Ignition 执行,并判断当该代码片段执行次数达到设定值时变为热点代码,由TurboFan把它编译为更高效的机器码储存起来,等到下次再执行到这段代码时,就会用现在的机器码替换原来的字节码进行执行,这样大大提升了代码的执行效率。

2. V8工作流程中一些具体实现

2.1 隐藏类 JavaScript 动态类型

我们知道,JavaScript是动态类型语言,运行时数据类型会发生变化。V8 采用 C++ 编写,C++ 是强类型语言,要求类型确定。类型确定的 C++ 是如何表达类型不确定的 JavaScript 呢?

答案是:C++ 申请一块内存,这块是 JavaScript 的一个对象,并约定这块内存的开始位置保存内存的解读方式,操作这块内存之前先查解读方法,通过改变解读方式来模拟 JavaScript 对象的动态变化。

具体来说:操作 JS 数据前先查询类型,再操作。为此,V8采用了隐藏类(Hidden Class),因为 JavaScript 程序员看不到这个 class,所以称为隐藏类。注意: 隐藏类的术语是 Map,它的含义是地图,说明如何解读内存,不是 JavaScript Map 机制。

V8通过查询存储空间的第一个位置,就可以找到Map。这个Map大小是80byte,存储信息的格式与位置也是固定的,存储信息包括:JavaScript 对象的存储空间有哪些成员,成员类型,成员偏移地址等。所以说,Map 就是地图。

2.2 Inline Cache 内联缓存

我们已经知道,V8在查找对象的属性(例如obj.x)时流程是这样的:查找对象 obj 的隐藏类,再通过隐藏类查找 x 属性偏移量,然后根据偏移量获取属性值。

我们把上面这个获取 x 属性偏移量的过程称为寻址方法。

简单的来说:IC负责缓存对象的寻址方法,下次执行该函数时,直接使用寻址方法(V8 中的术语是 Handler),节省了计算寻址方法的时间。

3. 如何编写优化的 JavaScript

3.1 隐藏类

我们已经知道,V8 通过查询 Map,可以知道存储空间内存放了什么,怎么存放的,进而正确操作该对象。

例如有以下代码:

1
2
3
4
5
function Point(x,y) { 
this.x = x;
this.y = y;
}
const obj = new Point(1,2);

一旦声明了一个新的方法实例,Javascript就会创建一个隐藏类C0。

在此时还没有声明任何的属性,所以C0现在为空。
一旦第一个语句“this.x=x”被执行,V8将会基于C0创建第二个隐藏类C1。C1记录了可以找到属性x在内存中的位置。

在这个例子中,x保存在偏移量为0的位置,这表示可以将一个内存中的对象目标看作是一段连续的空间。而这段空间中的第一段偏移代表着属性x。与此同时,V8将会用“类偏移”操作更新C0,这代表着属性x已经添加到了目标对象。之后,目标对象所对应的隐藏类指针将指向C1。

每当目标对象添加一个新的属性,对象的旧的隐藏类就会变换路径到一个新的隐藏类。隐藏类的重要之处在于可以使经过相同创建过程创建的对象共享隐藏类。假如两个对象共享一个隐藏类,并向两个对象中同时添加相同的属性,那么这种变换将会保证变换后得到相同的隐藏类,这样代码就得到了优化。
当“this.y=y”执行时将重复上面的操作。一个新的叫C2的隐藏类将被创建,然后对C1进行类变换表明属性y已经添加到了目标对象,最后将隐藏类指向C2。这样目标对象的隐藏类就更新到了C2。

注意:隐藏类的变换取决于对目标对象的属性添加顺序。请注意下面的代码:

1
2
3
4
5
6
7
8
9
10
11
function Point(x,y) { 
this.x = x;
this.y = y;
}
var obj1 = new Point(1,2);
var obj2 = new Point(3,4);
// 至此为止 obj1和obj2都共享同一个隐藏类
obj1.a = 5;
obj1.b = 10;
obj2.b = 10;
obj2.a = 5;

直到第6行为止,obj1和obj2都共享同一个隐藏类。但是,当属性a和b以相反的顺序添加到了两个对象中,这导致最后两个对象以不同的变换路径产生了两个不同的的隐藏类。

因此,隐藏类更希望开发者不要频繁添加对象的属性,更希望对象的属性实例化顺序是固定的。像上述的例子,希望你只按照相同的顺序添加属性,先添加a,后添加b,这样obj1与obj2就可以共享同一个隐藏类。如果经常毫无规律的变动对象的内部成员,就会保存越来越多的隐藏类,带来不必要的性能开销。

3.2 Inline Cache

inline cache 提升了 JavaScript 的运行性能,但对于使用次数较少的 JavaScript 函数使用 inline cache 不会带来太多的性能,反而影响 V8 的性能。因此 V8 规定,函数调用次数超过 8 次时才开启 inline cache。
例如有以下函数:

1
2
3
4
5
6
7
function getfoo(obj){
return obj.foo;
}
const o1 = {"x":42,"y":43,"foo","41"};
for(let i=0;i<10;i++){
getfoo(o1);
}

运行getfoo时,V8会先读取obj的Map,找到foo的偏移量,然后才能获取foo。 当运行超过8次时,obj.foo的寻址方法会被缓存。

所以inline cache更希望开发者不要变动o1的结构,更希望开发者对 o1 的读写操作是固定的。具体在本例中,希望你只对 foo 操作,inline cache 只需保存属性 foo 的寻址方式即可,如果经常变动o1的内部成员,inline cache就会保存越来越多的寻址方法,并且增加很多不必要的判断过程。影响性能。

3.3 TurboFan

TurboFan将字节码编译为机器码并存储起来执行,来使得JavaScript运行的更快,但相比解释执行也需要更多的首次编译时间,这也是为什么V8只针对热点代码使用TurboFan。

TurboFan不希望程序的行为发生变化,例如我们有以下代码:

1
2
3
4
5
function add(x,y){
return x+y;
}
console.log(add(1, 2));
console.log(add("hello ", "world"));

我们知道 add() 是多态的,可以传入多种类型的参数。但是灵活也就意味着效率不高,需要判断类型,越界检测等等…

Turbofan 针对 add(1,2) 优化时,它笃定你的 add() 只用于整数加法运行,把其编译为本地机器码的加法运算,去掉了各种不必要的判断。针对 add(“hello “,”world”) 优化时,TurboFan会把其编译为字符串的拼接操作。add() 的多态方便了开发者,减少了代码量,但如果 add() 毫无规律地在不同操作数之间使用,会导致TurboFan编译更多的时间,存储更多的机器码,优化效果很差,所以 Turbofan 不希望程序行为变化。

优化建议

  1. 对象属性: 以相同顺序实例化对象的成员,以便于共享隐藏类。
  2. 对象结构: 不要变动对象的结构,减少寻址方法内联缓存造成的额外开销。
  3. 无规律的程序行为:减少无规律的程序行为,例如随意的多态调用,减少turbofan编译和存储机器码的额外开销。
  4. 可复用的方法:方法尽量复用,提高使用频次,增加热点代码,使turbofan能够介入进行编译运行,提高程序运行效率

参考内容:

  1. 《Chrome V8 源码》55. 优化技术综述,如何提升 JS 运行速度
  2. How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code
  3. V8中的隐藏类(Hidden Classes)和内联缓存(Inline Caching)
  4. 浅析V8引擎,让你更懂JavaScript!

以上!まいど~


前情提要

hexo发现经过CI之后,所有文章的 更新时间(mtime) 变成了CI运行的时间。

解决思路

在CI checkout代码时,使用文件最后一次涉及的commit时间作为文件的修改时间,进行mtime的恢复。

解决方案

1
2
3
4
5
6
7
8
9
10
11
# .github/workflows/action.yml
...
- uses: actions/[email protected]
# 如果使用的是GithubAction的actions/checkout,记得加上下面的内容,对仓库历史进行完整的签出
with:
fetch-depth: 0
# 恢复文件修改时间
- name: Restore file modification time
run: |
find source/_posts -name '*.md' | while read file; do touch -d "$(git log -1 --pretty="@%ct" -- $file)" $file; done
...

代码解释

1
2
3
4
5
6
# 列出需要恢复修改时间的文件
find source/_posts -name '*.md'
# 循环
# 使用 git log -- $file 得到该文件最后修改的commit 并且使用pretty选项获取时间戳
# 使用 touch -d 来将文件的atime以及mtime修改为commit的时间戳
while read file; do touch -d "$(git log -1 --pretty="@%ct" -- $file)" $file; done

以上!まいど~


前情提要

公司里放了一台黑裙,但是没有公网IP,想到用openvpn拨到家里的路由器,再进行端口转发达到内网穿透,公网访问的目的。

总之的总之三步走,需要openwrt上有server,以及群晖能够有client进行拨入,最后配置端口转发


1. openwrt部署openvpn server

openwrt官方提供了详实的文档,如下
[OpenWrt Wiki] OpenVPN server

但是这个版本生成的配置,因为用到了<tls-crypt-v2>,群晖没法识别。可以使用下面这个 previous version of OpenWrt 的旧版本。

在这里插入图片描述
链接在这里↓
[OpenWrt Wiki] OpenVPN server (old)


接下来就是跟着文档走一遍, 我会对官方的文档进行一个翻译和加注:

1. 准备
如果需要请先设置DDNS客户端。 安装依赖包. 指定一些临时的环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 安装包
opkg update
opkg install openvpn-openssl openvpn-easy-rsa

# 配置环境变量
OVPN_DIR="/etc/openvpn"
OVPN_PKI="/etc/easy-rsa/pki"
OVPN_PORT="1194"
OVPN_PROTO="udp"
OVPN_POOL="192.168.8.0 255.255.255.0" # 注:这里可以指定openvpn的网段
OVPN_DNS="${OVPN_POOL%.* *}.1"
OVPN_DOMAIN="$(uci get [email protected][0].domain)"

# 拿到外网ip作为外网地址
. /lib/functions/network.sh
network_flush_cache
network_find_wan NET_IF
network_get_ipaddr NET_ADDR "${NET_IF}"
OVPN_SERV="${NET_ADDR}"

# 如果有ddns 则从ddns拿到全域名(FQDN)作为外网地址
NET_FQDN="$(uci -q get [email protected][0].lookup_host)"
if [ -n "${NET_FQDN}" ]
then OVPN_SERV="${NET_FQDN}"
fi

2. 秘钥管理
使用 EasyRSA 管理 PKI. 必要时使用私钥密码保护.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 配置环境变量
export EASYRSA_PKI="${OVPN_PKI}"
export EASYRSA_REQ_CN="ovpnca"
export EASYRSA_BATCH="1"

# 移除并且重新初始化pki目录
easyrsa init-pki

# 生成DH变量
easyrsa gen-dh

# 创建新的CA
easyrsa build-ca nopass

# 为服务端生成密钥对并在本地签名
easyrsa build-server-full server nopass

# 为客户端生成密钥对并在本地签名
easyrsa build-client-full client nopass
#注:此处为生成客户端配置必要,如果需要多个客户端的,可以多次运行本行 只需要更改“client”, 如:
#easyrsa build-client-full client1 nopass
#easyrsa build-client-full client2 nopass
#easyrsa build-client-full client-dsm nopass
#注:更改后的名称会作为生成后配置文件的名称

# 生成TLS PSK
openvpn --genkey --secret ${OVPN_PKI}/tc.pem

3. 防火墙
将VPN网络视为私有网络,将VPN接口分配到LAN区域,以减少防火墙的设置。允许从WAN区域访问VPN服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 配置防火墙
uci rename [email protected][0]="lan"
uci rename [email protected][1]="wan"
uci del_list firewall.lan.device="tun+"
uci add_list firewall.lan.device="tun+"
uci -q delete firewall.ovpn
uci set firewall.ovpn="rule"
uci set firewall.ovpn.name="Allow-OpenVPN"
uci set firewall.ovpn.src="wan"
uci set firewall.ovpn.dest_port="${OVPN_PORT}"
uci set firewall.ovpn.proto="${OVPN_PROTO}"
uci set firewall.ovpn.target="ACCEPT"
uci commit firewall
/etc/init.d/firewall restart

4. VPN服务
配置VPN服务,生成客户端配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 配置环境变量
OVPN_DH="$(cat ${OVPN_PKI}/dh.pem)"
OVPN_TC="$(sed -e "/^#/d;/^\w/N;s/\n//" ${OVPN_PKI}/tc.pem)"
OVPN_CA="$(openssl x509 -in ${OVPN_PKI}/ca.crt)"
NL=$'\n'

# 配置VPN服务,生成客户端配置文件。
#注:如果你清楚的知道你在做什么,可以更改下方ovpn配置相关的代码,可以省掉后续多个客户端单独修改配置的工作量
umask go=
ls ${OVPN_PKI}/issued \
| sed -e "s/\.\w*$//" \
| while read -r OVPN_ID
do
OVPN_KEY="$(cat ${OVPN_PKI}/private/${OVPN_ID}.key)"
OVPN_CERT="$(openssl x509 -in ${OVPN_PKI}/issued/${OVPN_ID}.crt)"
OVPN_EKU="$(openssl x509 -in ${OVPN_PKI}/issued/${OVPN_ID}.crt -purpose)"
case ${OVPN_EKU} in
(*"SSL server : Yes"*)
OVPN_CONF="\
port ${OVPN_PORT}
proto ${OVPN_PROTO}
server ${OVPN_POOL}
topology subnet
client-to-client
keepalive 10 60
persist-tun
persist-key
push \"dhcp-option DNS ${OVPN_DNS}\"
push \"dhcp-option DOMAIN ${OVPN_DOMAIN}\"
push \"redirect-gateway def1\"
push \"persist-tun\"
push \"persist-key\"
<dh>${NL}${OVPN_DH}${NL}</dh>"
OVPN_EXT="conf" ;;
(*"SSL client : Yes"*)
OVPN_CONF="\
nobind
client
remote ${OVPN_SERV} ${OVPN_PORT} ${OVPN_PROTO}
auth-nocache
remote-cert-tls server"
OVPN_EXT="ovpn" ;;
esac
cat << EOF > ${OVPN_DIR}/${OVPN_ID}.${OVPN_EXT}
user nobody
group nogroup
dev tun
${OVPN_CONF}
<tls-crypt>${NL}${OVPN_TC}${NL}</tls-crypt>
<key>${NL}${OVPN_KEY}${NL}</key>
<cert>${NL}${OVPN_CERT}${NL}</cert>
<ca>${NL}${OVPN_CA}${NL}</ca>
EOF
done
/etc/init.d/openvpn restart
# 此处就能够看到client.ovpn 把这个文件通过openwrt的备份功能导出来,上传到群晖即可
ls ${OVPN_DIR}/*.ovpn

到这里openwrt的openvpn服务端配置完毕,可以使用openvpn的备份功能(System -> Backup/Flash Firmware). 从备份包导出客户端的配置文件。


2. 群晖连接openvpn

到了群晖这一步就比较简单,按照官网的流程,打开 控制面板->网络->网络界面->新增->创建VPN配置文件,把刚才导出的client.ovpn上传即可

注:其实这里因为群晖的错误提示非常的“友好”,连接失败只会提示“失败,请检查配置”或者“认证错误”,导致排错非常的困难,踩了不少坑。比如不支持的标签,属性等等,总之注意点都写在第一步了。如果还是有问题,请自行前往/var/log/messages进行openvpn相关日志的排查。

按照下图位置上传client.ovpn
在这里插入图片描述
这里只需要填写 “导入.ovpn” 文件这一项
在这里插入图片描述
记得勾选这两个选项
在这里插入图片描述
到这一步 openvpn的连接可以正常建立了。


3.配置端口转发

需要完成端口转发,首先我们得先把群晖的ip固定下来,否则ip一变动,端口转发规则就失效了。
需要完成openvpn客户端的ip固定,我们需要在server配置中添加规则。

编辑服务端配置

1
vim ${OVPN_DIR}/server.ovpn

添加下面这一行,用来指定客户端config文件夹

1
2
# 这里的文件夹路径可以自定
client-config-dir /etc/openvpn/ccd

保存后新增客户端config 注意这里的“client”需要和.ovpn文件名相符,也就是easyrsa创建客户端秘钥时的名字

1
vim /etc/openvpn/ccd/client

添加下面这一行,来绑定ip

1
2
# 这里的192.168.8.108 即为你想要绑定的ip
ifconfig-push 192.168.8.108 255.255.255.0

保存后重启openvpn

1
/etc/init.d/openvpn restart

接下来正式开始配置端口转发,进入openwrt, 菜单选择 Network->Firewall->Port Forwards
在这里插入图片描述
新增一条端口转发,按照下图填写
在这里插入图片描述
至此,应该就能够使用openwrt的端口转发达到内网穿透的目的了。

以上!まいど~


0.概念说明

一切开始的开始 先说明一些概念

物理卷(Physical Volume)即PV: 物理卷就是指硬盘分区或从逻辑上与磁盘分区具有同样功能的设备(如RAID),是LVM的基本存储逻辑块,但和基本的物理存储介质(如分区、磁盘等)比较,却包含有与LVM相关的管理参数。
卷组(Volume Group)即VG: LVM卷组类似于非LVM系统中的物理硬盘,其由物理卷组成。可以在卷组上创建一个或多个“LVM分区”(逻辑卷),LVM卷组由一个或多个物理卷组成。
逻辑卷(Logical Volume)即LV: LVM的逻辑卷类似于非LVM系统中的硬盘分区,在逻辑卷之上可以建立文件系统(比如/home或者/usr等)。

总之 LV建立在VG之上,VG建立在PV之上,是PV的集合,PV即物理卷。

本文的总体思路为: 扩容PV-> 扩容LV -> 扩容根目录空间

1.VM添加空间

使用vmware增加空间 ↓
在这里插入图片描述

2.使用fdisk扩展原有分区

此处参考了Linux下使用fdisk扩展分区容量,原文关键部分摘录如下

对/dev/sda4进行扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
[[email protected] ~]: fdisk /dev/sda #对/dev/sda进行操作

WARNING: DOS-compatible mode is deprecated. It's strongly recommended to
switch off the mode (command 'c') and change display units to
sectors (command 'u').

Command (m for help): p #查看分区表信息

Disk /dev/sda: 32.2 GB, 32212254720 bytes
255 heads, 63 sectors/track, 3916 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0005210c

Device Boot Start End Blocks Id System
/dev/sda1 * 1 26 204800 83 Linux
Partition 1 does not end on cylinder boundary.
/dev/sda2 26 1301 10240000 83 Linux
/dev/sda3 1301 1497 1572864 82 Linux swap / Solaris
/dev/sda4 1497 2611 8952832 83 Linux

Command (m for help): d #删除分区
Partition number (1-4): 4 #删除需要扩容的分区(这里为sda4)

Command (m for help): p #再次查看分区信息,/dev/sda4已被删除

Disk /dev/sda: 32.2 GB, 32212254720 bytes
255 heads, 63 sectors/track, 3916 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0005210c

Device Boot Start End Blocks Id System
/dev/sda1 * 1 26 204800 83 Linux
Partition 1 does not end on cylinder boundary.
/dev/sda2 26 1301 10240000 83 Linux
/dev/sda3 1301 1497 1572864 82 Linux swap / Solaris

Command (m for help): n #创建新的分区
Command action
e extended
p primary partition (1-4)
p #创建为主分区
Selected partition 4
First cylinder (1497-3916, default 1497): #经对比,正好和上一个磁盘柱一致,默认即可
Using default value 1497
Last cylinder, +cylinders or +size{K,M,G} (1497-3916, default 3916):
Using default value 3916 #直接默认就可以

Command (m for help): p #查看分区表信息

Disk /dev/sda: 32.2 GB, 32212254720 bytes
255 heads, 63 sectors/track, 3916 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0005210c

Device Boot Start End Blocks Id System
/dev/sda1 * 1 26 204800 83 Linux
Partition 1 does not end on cylinder boundary.
/dev/sda2 26 1301 10240000 83 Linux
/dev/sda3 1301 1497 1572864 82 Linux swap / Solaris
/dev/sda4 1497 3916 19436582 83 Linux

Command (m for help): wp #保存并退出,如果创建有误,直接退出不要保存即可
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

3.刷新PV空间

对分区扩容完成后,会发现空间依旧没有增加,此时需要先刷新pv大小,使用pvresize:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[[email protected]_ubuntu~]: pvdisplay #先列出pv
--- Physical volume ---
PV Name /dev/sda4
VG Name ubuntu-vg
PV Size <19.00 GiB / not usable 16.50 KiB
Allocatable yes (but full)
PE Size 4.00 MiB
Total PE 9983
Free PE 0
Allocated PE 9983
PV UUID 2CF0fb-auL8-N1Xk-Lpo1-pdzo-MLN5-R0QAIX

[[email protected]_ubuntu~]: pvresize /dev/sda4 #调用pvresize进行刷新
Physical volume "/dev/sda3" changed
1 physical volume(s) resized / 0 physical volume(s) not resized

[[email protected]_ubuntu~]: pvdisplay #再次查看pv大小 发现pv大小已经正常
--- Physical volume ---
PV Name /dev/sda4
VG Name ubuntu-vg
PV Size <39.00 GiB / not usable 16.50 KiB
Allocatable yes (but full)
PE Size 4.00 MiB
Total PE 9983
Free PE 0
Allocated PE 9983
PV UUID 2CF0fb-auL8-N1Xk-Lpo1-pdzo-MLN5-R0QAIX

4.为LV增加空间

对pv扩容完成后,会发现lv的空间依旧没有增加,此时需要扩展lv大小,使用lvextend:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[[email protected]_ubuntu~]: lvdisplay #列出lv信息 发现lv空间没有增加
--- Logical volume ---
LV Path /dev/ubuntu-vg/ubuntu-lv
LV Name ubuntu-lv
VG Name ubuntu-vg
LV UUID 3THGqb-Me9D-KFEK-QxT5-dP8Y-hUqa-knbAa3
LV Write Access read/write
LV Creation host, time ubuntu-server, 2021-10-12 06:49:31 +0000
LV Status available
# open 1
LV Size <19.00 GiB
Current LE 9983
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:0
[[email protected]_ubuntu~]: lvextend -L +20G /dev/ubuntu-vg/ubuntu-lv /dev/sda4 #调用lvextend进行扩容
[[email protected]_ubuntu~]: lvdisplay #再次列出lv信息 发现lv空间已经增加
--- Logical volume ---
LV Path /dev/ubuntu-vg/ubuntu-lv
LV Name ubuntu-lv
VG Name ubuntu-vg
LV UUID 3THGqb-Me9D-KFEK-QxT5-dP8Y-hUqa-knbAa3
LV Write Access read/write
LV Creation host, time ubuntu-server, 2021-10-12 06:49:31 +0000
LV Status available
# open 1
LV Size <39.00 GiB
Current LE 9983
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:0

5.扩展根目录空间

对lv扩容完成后,会发现根目录的空间依旧没有增加,此时需要刷新根目录大小,使用resize2fs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[[email protected]_ubuntu~]: df -h #发现/根目录空间还是没有增加
Filesystem Size Used Avail Use% Mounted on
udev 1.9G 0 1.9G 0% /dev
tmpfs 393M 1.8M 391M 1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv 19G 18G 19G 99% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
/dev/loop0 56M 56M 0 100% /snap/core18/2128
/dev/loop1 56M 56M 0 100% /snap/core18/2253
/dev/loop3 43M 43M 0 100% /snap/snapd/14066
/dev/loop2 117M 117M 0 100% /snap/docker/1125
/dev/loop4 33M 33M 0 100% /snap/snapd/13270
/dev/sda2 976M 150M 759M 17% /boot
tmpfs 393M 0 393M 0% /run/user/0

[[email protected]_ubuntu~]: resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv #使用resize2fs进行刷新
resize2fs 1.44.1 (24-Mar-2018)
Filesystem at /dev/mapper/ubuntu--vg-ubuntu--lv is mounted on /; on-line resizing required
old_desc_blocks = 3, new_desc_blocks = 5
The filesystem on /dev/mapper/ubuntu--vg-ubuntu--lv is now 10222592 (4k) blocks long.

[[email protected]_ubuntu~]: df -h #再次查看,发现/根目录空间已经增加
Filesystem Size Used Avail Use% Mounted on
udev 1.9G 0 1.9G 0% /dev
tmpfs 393M 1.8M 391M 1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv 39G 18G 19G 49% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
/dev/loop0 56M 56M 0 100% /snap/core18/2128
/dev/loop1 56M 56M 0 100% /snap/core18/2253
/dev/loop3 43M 43M 0 100% /snap/snapd/14066
/dev/loop2 117M 117M 0 100% /snap/docker/1125
/dev/loop4 33M 33M 0 100% /snap/snapd/13270
/dev/sda2 976M 150M 759M 17% /boot
tmpfs 393M 0 393M 0% /run/user/0

以上!まいど~


问题表现

vue.config.js中无法配置自定义devtool选项,无论是使用configureWebpack还是chainWebpack都无法改变source-map的类型。官方文档也没有提供相关的说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// vue.config.js
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
// 两种方式均无法更改devtool
module.exports = {
configureWebpack: {
devtool: "source-map",
// 2021.10.26更新 ↓罪魁祸首
plugins: [
Components({
resolvers: [ElementPlusResolver()]
})
]
},
chainWebpack: config => {
config
.devtool('source-map')
}
}

问题原因

2021.10.26更新: 问题产生的原因不是vue-cli,而是element-ui提供的按需加载插件unplugin-vue-components/resolvers破坏了sourcemap,具体的原因不清楚,似乎插件作者也不是很有sourcemap方面的开发意愿。 总之遇到这个问题的话看一看有没有这个玩应吧。
在这里插入图片描述


本来下面这里写了一大通,最后发现不是这个原因,错怪尤老师了,对不起x 下面的都可以不用看了

vue-clinpm run serve时强制使用了'eval-cheap-module-source-map'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// @vue/cli-service/lib/commands/serve.js
// line: 51
api.chainWebpack(webpackConfig => {
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
// 注意这里:在npm run serve时强制使用了'eval-cheap-module-source-map'
webpackConfig
.devtool('eval-cheap-module-source-map')

webpackConfig
.plugin('hmr')
.use(require('webpack/lib/HotModuleReplacementPlugin'))

// https://github.com/webpack/webpack/issues/6642
// https://github.com/vuejs/vue-cli/issues/3539
webpackConfig
.output
.globalObject(`(typeof self !== 'undefined' ? self : this)`)

if (!process.env.VUE_CLI_TEST && options.devServer.progress !== false) {
webpackConfig
.plugin('progress')
.use(require('webpack/lib/ProgressPlugin'))
}
}
})

截止目前(2021年10月26日),vue-cli的最新realese版本(v4.5.14)中,依旧存在这个问题。但是github的开源代码的dev分支已经修复此问题(查看代码),不知道什么时候会发布到正式版本中去。

解决方案

方案一: 手动修改node_modules/@vue/cli-service/lib/commands/serve.js:54, 将'eval-cheap-module-source-map'改为想要的模式。弊端是install之后可能需要再改一下,如果不在意source-map的话还是权衡一下利弊。

方案二: 使用vue-clidev分支源代码编译一份最新的来使用。


以上!まいど~


问题表现

在windows安装完ssh-agent,添加多个ssh key之后,git clone依旧提示Permission Denied。
(安装参见:https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_keymanagement

问题原因

Git需要知道Windows ssh-agent服务的位置, 可以通过添加GIT_SSH环境变量来解决该问题。

以下文字来源:https://snowdrift.tech/cli/ssh/git/tutorials/2019/01/31/using-ssh-agent-git-windows.html

Git for Windows uses the ssh binaries included with git by default. While this works well enough in most situations, one side-effect is that git has no idea how to talk to the Windows ssh-agent service. In order for git commands to use the Windows ssh-agent service, git needs to be informed of the system OpenSSH path. To accomplish this, the environment variable GIT_SSHneeds to be set with the path of the system OpenSSH executable.

解决方法

Run the following command to update the environment variable:
在PowerShell中运行以下命令添加环境变量:

1
PS >  [Environment]::SetEnvironmentVariable("GIT_SSH", "$((Get-Command ssh).Source)", [System.EnvironmentVariableTarget]::User)

问题表现

添加~/.bash_profile之后,bash失去高亮,~/.bashrc中的逻辑不运行。
添加前
添加前↑
添加后
添加后↑

问题原因

在添加~/.bash_profile之后,~/.profile会被覆盖不再运行,而load bashrc的逻辑在~/.profile中,导致~/.bashrc也一并失效。
~/.profile中加载bashrc的逻辑

1
2
3
4
5
6

## 解决方案
- 方案一:将```~/.bash_profile```中的逻辑移至```~/.profile```,并删除```~/.bash_profile```。
- 方案二:在```~/.bash_profile```中添加以下代码保证```~/.profile```中的代码运行。
```bash
source ~/.profile

以上!まいど~

1
sudo apt-get -o Acquire::http::proxy="http://host:port/" install xxx

问题代码

1
2
<!--这里的muted绑定不起作用-->
<audio :muted="isMuted" ref="audioPlayer"></audio>

援引Github上的issue回复

在这里插入图片描述

翻译:muted属性类似于<input>value属性,如果他在元素初始化时是内联的,那么属性值将作为初始值;但是如果在一开始并没有内联,只是后续更改了attribute ^1 值,那么将不会起任何效果,正确的做法是设置相对应的property ^1 值。

解决方式:使用watch来手动设置muted

html

1
<audio ref="audioPlayer"></audio>

javascript

1
2
3
4
5
6
7
8
9
10
watch:{
isMuted(val){
if(val){
this.$refs.audioPlayer.muted = 'muted'
}
else{
this.$refs.audioPlayer.muted = ''
}
}
}