关于 AxionL

Arch Linux,博客名:初等记忆体。

RSS 地址: https://axionl.me/index.xml

请复制 RSS 到你的阅读器,或快速订阅到 :

AxionL RSS 预览

About

2022-11-23 08:00:00

記憶體自述

直到一切都消失不见,才发现曾经近在眼前。

无法阻止,也无法释怀。

唯有记录,

在我身边。

其餘資訊

Photos

2022-11-23 08:00:00

Maddy 自建邮件服务

2022-01-10 22:15:41

Featured image of post Maddy 自建邮件服务

从白嫖 Yandex 域名邮箱到 FastMail 付费托管邮箱,一直在找适合自己的域名邮件服务,传统的自建方式模块过于分散,上手难度较大,后在 NickCao 老师推荐下尝试了 Maddy。Maddy 目前只提供了命令行运行的 Linux 服务端,WebUI 或者本地客户端需自行选择,仅需手动配置一下 POP3/IMAP(收) 和 SMTP(发) 服务端地址即可。我目前使用的是 Thunderbird (电脑)和 K-9 Mail(Android)。

下载:Github | 上游服务器 文档:maddy.email

服务器

部分 VPS 提供商会为了防止广告等原因会禁用 25 号 TCP 端口的 SMTP 端口,但多数情况下(比如 Google Cloud 就不允许)也可以开工单说明邮箱用途和性质,对于个人性质的域名邮箱来说一般不会有太多限制。如果机器本身或者 VPS 提供商还配有防火墙,请打开对应端口(25,465,993)。

使用邮件服务需要关闭 CDN 代理,故存在机器 IP 暴露风险,在选择机器时候注意避开其它敏感数据服务

配置

可以从 docker 拉取,但这里以 binary + 自己配置方式为主。

maddy.conf

完整版配置

一般放在 /etc/maddy/maddy.conf

 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
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# 预设了三个变量,方便后续使用
$(hostname) = mail.example.com
$(primary_domain) = example.com
$(local_domains) = $(primary_domain)

# 如果要使用 nginx 反代,这里可以选择 tls off,但如此一来没法生成 dkim 密钥对
# 在之后检查时日志内会有安全警告,故推荐直接用 maddy 管理
# tls off
tls file /etc/letsencrypt/live/$(local_domains)/fullchain.pem /etc/letsencrypt/live/$(local_domains)/privkey.pem

# 数据用 SQLite3 存储较为简单、轻量
auth.pass_table local_authdb {
 table sql_table {
 driver sqlite3
 dsn credentials.db
 table_name passwords
 }
}

storage.imapsql local_mailboxes {
 driver sqlite3
 dsn imapsql.db
}

# ----------------------------------------------------------------------------
# SMTP endpoints + message routing

hostname $(hostname)

table.chain local_rewrites {
 optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
 optional_step static {
 entry postmaster postmaster@$(primary_domain)
 }
 optional_step file /etc/maddy/aliases
}

msgpipeline local_routing {
 destination postmaster $(local_domains) {
 modify {
 replace_rcpt &local_rewrites
 }

 deliver_to &local_mailboxes
 }

 default_destination {
 reject 550 5.1.1 "User doesn't exist"
 }
}

# smtp 使用 25 号端口发送邮件
smtp tcp://[::]:25 {
 # tls self_signed
 limits {
 # Up to 20 msgs/sec across max. 10 SMTP connections.
 all rate 20 1s
 all concurrency 10
 }

 dmarc yes
 check {
 require_mx_record
 dkim # 若无则不检查
 spf
 }

 source $(local_domains) {
 reject 501 5.1.8 "Use Submission for outgoing SMTP"
 }
 default_source {
 destination postmaster $(local_domains) {
 deliver_to &local_routing
 }
 default_destination {
 reject 550 5.1.1 "User doesn't exist"
 }
 }
}

# 如果使用 nginx 反代则这里监听到本地端口即可 tcp://127.0.0.1:587
# 不使用则邮件客户端以 SSL/TLS 方式直接访问该地址
submission tls://[::]:465 {
 limits {
 # Up to 50 msgs/sec across any amount of SMTP connections.
 all rate 50 1s
 }

 auth &local_authdb

 source $(local_domains) {
 check {
 authorize_sender {
 prepare_email &local_rewrites
 user_to_email identity
 }
 }

 destination postmaster $(local_domains) {
 deliver_to &local_routing
 }
 default_destination {
 modify {
 dkim $(primary_domain) $(local_domains) default
 }
 deliver_to &remote_queue
 }
 }
 default_source {
 reject 501 5.1.8 "Non-local sender domain"
 }
}

target.remote outbound_delivery {
 limits {
 # Up to 20 msgs/sec across max. 10 SMTP connections
 # for each recipient domain.
 destination rate 20 1s
 destination concurrency 10
 }
 mx_auth {
 dane
 mtasts {
 cache fs
 fs_dir mtasts_cache/
 }
 local_policy {
 min_tls_level encrypted
 min_mx_level none
 }
 }
}

target.queue remote_queue {
 target &outbound_delivery

 autogenerated_msg_domain $(primary_domain)
 bounce {
 destination postmaster $(local_domains) {
 deliver_to &local_routing
 }
 default_destination {
 reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
 }
 }
}

# ----------------------------------------------------------------------------
# IMAP endpoints
# 同上,使用 nginx 反代则改为监听本地端口 tcp://127.0.0.1:143
imap tls://[::]:993 {
 auth &local_authdb
 storage &local_mailboxes
}

Nginx Config(可选)

如果是由 Maddy 自身直接处理 TLS 则不需要该项配置

 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
upstream maddy_imaps {
 # 指向本地 Maddy IMAP 监听服务端口
 server 127.0.0.1:143;
}

upstream maddy_smtps {
 # 指向本地 Maddy SMTP 服务监听端口
 server 127.0.0.1:587;
}

server {
 listen 993 ssl;
 proxy_pass maddy_imaps;
 ssl_certificate /etc/letsencrypt/live/<example.com>/fullchain.pem; # 换成自己的域名证书
 ssl_certificate_key /etc/letsencrypt/live/<example.com>/privkey.pem; # 换成自己的域名证书
 ssl_protocols SSLv3 TLSv1.1 TLSv1.2 TLSv1.3;
 ssl_ciphers HIGH:!aNULL:!MD5;
 ssl_session_timeout 4h;
 ssl_handshake_timeout 30s;
}

server {
 listen 465 ssl;
 proxy_pass maddy_smtps;
 ssl_certificate /etc/letsencrypt/live/<example.com>/fullchain.pem; # managed by Certbot
 ssl_certificate_key /etc/letsencrypt/live/<example.com>/privkey.pem; # managed by Certbot
 ssl_protocols SSLv3 TLSv1.1 TLSv1.2 TLSv1.3;
 ssl_ciphers HIGH:!aNULL:!MD5;
 ssl_session_timeout 4h;
 ssl_handshake_timeout 30s;
}

注册账户

maddy 的 systemd service 正常启动后可以使用:

1
2
3
4
5
6
7
$ sudo -u maddy -s # 切换到 maddy 用户
$ maddyctl creds create <[email protected]> --password <YourPassword>
$ maddyctl creds list # 查看你刚创建的用户名
$ maddyctl imap-acct create <[email protected]> # 创建一个邮件储存账户
$ maddyctl imap-acct list # 查看刚创建的 imap 储存账户
$ maddyctl imap-mboxes list <[email protected]> # 可以看到该账户下有哪些分类
$ maddyctl imap-msgs list <[email protected]> <INBOX> # 可以查看当前账户对应分类接收到的邮件,一般收件在 INBOX 中

DNS 设置

这里以 Cloudflare 托管 DNS 服务为例,其它应该也能找到对应 DNS 设置面板。使用了子域名 mail.example.com 作为邮件服务专用。

类型 名称 内容 代理状态
A mail 服务器实际 IPv4 地址 仅限 DNS
AAAA mail 服务器实际 IPv6 地址(如果有) 仅限 DNS
MX @ mail.example.com 仅限 DNS
TXT mail v=spf1 mx ~all 仅限 DNS

spf 值这里推荐使用仅允许 mx,若有其他来源也可以添加。

Reverse DNS

为了避免拒收,进一步提高邮件投递率,需要配置 rDNS 以便收信方邮件服务商溯源。

以 Vultr 为例,其 Reverse DNS 页面在 机器详情 > Settings > IPv4 / IPv6 选项卡内,在 Reverse DNS 中填入自己的邮件服务域名即可,如 mail.example.com,如果存在多条公共 IP 地址,则都需要填写。

多域名配置 (如无可跳过)

  1. local_domains 后面加上新的域名
1
2
3
$(primary_domain) = example.com
$(secondary_domain) = example.org
$(local_domains) = $(primary_domain) $(secondary_domain)
  1. 在 tls file 后面添加对应的证书,如果用 nginx 反代的话则添加对应的 host 路由和域名。
1
tls file /etc/letsencrypt/live/$(primary_domain)/fullchain.pem /etc/letsencrypt/live/$(primary_domain)/privkey.pem /etc/letsencrypt/live/$(secondary_domain)/fullchain.pem /etc/letsencrypt/live/$(secondary_domain)/privkey.pem
  1. 将新域名的 dkim 密钥内容也添加到 DNS 的 TXT 记录中

  2. dmarc 和 rDNS 同理

客户端

客户端有诸多选择,不变的是手动设置方式:

小结

Oh My Yandex

可以写一些稍微正经的内容,发送到 https://www.mail-tester.com 内所给的邮件地址,来测试自己的邮箱评分。

虽然说自建邮箱可能还是存在邮件投递到垃圾箱里的问题,但有了 Maddy 后搭建一个自己的域名邮箱确实变成了相对简单的工作。

[OhMyQt 系列] _00__搭建环境

2021-07-24 00:39:13

Featured image of post [OhMyQt 系列] _00__搭建环境

前言

项目地址: axionl/OhMyQt

本文是 OhMyQt 系列的第 00 章节,主要介绍在 Windows 11 和 ArchLinux 下如何搭建一个可用的 Qt 开发环境。

在 ArchLinux 上搭建

基本软件安装

首先你需要安装 qt5-base,同时推荐安装对应的文档包 qt5-doc

1
2
$ sudo pacman -S qt5-base
$ sudo pacman -S qt5-doc # 可选

然后安装一个合用的编辑器,这里推荐使用 qtcreator

1
$ sudo pacman -S qtcreator

因为本教程主要是介绍 QML 的开发,所以还需要安装 qt5-declarative

1
$ sudo pacman -S qt5-declarative

安装完成之后验证版本信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ qmake -v
QMake version 3.1
Using Qt version 5.15.2 in /usr/lib

$ pacman -Qs qt5-declarative
local/qt5-declarative 5.15.2+kde+r29-1 (qt qt5)
 Classes for QML and JavaScript languages

$ qtcreator -version

Qt Creator 4.15.2 based on Qt 5.15.2

软件配置

首先打开 qtcreator,新建一个 QtQuick 项目

新建项目

然后输入名称和路径

项目名和路径

下一步编译系统建议选择 cmake

编译系统

QT 版本保持默认

QT 版本

因为暂时是教学项目,暂时不添加语言文件

语言文件

kit 保持默认的 Desktop 就好,因为我们最终也是要做一个桌面应用

Kit

会创建的文件如下所示,你也可以选择加入 git 作为版本控制

summary

点击左下角的运行按钮,就可以看到运行的 HelloWorld 窗口了

HelloWorld

恭喜你,已经搭建好了本教程的开发环境!

在 Windows 11 上搭建

通过 Installer 安装

https://www.qt.io/download

注册账号并下载开源版本(需遵守 GPL 许可并限于个人使用)

installation folder

此处安装路径至今未支持带空格,故不要选择常见的 C:\Program Files 等目录

手动最小化安装则选择 Custom Installation

select components

至少需要选择一个版本的 Qt 支持(如 MinGW 8.1.0 64-bit 包含了 Qt 和其他所需的一些环境),而 QtCreator 作为 IDE 在下方已经被默认勾选了,还有 CMake 如果需要或是已经手动安装则可消去。等待联网装好后即可使用

通过第三方包管理器安装

chocolately 也提供了 QtCreator 的安装脚本和基本的 Qt5 SDK 环境

1
choco install -y qt5-default qtcreato

脚本会自动下载安装,默认路径为 C:\Qt

配置工具链

toolkits

QtCreator 左边 Projects 有配置工具链的地方,通常来说会自动检测是否已经安装对应工具并设置,如果需要自定义或者不在查找范围内的目录,则可以手动进行指定。

若在开发时终端需要运行 Qt 程序,则需要将库所在目录添加到环境变量 PATH 中(如 C:\Qt\5.15.2\mingw81_64\bin

environment

若是打包和分发的话则需要带上对应的动态链接库。

小结

Linux 平台上开发相对方便,而 Windows 平台上开发则受到没有合适包管理的限制,需要自己折腾一下。下一节将会正式开始教程中的第一个 Demo,一个用于展示自己内容的 Hello World 程序。

[OhMyQt 系列] 01_HelloWorld

2021-07-20 10:58:40

Featured image of post [OhMyQt 系列] 01_HelloWorld

前言

项目地址: axionl/OhMyQt

本文以项目构建的角度介绍一个简单 Qt 应用的开发,其余具体的环境安装和配置稍后会在第 0 节内容中放出。

从看见到设计

我们需要一个简单的展示页面作为上手的第一个项目:

假设我们已经做了出来,它长下面这个样子,你需要做的是把它保留在你的想象中,我们再回过头来考虑如何实现。

preview

布局

我们把窗口想象成一个盒子(而不是一个平面或者一张桌子,因为盒子横看有四壁,俯瞰有纵深),而所需要填入的内容当成另一个小些的盒子,套入到窗口这个大盒子中。结合我们多年糊 PPT 的水平,可以对界面有如下设计要求:

layout

如上图所示,我们把这一些要求的集合称为 “布局” 也即 Layout,它决定了我们软件设计的基本框架。上面的每一项具体内容称为 “元素” 即 Element / Item。有的人设计 PPT 的时候会说:“应甲方要求,标题一定要大!”,那这属于元素的“属性”,即 Property。于是我们来抽象实现一下这个布局(注意不是直接能用的代码):

1
2
3
4
5
6
7
// 基本元素如下
Window { // 窗口一个大盒子
 Box { // 里面套个小盒子
 Title {} // 盒子里面有标题
 Description {} // 标题下面有描述
 }
}

进一步加上居中对齐和间距:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Window {
 Box {
 anchors.centerIn: parent // 居中对齐大盒子

 Title {
 anchors.horizontalCenter: parent.horizontalCenter // 水平对齐小盒子
 }

 Description {
 anchors.horizontalCenter: parent.horizontalCenter // 水平对齐小盒子
 }
 }
}

可以看到,这里引用了一个 parent 的概念,实际上指代上一层父级元素,套娃套在里面的才是娃,文字和描述的父级元素是小盒子,小盒子的父级元素是大窗口。再补上元素的属性和间距:

 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
Window {
 width: 960 // 窗口宽度
 height: 720 // 窗口高度

 Box {
 anchors.centerIn: parent
 implicitWidth: 360 // 宽度 360 单位(多数情况下理解为像素)
 implicitHeight: 128 // 高度 128 单位

 Title {
 anchors.horizontalCenter: parent.horizontalCenter // 水平对齐小盒子
 font.pixelSize: 24 // 标题一定要大
 }

 Box {
 visible: false // 一个看不见的盒子
 implicitHeight: 16 // 用于拉开标题和描述的间距
 }

 Description {
 anchors.horizontalCenter: parent.horizontalCenter // 水平对齐小盒子
 font.pixelSize: 16 // 精致的描述用小字
 }
 }
}

这些具体元素我们以后会写到如何实现,现在需要用 QtQuick.Controls 提供的一些默认元素替代:

 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
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

Window {
 width: 960
 height: 720

 Rectangle {
 anchors.centerIn: parent
 implicitWidth: 360
 implicitHeight: 128
 color: "#ef7e9ceb"

 Text {
 id: title
 anchors.horizontalCenter: parent.horizontalCenter
 text: "这是一个大标题"
 font.pixelSize: 48
 }

 Rectangle {
 color: "transparent"
 implicitHeight: 16
 }

 Text {
 id: description
 anchors.horizontalCenter: parent.horizontalCenter
 text: "我吞下玻璃会伤身体"
 font.pixelSize: 24
 }
 }
}

实现之后的效果可能长成了这个样子,对了,但没全对。因为我们这个 Rectangle 不太智能,是个“硬盒”,元素之间会挤在一起,而不是自动拉开保持社交距离。

awesome_qml

 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
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12

Window {
 width: 960
 height: 720

 Rectangle {
 anchors.centerIn: parent
 implicitWidth: 360
 implicitHeight: 128
 color: "#ef7e9ceb"

 ColumnLayout { // 一个上下布局的模板
 anchors.fill: parent // 沾满小盒子的空间
 spacing: 16 // 原先看不见的盒子用默认提供的间距属性实现

 Text {
 id: title
 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter // 换成 Layout 下的居中对齐模式
 text: "这是一个大标题"
 font.pixelSize: 48
 }

 Text {
 id: description
 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
 text: "我吞下玻璃会伤身体"
 font.pixelSize: 24
 }
 }
 }
}

可以把 ColumnLayout 当作一个更加智能的盒子,它能够把挤在一起的元素上下依次排开。而与之相对的 RowLayout 则是将元素左右排开。

column_layout

对比原本想像中的布局要求,可以说是基本实现了(打个九折不过分吧)。

样式

基本的元素提供了默认的样式和属性,回顾想象图目前还缺少:

以一个基本的矩形为例,有如下常用属性(QtQuick-Rectangle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import QtQuick.Controls 2.12

Rectangle {
 width: 16
 height: 16
 radius: 8 // 圆角半径
 color: "white" // 颜色
 border.width: 1 // 边框宽度
 border.color: "whitesmoke" // 边框颜色
}

带阴影的矩形可以这样实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import QtQuick.Controls 2.12
import QtGraphicalEffects 1.0 // 包含 DropShadow 效果

Rectangle {
 // ...

 layer.enabled: true
 layer.effect: DropShadow {
 horizontalOffset: 1 // 横向偏移
 verticalOffset: 1 // 纵向偏移
 radius: 16 // 阴影半径
 samples: 17 // 采样率(越高效果越好,性能消耗也增大)
 color: "#10000000" // ARGB(透明度,红,绿,蓝)
 }
}

为矩形添加渐变色:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Rectangle {
 // ...

 rotation: 0 // 渐变角度
 gradient: Gradient {
 GradientStop {
 position: 0
 color: "#ef7e9ceb" // 起始颜色
 }

 // 可以添加多段

 GradientStop {
 position: 1
 color: "#c5000000" // 结束颜色
 }
 }
}

qml.qrc 文件中管理所有的静态资源,可以右键在编辑器中打开,然后添加图片资源:

qml.qrc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Rectangle {
 id: background

 // ...

 Image {
 anchors.fill: background // 填充背景矩形
 source: "qrc:/background.png" // 图片资源
 fillMode: Image.PreserveAspectCrop // 填充方式
 z:-1 // 由于需要将渐变色作为滤镜效果,所以图片的层级下调
 }
}

如果去掉 z 轴高度设置会发现渐变色在图片下层不可见:

no_z_index

事件和交互

事件触发

虽然 QtQuick.Controls 中提供了 Button 控件,但是我们仍然可以先为自己创立一个简单的按钮,通过鼠标点击这一事件理解其基本实现。

 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
Rectangle {
 id: button
 implicitHeight: 48
 implicitWidth: 156
 radius: implicitHeight / 2
 border.color: "white" // 边框颜色
 color: "#ef7e9ceb" // 背景颜色

 Text {
 anchors.verticalCenter: parent.verticalCenter // 居中对齐
 anchors.horizontalCenter: parent.horizontalCenter

 text: "初等記憶體" // 文字内容
 color: "white" // 文字颜色
 }

 // 大小和位置覆盖全按钮的鼠标动作区域
 MouseArea {
 anchors.fill: parent
 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
 hoverEnabled: true // 允许响应鼠标停留

 onEntered: {
 parent.color = Qt.lighter(button.color, 0.8); // 进入颜色变深
 parent.opacity = 1.0; // 不透明
 }

 onExited: {
 parent.color = Qt.lighter(button.color, 0.9); // 退出颜色变浅
 parent.opacity = 0.7;
 }

 onClicked: {
 parent.color = Qt.lighter(button.color, 1.1); // 点击颜色变亮
 }
 }
}

Qt.lighter()Qt.darker()color 属性内置的两个方法,用于相对调整颜色的深浅。运行时,鼠标放到按钮上方即可看见按钮颜色变化。

darker-method | lighter-method

这里有一个常见用法可以将需要设置的属性用 property 暴露出来,这样便于统一设置和更改,以及将来要写自己组件时便于外部设置,使用 state 可以提供若干个对象默认状态进行切换,这部分后面会见到。

1
2
3
4
5
6
7
Rectangle {
 // ...
 property string buttonColor: "#ef7e9ceb"
 property string buttonText

 color: buttonColor // 引用属性值
}

事件交互(响应)

那么如何使得按钮能够响应我需要的事件呢,比如说点击按钮后用系统默认浏览器打开一个网址:

1
2
3
4
5
6
7
8
9
Rectangle {
 property string url: "https://axionl.me"

 text: "ClickMe!"

 onClicked: {
 openURL(url); // 我们需要实现一个形如这样的方法
 }
}

好在作为一个常见功能,QML 已经将其置为默认对象的方法之一,点击后即可调用默认浏览器打开所设置的网址

openUrlExternally-method

1
2
3
4
5
6
7
8
9
Rectangle {
 property string url: "https://axionl.me"

 text: "ClickMe!"

 onClicked: {
 Qt.openUrlExternally(url); // 使用默认提供的方法
 }
}

自定义类型和方法

在 C++ 中先写一个基于 QObject 父类的类对象,并实现所需的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// my_button.hpp
class MyButton : public QObject
{
 Q_OBJECT
public:
 explicit MyButton(QObject *parent = nullptr) {};

 Q_INVOKABLE void openUrl(const QUrl& url) {
 bool err = QDesktopServices::openUrl(url);
 if (err) {
 qDebug() << "Failed to open url";
 }
 };
};

我们用 Q_INVOKABLE 宏声明了一个可以被外部调用(指 QML 中用 javascript 调用)的方法 void openUrl(const QUrl& url)

QDesktopServices | 该方法是对 QDesktopServices 类中打开链接方法的套用,其在不同平台下其会调用系统浏览器来打开链接。当然也可以自行实现,比如 Linux 平台如果装了 extra/xdg-utils 可以利用 xdg-open 来打开链接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// main.cpp
import "my_button.hpp"

int main(int argc, char *argv[])
{
 QGuiApplication app(argc, argv);

 // ...

 // 声明自定义对象
 qmlRegisterType<MyButton>("MyApp", 1, 0, "MyButton");

 // ...

 QQmlApplicationEngine engine;

 return app.exec();
}

简单注册如上,qmlRegisterType<类名>("包名", 主版本号, 次版本号, "对象名");,然后在所需的 qml 文件中引入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import MyApp 1.0

Item {
 // 以对象名为组件名
 MyButton {
 id: myButton // 实例化对象
 }

 Rectangle {
 property string url: "https://axionl.me"

 text: "ClickMe!"

 onClicked: {
 myButton.openUrl(url); // 使用自己实现的方法
 }
 }
}

也可以先在 C++ 中实例化一个对象,再传入对象引用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// main.cpp
import "my_button.hpp"

int main(int argc, char *argv[])
{
 QGuiApplication app(argc, argv);

 // ...


 // 实例化一个对象
 MyButton my_button;

 // ...

 QQmlApplicationEngine engine;

 // 传入对象引用
 engine.rootContext()->setContextProperty(QStringLiteral("myButtonObject"), &my_button); // 全局使用时,设置唯一名称

 return app.exec();
}

此时我们不再需要注册声明该对象,也无需在 QML 中引入和实例化,而是直接调用该对象的方法。

1
2
3
4
5
6
7
8
9
Rectangle {
 property string url: "https://axionl.me"

 text: "ClickMe!"

 onClicked: {
 myButtonObjecton.openUrl(url); // 直接调用对象方法
 }
}

小结

preview

至此已经实现了 Demo 的全部功能:

本节基本介绍了 QML 的组织结构和简单事件交互的实现方法,下一节将以一个新的例子来介绍信号量和信号槽这一对重要的概念,以及 C++ 后端代码到 QML 的数据绑定实现。

另外还建立了个人讨论群方便大家互相交流: https://t.me/Qt_CN

The Wayland Protocol

2021-04-24 17:32:39

Featured image of post The Wayland Protocol

最近还在填坑 “The Wayland Protocol” 这本书的翻译,由于内容比较独立,所以用 mdbook 单独构建了一份文档页。目前最大的个感受是其设计模式上和 grpc 有异曲同工之妙,等全篇完工后在博客简要介绍一下,希望不咕。

文档地址:wayland.axionl.me

Linux 用户环境变量设置

2021-02-23 20:35:26

Featured image of post Linux 用户环境变量设置

序言

Linux 下的用户环境变量配置常显得十分琐碎,如 .xprofile、.pam_environment 亦或是各种 shell 配置文件。

参考了依云的 Linux 的环境变量怎么设 一文,遂选用 systemd 的 environment.d 作为用户环境变量配置方案。

介绍

配置文件目录如下:

其写法如下:

apps.conf

由于我是 fish + tmux 用户,需要自己导出一下生成的环境变量文件到命令行。

~/.config/fish/conf.d/env_init.fish

1
2
3
if not contains $PATH $USER
 export (/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator)
end

show environments

关于 KDE/Plasma 的补充

Plasma and the systemd startup

在 Plasma 5.21 和 Systemd 246 以及后续更新版本中,可以使用 systemd 来启动和管理 KDE 服务。

其中一个好处在于:其环境变量可直接继承自 environment.d 而无需再手动设置 systemd-environment-d-generator 生成的环境变量。

启用后重启生效。

1
$ kwriteconfig5 --file startkderc --group General --key systemdBoot true

使用 Systemd 进行管理后可利用其 CGroups 限制资源分配和使用等诸多特性(或许可以给你的应用加上 cgproxy?),进一步细化和统一用户配置方案,再次感谢开发者为此做出的努力。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ systemctl --user set-property app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope MemoryMax=1500M MemoryLimit=1G
$ systemctl --user status app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope
 ● app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope - Telegram Desktop
 Loaded: loaded (/usr/share/applications/telegramdesktop.desktop; transient)
 Transient: yes
 Drop-In: /run/user/1000/systemd/transient/app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope.d
 └─50-MemoryLimit.conf, 50-MemoryMax.conf
 Active: active (running) since Tue 2021-02-23 21:02:37 CST; 5min ago
 Tasks: 44 (limit: 18425)
 Memory: 351.0M (max: 1.4G limit: 1.0G)
 CPU: 20.056s
 CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/app-telegramdesktop-b9317feb02e54b4c93dd1c97a06711a4.scope
 └─15363 /usr/bin/telegram-desktop --

[归档] 用 Chezmoi 管理配置文件

2021-01-29 10:04:51

Featured image of post [归档] 用 Chezmoi 管理配置文件

前言

项目地址:twpayne/chezmoi

早前看了 Farseerfc 老师这篇译文,开始用 Stow 打理自己家目录中的配置文件,其实现非常简单直接:在一个特定的目录保留原始配置文件,并在其原本对应的位置创建软链接。

【譯】使用 GNU stow 管理你的點文件

相比之下,其有如下缺点促使我切换到 Chezmoi:

安装

https://www.chezmoi.io/docs/install/

多数包管理器可以搜索到此包进行安装,包括 Linux、BSD、macOS、Windows 在内的多种平台。

1
$ curl -sfL https://git.io/chezmoi | sh

快速上手

初始化

1
$ chezmoi init --apply

这会在 ~/.local/share/chezmoi 中创建一个目录权限为 700 的 git 仓库,用于保存配置文件。

若包管理器未安装自动补全,则可由下例所示,添加到 <bash/zsh/fish/powershell> 配置当中。

1
$ chezmoi completion fish > ~/.config/fish/completions/chezmoi.fish

添加文件或目录

1
2
3
4
5
6
$ chezmoi add .nanorc # 添加文件
$ chezmoi add -x .config/fish/functions/ # 添加文件夹
$ chezmoi add -xa .config/fish/functions # 递归添加文件夹和子目录下的全部内容
$ chezmoi add -T .xprofile # 添加临时内容
$ chezmoi add .tmux.conf --follow # 添加软链接对应的原始内容,而不是软链接符号
$ chezmoi managed # 列出所管理的内容路径

编辑和应用

1
$ chezmoi edit ~/.bashrc --apply

同步与合并

1
2
$ chezmoi source pull -- --rebase && chezmoi diff
$ chezmoi apply --verbose

其同步操作与 git 本身无异,通过 -- 可以传递所需参数。

1
2
$ chezmoi source add .nanorc
$ chezmoi source commit -- -m "Initial commit"

支持自动提交,但考虑到用户可能意外添加敏感数据的情况下不建议开启,配置文件的介绍将在稍后提到。

1
2
3
4
5
# ~/.config/chezmoi/chezmoi.toml

[sourceVCS]
 autoCommit = true
 autoPush = false

也可以一次打包成压缩包放 U 盘上硬件备份

1
$ chezmoi archive --output=dotfiles.tar

小结

doctor

其还提供了一个名为 secret 的参数,是各种加密存储管理软件命令行客户端的包装 (cli wrapper),chezmoi doctor 可以检测机器中对应可执行文件的安装状态,此部分内容和应用会在后面的数据模板中提及。

至此,快速上手基本功能已介绍完毕,初次使用发现它像是一个功能丰富的 wrapper,提供了诸多自动化的帮助,包括编辑、冲突合并、同步和导出备份等。可以注意到一个与其他以软链接形式的 dotfiles manager 不同,由于在设计之初就考虑到了多设备、跨平台的兼容方案,chezmoi 并没有选择以软链接的形式来替换源文档所在路径,一方面提供了改动与应用之间的缓冲,另一方面规避了多平台的兼容性问题。

数据模板

Hugo 作为博客或者使用过 go template 的朋友一定不会对此感到陌生,得益于 golang 的实现,chezmoi 不仅做到了单文件、跨平台使用,还继承了其强大的数据模板功能,可以对于不同的设备实现不同的数据配置,比如配置不同的 ssh key,这也是与诸多的 dotfiles manager 的不同之处。

创建模板

.gitconfig 为例,原文如下:

1
2
3
[user]
 name = axionl
 email = axionl@example.com

为了在不同的设备电脑上使用不同的 git 账户这一需求,要将其中的用户信息数据与配置文件进行绑定,而原文件将作为 .tmpl 为后缀的模板文件保存。

1
2
3
4
# ~/.local/share/chezmoi/dot_gitconfig.tmpl
[user]
 name = "{{ .name }}"
 email = "{{ .email }}"

模板文件如上,{{ }} 是 go template 的数据变量标记符,. 代表了当前变量,即在非循环体内,. 就代表了传入的那个变量。一般的变量定义可由赋值表达式定义 {{ $variable := Balabala }},不过这里 chezmoi 会根据配置文件自动生成和对应。

1
$ chezmoi add --autotemplate ~/.gitconfig

chezmoi 提供了自动生成模板的功能,但是聪明的生成器未必懂你心意,正如我的 hostname 恰好等于 username 而在其他机器上未必如此。

dot_gitconfig.tmpl

此时重新 $ chezmoi edit ~/.gitconfig 对应编辑的就是模板文件(就不要图快加上 --apply 了,以免产生不必要的手滑)。$ chezmoi data 的内容便是其妄加猜测的根源,对应变量也可以手动添加使用,如下,根据系统类型来判断模板中的内容是否作用于该机器,- 符号用于移除前或者后的空格,更多语法可去 go template 参阅。

1
2
3
4
5
6
7
8
# ~/.local/share/chezmoi/dot_gitconfig.tmpl
{{ if eq .chezmoi.os "linux" -}}
[core]
 editor = nvim
{{ else }}
[core]
 editor = {{ .github.editor }}
{{ end -}}

对于大量需要编辑内容的配置文件,也可根据不同机器直接做多份同名文件,并对应进行切换。

1
2
# symlink_dot_bashrc.tmpl
.bashrc_{{ .chezmoi.os }}

这将会创建一个 ~/.bashrc 的软链接到对应的配置文件, 若不希望以软链接形式,{{ include ".bashrc_linux" }} 可以直接替换为文件。.chezmoiignore 用于确保对应的系统安装对应的文件,意为如果不匹配,则忽略对应的配置文件,此外还有 .chezmoiremove 等,详见 reference

1
2
3
4
5
6
7
# .chezmoiignore
{{ if ne .chezmoi.os "darwin" }}
.bashrc_darwin
{{ end }}
{{ if ne .chezmoi.os "linux" }}
.bashrc_linux
{{ end }}

在配置文件内写入对应的数据,TOML 语法可去 toml.io 快速上手,大有(声)裨(安)益(利)。除此之外,chezmoi 还支持由 github.com/spf13/viperjson, hcl, yaml 等格式,均以 chezmoi 加不同扩展名命名,将会使用第一个被找到的配置文件。

配置文件模板

1
2
3
4
5
# ~/.config/chezmoi/chezmoi.toml
[data]

[data.github]
editor = "nano"

如下命令可临时调试时,脱离模板文件查看变量值是否正确。

1
2
$ chezmoi execute-template '{{- .github.editor -}}'
nano

编辑配置

相比于自动创建模板的南辕北辙,通过智能人工从模板中自动生成 TOML 配置则显得较为靠谱。

1
2
3
4
# ~/test.tmpl
{{- $email := promptString "email" -}}
[data]
 email = "{{ $email }}"

通过 promptString 这个函数解析等下从命令行中传入的参数,并传入到配置文件中。

1
2
3
4
$ chezmoi execute-template --init --promptString email=[email protected] < ~/test.tmpl

[data]
 email = "[email protected]"

管理私有数据

chezmoi 通过检测原有目录和文件权限来判断是否属于私有,通常以 private_ 为前缀, 0644 为权限,~/.local/share/chezmoi 则为 700 权限,并且运行时会检查该文件夹权限是否正确。

以下面三种方式为例,介绍 chezmoi 的加密部分:

其余还有 Lastpass, OnePassword 等用法

GPG

支持对称和非对称密钥加密两种方式。

非对称加密方式中,可用如下命令查看接受方名称,所用 shell 支持的话也可以 $ gpg --recipient <Tab> 进行补全。

1
2
3
4
$ gpg --list-public-keys
pub ed25519 2019-07-04 [SC] [expires: 2024-07-02]
 1FDBDCE2D26BD8F100EE2E73B1B9AAD8BE7E7326
uid [ultimate] ArielAxionL <[email protected]>

以接受方竟是我自己为例,在配置文件 chezmoi.toml 中填入:

1
2
3
# ~/.config/chezmoi/chezmoi.toml
[gpg]
 recipient = "ArielAxionL"

将文件加密并添加,chezmoi 默认使用了 gpg --armor 参数使得加密文件可读,可以通过配置中,的 command 字段来定义 gpg 的其他参数。

1
$ chezmoi add --encrypt test.toml

加密内容 解密内容

对称加密:

1
2
3
4
# ~/.config/chezmoi/chezmoi.toml

[gpg]
 symmetric = true

Gnome Keyring

github.com/zalando/go-keyring

Linux 中环境下,keyring 的实现目前还只支持 gnome-keyring,希望以后也能提供 kwallet 版本的接口。macOS 则支持 Keychain 存储帐号密码。

1
2
$ chezmoi secret keyring set --service=github --user=<github-user>
Password: <github-token>

set 替换成 get 从命令行拿到所存密令,或者使用 Seahorse 客户端查看。

Seahorse 是一个管理 gnome-keyring 的软件

1
2
3
4
5
# ~/.local/share/chezmoi/dot_gitconfig.tmpl

[github]
 user = "{{ .github.user }}"
 token = "{{ keyring "github" .github.user }}"

chezmoi.toml 配置文件中写明用户名,模板中便可自动取用 gnome-keyring 先前所存 token,keyring 一般会在用户登陆时自动解锁 GNOME/Keyring

KeepassXC

配置文件

1
2
3
4
5
# ~/.config/chezmoi/chezmoi.toml

[keepassxc]
 args = ["--key-file", "/path/to/your/key"]
 database = "/path/to/your/kdbx"

默认字段有 Notes, Password, URL, Username, 测试获得密码字段,Entry 可以填入保存密码的 Title,keepassxc-cli 会返回一个匹配的结果。

1
$ chezmoi execute-template '{{ (keepassxc "<YourEntry>").Password }}'

KeepassXC 支持自定义字段,比如你的密钥: KeepassXC Entry Attributes

在模板中用 keepassxcAttribute 获取:

1
$ chezmoi execute-template '{{ keepassxcAttribute "VPS Keyring" "public-key" }}'

结束

得益于数据模板的引入,chezmoi 作为一款 dotfiles manager,其配置管理功能相较之下更加强大,也有更多新的功能和应用场景可以发掘。

[归档] Hello, My Firefox

2021-01-27 16:24:43

Featured image of post [归档] Hello, My Firefox

自用配置项,持续更新中……

选项

1
toolkit.legacyUserProfileCustomizations.stylesheets = true
1
browser.tabs.tabClipWidth = 83
1
2
media.ffmpeg.vaapi.enabled = true
media.ffvpx.enabled = false

配合系统环境变量使用 MOZ_X11_EGL=1MOZ_ENABLE_WAYLAND=1MOZ_WEBRENDER=1

1
2
devtools.editor.tabsize = 4
devtools.debugger.remote-enabled = true

DevTools 中还有 Enable browser chrome and add-on debugging toolboxes 可以打开,调试扩展常用选项

1
accessibility.typeaheadfind.enablesound = false

默认开了这个选项,导致 Ctrl+F 搜索不到结果的时候笔记本的蜂鸣器会吼得很大声

主题

MaterialFox Preview

MaterialFox

扩展

Keepassxc Browser Preview

Plasma Integration Preview

在 QEMU/KVM 虚拟机上安装 NixOS 发行版

2021-01-07 22:57:51

Featured image of post 在 QEMU/KVM 虚拟机上安装 NixOS 发行版

前言

2019 年 1 月的时候知道了这个发行版,当时 @NixOS_zh 群刚建立(后来这群凉了),就开虚拟机玩了一下,时隔多年发现又有不少人对其颇感兴趣,便决定重新写一下安装相关的教程。

本文以 Arch Linux 作为宿主机,大体步骤与 Arch Wiki 相近

QEMU/KVM 虚拟机配置

ArchLinux Wiki: KVM | QEMU | Libvirt

0. 检测硬件是否支持 KVM

一般情况下需要进入到 BIOS 对应页面打开虚拟化支持,常见对应设置项如下:

开启虚拟化后在宿主机上用命令行检测(比如我的是 AMD 的处理器):

1
2
$ LC_ALL=C lscpu | grep Virtualization
Virtualization: AMD-V

内核支持检测,如果使用的是 ArchLinux 提供的官方内核,即 core/linux 则已经包含了对应的 kvm 模块(kvmkvm_amdkvm_intel):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ zgrep CONFIG_KVM /proc/config.gz
CONFIG_KVM_GUEST=y
CONFIG_KVM_MMIO=y
CONFIG_KVM_ASYNC_PF=y
CONFIG_KVM_VFIO=y
CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT=y
CONFIG_KVM_COMPAT=y
CONFIG_KVM_XFER_TO_GUEST_WORK=y
CONFIG_KVM=m
CONFIG_KVM_INTEL=m
CONFIG_KVM_AMD=m # 可以看到有该模块
CONFIG_KVM_AMD_SEV=y
CONFIG_KVM_MMU_AUDIT=y

查看这些内核模块是否已自动加载:

1
2
3
4
5
$ lsmod |grep kvm
kvm_amd 114688 8
ccp 118784 1 kvm_amd
kvm 933888 1 kvm_amd
irqbypass 16384 1 kvm

如果没有自动加载则手动:

1
2
$ sudo modprobe kvm
$ sudo modprobe kvm_amd # 对应你的 CPU 类型

1. 准虚拟化(使用 VIRTIO)

检测 VIRTIO 模块是否可用:

 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
$ zgrep VIRTIO /proc/config.gz
CONFIG_BLK_MQ_VIRTIO=y
CONFIG_VIRTIO_VSOCKETS=m
CONFIG_VIRTIO_VSOCKETS_COMMON=m
CONFIG_NET_9P_VIRTIO=m
CONFIG_VIRTIO_BLK=m
CONFIG_SCSI_VIRTIO=m
CONFIG_VIRTIO_NET=m
CONFIG_VIRTIO_CONSOLE=m
CONFIG_HW_RANDOM_VIRTIO=m
CONFIG_DRM_VIRTIO_GPU=m
CONFIG_VIRTIO=y
CONFIG_VIRTIO_MENU=y
CONFIG_VIRTIO_PCI=m
CONFIG_VIRTIO_PCI_LEGACY=y
CONFIG_VIRTIO_VDPA=m
CONFIG_VIRTIO_PMEM=m
CONFIG_VIRTIO_BALLOON=m
CONFIG_VIRTIO_MEM=m
CONFIG_VIRTIO_INPUT=m
CONFIG_VIRTIO_MMIO=m
CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
CONFIG_VIRTIO_DMA_SHARED_BUFFER=m
CONFIG_RPMSG_VIRTIO=m
CONFIG_VIRTIO_FS=m
CONFIG_CRYPTO_DEV_VIRTIO=m

准虚拟化设备列表(主要确保以下几个模块有对应开启,若未开启则手动用 modprobe 命令开启):

2. 安装 QEMU

1
$ sudo pacman -S qemu

Arch Wiki: PCI 直通

如果需要启用 PCI 直通功能则需要在内核参数中添加 intel_iommu=on 或者 amd_iommu=on,同时可以在其后添加 iommu=pt,以防前者失效,以下命令检测是否开启成功,由于本人所用 AMD Ryzen 5 4600U 支持方面还有些问题,故此不做展示。

1
$ sudo dmesg | grep -i -e DMAR -e IOMMU

3. 安装 libvirt

1
$ sudo pacman -S libvirt virt-manager dnsmasq edk2-ovmf

为了避免每次都需要询问 root 密码,建议将自己的用户添加到 libvirt 组:

1
$ sudo usermod -aG libvirt <YourUserName>

编辑服务端配置文件 /etc/libvirt/libvirtd.conf,取消如下几行的注释:

1
2
3
4
5
unix_sock_group = "libvirt"
unix_sock_ro_perms = "0777" # set to 0770 to deny on-group libvirt users
unix_sock_rw_perms = "0770"
auth_unix_ro = "none"
auth_unix_rw = "none"

同时添加 ipv4 的内核转发参数:

1
$ sudo echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/00-network.conf

设置开机启动和运行服务。

1
$ sudo systemctl enable --now libvirtd.service

4. 配置 virt-manager

添加连接

建议重启以应用之前的设置,此时在 Virtual Machine Manager 的界面应该可以看到一些已经连接上的服务端,如果没有则在菜单栏自行添加,推荐初次连接系统级服务来创建虚拟机。

添加虚拟机

NixOS 镜像下载

将下载到的镜像文件所在目录创建为文件系统池,随后在其中选择镜像文件进行加载。

添加镜像池 选择镜像

设置合适的系统资源和网络配置等(初次使用推荐用 NAT 模式较为简单,Bridge 模式之后会提到如何配置)。

虚拟机能用的内存和CPU核心数量设置 存储空间设置

如果你的宿主机支持的话,推荐使用 UEFI 模式启动(由 extra/edk2-ovmf 这个提供,中途安装的话要重启 libvirtd 服务以生效)。

EFI Firmware

调整镜像到启动优先级最高,最后启动工具栏上的 Begin Install 就可以安装了。 更改启动优先级

NixOS 系统安装

NixOS 使用手册

0. 进入引导界面

ISO Grub NixOS 镜像启动

由于我下载的是最小化镜像,所以并没有图形界面,如果下载的是带 Gnome 或者 KDE 的镜像的话应该可以看到界面了,稍后我也会以最小化镜像的方式开始安装图形界面。

1. 磁盘分区

查看当前块设备状态,可以看到我们之前分配的盘 vda 还未被挂载

lsblk

建议使用 GPT 分区表,按照可以按照图中对 bootswap(可选)和 root 分区进行创建,注意下方 Type 选择对应的分区类型,Write 写入后退出。

1
$ sudo cfdisk /dev/vda

cfdisk

格式化分区,可以看到格式化后效果如下:

1
2
3
4
$ sudo mkfs.fat -F32 /dev/vda1
$ sudo mkswap /dev/vda2
$ sudo swapon
$ sudo mkfs.xfs -L root /dev/vda3

mkfs check

2. 分区挂载

1
2
3
$ sudo mount /dev/vda3 /mnt
$ sudo mkdir -p /mnt/boot
$ sudo mount /dev/vda1 /mnt/boot

挂载后可以检查是否挂载成功,不要重复挂载。

挂载

3. 系统配置

由命令生成默认的配置文件:

1
2
$ sudo nixos-generate-config --root /mnt
$ sudo nano /mnt/etc/nixos/configuration.nix

可以看到已经有了 systemd-boot 作为 bootloader 引导操作系统。其他一些基本配置,按照自己的需求取消注释并修改内容即可,注意创建用户 users.users.<YourUserName> 及其对应的用户组,完成后 Ctrl + O 保存。

config

如果网络情况欠佳的话可以设置 http_proxy 或者更换更新频道到国内镜像站:

TUNA Nix Help

1
$ sudo nix-channel --add https://mirrors.tuna.tsinghua.edu.cn/nix-channels/nixos-20.09 nixos

使用 sudo nixos-install 进行安装并设置 root 密码,完成之后取消挂载并重启(记得更改启动项顺序到虚拟硬盘)。

1
2
3
4
5
6
7
8
$ sudo nixos-install
...
setting root password...
Enter new UNIX password: ***
Retype new UNIX password: ***

$ sudo umount -r /mnt
$ reboot

调整启动顺序

NixOS 系统配置和使用

0. 检查引导状态

重启登陆后可以查看引导状态:

1
$ sudo bootctl status

systemd-boot 状态

1. 配置桌面环境

/etc/nixos/configuration.conf 配置文件参考如下

 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
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running ‘nixos-help’).

{ config, pkgs, ... }:

{
 imports =
 [ # Include the results of the hardware scan.
 ./hardware-configuration.nix
 ];

 # Use the systemd-boot EFI boot loader.
 boot.loader.systemd-boot.enable = true;
 boot.loader.efi.canTouchEfiVariables = true;

 networking.hostName = "axionl"; # 设置 hostname.
 # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.

 # Set your time zone.
 time.timeZone = "Asia/Shanghai"; # 设置时区

 # The global useDHCP flag is deprecated, therefore explicitly set to false here.
 # Per-interface useDHCP will be mandatory in the future, so this generated config
 # replicates the default behavior.
 networking.useDHCP = false;
 networking.interfaces.enp1s0.useDHCP = false;
 networking.networkmanager.enable = true; # 启用 NetworkManager 替代默认的 DHCP

 # Configure network proxy if necessary
 networking.proxy.default = "http://192.168.122.1:8888"; # 设置一个外部代理(可选)
 # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";

 # Select internationalisation properties.
 i18n.defaultLocale = "en_US.UTF-8"; # 默认语言环境
 # console = {
 # font = "Lat2-Terminus16";
 # keyMap = "us";
 # };



 # Configure keymap in X11
 services.xserver.layout = "us"; # 设置键盘布局
 # services.xserver.xkbOptions = "eurosign:e";

 # Enable CUPS to print documents.
 services.printing.enable = false; # 启用打印服务(不需要可禁止)

 # Enable sound.
 sound.enable = true; # 允许声音
 hardware.pulseaudio.enable = true;

 # Enable touchpad support (enabled default in most desktopManager).
 services.xserver.libinput.enable = true; # 允许触摸板

 # Define a user account. Don't forget to set a password with ‘passwd’.
 # 创建用户并添加到用户组
 users.users.axionl = {
 isNormalUser = true;
 extraGroups = [ "wheel" "networkmanager" ]; # Enable ‘sudo’ for the user.
 shell = pkgs.fish; # 指定终端(默认为 bash)
 };

 # List packages installed in system profile. To search, run:
 # $ nix search wget
 # 在系统层面安装软件包
 environment.systemPackages = with pkgs; [
 htop
 neofetch
 fish
 spice-vdagent
 virglrenderer
 ];

 # Some programs need SUID wrappers, can be configured further or are
 # started in user sessions.
 # programs.mtr.enable = true;
 # programs.gnupg.agent = {
 # enable = true;
 # enableSSHSupport = true;
 # };

 # List services that you want to enable:

 # Enable the OpenSSH daemon.
 # services.openssh.enable = true;

 # Open ports in the firewall.
 # networking.firewall.allowedTCPPorts = [ ... ];
 # networking.firewall.allowedUDPPorts = [ ... ];
 # Or disable the firewall altogether.
 # networking.firewall.enable = false;

 # This value determines the NixOS release from which the default
 # settings for stateful data, like file locations and database versions
 # on your system were taken. It‘s perfectly fine and recommended to leave
 # this value at the release version of the first install of this system.
 # Before changing this value read the documentation for this option
 # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
 system.stateVersion = "20.09"; # Did you read the comment?

 # X Windows Server
 # 启动 X 显示服务
 services.xserver.enable = true;
 # services.qemuGuest.enable = true;
 # services.spice-vdagentd.enable = true;
 # 允许 SDDM 作为窗口管理器
 services.xserver.displayManager.sddm.enable = true;
 # 安装 Plasma KDE 作为桌面环境
 services.xserver.desktopManager.plasma5.enable = true;

 # Packages
 # 允许第三方闭源软件包
 nixpkgs.config.allowUnfree = true;
}

修改配置文件之后需要使用命令重建并推荐重启生效:

1
$ sudo nixos-rebuild switch --upgrade

日常使用的时候理论上可以多套配置(profile)兼容和切换,当配置过多的时候可用 nix-collect-garbage -d 来完成,详见文档

在用户层面安装软件包使用 nix 包管理器进行搜索和安装:

1
2
3
4
5
$ nix search <软件包名称> # 搜索
$ nix-env -i <软件包名称> # 安装
$ nix-env -qa # 列出可安装的包
$ nix-env -e <软件包名称> # 卸载软件包
$ nix-env --rollback # 软件包回滚

更多用法可见官方文档

2. 截图

预览

Protocol Buffers 和 gRPC(一)

2021-01-03 23:46:51

Featured image of post Protocol Buffers 和 gRPC(一)

前言

官方网站

通俗易懂的说明

introduction

以宽泛的概念来说就是找到一种公共的中间体,以达到两者“交流”的目的。

有点复杂的介绍

RPC(Remote Procedure Call) 远程过程调用,可简单理解为远程的程间通讯。gRPC 是 Google 所实现的一个开源框架,Protocol Buffers 是用于通信的数据载体格式,有着高压缩率(序列化后)、跨平台、多语言等优点。

虽然定义上是远程通讯,但扩展到仅本地使用就成了一种形式上的程间通讯。gRPC 通过网络栈实现这一过程,而 Apache Thrift 则包含了网络、命名管道、内存共享等多种数据交换形式,留作以后介绍和对比。通过序列化工具 (Protocol Buffers) 在节省带宽和减少传输时间的基础上,又能够轻易还原回原本的结构数据,便于多语言开发,部分软件也以此作为其 API 暴露方式,如 v2ray 等。

流程示意图

一个简单的例子

以一个消息内容为空的协议为例子,仅用于确认发送和响应。对于 golang 或者 java 等语言来说 proto3 支持以 option 的方式产生适合其代码引入的包封装 (详见)。

公共协议

1
2
3
4
5
6
7
8
9
syntax = "proto3";
option go_package = "example.com/user/grpcGoExample"

message HelloMessage {} // 公共消息体,未包含任何字段

/* 通讯服务声明,类似于我们常说的接口 */
service Greeter {
 rpc SayHello(HelloMessage) returns (HelloMessage);
}

使用 protocol buffers 第一步便是在 proto 文件中定于需要序列化的数据结构:这是一个以 .proto 为扩展名的普通文本文件。数据被构造为消息(message),其中每条消息包含数量不等的键值对(也可以为空),下面简单举例,传输到接收方可以直接从结构体中取出想要的数据,而从结构体到序列化压缩传输,再到还原成结构体这一过程对程序员来说是透明的,由 protobuf 生成的库文件自动完成。

1
2
3
4
5
message Person {
 string name = 1;
 int32 id = 2;
 bool has_ponycopter = 3;
}

golang 客户端

 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
import (
 ...
 pb "example.com/user/grpcGoExample"
)

// 具体方法实现
func (g *Greeter) sayHello() {
 // 生成上下文
 ctx, cancel := context.WithTimeout(context.Background(), time.Second)
 defer cancel()

 // 获取服务端响应,在执行这条指令后可以看到进入了服务端对应的方法
 response, err := g.client.SayHello(ctx, &pb.HelloMessage{})
 if err != nil {
 log.Fatalf("Failed to get the response: %v", err)
 }
 if response != nil {
 fmt.Print("Hello Server")
 }
}

func main() {
 // 本地临时创建一个不安全的链接(socks5)
 const Address = "localhost:50051"
 conn, err := grpc.Dial(Address, grpc.WithInsecure(), grpc.WithBlock())
 if err != nil {
 log.Fatalf("Failed to connect: %v", err)
 }
 defer conn.Close()

 var client = pb.NewGreeterClient(conn)
 greeter := Greeter{client: client}
 greeter.sayHello() // 调用方法
}

golang 服务端

 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
import (
 ...
 pb "example.com/user/grpcGoExample"
)

type server struct {
 pb.UnimplementedGreeterServer
}

// 方法具体实现
func (s *server) SayHello(ctx context.Context, message *pb.HelloMessage) (*pb.HelloMessage, error) {
 // 请求陷入该方法后输出字符串,表示服务器已经收到该指令
 log.Println("Hello Client!")

 // 返回响应
 return &pb.HelloMessage{}, nil
}

func main() {
 // 监听一个本地端口
 lis, err := net.Listen("tcp", "0.0.0.0:50051")
 if err != nil {
 log.Fatalf("failed to listen: %v", err)
 } else {
 log.Printf("Listen on: %v...", Address)
 }

 // 创建服务端
 s := grpc.NewServer()
 pb.RegisterGreeterServer(s, &server{})

 if err := s.Serve(lis); err != nil {
 log.Fatalf("failed to serve: %v", err)
 }
}

可以看到,在基本不需要涉及底层网络开发和同步协定的情况下实现了两个程序之间的通讯,其关键在于服务端重写方法的实现和客户端上下文的获取。在后续的介绍中还会就具体操作过程作详细说明。

Protocol Buffers 简要

跨语言类型的桥梁

Protocol Buffers

作为一种语言中立的协议,其在多种语言中都有对应的变量类型转换,需要特别注意是否在转换过程中出现精度丢失现象,或者并非预期类型的现象,比如 bytes 对应到 C++ 的类型为 string,需要自己适当转换。

变量类型对应表 (https://developers.google.com/protocol-buffers/docs/overview#scalar)

此外支持通过嵌套类型,实现结构体形式的信息的传输和还原。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
message PersonalInfo {
 int32 id = 1; 
 string name = 2;
 int32 score = 3;
 enum Hobby {
 GAMES = 1;
 MUSIC = 2;
 }
 Hobby hobby = 4;
}

message PersonalResponse {
 string msg = 1;
}

message ClassInfo {
 repeated PersonalInfo c_info = 1;
}

service SearchInfo {
 rpc Add(stream PersonalInfo) returns (PersonalResponse);
}

更多语法说明可以参考官方文档

生成库文件

Protobuf Github Release 下载 (含 protoc)

生成所需要用到两个工具,一个是 protoc 其本身,另一个是用于生成对应其他语言文件的插件,后者可能有些发行版已经将对应语言的插件打包到了 grpc 包内一起,视具体情况而定,比如 protoc-gen-go-grpc 正好没有,那就找到上游项目自己安装一个并添加到环境变量中(方便输入)。

1
$ protoc --plugin=protoc-gen-go-grpc --go_out=. --go-grpc_out=. -I=. info.proto

然后生成了 info.pb.goinfo_grpc.pb.go 两个文件,大体上看前者管定义,后者管实现,属于同一个包。其他语言主要是将二进制插件和输出参数替换成对应语言的。

可以参考如下 .cmake 文件,自行在 CMakeLists.txt 引入库文件和生成的头文件。

protobuf.cmake

 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
# Find Protobuf
find_package(Protobuf REQUIRED) # 如果没有需要自行安装

# Find Generator Executable # 找到两个用于生成库文件的可执行文件
find_program(PROTOBUF_PROTOC_EXECUTABLE protoc)
find_program(GRPC_CC_PLUGIN_EXECUTABLE grpc_cpp_plugin) # 对应所需要生成的语言

# Set Proto File Name
list(APPEND PROTO_NAME_LISTS
 "api"
 )

# Generate gRPC and protobuf Sources
foreach(PROTO_NAME ${PROTO_NAME_LISTS})
 set(GRPC_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.grpc.pb.cc")
 set(GRPC_HEADER "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.grpc.pb.h")
 set(PB_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb.cc")
 set(PB_HEADER "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb.h")
 set(PROTO_DIR "${CMAKE_SOURCE_DIR}/misc/")
 set(PROTO_FILE "${PROTO_DIR}/${PROTO_NAME}.proto")

 add_custom_command(
 OUTPUT "${GRPC_SOURCE}" "${GRPC_HEADER}" "${PB_SOURCE}" "${PB_HEADER}"
 COMMAND "${PROTOBUF_PROTOC_EXECUTABLE}"
 ARGS
 --grpc_out="${CMAKE_CURRENT_BINARY_DIR}"
 --cpp_out="${CMAKE_CURRENT_BINARY_DIR}"
 -I="${PROTO_DIR}"
 --plugin=protoc-gen-grpc="${GRPC_CC_PLUGIN_EXECUTABLE}"
 "${PROTO_FILE}"
 DEPENDS "${PROTO_FILE}")

 list(APPEND GRPC_HEADERS "${GRPC_HEADER}")
 list(APPEND GRPC_SOURCES "${GRPC_SOURCE}")

 list(APPEND PROTO_HEADERS "${PB_HEADER}")
 list(APPEND PROTO_SOURCES "${PB_SOURCE}")
endforeach()

一个综合的案例

v2ray config.proto

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
syntax = "proto3";

package v2ray.core;
option csharp_namespace = "V2Ray.Core";
option go_package = "v2ray.com/core";
option java_package = "com.v2ray.core";
option java_multiple_files = true;

import "common/serial/typed_message.proto";
import "transport/config.proto";
...

可以看到首先引入了两个子 proto 文件,其中包含一些自己项目具体定义的消息类型,以 typed_message.proto 为例子,其中只包含了两种类型的两个字段。

1
2
3
4
5
6
message TypedMessage {
 // The name of the message type, retrieved from protobuf API.
 string type = 1;
 // Serialized proto message.
 bytes value = 2;
}

最终通过层层嵌套构成了一个综合的信息结构体,一并打包发送。

Banner Artwork

[归档] SmartDNS 一个智能分流的 DNS 服务

2021-01-02 09:41:20

Featured image of post [归档] SmartDNS 一个智能分流的 DNS 服务

介绍

项目地址

DNS

域名系统(英语:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。通常情况下本地的解析请求会发送到离你最近的 DNS 服务器,它可能是你的无线路由器、宿舍楼梯下的服务器或者运营商的地区性服务器等,但由于各种原因导致解析效果不理想,延迟高等问题,这时可以考虑换一个网络质量好的域名解析服务,错峰出行,减少拥堵。

SmartDNS

SmartDNS 集成了多种出栈请求协议,包括常用的 UDP 和较为现代的 DNS-Over-Https 等,能够在给定的规则列表中挑选出一个延迟最低的域名解析服务,并向其发送请求。同时其内部也提供 DNS 缓存,如果缓存能够命中则直接从本地缓存中返回对应 IP 地址,如未能查找到则继续向上级 DNS 服务传播请求。

structure

配置

默认配置文件

推荐项目:dnsmasq-china-list

作者肥猫在其项目中提供了几个较为常用的匹配规则列表,对于非 Arch Linux 用户而言可以把项目克隆到本地然后构建对应的配置文件,而 Arch 用户可以从 CN 源 里安装 smartdns-china-list-git 以获取自动更新。

生成对应的配置文件操作如下,具体参考,可见其生成形式为 nameserver /<域名>/<组名>,后面的组名就是对应我们后来需要的匹配规则而制定的。

1
2
$ cd dnsmasq-china-list
$ make smartdns SERVER=china

默认的配置文件中有很多不常用的项目,作为自己的配置文件一般精简到自己需要的功能配置即可。

 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
 # (可选)引入额外的规则列表,用绝对路径
conf-file /etc/smartdns/accelerated-domains.china.smartdns.conf
conf-file /etc/smartdns/apple.china.smartdns.conf
conf-file /etc/smartdns/google.china.smartdns.conf

# 本地监听端口
bind [::]:53

# 缓存大小
cache-size 4096

# 重启时读取之前的缓存
cache-persist yes

# 缓存文件存放位置
cache-file /var/cache/smartdns.cache

# 传统 UDP 协议(以阿里 DNS 为例)
server 223.5.5.5

# DNS Over TLS (以 CloudFlare DNS 为例)
server-tls 1.0.0.1

# DNS Over Https (以烧饼 DNS 为例)
server-https https://doh.dns.sb/dns-query -group china -group example

更多 DNS 服务地址:dnscrypt.info

如果有多个分组需求,可以自定义规则配置文件,服务配置后面再添加 -group [组名]

-exclude-default-group 标记为排除在默认组之内的服务需至少在一个组才可能被访问到。

启动

1
2
3
# smartdns -c smartdns.conf
or
# systemctl enable --now smartdns.service

默认在后台运行,推荐使用自带的 systemd service 来进行管理,如果 /ect/resolv.conf 没有被更改成监听本地的话可以检查一下文件是否有特殊标志位(lsattr),手动修改即可(chattr )。

1
2
3
4
$ cat /etc/resolv.conf

# DNS managed by SmartDNS
nameserver 127.0.0.1

Banner Artwork

[新手篇] 如何写一个简单的上膛机器人

2020-12-31 17:26:38

Featured image of post [新手篇] 如何写一个简单的上膛机器人

介绍

一个检测关键词并自动回复表情包的 Telegram 机器人。

开发

注册一个机器人

  1. 访问 Telegram 的官方注册机器人 @BotFather

  2. 使用 /newbot 命令创建一个新的机器人。它会询问你所要创建机器人的名字 (nickname) 和用户名 (username, 以_bot结尾),注册成功后会将机器人的 TOKEN 返回给你,而这个 TOKEN 用于告知服务器这个机器人就是(大明湖畔那个夏雨荷)对应注册的机器人。

botfather

1
2
3
4
...
Use this token to access the HTTP API:
`<1145141919810:TheWholeSentenceIsToken>`
Keep your token secure and store it safely, ...

安装 Python 环境

官方网站: https://www.python.org/downloads

对于绝大多数 Linux 发行版来说从其自己的包管理器中安装 Python 即可(2021 年了应该都默认 Python3?)

Windows 则 Python 官方提供了安装器,记得把 Python 加到环境变量 (PATH) 的框给勾选上。

为了避免开发环境不干净对后续开发和使用系统造成影响,建议创建一个用于该机器人项目的虚拟环境(virtual environment)

1
$ python -m venv .venv // 创建了一个名为 .venv 的隐藏文件夹

对于国内使用 PyPi 源速度不是很给力的情况,可以考虑替换镜像源为清华源

也可以写入默认配置文件 ~/.config/pip/pip.conf(如果没有则创建

1
2
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple

可以查看设置是否生效

1
2
3
$ pip config list

global.index-url='https://pypi.tuna.tsinghua.edu.cn/simple'

创建虚拟环境后,您可以激活它,进入项目所在目录。

1
tutorial-env\Scripts\activate.bat
1
$ source tutorial-env/bin/activate

对于 cshfish shell 分别对应名为 activate.cshactivate.fish 的脚本

安装软件包依赖

python telegram bot 项目地址

1
$ pip install python-telegram-bot --upgrade

也可以将依赖导出到文件,便于开发迁移

1
$ pip freeze > requirements.txt

Coding Time

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def main():
 # 从环境变量中读取 TOKEN 减少硬编码可能带来的泄漏风险
 # 如果只是本地测试的话可以直接把 TOKEN 粘贴到程序中
 TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')

 # Updater 是 Dispatcher 的实现,用于机器人数据交互的前端,
 # 负责更新消息队列并交付给其他调度程序
 updater = Updater(token=TOKEN, use_context=True)
 dispatcher = updater.dispatcher

 # 开始拉取信息
 updater.start_polling()

 # 响应终止信号 Ctrl+C
 updater.idle()

if __name__ == "__main__":
 main()

上述几行代码构成了整个机器人的基本框架,包含了机器人身份认证和开启机器人服务等。我们还需要向该框架内注册具体的方法,以实现所需功能。

 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
def start(update: Update, context: CallbackContext) -> None:
 """Send a message when the command /start is issued."""
 reply_text = "你好~\n"
 user = update.message.from_user

 if user.username is not None:
 reply_text += f"您的用户名是: {user.username}\n"

 reply_text += f"ID: {user.id}"

 update.message.reply_text(reply_text)

def help_command(update: Update, context: CallbackContext) -> None:
 """Send a message when the command /help is issued."""
 update.message.reply_text('这里是帮助命令')

def echo(update: Update, context: CallbackContext) -> None:
 """Echo the user message."""
 update.message.reply_text(update.message.text)

def main():
 token = os.getenv('TELEGRAM_BOT_TOKEN')
 updater = Updater(token=token, use_context=True)
 dispatcher = updater.dispatcher
 dispatcher.add_handler(CommandHandler("start", start_command))
 dispatcher.add_handler(CommandHandler("help", help_command))
 dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))
 updater.start_polling()
 updater.idle()

Update 中可以拿到消息的类型、具体内容、发送者等关键信息,从 CallbackContext 中可以获取机器人本身的一些信息等。在使用 \ 开头的对应命令后就能进入到对应的方法,也可以对此进行类封装。

注意到除了 CommandHandler 之外还有其他类型的句柄如常规消息类的 MessageHandler、用于行内输入的的 InlineQueryHandler 等,请详见上游 API 文档

回到需求,我们需要的检测关键词部分也是从 Update 中来,用关键词列表简单演示,当消息中包含关键词的时候发送一个表情,或者其他类型的回复详见 Message 类文档

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from telegram import Update, Sticker

# 临时创建一个表情量
sticker = Sticker(file_id="CAACAgUAAxkBAAIMOV_jA7I0IAABMbqNVNGkJWZNiDRT6QACiwIAArL6ew6tFsY1eQy9Lx4E",
 file_unique_id="AgADiwIAArL6ew4", width=480, height=512, is_animated=False)

def echo(update: Update, context: CallbackContext) -> None:
 keys_list = ["上膛", "车主", "特斯拉"]
 for item in keys_list:
 if item in update.message.text:
 msg = update.message
 msg.reply_sticker(sticker)

echo

至此初步效果已经达成,剩下要解决的两个疑问就是:

Sticker 信息

可以添加一个专用于获取表情包信息的句柄

1
2
3
4
5
6
7
8
def get_sticker(update: Update, context: CallbackContext) -> None:
 logger.info(update.message.sticker)

def main():
 ...
 dispatcher.add_handler(MessageHandler(Filters.sticker & ~Filters.command,
 get_sticker))
 ...

为了便于理解这里在服务端后台直接输出表情包信息作为日志,对于一些要动态添加表情包和对应关键字的需求来说,一个轻便的可持久化数据才是更好的选择。创建 Sticker 实例的时候注意不要遗漏必要的传入参数。

sticker_info

如何回复指定的消息

简单来说这就是一个套娃过程,从包含关键字消息的 reply_to_message 成员可以判断并拿到上一条回复的消息,回复拿到的上一条消息即可,从而实现精准打击(x

1
2
3
4
5
6
 ...
 msg = update.message.reply_to_message

 if msg is not None:
 msg.reply_sticker(sticker)
 ...

reply to your reply

结束

以简单的案例来介绍 Telegram 机器人的玩法,希望能够帮助到入门的萌新打造属于自己的机器人。(撒花 ~=o(^▽^)o~♪

Friends

2020-12-14 08:00:00

Teleport 小记

2020-12-06 23:04:45

Featured image of post Teleport 小记

简介

常用于跨平台集群管理,提供多设备认证、远程操作和反向代理。

项目地址:github.com/gravitational/teleport

官方网站:goteleport.com

安装

有以下几种方式,单个可执行文件内已经包含 web 管理平台、服务端和客户端功能。(Windows 目前仅支持客户端)

快速配置

服务端及 Web 管理平台

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
teleport:
 data_dir: /var/lib/teleport # 数据存放目录
auth_service:
 enabled: true
 cluster_name: "demo_cluster"
 listen_addr: 0.0.0.0:3025 # 认证监听地址
 tokens:
 - proxy,node,app:<nodes_auth_token> # 节点认证密令
ssh_service:
 enabled: true
 labels:
 env: staging
app_service:
 enabled: true
 debug_app: false
proxy_service:
 enabled: true
 listen_addr: 0.0.0.0:3023 # 反向代理监听地址
 web_listen_addr: 0.0.0.0:3080 # web 管理页面地址
 tunnel_listen_addr: 0.0.0.0:3024 # 隧道监听地址
 public_addr: <https://example.domain.org:3025> # 反向代理公共地址
 https_keypairs: # 本地测试时可不设置证书
 - key_file: <key_file_path>
 - cert_file: <cert_file_path>

可由如下命令启用服务端进行测试,要点如下:

1
$ teleport start --config teleport.yaml

tctlteleport 的一个命令行管理工具,在 teleport 服务运行时,可以由它对用户、节点、密令等进行动态管理。

1
2
# 创建一个初始用户
$ tctl users add <username> <login_user, login_group> --config teleport.yaml

添加好用户后可访问提示的地址,输入账户密码,用手机两步验证器扫码后填入验证码即可初始化成功。配置文件中支持除 otp 外还支持 github auth 认证等方式。

注册界面 登陆界面

常用的两步验客户端

子节点

除了服务端平台自己可以作为节点外,还可以添加其他的子节点构成集群,大体上分为两类:

静态子节点

静态子节点配置文件需要对应服务端里的地址和 <nodes_auth_token>,随后直接在节点上运行即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
teleport:
 nodename: "my_laptop"
 data_dir: /var/lib/teleport/
 auth_token: <nodes_auth_token>
 auth_servers:
 - <https://example.domain.org:3025> # 认证地址
proxy_service:
 enabled: false # 本地子节点暂时无需启动反向代理服务端
ssh_service:
 enabled: true
 labels:
 env: local_node
auth_service:
 enabled: false # 本地子节点暂时无需启动认证服务端

动态子节点

动态子节点需要在服务端上进行添加,由以下命令生成一个临时的 token 以添加子节点。

1
$ tctl nodes add --roles=node,proxy --ttl=5m --config teleport.yaml

得到形如下方的命令:

1
2
3
4
$ teleport start \
 --roles=node,proxy \
 --token=<random_token> \
 --auth-server=<https://example.domain.org:3025>

添加成功后可以在 web 界面内看到所有的节点。 Server Page

客户端

安装包内自带一个名为 tsh 的可执行文件,用于命令行认证和登陆

1
$ tsh login --proxy=<https://example.domain.org:3025> --user=<username>

登陆后可以使用 ls 命令查看已添加的节点

1
2
3
4
5
6
$ tsh ls

Node Name Address Labels
-------------- -------------- ----------------
demo_cluster 127.0.0.1:3022 env=core_service
my_laptop ⟵ Tunnel env=local_node

使用 ssh 来登陆节点终端

1
$ tsh ssh <node_name>

高级配置

配合 K8S 管理服务

TBC…


Banner Artwork

Archives

2019-05-28 08:00:00

[归档] TinyTeX + VSCode

2018-12-26 18:26:33

Featured image of post [归档] TinyTeX + VSCode

TinyTeX 是 yihui 制作的一款基于 TeX Live 的轻量级、跨平台、简单易用的 LaTeX 发行版。与安装占用空间庞大的完整版相比,TinyTex 按需所取、大幅精简,用 tlmgr 包管理器安装所需依赖即可基本使用。

项目链接

Github | 文档

安装 TinyTex

从 R 语言终端安装

初始化 R 语言环境设置,主要是修改软件源方便后续下载,这部分参考 Arch Wiki

1
2
$ sudo pacman -S r
$ touch ~/.Renviron ~/.Rprofile # 创建两配置文件内容如下
1
2
3
4
5
R_HOME_USER = /path/to/your/r/directory # 设置你自己的目标路径,下同
R_PROFILE_USER = ${HOME}/.config/r/.Rprofile
R_LIBS_USER = /path/to/your/r/library
R_HISTFILE = /path/to/your/filename.Rhistory # Do not forget to append the .Rhistory
MYSQL_HOME = /var/lib/mysql
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# The .First function is called after everything else in .Rprofile is executed
.First <- function() {
 # Print a welcome message
 message("Welcome back ", Sys.getenv("USER"),"!\n","working directory is:", getwd())
}

options(digits = 12) # number of digits to print. Default is 7, max is 15
options(stringsAsFactors = FALSE) # Disable default conversion of character strings to factors
options(show.signif.stars = FALSE) # Don't show stars indicating statistical significance in model outputs
error <- quote(dump.frames("${R_HOME_USER}/testdump", TRUE)) # post-mortem debugging facilities

options("repos" = c(CRAN="https://mirrors.tuna.tsinghua.edu.cn/CRAN/")) # 换用 tuna 源

如果不在配置文件中修改源的话,接下来在交互模式内,第一次安装包的时候也会出现一个窗口需要你来选择源。由于弹出窗口是 tk 实现的,所以需手动安装其作为依赖。如果第一次安装选择错误,那么可以进入交互模式后输入 chooseCRANmirror() 来修改,如提示所言,退出交互模式为 q(),并且会问你是否保存会话。

在命令行输入 R,进入交互模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ R

R version 3.5.2 (2018-12-20) -- "Eggshell Igloo"
Copyright (C) 2018 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)
R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.
Natural language support but running in an English locale
R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.
Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

> install.packages('tinytex') # 安装 tinytex
> tinytex::install_tinytex()

如果使用非管理员权限安装的话,会出现以下提示,输入 yes 即将写入目录置于用户家目录下。

1
2
3
4
> install.packages('tinytex')
Warning in install.packages("tinytex") :
'lib = "/usr/lib/R/library"' is not writable
Would you like to use a personal library instead? (yes/No/cancel)

如果你看的是中文文档的话,其中默认使用了 devtools 这个包,你需要自己先手动安装,即 install.packages('devtools'),无特殊需求则等待安装结束。

从脚本安装

1
wget -qO- "https://yihui.name/gh/tinytex/tools/install-unx.sh" | sh

添加环境变量

安装完成之后会在 $HOME 目录下出现一个 .TinyTeX 的文件夹,并且将其中的二进制软链接到了 $HOME/bin,你需要将这个文件夹添加到自己的 $PATH 中才可正常使用 tlmgr 等一系列工具。环境变量生效之后,TinyTeX 安装部分至此结束。

中文常用软件包配置

对于中文用户常用的包有 ctexxecjkcjkcjkpunctfandol 等,如果是直接用脚本安装的话可能需要手动装更多依赖,如果不确定要装什么包的话可以先找个简单的示例文档进行编译和预览,再根据报错提示来安装缺失的依赖包。

tlmgr --gui 提供了一个简易的图形界面用于软件包安装和配置。

texLiveManager

也使用 R 语言提供的交互式命令行安装:

1
tlmgr_install('PackageName')

或直接在命令行安装:

1
tlmgr install 'PackageName' # 安装

VSCode 配置

LaTeX Workshop 插件提供 LaTeX 渲染、高亮和补全支持,可以在扩展商店中搜索安装。

texpreview

预览效果如上,其默认编译器的 pdflatexctex 中文包支持不是很好,建议参考照官方文档说明,在文档中添加 % !TEX program = xelatex ( Magic comment),使用 xelatex 作为指定编译器。

其余 LaTeX 具体书写语法可参见 TUNA 的演示文档

Search

0001-01-01 08:00:00