自建邮件服务器的选择和比较

在过去几年的时间里面,一直再寻求各种服务的 Self-hosted ,但唯独邮件服务器自建起来的步骤比较麻烦,但也不是不可能,甚至邮件服务器的 Self-hosted 方案更加全面。

下面这些邮件服务器架设方案是过去几年里面尝试和使用的。

  • [[Poste]] 分为免费版和收费版,可以使用 Docker 部署,所有服务集中在一个镜像中,部署比较简单,适合商用服务,如果出现问题还可以付费进行咨询。Poste 的付费版本提供了更加强大的日志审查,诊断分析工具等等,具体可以参考官网
  • [[Mailu]] 是一个使用 Python 编写的邮件服务器,可以使用 Docker 安装部署,集成了 dovecot, postfix 等等。个人的使用体验就是比较小巧,但是功能齐全。还自带 Webmail。 使用 Mailu 搭建邮件服务器
  • [[mailcow]] 相对于 Mailu 更加强大,但也相对比较消耗资源,mailcow 可以管理多用户,多域名,后台功能非常详细,使用 [[SOGo]] webmail。 使用 Mailcow 搭建邮件服务器.
  • [[postal]] 一个使用 Rust 编写的邮件服务器,可以发送和接收邮件。可以使用 HTTP 接口
  • [[Maddy]] 是一个使用 Go 语言实现的多合一邮件服务器。没有 Web 界面,需要借助客户端

其他一些集成方案:

  • [[Mail-in-a-Box]]
  • [[iRedMail]]
  • [[modoboa]]
  • [[hMailServer]] 是一款为 Windows Server 编写的邮件服务器。
  • [[Salmon]] Python 实现的邮件服务器
  • docker-mailserver
  • [[zimbra]]

总结

我个人在首先尝试 Mailu ,学习基本的邮件服务器的协议必要的 DNS 配置之后,从 Mailu 切换长期使用 Mailcow。Mailcow 支持多用户,多域名配置,相对 Mailu 要重一些。所以如果是简单的轻度使用推荐 Mailu,Python 编写,Docker 镜像依赖简单,后台简洁。如果是重度使用,那么推荐 Mailcow,虽然镜像搭建稍微复杂,后台管理也稍微复杂一些,但是更强大。

推广

如果你不想自己搭建邮件服务器,那么也可以选购 EV Hosting 推出的域名邮箱托管服务,可以以极低的价格获得无限域名无限邮箱服务。

reference

更多总结: https://medevel.com/list-os-mail-server/


2022-04-25 mail-server , email , self-hosted , self-host , linux , docker , smtp

使用 Mailcow 自建邮件服务器

Mailcow 是一个可以使用 Docker 容器化部署的邮件服务器。

GitHub: https://github.com/mailcow/mailcow-dockerized

Mailcow 的优点

[[Mailcow]] 相较于 [[Mailu]] 整体比较完整,功能相对比较丰富。

  • 支持二步验证,甚至支持 macOS 指纹验证
  • Webmail 使用 SOGo
  • 支持给每个域名增加管理员
  • 有良好的日志查看系统
  • Sync job 功能可以快速、简单地迁移到另外的提供商
  • Web 界面上可以下载 profile,快速在 macOS 和 iOS 中配置使用

缺点

  • 内存占用比较大,只是部署了 Mailcow 就占去了 3G 内存,如果禁用 Solr 和 ClamAV 可以节省内存,日常占用在 1G~2G,但就丧失了搜索和病毒扫描
  • 用的组件比较多,Web 端整体使用体验比 Mailu 慢

安装

Mailcow 整体的搭建过程比较简单,如果之前搭建过 Mailu,实际上,参考官网的教程,并没有太多需要再强调的,甚至如果在同一台机器上,从 Mailu 上迁移过来,只需要修改 [[DKIM]] 记录即可。

提前的 DNS 设置可以参考官网。主要需要配置一些 A 记录,CNAME,[[DKIM]], [[SPF]],[[DMARC]] 等等。

配置 DNS

# Name              Type       Value
mail                IN A       1.2.3.4
autodiscover        IN CNAME   mail.example.org. (your ${MAILCOW_HOSTNAME})
autoconfig          IN CNAME   mail.example.org. (your ${MAILCOW_HOSTNAME})
@                   IN MX 10   mail.example.org. (your ${MAILCOW_HOSTNAME})

SPF 记录配置

# Name              Type       Value
@                   IN TXT     v=spf1 mx a -all

安装:

git clone https://github.com/mailcow/mailcow-dockerized
cd mailcow-dockerized

然后执行:

./generate_config.sh

交互命令中输入自己的 MAILCOW_HOSTNAME,比如 mail.example.com

然后启动:

docker-compose pull
docker-compose up -d

然后使用配置好的域名 mail.example.com 登录,默认的用户名 admin 密码 moohoo

配置 SSL

编辑 mailcow.conf 然后配置 HTTP_BIND=

编辑 data/conf/nginx/redirect.conf:

server {
  root /web;
  listen 80 default_server;
  listen [::]:80 default_server;
  include /etc/nginx/conf.d/server_name.active;
  if ( $request_uri ~* "%0A|%0D" ) { return 403; }
  location ^~ /.well-known/acme-challenge/ {
    allow all;
    default_type "text/plain";
  }
  location / {
    return 301 https://$host$uri$is_args$args;
  }
}

重启:

docker-compose up -d
docker-compose restart nginx-mailcow

使用

进入后台之后,在 Configuration->Domains -> 添加域名。

添加域名之后需要修改域名的对应 DNS 记录。在界面会有显示。

添加完域名之后可以点击页面中的 Mailboxes 来添加域名邮箱来收发邮件。

Mailcow 使用 [[SOGo]] 作为默认的 Webmail 客户端。

客户端设置

几个常用的客户端连接端口:

Service Encryption Port
IMAP STARTTLS 143
IMAPS SSL 993
POP3 STARTTLS 110
POP3S SSL 995
SMTP STARTTLS 587
SMTPS SSL 465

升级

我的 Mailcow 是使用 Docker 安装的,所以升级也比较好升级,直接 git pull origin master 更新最新的代码,然后执行 sudo ./update.sh 即可。

但是我在升级的过程中遇到点问题,我本地的 Mailcow docker 项目存在一些无意中被修改的文件,并且我因为半年的时间没有更新,所以本地好多文件和远程的冲突了,git pull origin master 是无法拉取并合并到本地的,我自己的看了一下冲突的文件(如下),我判断这些文件直接可以覆盖更新。

On branch master
Your branch is behind 'origin/master' by 717 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   update.sh

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   data/conf/postfix/main.cf

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .github/workflows/assets/
        data/Dockerfiles/backup/
        data/web/css/themes/

所以我就直接执行:

git fetch --all
git reset --hard origin/master

直接使用远程的分支重置覆盖本地的修改。

然后再执行 sudo ./update.sh,升级的过程中 update 脚本会处理很多问题。这个地方需要注意 docker-compose 的版本要保持最新,用 v1 版本的 docker-compose 可能会出问题。

sudo apt install docker-compose-plugin

或者自己手动安装

sudo curl -SL https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

然后再执行 sudo ./update.sh,等脚本执行完成即升级完成。

特性梳理

Alias 别名

Mailcow 的 alias 是指可以通过该功能,将收件人的电子邮件地址映射到其他多个电子邮件地址的设置。用户别名将一个或多个电子邮件地址映射到一个用户帐户。例如,如果您有一个名为 john@example.com 的用户,您可以创建一个用户别名 jdoe@example.com,将所有发送到 jdoe@example.com 的邮件都转发到 john@example.com 。

如果要配置 Catch-All,直接在 Alias 中将 @ 符号前的用户名省略,比如要所有发送到 @example.com 的邮件全部都发送给另一个邮箱, 直接配置 @example.com 即可。

Domain aliases 域名别名

在 Mailcow 中,域名别名是指,将一个域名映射到另外一个域名。这在很多情况下都非常有用,比如当公司开设新品牌或更改其主要域名时,或者需要合并多个域名(例如“example.com”和“example.net”)时。

通过在 Mailcow 中设置域名别名,发送到备用域名的电子邮件将会被传递到与主要域名相同的邮箱中。这意味着用户可以继续收到发送到任何一个域名的电子邮件,而不必管理多个电子邮件帐户。

总的来说,Mailcow 域名别名是管理电子邮件域名和确保消息传递到正确收件人的功能。

ObDH

Address rewriting

Address rewriting (地址重写) 的功能就是在用户配置的域名邮箱往外发送邮件的时候,通过 BCC 的方式重写发件人的信息。

这个功能下面有两个部分:

  • BCC maps,用于将发送和接收到的邮件的副本静默转发到后台配置的一个地址。BCC map 下有两种类型
    • Sender map,如果发件人是配置的 Local destination,则增加 BCC 邮箱地址
    • Recipient map,如果收件人是配置的 Local destination,则增加 BCC 邮箱地址
  • Recipient maps,用于在发送邮件之前替换邮件上的目标地址。

O0S2

手工配置

推广

如果你不想自己搭建邮件服务器,那么也可以选购 EV Hosting 推出的域名邮箱托管服务,可以以极低的价格获得无限域名无限邮箱服务。

reference


2022-04-23 mailcow , mail , email , email-server , linux , docker , smtp , spf

法官能为民主做什么 读书笔记

这是 [[20220417-21 天计划]] 中的第一本书,记录一下读书笔记。

这本书的作者是最高法院[[斯蒂芬-布雷耶大法官]],英文的标题是 Making our Democracy Work: A Judge’s View 可以看到这是布雷耶大法官多年的观察记录。

三句话总结书的内容

本书主要分为三个部分:

  • 第一部分通过具体的案例介绍民众是如何建立起对最高法院的信任,并接受他们心中不认同的判决的过程。
    • [[马伯里案]] 中确立了[[司法审查权]]
    • 切诺基印第安人土地纷争中涉及到了「判决的可执行性」,如果一个判决没有人愿意执行怎么办?该案件确立了「同案同判」原则。总统动用权力破坏最高法院的判决执行,损害了最高法院的权威
    • 1857 年,臭名昭著的 [[德雷德·斯科特案]]引发内战
    • [[布朗案]] 终结了种族隔离,总统派兵保护黑人学生上学,司法至上的确立
    • [[布什诉戈尔案]],证明了官员和人民已经逐步接受了最高法院的判决和宪法解释,已经形成一种习惯,哪怕内心不认同,也遵循最高法院的宪法解释
  • 第二部分[[斯蒂芬-布雷耶大法官]]提出了一些实用主义的宪法解释方法。
    • 对国会立法,合理推断理性立法者的立法意图
    • 对于行政行为,考虑相关法律的立法目的,兼顾行政分支的专长
    • 解释与联邦主义相关的法律时,注重辅从原则
    • 处理下级法院职能相关问题,考虑专门分工
    • 处理最高法院与本院先例关系时,强调稳定性
  • 第三部分,在解释与个人基本权利相关的法律是,注重价值判断与比例原则

启发或想法

自上一次美国提名大法官开始,已经陆陆续续看过不少的关于最高法院,美国宪政历程,大法官的相关传记也看了不少,但是这一本由大法官主笔的著作依然让我看到了一些不一样的内容。

虽然那些经典的案例在其他著作中也曾经被一遍一遍的分析,但都是旁观者的视角,[[斯蒂芬-布雷耶大法官]]用全书的三分之一的篇幅列举了一个又一个经典案例来回答了一个问题,最高法院的信任是从何而来的。

可以看到的是最高法院从美国建国以来一步步走来并不顺利,也曾有过多次臭名昭著的判决,但是最高法院拥有[[司法审查权]],只要好好的利用这一个至上的权力,不过分迎合主流,为社会的少数群体发声,维护他们的权利,最高法院就可以一步一步慢慢地获得民众的信任。

而且可以看到的是,民众的权利也只有在最高法院,在宪法的保护下,才不会一步步被庞大的国家机器所侵犯,遇到不公和政府的权力扩张,可以随时通过最高法院去维护。

公民自由的要旨就在于,任何个人受到侵害,都有申请法律保护的权利

第二个与众不同的便是,在其他的著作中我们只能通过最后的判决以及判词来揣摩大法官的思考与想法,而这一部著作的第二部分,作者让我们窥探到了其判案的逻辑,以及内心的思考。

书中的第二部分更像是一个如何做法官的说明书,[[斯蒂芬-布雷耶大法官]]讲述大法官应该怎么做才能赢得并延续公众对大法官的信任。

布雷耶大法官总结了一套实用主义法律解释方法。

  • 不拘泥于法律条文起草的时代,应用到不断变化的现实中
  • 解释先发时,充分考虑其他政府的职能能彼此之间的关系

司法权的存在一方面是为少数族群争取应得的权利,另一方面是防止政府,国会权力的无限扩张,而这两者本身就不矛盾,政府权力的扩张一定会侵害到个体权利。

民众对大法官的信任从何而来?

最高法院通过解释和裁判,尽职履责,促进宪法有效实施,从而赢得并延续公众对它的信任。

过分迎合主流政治势力的判决,会削弱甚至剥夺宪法对人民的保护,尤其是对非主流个人、群体的保护力度。[[德雷德·斯科特案]]

法官能为民主做什么?

布雷耶认为,最高法院能实现制宪者心中切实可行的民主,推动宪法在实践中有效地运行。

如何界定个人自由?

[[比例原则]] 来平衡宪法价值观与实际利益诉求之间的冲突。

谁应该看这本书

对三权分立感兴趣,想要了解最高法院,以及大法官的人。

三个 Quotes

公民自由的要旨就在于,任何个人受到侵害,都有申请法律保护的权利。

公众的信任,并非自动脱胎于成文宪法的存在。它必须经过构建、打造,而且一旦形成,就必须永久维持。

我们的社会,自由是一项基本标准”,未经审判的拘押“即使有例外,也必须给予严格限制”。“值此最具挑战与最不确定的时刻,我国对正当程序的承诺正经受遭受最严峻的考验;也正是在这个时候,我们也必须在自己的家园,守护那些我们在海外为之浴血奋战的原则”

制宪先贤们试图创造的民主政体,能够维护自由,有效运行,并垂范久远。他们也意识到,确有必要由一个机构来守护宪法创制的法律边界。

《积极自由》中指出,如果公众不积极参与美国的政治生活,宪法对创制民主政体的影响将微乎其微。同样,如果公众仅仅因为不喜欢对宪法的某种解释,就恣意置之不理,宪法对实现切实可行的宪政民主之影响,也将乏善可陈。

当本杰明·富兰克林被问及制宪会议创立了什么样的政府时,他给出了那句著名的回答:“一个共和国,夫人,如果你们能好好维系它的话。”由此带来的挑战是,如何令我们继承的民主宪法,始终处于切实可行的状态。

司法独立是一个国家的基本理念。但是,当苏共一名党委书记打电话给法官,告诉他某个案子该如何裁判时,司法独立将荡然无存——因为某些潜在的惩罚是不言自明的,这个法官可能再没有像样的住房,子女也可能不会再得到很好的教育。


2022-04-22 reading , reading-2022 , 司法 , 大法官 , 布雷耶

使用 Docker 安装 Mastodon 实例搭建自己的社交网络

早之前就对 Mastodon 有所耳闻,当时 Google+ 关闭的时候就想着自建一个 Mastodon 实例,但是一直因为没有服务器,也没抽出时间就耽搁了,期间一直在寻找一个比较合适的栖息地,但国内的豆瓣越来越封闭,动不动就删贴,Twitter 是进来使用比较多的社交媒体,但也并没有怎么深入的用,只是在上面关注了一些行业里面的大佬。最近正好 So you Start 服务器中的资源很有剩余,就尝试安装一下 Mastodon。

[[Mastodon]] 是一个开源的、分布式社交网络,他创立的目的就是用来代替 Twitter。和 Twitter 一样,用户可以 Follow 他人,也可以相互关注,用户可以发布消息、图片、视频等等。但是和 Twitter 不同的是,Mastodon 没有一个中心化的存储和内容审查。Mastodon 使用 [[ActivityPub]] 协议进行通信,任何实现了该协议的站点都可以同 Mastodon 实例进行通信。

Mastodon 由无数社区维护的服务器运行,用户在某一台服务器上注册的账号可以和其他网络节点进行通信,也可以跨节点交换数据。

先来看看成果:

欢迎来玩。

Prerequisites

  • Mastodon 需要比较高的服务器配置,推荐至少 2 核 4GB 内存以上,空间最好也稍微大一些,前期可以把资源文件放本地,但扩展起来把媒体资源文件放到云存储上比较好
  • 一个域名
  • 可以发送邮件的 [[SMTP]] 配置,可以使用 Mailcow 自建,或者这些,或者使用邮件服务提供商
  • 系统:Ubuntu/Debian
  • 安装 dockerdocker-compose
  • 可选 Cloudflare

安装

本文使用 Docker + Nginx 的方式配置,使用 Docker 起服务,然后在宿主机上使用 Nginx 反向代理(因为 So you Start 这台机器之前就已经安装了 Nginx,否则的话会使用 Nginx-Proxy 或者 [[Nginx Proxy Manager]] 来直接管理 Docker 容器)。

因为 Docker Compose 的配置比较直观,我就直接放一个链接

首先创建网络:

docker network create external_networks
docker network create internal_network

Database

这里直接选用 [[PostgreSQL]] 作为数据库存储。

Redis

选择 Redis 作为缓存和队列。

Elasticsearch

Elasticsearch 是可选的组件,主要用来对内容做全文检索。

初始化安装

之后就是 tootsuite/mastodon 本体。

第一次安装需要先执行一个初始化的动作,将这部分内容另存为一个 docker-compose.init.yml 文件:

version: "3"
services:
  web:
    image: tootsuite/mastodon:v3.4.4
    restart: always
    environment:
      - "RAILS_ENV=production"
    command: bash -c "rm -f /mastodon/tmp/pids/server.pid; tail -f /etc/hosts"
    networks:
      - mastodon_networks

networks:
  mastodon_networks:
    external: true

然后指定该文件

docker-compose -f docker-compose.init.yml run --rm web bundle exec rake mastodon: setup

在初始化的过程中会要求提供域名,数据库用户名密码,以及 SMTP 等等配置。按照提前准备的内容依次输入即可。然后就可以获得一份初始配置。记得从终端拷贝下来,并保存为 .env.production

然后再使用 docker-compose up -d 启动所有的容器。

Nginx 反向代理

在 Mastodon 的 GitHub 仓库中官方提供了 Nginx 配置文件模板

可以拉一下代码,然后直接拷贝使用。

git clone https://github.com/mastodon/mastodon.git
cd mastodon
sudo cp dist/nginx.conf /etc/nginx/sites-available/mastodon
sudo ln -s /etc/nginx/sites-available/mastodon /etc/nginx/sites-enabled/mastodon

然后修改配置文件中的 example.com 为自己的域名,然后记得将网站的 root 修改为自己服务器上的路径。

sudo /etc/init.d/nginx reload

等待 Nginx 加载文件之后,就可以访问域名,进入实例。

实例维护

查看 Mastodon 实例的性能和日志,可以访问:https://domain.com/sidekiq [[Sidekiq]] 是一个 Ruby 的后台异步任务系统。

查看数据库的信息和查询效率,可以访问:https://domain.com/pghero

其他搭建教程

本文因为已经使用了 Nginx,所以直接使用 Nginx 做反向代理,如果你是一个台没有占用 80,443 端口的服务器,那么还可以直接使用 Nginx-Proxy,或者 Nginx Proxy Manager 来直接接管 Docker 容器。

升级实例版本

修改 docker-compose.yml 文件中的版本,然后重启实例。

docker-compose run --rm web rails db:migrate
docker-compose run --rm web rails assets: precompile

其他

单用户节点

修改 .env.production 中增加:

SINGLE_USER_MODE=true

然后重启服务。

SSL/TLS certificate

使用 Certbot

sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo certbot --nginx

每周一开放注册

00 0 * * 1 root docker exec mastodon_web_1 tootctl settings registrations approved >> /home/mastodon/log/mastodon/all.log 2>&1
00 0 * * 2 root docker exec mastodon_web_1 tootctl settings registrations close >> /home/mastodon/log/mastodon/all.log 2>&1

开启 ES 全文检索

ES_ENABLED=true
ES_HOST=es
ES_PORT=9200

然后停止服务重新启动。然后在 https://domain.com/admin 中就能看到全文搜索已经启用了。

然后执行:

docker-compose run --rm web bin/tootctl search deploy

就可以建立索引。

遇到问题

上传图片 500

网上大致看了一下,然后 docker-compose logs -f 看了一下日志,发现 ERROR:

web_1        | [9c1db729-7032-4c87-a85c-c38dc47380cf] Errno::EACCES (Permission denied @ dir_s_mkdir - /opt/mastodon/public/system/media_attachments):

可以看到权限不足,因为 Docker 部署,挂载到了本地的 ~/mastodon/public 目录下,所以:

sudo chown -R 991:991 ~/mastodon/public

上传图片 422 Error processing thumbnail for uploaded media

同样也是上传文件的报错。但是这个错误发生在 sidekiq

docker-compose logs -f sidekiq

查看错误,发现:

sidekiq_1    | 2022-05-12T03:59:56.171Z pid=6 tid=2fmy WARN: Errno::ENOENT: No such file or directory @ rb_sysopen - /opt/mastodon/public/system/media_attachments/files/108/286/964/537/181/703/original/559172f05be085c3.jpeg

一查配置发现,Sidekiq 挂载的时候没有暴露其中最后一行。

     volumes:
       - /etc/localtime:/etc/localtime:ro
       - /etc/timezone:/etc/timezone:ro
       - ~/mastodon/public/system:/mastodon/public/system

添加最后一行,重启即可。这个只能怪我太粗心了。

reference


2022-04-21 mastodon , linux , docker , social-media , twitter , distributed-system

使用 Husky 来管理 git hook

今天在 GitHub 上看到一个 repo,在其根目录中包含了一个 .husky 的文件夹,好奇之下就去搜索了以,于是发现了 husky 这个项目,这是一个使用 JavaScript 实现的用来管理 Git hooks 的工具。

GitHub: https://github.com/typicode/husky

什么是 Git hook

首先要先了解一下 Git hooks,对于 git 已经是现代开发中必不可少的一个工具了,大家应该都比较熟悉,但是可能很多人在项目中并没有使用过 Git 的 hooks。

Git 的 hooks 允许用户在特定的时机执行用户自定义的脚本。

比如非常常见的,在提交之后自动对代码内容进行一些常规检查,如果失败则不允许提交等等。常用的 hook 有 pre-commit, commit-msgpre-push 等。

Git hooks 是基于事件的,Scott Chacon 在 Pro Git 书中将 hooks 分成几个类型:

  • 客户端 hook,在使用者自己的本地环境中被调用。
    • 代码提交相关的 hook,在提交动作前后,通常用于检查完整性,生成提交信息,校验,发出通知等等
    • Email 相关的 hook,主要用于 Email 提交的代码。像 Linux 内核使用 Email 提交补丁会使用到。工作方式和提交类 hook 相似
    • 其他类,包括代码合并,check-out,rebase,rewrite,clean 等等
  • 服务端 hook,一般在服务器端执行,用于接受推送,部署在 git 仓库的服务器上
    • 触发类,在服务器接收到一个推送之前或之后执行动作,前触发用于检查,后触发用于部署
    • 更新,类似前触发,更新的 hook 是以分支作为作用对象,在分支更新通过之前执行

hook 列表:

hook 名称 触发命令 描述 参数个数描述
applypatch-msg git am 编辑 commit 时提交的 message,通常用于验证或纠正提交的信息以符合项目标准 包含预备提交信息的文件名
pre-applypath git am 变更 commit 之前,如果以非 0 退出,会导致 uncommit 状态,用于 commit 之前的检查  
pre-applypath git am commit 完成提交之后,主要用于通知  
pre-commit git commit 获取 commit message 之前,非 0 退出会取消本次 commit,检查 commit 自身,而不是 commit message  
prepare-commit-msg git commit 接收默认 commit message 之后,启动 commit message 编辑器之前。  
commit-msg git commit message 提交之后修改 message 的内容或退回不合格的 commit  
post-commit git commit commit 完成之后调用,主要用于通知  
pre-rebase git rebase 执行 rebase 时,可用于中断不想要的 rebase  
post-checkout git checkoutgit clone 更新工作树后调用 checkout 时,或执行 git clone 后,主要用于验证环境、显示变更、配置环境  
post-merge git merge or git pull 合并之后调用  
pre-push git push 推送远程之前  
pre-receive 远程 repo 进行 git-receive-pack 远程 repo 更新刚被 push 的 ref 之前调用,非 0 会中断本次  
update 远程 repo 进行 git-receive-pack 远程 repo 每一次 ref 被 push 的时候调用  
post-receive 远程 repo 进行 git-receive-pack 远程 repo 所有的 ref 更新之后  
post-update git-receive-pack 所有 ref 被 push 后执行一次  
pre-auto-gc git gc --auto 用于在自动清理 repo 之前做一些检查  
post-rewrite git commit --amendgit rebase git 命令重写 rewrite 已经被 commit 的数据时调用  

.git 这个隐藏的文件目录下,有一个 hooks/ 文件夹,下面通常会有一些 sample 文件。如果要使其生效,去掉后缀 sample 即可

目录下的每一个文件都是可执行的文件,脚本通过文件名调用,内容的第一行必须有 Shebang #!,引用正确的脚本解析器,比如 bash, perl, python, nodejs 等。Git hooks 使用的脚本语言是没有限制的。

比如在 pre-commit 中执行代码检查:

#!/bin/sh
npm run lint

# 获取上面脚本的退出码
exitCode="$?"
exit $exitCode

什么是 Husky

通过上面的简单了解也可能看到本地的 git hook 是保存在本地的 .git 文件夹中的,本地的 git hook 是不会被提交到版本控制中的。这就存在了一个问题,便是如何在项目中管理 Git hooks,这个时候就需要 Husky 登场了。

目前已经有非常多的项目在使用 husky 了,包括:

  • webpack
  • babel
  • create-react-app

Husky 的原理就是在项目的根目录中使用一个配置文件,然后在安装 Husky 的时候把配置文件和 Git hook 关联起来,在团队之间共享。

安装

安装:

npm install husky --save-dev

在项目中安装:

npx husky install

配置 Husky

使用 Husky 之前确保安装了 npm

npm install husky -D

Husky 支持如下几种格式配置:

  • .huskyrc
  • .huskyrc.json
  • .huskyrc.yaml
  • .huskyrc.yml
  • .huskyrc.js
  • husky.config.js

.huskyrc 为例:

{
  "hooks": {
    "pre-commit": "git restore -W -S dist examples/dist && eslint ."
  }
}

这一个例子就是每次执行 git commit 之前把 distexamples/dist 中的修改撤掉,不提交到仓库中,然后执行 EsLint。

Husky 原理

Husky 做了两件事情:

  • 创建 ~/.husky 目录
  • husky install 时设置 ~/.husky 目录为 git hooks 目录。

创建 Hook

使用 husky add:

npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

卸载还原 Husky

npm uninstall husky
// 删除.husky文件夹,并且重置core.hooksPath
rm -rf .husky && git config --unset core.hooksPath

Husky 注意事项

Husky 不支持服务端 hooks。

包括 pre-receiveupdatepost-receive

如果想跳过所有的 hooks,可以使用:

HUSKY_SKIP_HOOKS=1 git rebase ...

不使用 Husky 同步 Git hooks

在 git 2.9 中引入了 core.hooksPath 配置,可以手动配置 git hooks 所在的目录。这也就使得我们可以在另外的目录中创建 Git hooks,然后手动设置 Hooks 目录来实现 hooks 脚本的同步。

reference


2022-04-15 husky , git-hook , git

每天学习一个命令:tail 输出文件的最后部分内容

tail 命令是一个日常查看日志非常常用的命令,用来在终端显示文件的最后部分内容。

用例

tail 不加任何参数的情况下,默认显示文件最后 10 行内容。

tail /path/to/file.log

显示文件追加的内容

通常业务系统中以文件形式记录日志时会一直追加到文件末尾,可以使用 -f 来显示新追加的内容:

tail -f /path/to/file.log

显示文件结尾 100 行

tail -100 mail.log
tail -n 100 mail.log

显示文件第20行至末尾

tail -n +20 mail.log

显示结尾10个字符

tail -c 10 mail.log

2022-04-12 linux , tail , log

Gatsby 静态站点使用入门

Gatsby 是一个基于 [[React]] 的、免费开源的、用于搭建静态站点的框架。Gatsby 虽然是一个静态站点框架,但其数据却可以从任何地方获取之后渲染。 Gatsby 是基于 React 和 [[GraphQL]]。 结合了 webpack, babel, react-router 等前端领域中最先进的工具。 对开发人员来说开发体验非常好。

Gatsby 采用数据层和 UI 层分离的现代前端开发模式。静态 HTML 访问快,对 SEO 非常友好。

数据来源多样化: Headless CMS, markdown, API 等多种方式获取数据。

之前学习的时候建的券商推荐网站 就是使用 Gatsby 搭建。

为什么使用 Gatsby

  • 充分享受现代 Web 开发工具带来的便捷,GraphQL,React,Webpack
  • 数据与界面的分离
  • 丰富的插件生态系统

Gatsby 项目结构

  • /src/pages 目录下的组件会被生成同名页面
  • /src/templates 目录下放渲染数据的模板组件,如渲染 Markdown 文章,在其它博客系统中一般叫 layout。
  • /src/components 公共可复用的组件
  • /static 静态资源, webpack 会跳过
  • gatsby-config.js 配置
    • siteMetadata 全局配置
    • plugins 插件配置
  • gatsby-node.js 可以调用 Gatsby node APIs
    • 添加额外的配置
  • gatsby-browser.js 调用 Gatsby 浏览器 API

GraphQL

作为 Gatsby 在本地管理资源的一种方式,作为一个数据库查询语言,它有非常完备的查询语句。

src/pages 下的页面可以直接 export GraphQL 查询,在其它页面需要用 StaticQuery 组件或者 useStaticQuery hook。

在开发时默认的查询地址: http://localhost:8000

Installation

Gatsby 命令行:

npm install -g gatsby-cli

新建项目:

gatsby new gatsby-start

gatsby 默认会使用 gatsby-starter-default 来新建项目,你可以在后面加上其他 starter 的 GitHub 地址,来使用这个 starter 初始化项目。比如说:

gatsby new gatsby-start https://github.com/gatsbyjs/gatsby-starter-hello-world

启动项目:

cd gatsby-start
gatsby develop
# 或者
yarn develop

打开 localhost:8080 查看生成页面,可以打开 http://localhost:8000/__graphiql GraphiQL 调试界面。

几个常用的命令:

  • gastby develop:开启热加载开发环境
  • gastby build:打包到 public 下,构建生产环境用的优化过的静态网站所需的一切静态资源、静态页面与 js 代码
  • gatsby serve:在打包之后,启动一个本地的服务,供你测试刚才”gatsby build”生成的静态网页

src/pages/` 目录下,新建一个 JS 文件即可。在 src/pages/ 目录下的页面,Gatsby 会自动添加路由,生成页面。

新建 about.js

import React from 'react'

export default () => (
  <div>
    <h1>About</h1>
    <p>location: </p>
  </div>
)

访问 http://localhost:8000/about 即可查看。

Plugins

在 Gatsby 中有三种类型的插件: 分别为数据源插件 ( source ), 数据转换插件 ( transformer ), 功能插件 ( plugin )

  • 数据源插件负责从应用外部获取数据,将数据统一放在 Gatsby 的数据层中。
  • 数据转换插件负责转换特定类型的数据的格式,比如我们可以将 markdown 文件中的内容转换为对象形式。
  • 功能插件是为应用提供功能,比如通过插件让应用支持 Less 或者 [[TypeScript]]。

插件的命名是有规范的,数据源插件名称中必须包含 source,数据转换插件必须包含 transformer,功能插件名称必须包含 plugin。

所有插件的地址:https://www.gatsbyjs.org/plugins/

插件的使用

  • 首先需要下载插件,npm install
  • 在根目录的 gatsby-config.js 文件中配置插件,在 plugins 属性中添加我们的插件。

plugins 是一个数组,每一项都是一个插件,他支持字符串和对象两种类型,如果需要配置插件参数就是用对象,resolve 指明要配置什么插件,options 就是配置选项,name 表示资源类别,这个是自定义的这里写 json,path 是数据源文件路径。

modules.exports = {
    plugins: [
        {
            resolve: "gatsby-source-filesystem",
            options: {
                name: "json",
                path: `${__dirname}/data.json`
            }
        },
        "gatsby-transformer-json"
    ]
}

gatsby-source-filesystem 他是用于将本地文件信息添加至数据层当中

第二个是 gatsby-plugin-sharp:他是用于提供本地图像的处理功能(调整图像尺寸, 压缩图像体积 等等) 第三个是 gatsby-transformer-sharp 是将 gatsby-plugin-sharp 插件处理后的图像信息添加到数据层。

最后我们要用到 gatsby-image,这是一个 React 组件, 优化图像显示, 他是基于 gatsby-transformer-sharp 插件转化后的数据。

gatsby-plugin-image

安装:

npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-source-filesystem
  • gatsby-plugin-sharp 处理图片
  • gatsby-source-filesystem,从文件系统加载图片

通过 YAML 加载数据

  • https://www.gatsbyjs.com/docs/how-to/sourcing-data/sourcing-from-json-or-yaml/

  • The StaticImage component is for static image sources, like a hard-coded file path or remote URL. In other words, the source for your image is always going to be the same every time the component renders.
  • The GatsbyImage component is for dynamic image sources, like if the image source gets passed in as a prop.

https://www.gatsbyjs.com/docs/tutorial/part-7/#whats-the-difference-between-gatsbyimage-and-staticimage

https://www.gingerdoc.com/tutorials/how-to-handle-images-with-graphql-and-the-gatsby-image-api

https://www.labnol.org/code/gatsby-images-200607

Netlify

[[2018-03-24-netlify-to-host-static-website]]

reference


2022-04-10 gatsby , react , graphql , seo , static-website , cms , markdown , webpack

Nginx 中数据 Buffer size 相关配置

client_body_buffer_size

Nginx 分配给请求数据的 Buffer 大小,如果请求的数据小于 client_body_buffer_size ,那么 Nginx 会在内存中存储数据,如果请求的内容大小大于 client_body_buffer_size,但是小于 client_max_body_size,会先将数据存储到临时文件中。

默认的情况下,这个缓存大小是等于两个 memory pages,也就是在 x86 机器上是 8K,在 64-bit 平台上是 16K。

这个空间只有当请求有上传的时候才会被用到,一旦数据被传输到后端服务,内存就会被清空。这意味着你需要足够的内存空间来保存并发的上传,否则服务器会开始 swapping。

client_body_temp 指定的路径,默认是 /tmp/ 所配置的 client_body_temp 地址,一定让 Nginx 用户组有读写权限。否则当传输的数据大于 client_body_buffer_size 是写入临时文件会报错。

open() /nginx/client_body_temp/0000000019” failed (13: Permission denied)

在接口层面的表现为接口请求返回 403

client_max_body_size 默认大小是 1M,客户端请求服务器最大允许大小,如果请求正文数据大于 client_max_body_size 的值,那么 HTTP 协议会报错 413 Request Entity Too Large。 正文大于 client_max_body_size 一定是失败的,如果要上传大文件需要修改该值。

client_max_body_size 优先级

  • 可以选择在http{ }中设置:client_max_body_size   20m;
  • 也可以选择在server{ }中设置:client_max_body_size   20m;
  • 还可以选择在 location{ } 中设置:client_max_body_size   20m;

三者到区别是:http{} 中控制着所有nginx收到的请求。而报文大小限制设置在server{}中,则控制该server收到的请求报文大小,同理,如果配置在location中,则报文大小限制,只对匹配了location 路由规则的请求生效。

reference


2022-04-09 nginx , linux , web-server , buffer-size

Laravel 学习笔记:Blade Component

Blade 模板中的 Components 提供了和 section, layoutincludes 相似的机制。都可以用来复用构造的 Blade 模板。

但 Component 更容易理解,提供了两种方式:

  • class based components
  • anonymous components

使用命令创建:

php artisan make:component Alert

创建的文件在 App\View\Components 目录。

make:component 命令会创建一个 templateresources/views/components 目录中。

也能在子目录中创建 Components:

php artisan make:component Forms/Input

如果传参 --view:

php artisan make:component forms.input --view

就不会创建 class,只会创建模板。


2022-04-08 laravel , blade , php

Laravel 学习笔记:部署到生产环境

在本地开发调试的时候使用了 Laravel 提供的 Sail 依赖本地的 Docker 环境,Sail 提供了 Nginx,MySQL,Redis,等等容器,还提供了一个用于测试的 SMTP mailhog,但是生产环境可以使用更加稳定的组件。

Requirements

Laravel 应用需要一些基础的系统依赖,需要确保Web 服务器有如下的最低要求:

  • PHP >= 8.0
  • BCMath PHP Extension
  • Ctype PHP Extension
  • cURL PHP Extension
  • DOM PHP Extension
  • Fileinfo PHP Extension
  • JSON PHP Extension
  • Mbstring PHP Extension
  • OpenSSL PHP Extension
  • PCRE PHP Extension
  • PDO PHP Extension
  • Tokenizer PHP Extension
  • XML PHP Extension

Nginx

Web 服务器就用 Nginx。

记住 Web 服务器所有的请求都会先到 public/index.php 文件,千万不要将此文件放到项目的根目录,或者 Web 服务器的根目录,如果 Web 服务器可以访问项目根目录会造成带有敏感信息的配置文件泄漏。

server {
    listen 80;
    server_name example.com;
    root /srv/example.com/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

部署过程

这里使用 [[aapanel]] 来新建一个站点。

然后将代码 push 到 GitHub,然后到机器上 push 来下。

cp .env.example .env

根据自己的配置,修改数据库、Redis、SMTP 相关的配置。

执行:

sudo composer install

注意将站点的所有者修改为 www:

sudo chown -R www:www .

然后执行:

sudo php artisan key:generate
sudo php artisan migrate

禁用 php 方法:

  • proc_open
  • symlink
  • pcntl_signal
  • pcntl_alarm

Composer

安装 Composer:

wget https://getcomposer.org/download/1.8.0/composer.phar
mv composer.phar /usr/local/bin/composer
chmod u+x /usr/local/bin/composer
composer -V

自动加载优化:

composer install --optimize-autoloader --no-dev

优化配置加载,将配置文件压缩到一个缓存中

php artisan config:cache

优化路由加载:

php artisan route:cache

优化视图加载

php artisan view:cache

问题

如果遇到如下问题:

PHP Fatal error:  Uncaught Error: Call to undefined function Composer\XdebugHandler\putenv() in phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/Process.php:101
Stack trace:
#0 phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/Status.php(59): Composer\XdebugHandler\Process::setEnv()
#1 phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/XdebugHandler.php(99): Composer\XdebugHandler\Status->__construct()
#2 phar:///usr/local/bin/composer/bin/composer(18): Composer\XdebugHandler\XdebugHandler->__construct()
#3 /usr/local/bin/composer(29): require('...')
#4 {main}
  thrown in phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/Process.php on line 101

Fatal error: Uncaught Error: Call to undefined function Composer\XdebugHandler\putenv() in phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/Process.php:101
Stack trace:
#0 phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/Status.php(59): Composer\XdebugHandler\Process::setEnv()
#1 phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/XdebugHandler.php(99): Composer\XdebugHandler\Status->__construct()
#2 phar:///usr/local/bin/composer/bin/composer(18): Composer\XdebugHandler\XdebugHandler->__construct()
#3 /usr/local/bin/composer(29): require('...')
#4 {main}
  thrown in phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/Process.php on line 101

需要禁用 php 的 putenv 方法。


2022-04-07 laravel , php , laravel-deploy

电子书

本站提供服务

最近文章

  • 2024 年台北之行 去年的时候就得知了海外的大陆人可以通过官方网站申请入台证,从而可以在海外直接入境台湾,所以 4 月份女朋友过来日本之后就通过线上系统申请了入台证,入台证申请通过并付费之后是只有 3 个月有效期的,因为我们申请的比较晚,所以有效期的三个月正好落在了最热的 7,8,9 月份,但考虑到暑假有假期,我们还是决定硬着头皮买了机票。
  • macOS 上的多栏文件管理器 QSpace QSpace 是一个 macOS 上的多窗口平铺的文件管理器,可以作为 Finder 的代替,在 Windows 上曾经用过很长时间的 [[Total Commander]],后来更换到 Linux Mint 之后默认的文件管理器自带多面板,反而是用了很多年 macOS ,才意识到原来我缺一个多窗口,多面板的文件管理器。
  • Dinox 又一款 AI 语音转录笔记 前两天介绍过 [[Voicenotes]],也是一款 AI 转录文字的笔记软件,之前在调查 Voicenotes 的时候就留意到了 Dinox,因为是在小红书留意到的,所以猜测应该是国内的某位独立开发者的作品,整个应用使用起来也比较舒服,但相较于 Voicenotes,Dinox 更偏向于一个手机端的笔记软件,因为他整体的设计中没有将语音作为首选,用户也可以添加文字的笔记,反而在 Voicenotes 中,语音作为了所有笔记的首选,当然 Voicenotes 也可以自己编辑笔记,但是语音是它的核心。
  • Emote 又一款 AI 语音笔记应用 继发现了 Voicenotes 以及 Dinox 之后,又发现一款语音笔记 Emote,相较于前两款应用,Emote 吸引我的就是其实时转录的功能,在用 Voicenotes 的时候时长担心如果应用出现故障,没有把我要录下来的话录制进去,后期怎么办,而 Emote 就解决了这个问题,实时转录的功能恰好作为了一个声音录制的监听。
  • 音流:一款支持 Navidrome 兼容 Subsonic 的跨平台音乐播放器 之前一篇文章介绍了Navidrome,搭建了一个自己在线音乐流媒体库,把我本地通过 [[Syncthing]] 同步的 80 G 音乐导入了。自己也尝试了 Navidrome 官网列出的 Subsonic 兼容客户端 [[substreamer]],以及 macOS 上面的 [[Sonixd]],体验都还不错。但是在了解的过程中又发现了一款中文名叫做「音流」(英文 Stream Music)的应用,初步体验了一下感觉还不错,所以分享出来。