---
url: /zh/aphanite/deployment.md
description: 在生产环境中使用反向代理和 TLS 部署 Aphanite。
---

# 部署

理论上，只要你将 Aphanite 放在一个单独的文件夹并授予执行权限之后，双击就可以运行它。

然而，因为 Aphanite 自身并没有实现 TLS\[^1]，同时 Minecraft 强制要求 Yggdrasil 服务器实现 TLS，因此需要借助反向代理\[^2]来实现“外置”的 TLS。

\[^1]: TLS，简单来说，就是让 HTTP 变成 HTTPS 的东西。这有篇[来自 Cloudflare 的文章](https://www.cloudflare.com/zh-cn/learning/ssl/transport-layer-security-tls/)简单讲述了这项技术。

\[^2]: 这里有一篇[来自 Cloudflare 的文章](https://www.cloudflare.com/zh-cn/learning/cdn/glossary/reverse-proxy/)简单讲解了什么是反向代理。

同时，因为 Aphanite 需要所有玩家包括服主都能直接访问到，对于运行在本地电脑上的 Aphanite，这又需要配置内网穿透\[^3]才能让外界访问。

\[^3]: 这里有一篇[来自 SakuraFrp 的文章](https://doc.natfrp.com/basics.html)简单讲述了什么是内网穿透和端口。

本文详细介绍如何让 Aphanite 能正确运行。

## 我需要反向代理和/或内网穿透吗？

> \[!TIP]

**TL;DR: 反向代理：YES；内网穿透：按需。**

值得注意的是，大部分内网穿透工具本身就提供了反向代理的功能。因此，这里将两个名词的指代范围限定为：

* **内网穿透工具**：任意可以将端口转发到云端的工具。
* **反向代理工具**：只能在本机上监听端口的反向代理工具。

反向代理和内网穿透工具在 Aphanite 中扮演不同的角色：

* 反向代理：给 Aphanite 的“裸”HTTP 流量包装上 TLS，让它变成 HTTPS。
  * TLS 层是必须的，因为 Minecraft 不接受“裸” HTTP 的 Yggdrasil 服务器。从安全角度来看，裸 HTTP 意味着你的通信容易被篡改，因此也不能接受。
* 内网穿透：让 Aphanite 能够在公网上被访问。
  * 这是可选的。请问：你运行 Aphanite 的机器上是否有独立公网 IP，或有将本地端口映射出去的功能（这通常由服务器提供商提供）？如果你的回答是“是”，那么你就不需要内网穿透。

因此，问题就很明晰了——反向代理是必须的；内网穿透是可选的。

## 如何配置反向代理/内网穿透 {#port-forwarding}

下面提出三种实现思路，你可以按需选择。

### SakuraFrp 自动 HTTPS（反向代理+内网穿透一体，不需要域名） {#natfrp}

> \[!TIP]
>
> SakuraFrp 虽然基础使用上免费，但是需要每天签到获得流量。签到获得的流量对 Aphanite 来说是绰绰有余的，但是总归需要一些额外操作。如果你自己有域名，那么更建议考虑 [Cloudflare 方案](#cloudflare-tunnel)。

在[SakuraFrp 文档](https://doc.natfrp.com/)上有详细的操作教程。只需要仔细阅读这两章：

* [Web 应用穿透指南](https://doc.natfrp.com/app/http.html)。
* [配置子域绑定功能](https://doc.natfrp.com/bestpractice/domain-bind.html)。（请尤其注意其中的《创建 CNAME 记录并获取 SSL 证书进行建站》这一小节）

**一定要在你的隧道上启动“自动 HTTPS”**。

如果你决定在 Aphanite 上放一些整合包，那么 SakuraFrp 的免费流量大概率不够你使用。不过，你可以[配置 S3](/zh/aphanite/configuration#s3)，这样玩家下载整合包就不会花费你的流量了。

### Cloudflare Tunnel（反向代理+内网穿透一体，需要域名） {#cloudflare-tunnel}

[Cloudflare Tunnel](https://developers.cloudflare.com/tunnel/) 是由知名 CDN 服务商、网络基础设施提供商、~~赛博活菩萨~~ Cloudflare 提供的**免费**反向代理+内网穿透服务。

**前提条件**：有一个托管到 Cloudflare 的域名。如果你没有，[DigitalPlat](https://www.cnblogs.com/jsonhc/p/19631511) 或许是个解决方案。你也可以尝试用 Cloudflare Quick Tunnels 试一试（下面会讲），不过这样每次启动你的 Aphanite 服务器地址都会变化。

#### 操作步骤（已有域名）

1. 登录你的 Cloudflare 账号。
2. 点击侧边栏->联网->Tunnels。
3. 点击右上角的“创建隧道”。
4. 随便输入一个名称。
5. 按照页面上所标识的步骤安装并启动 `cloudflared`。
6. 点击“添加路由”。
7. 选择“已发布的应用程序”。
8. 输入你想部署到的域名以及 Aphanite 的链接（相对于运行 `cloudflared` 的主机。一般来说是 `http://127.0.0.1:<端口>`。这里的`<端口>`见[配置](/zh/aphanite/configuration)）。
9. Well done！你的 Aphanite 现在应该在你的新域名上可用了。

#### 操作步骤（没有域名）

下面的操作使用 Cloudflare Quick Tunnels。它会在每次启动时给你的服务新分配一个随机的域名，所以不推荐使用。没有域名的情况下，建议使用 [SakuraFrp 提供的二级域名](#natfrp)。

1. [下载并安装 `cloudflared`](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/)。
2. 运行：

```bash
cloudflared tunnel --url http://localhost:3000
```

3. 复制输出的域名。
4. 将输出的域名填入配置文件中 `service.domain` 里。（具体见[配置](/zh/aphanite/configuration)）
5. 启动 Aphanite。
6. 你的 Aphanite 现在应该在 `xxx.trycloudflare.com` 上可用了。

### Nginx（仅反向代理，需要公网 IP/公网端口转发，需要域名）

> \[!NOTE]
>
> 这一方案的配置步骤相对困难。如果你没有特殊的需求（如，你认为 SSL 证书必须掌握在自己的手里），建议使用 [Cloudflare Tunnel](#cloudflare-tunnel)。

这一章是给有经验的运维人员准备的。简单来说：

1. 获取 SSL 证书。可以使用 [Certbot](https://certbot.eff.org/) 或 [acme.sh](https://github.com/acmesh-official/acme.sh)。
2. 配置反向代理。下面是示例的 Nginx 反向代理配置块，以供参考。

```nginx {24}
server {
  listen 443 ssl;
  listen [::]:443 ssl;
  http2 on;
  listen 443 quic;
  listen [::]:443 quic;
  http3 on;

  server_name aphanite.yourdomain.com;

  ssl_certificate "/etc/letsencrypt/live/yourdomain.com/fullchain.pem";
  ssl_certificate_key "/etc/letsencrypt/live/yourdomain.com/privkey.pem";
  ssl_session_cache shared:SSL:1m;
  ssl_session_timeout 10m;
  ssl_ciphers PROFILE=SYSTEM;
  ssl_prefer_server_ciphers on;
  add_header Alt-Svc 'h3=":443"; ma=86400';
  ssl_early_data on;

  # Load configuration files for the default server block.
  include /etc/nginx/default.d/*.conf;

  location / {
    proxy_pass http://127.0.0.1:3000/; # 记得将这个修改成 Aphanite 的本地监听地址。
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    client_max_body_size 1024M;
  }
}
```

3. `nginx -t && nginx -s reload`。

或者，如[宝塔面板](https://www.bt.cn/new/index.html)之类的服务器管理软件提供了一键配置反向代理的功能，如果你安装了，可以参考它们的文档。

## 持久化运行

> \[!NOTE]
>
> 下面的内容仅适用于 Linux。

如果你在服务器上跑 Aphanite，你或许会想让它持久化运行，以避免退出 ssh 之后就关掉了。下面的教程可以让你创建 Systemd 单元文件来管理 Aphanite。

下面的步骤只适用于纯 ssh 管理的情况，如果你在桌面/面板上运行它，那么大概率是不需要往下看的。

下面假定你将 `aphanite` 二进制文件放在 `/opt/aphanite` 下，并且名为 `aphanite`。请根据实际情况替换。

### 创建用户

如果你想让 Aphanite 持久化运行，那么最好给它创建一个单独的用户，来隔离 Aphanite 可以访问的目录，防止意外的发生。

```bash
sudo useradd -U -d /opt/aphanite -s /sbin/nologin aphanite
sudo chown -R aphanite:aphanite /opt/aphanite
```

### 编写 Systemd 单元文件

下面提供一个示例的单元文件，可以按需修改，注意服务的启动指令。

```ini {11}
[Unit]
Description=Aphanite Yggdrasil server
After=network.target

[Install]
WantedBy=multi-user.target

[Service]
Type=simple
User=aphanite
ExecStart=/opt/aphanite/aphanite --config /opt/aphanite/config.toml --verbose
WorkingDirectory=/opt/aphanite
Restart=on-failure
```

接下来，把你编写的单元文件放在 `/etc/systemd/system/aphanite.service`，然后运行

```bash
sudo systemctl daemon-reload
```

就可以让 Systemd 看到你编写的单元文件了。

### SELinux 策略

> \[!NOTE]
>
> 下面的内容可能不适用于你的发行版。如果出错了，建议询问 AI。

如果你的发行版默认开启 SELinux，且 SELinux 策略比较激进，可能需要额外配置，即给 Aphanite 二进制文件标上 `bin_t`，才能让 Systemd 正常启动它。

你可以运行以下命令来查看 SELinux 状态：

```bash
getenforce
```

如果命令输出是 Enforcing，很遗憾，你可能需要遵循以下步骤来让 SELinux 认可 Aphanite。

```bash
# 记得把 /opt/aphanite/aphanite 替换成你服务器上 Aphanite 的**绝对**路径。
sudo semanage fcontext -a -t bin_t /opt/aphanite/aphanite
sudo restorecon -Rv /opt/aphanite/aphanite
```

### 启动服务 {#systemd-commands}

最后，你可以运行下面的命令管理 Aphanite：

```bash
sudo systemctl start aphanite # 启动 Aphanite 服务
sudo systemctl enable aphanite # 让 Aphanite 会开机自启动
sudo systemctl enable --now aphanite # 让 Aphanite 会开机自启动；并启动 Aphanite。
sudo systemctl stop aphanite # 停止 Aphanite
sudo systemctl disable aphanite # 让 Aphanite 不再开机自启动

systemctl status aphanite # 查看当前的状态
journalctl -u aphanite # 阅读 Aphanite 过往产生的日志
```

## 持久化运行（容器）

> \[!NOTE]
>
> 下面的内容仅适用于 Linux。

如果你使用容器化的方式来运行 Aphanite，那么让它持久化运行十分简单。下面讲解三个思路。

> \[!NOTE]
>
> 官方镜像以 UID 1000 的非 root 用户运行。请确保你挂载的宿主机目录（如 `/opt/aphanite`）对该用户可写——对于下面的示例而言，执行 `sudo chown -R 1000:1000 /opt/aphanite` 即可。

### 方案一：`--detach` 参数

这是最简单的方式，你只需要在启动容器的时候加上 `-d` 即 `--detach` 参数即可，容器后端会自动让它在后台持续运行。

```bash{2}
podman run \ # 或 docker
  -d \
  --name aphanite \
  -p 3000:3000 \
  -v "~/.aphanite:/app:Z" \
  quay.io/feniota/aphanite:latest
```

此时配上 `--restart unless-stopped` 参数可能会更好。这让容器后端在出现意料之外的故障时自动重启容器，保证服务的可用性。

```bash{2,3}
podman run \ # 或 docker
  -d \
  --restart unless-stopped \
  --name aphanite \
  -p 3000:3000 \
  -v "~/.aphanite:/app:Z" \
  quay.io/feniota/aphanite:latest
```

### 方案二：Podman Quadlet

Podman Quadlet 是来自 Podman 的一个 systemd 单元生成器。它可以让你用编写 Systemd 单元文件相同的 INI 语法来管理 Podman 容器。

该方案的好处是：

* 与底层系统服务（Systemd）深度集成，具有比手动启动容器更高的可靠性。
* 声明式的配置文件，便于管理和迁移。
* 可以使用 Systemd 的日志管理功能来查看容器的日志。
* 0 配置开机自启动。

当然也有明显的缺点：

* Docker 无法使用该方案。
* 不适合喜欢用面板管理服务器的情况。
* 需要学习 Systemd 的配置。

下面是一个示例的 Quadlet 配置文件，它假定你把 Aphanite 的配置文件和数据库放在 `/opt/aphanite` 下。

```ini
[Unit]
Description=Aphanite Yggdrasil Server
After=network-online.target

[Container]
Image=quay.io/feniota/aphanite:v0.1.1 # 不建议在此处使用 latest
PublishPort=3000:3000
Volume=/opt/aphanite:/app:Z

[Install]
WantedBy=multi-user.target
```

保存它，并把它放置在 `/etc/containers/systemd/aphanite.container`，随后运行：

```bash
sudo systemctl daemon-reload
```

Podman Quadlet 会基于该配置文件自动生成 `aphanite.service`，你可以像管理 Systemd 服务一样管理它。比如说：

```bash
sudo systemctl start aphanite # 启动 Aphanite
sudo systemctl stop aphanite # 停止 Aphanite
sudo systemctl status aphanite # 查看 Aphanite 的状态
sudo journalctl -u aphanite # 查看 Aphanite 的日志
sudo journalctl -fu aphanite # 查看 Aphanite 的实时日志
```

启动 Aphanite 服务之后，Systemd 会自动调用 Podman 来下载和启动容器。如果你在中途需要修改 Aphanite 的配置文件，可能需要在那之后重启服务。

> \[!NOTE]
> 由于 Podman Quadlet 生成的单元文件被 systemd 视为临时的，因此无法直接通过 `enable` 或 `disable`
> 来设置开机自启动。然而，因为上面的示例里包含了 `[Install]` 小节，Podman Quadlet 会自动在 `multi-user.target.wants` 里添加该服务。
> 也就是说，在你执行 `systemctl daemon-reload` 后，Aphanite 就已经被设置为会开机自启动了。

### 方案三：手动编写 Systemd 单元文件

```ini
[Unit]
Description=Aphanite Yggdrasil Server
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop %n
ExecStartPre=-/usr/bin/docker rm %n
ExecStartPre=/usr/bin/docker pull quay.io/feniota/aphanite:latest
ExecStart=/usr/bin/docker run --rm --name %n -p 3000:3000 -v /opt/aphanite:/app:Z quay.io/feniota/aphanite:v0.1.0
ExecStop=/usr/bin/docker stop %n

[Install]
WantedBy=multi-user.target
```

将它放置在 `/etc/systemd/system/aphanite.service`，然后运行：

```bash
sudo systemctl daemon-reload
```

就能像管理 Systemd 服务一样管理 Aphanite 了。具体管理方法可参考[上文中的命令](#systemd-commands)。

该方案的可维护性弱于 Podman Quadlet，因此在有条件安装 Podman（Why not？）的情况下，不建议使用该方案，除非你只能用 Docker 且迁移到 Podman 会很困难并且你还特别需要 Systemd 的功能。
