44 个稳定版本
3.3.8 | 2024年7月24日 |
---|---|
3.3.6 | 2024年3月22日 |
3.3.3 | 2023年12月27日 |
3.3.1 | 2023年8月6日 |
1.2.1 | 2020年6月20日 |
#18 在 网络编程
每月396次下载
68KB
917 行
Agate
静态文件的简单 Gemini 服务器
Agate 是一个用于 Gemini 网络协议的服务器,使用 Rust 编程语言构建。Agate 功能非常少,只能提供静态文件服务。它使用异步 I/O,即使在低端硬件上运行并处理大量并发请求时也非常高效。
了解更多
安装和设置
- 获取 agate 的二进制文件。您可以使用以下任何一种方式
预编译
下载并解压 预编译的二进制文件。
NixOS/Nix
使用 nix 软件包管理器运行 nix-env -i agate
Guix 系统
部署 agate 到 GNU Guix 系统中,通过将 agate-service-type 添加到您的系统 服务。
Arch Linux
安装 agate-bin
AUR 包以获取预编译的二进制文件。否则,安装 agate
AUR 包以从源代码编译 agate。
Cargo
如果您已安装 Rust 工具链,则运行 cargo install agate
以从 crates.io 安装 agate。
源代码
下载源代码,然后在源代码库中运行 cargo build --release
,然后找到二进制文件位于 target/release/agate
。
如果您的系统中存在,您可以使用 tools
目录中的安装脚本进行后续步骤。
如果没有,请考虑贡献一个,以便让技术不太熟练的用户更容易使用!
- 运行服务器。您可以使用以下参数来指定内容目录的位置、监听的 IP 地址和端口、请求 URL 中预期的域名以及要包含在 MIME 类型文本/gemini 文件中的默认语言代码:(将主机名
example.com
替换为您 Gemini 服务器的地址。)如果您尚未自己进行,Agate 首次运行时将为您生成私钥和证书,并使用指定的主机名。
agate --content path/to/content/ \
--addr [::]:1965 \
--addr 0.0.0.0:1965 \
--hostname example.com \
--lang en-US
所有命令行参数都是可选的。运行 agate --help
查看省略参数时使用的默认值。
当客户端请求 URL gemini://example.com/foo/bar
时,Agate 将响应位于 path/to/content/foo/bar
的文件。如果请求路径的任何部分以点开头,无论文件是否存在,Agate 都将响应状态码 52。此行为可以通过 --serve-secret
或在 .meta
配置文件中的特定文件条目来禁用(参见元预设)。如果该路径存在目录,Agate 将在该目录中查找名为 index.gmi
的文件。
配置
自动证书生成
如果使用了 --hostname
参数,Agate 将为指定的每个域名生成密钥和自签名证书。根据规范,Gemini 建议使用自签名证书,因为 Gemini 使用证书的 TOFU(首次使用信任)原则。因此,生成的证书也将具有较长的有效期 4096-01-01
。
有关密钥和证书的手动配置,请参阅下文的 证书部分。
TLS 版本
Agate 默认支持 TLSv1.2 和 TLSv1.3。您可以使用标志 --only-tls13
(或其简写 -3
)禁用对 TLSv1.2 的支持。这 不推荐 使用,因为它可能会破坏与某些客户端的兼容性。Gemini 规范要求“目前”与 TLSv1.2 兼容,因为并非所有平台都对 TLSv1.3 有很好的支持(参见规范第 4.1 节)。
目录列出
您可以通过在该目录中放置名为 .directory-listing-ok
的文件来启用该目录的基本目录列出。这不会影响子目录。此文件必须是 UTF-8 编码的文本;它可以是空的。文件中的任何文本都将添加到目录列表的开头。目录列表将隐藏以点开头的文件和目录(例如,.directory-listing-ok
文件本身、.meta
配置文件或 ..
目录)。
名为 index.gmi
的文件将始终优先于目录列出。
元预设
您可以将名为 .meta
的文件放入任何内容目录中。该文件存储了Agate在提供这些文件时使用的相邻文件的一些元数据。该 .meta
文件必须使用UTF-8编码。您还可以使用 -C
标志(或长格式 --central-conf
)启用中央配置文件。在这种情况下,Agate将始终在内容根目录中查找 .meta
配置文件,并忽略其他目录中的 .meta
文件。
.meta
文件具有以下格式(*1)
- 空行将被忽略。
- 同一行上的
#
之后的所有内容都是注释,将被忽略。 - 所有其他行必须具有以下形式:
<路径>:<元数据>
,即以文件路径开头,后面跟一个冒号,然后是元数据。
<path>
是大小写敏感的文件路径,可能在磁盘上存在或不存在。如果指向目录,则会被忽略。如果不使用中央配置文件模式,使用当前目录中不是文件的路径是未定义的行为(例如 ../index.gmi
将是未定义的行为)。您可以在现有路径中使用Unix样式的模式。例如 content/*
将匹配 content
中的任何文件,而 content/**
还将匹配 content
的子目录中的任何文件。然而,由于它们的特殊意义,默认情况下,*
和 **
模式不会匹配以点开头的文件或目录。可以通过 --serve-secret
禁用此行为,或者通过例如 content/.*
或 content/**/.*
分别显式匹配以点开头的文件。有关您可以使用模式的更多信息,请参阅 glob::Pattern
的文档。规则可以覆盖其他规则,因此如果一个文件被多个规则匹配,则最后一条规则生效。
<metadata>
可以采用以下四种可能的形式
- 空的
Agate不会发送默认语言参数,即使它在命令行中指定了。 - 以分号和MIME参数开始
如果找到文件,Agate将把指定的字符串附加到MIME类型上。 - 以Gemini状态码(即1-6之间的数字后面跟另一个数字)和一个空格开始
无论文件是否存在,Agate都会发送元数据。文件不会被发送或访问。 - 一个MIME类型,可能包括参数
如果找到文件,Agate将使用此MIME类型而不是它猜测的类型。即使它在命令行中指定了,也不会使用默认语言参数。
如果一行违反了格式或看起来像案例 3,但又是不正确的,可能会被忽略。你应该检查你的日志。请知道,当访问相应目录的文件时,首先读取这个配置文件。所以启动后没有日志消息并不意味着 .meta
文件是好的。
这样的配置文件可能看起来像这样
# This line will be ignored.
**/*.de.gmi: ;lang=de
nl/**/*.gmi: ;lang=nl
index.gmi: ;lang=en-GB
LICENSE: text/plain;charset=UTF-8
gone.gmi: 52 This file is no longer here, sorry.
如果这是内容根目录中的 .meta
文件,并且使用了 -C
标志,这将导致以下响应头
/
或/index.gmi
->20 text/gemini;lang=en-GB
/LICENSE
->20 text/plain;charset=UTF-8
/gone.gmi
->52 This file is no longer here, sorry.
- 任何以
.de.gmi
结尾的非隐藏文件(包括在非隐藏子目录中)->20 text/gemini;lang=de
- 任何在
nl
目录中的非隐藏文件,以.gmi
结尾(包括在非隐藏子目录中)->20 text/gemini;lang=nl
(*1) 从理论上讲,语法类似于典型的 INI 类文件,并允许使用具有 [section]
(默认部分设置为解析器中的 mime
)的节(由于所有其他部分都被忽略,这并没有什么区别)。这也意味着理论上也可以使用 =
而不是 :
。有关更多信息,请访问 configparser
的文档。
日志详细程度
Agate 使用 env_logger
crate,并允许您通过设置 RUST_LOG
环境变量来设置日志详细程度。要关闭所有日志,请使用 RUST_LOG=off
。有关更多信息,请参阅 env_logger
的文档。
虚拟主机
Agate 具有基本的虚拟主机支持。如果您指定了多个 --hostname
,Agate 将在内容根目录中的相应主机名目录中查找。例如,如果其中一个主机名是 example.com
,并且内容根目录设置为默认的 ./content
,并且请求 gemini://example.com/file.gmi
,那么 Agate 将查找 ./content/example.com/file.gmi
。只有当指定了多个 --hostname
时,此行为才会启用。Agate 还支持不同主机名的不同证书,请参阅下面的证书部分。
如果您想要为多个域名提供相同的内容,您可以通过不指定 --hostname
来禁用主机名检查。在这种情况下,Agate 将忽略请求的主机名,除了检查是否存在之外。
当指定了一个或多个 --hostname
时,Agate 将检查请求 URL 中的主机名和端口是否与指定的主机名和监听端口匹配。如果 Agate 在另一个端口后的代理后面,并且收到一个指定代理端口的 URL 请求,则该端口可能不匹配 Agate 的任何监听端口,请求将被拒绝:可以使用 --skip-port-check
来禁用端口检查。
证书
Agate 支持 --certs
选项使用多个证书。因此,Agate 总是要求客户端使用 SNI,这应该不是问题,因为 Gemini 规范也要求使用 SNI。
默认情况下,证书存储在 .certificates
目录中。这是一个隐藏目录,目的是为了防止粗心大意的人将内容根目录设置为当前目录,这也可能包含证书目录。在这种情况下,证书和私钥仍然会被隐藏。证书仅在 Agate 启动时加载,并在运行时不会重新加载。证书目录可以直接包含一个密钥和证书对,如果没有其他匹配的密钥,则使用默认对。证书目录还可以包含特定域的子目录,例如 example.org
和 portal.example.org
的文件夹。请注意,子域(如 portal.example.org
)的子文件夹不应在其他子文件夹内部,而应直接在证书目录中。Agate 将选择最接近名称的证书/密钥对。例如,以下目录结构:
.certificates
|-- cert.der (1)
|-- key.der (1)
|-- example.org
| |-- cert.der (2)
| `-- key.der (2)
`-- portal.example.org
|-- cert.der (3)
`-- key.der (3)
这会被理解为以下内容:
- 证书/密钥对(1)将用于整个域名树(以下有例外)。
- 证书/密钥对(2)将用于
example.org
的整个域名树,包括子域如secret.example.org
。它覆盖了此子树的密钥对(1)(以下有例外)。 - 证书/密钥对(3)将用于
portal.example.org
的整个域名树,包括子域如test.portal.example.org
。它覆盖了此子树的密钥对(1)和(2)。
使用仅命名 .
的目录会导致未定义的行为,因为这将与顶级证书/密钥对(如上例中的密钥对(1))具有相同的意义。
证书/密钥对的文件必须命名为 cert.der
和 key.der
。证书必须是一个包含域名名称的主题备用名称的 X.509 证书,格式为 DER 文件。私钥必须以 DER 格式存在,并且必须是 RSA、ECDSA 或 Ed25519 密钥。
日志记录
所有通过 TCP 套接字发出的请求都将使用此格式进行记录
<local ip>:<local port> <remote ip or dash> "<request>" <response status> "<response meta>"[ error:<error>]
所有通过 Unix 套接字发出的请求都将使用此格式进行记录
unix:[<unix socket name>] - "<request>" <response status> "<response meta>"[ error:<error>]
方括号表示可选部分。
只有发生错误时,“错误:”部分才会被记录。这仅应用于信息目的,因为状态码应提供发生错误的信息。如果错误是由于连接未建立(例如由于TLS错误)造成的,则可以使用下面列出的特殊状态码。
默认情况下,Agate不会记录远程IP地址,因为这可能是一个问题,因为IP地址在欧盟的GDPR中被视为私人数据。要启用IP地址的记录,可以使用--log-ip
选项。请注意,在这种情况下,一些错误条件可能会强制Agate记录破折号而不是IP地址。Unix套接字连接的连接也无法记录IP地址。
除了这些之外,还可能根据选择的日志级别在日志中发生一些行。例如,初始的“正在监听...”行或关于列出特定目录的信息。
Agate在记录错误时使用一些不是有效Gemini状态码的状态码
- 00 - 建立TLS连接时出错
- 01 - 获取对等方的IP地址时出错
安全考虑
如果您想在多用户系统上运行agate,您应该知道所有证书和密钥数据都会被加载到内存中,并存储在那里,直到服务器停止。由于内存在使用后也没有被明确覆盖或清零,因此敏感数据可能在服务器终止后仍然留在内存中。
依赖项
~13–22MB
~424K SLoC