Debian 12 安装 Keila 0.30.3 指南
在 Debian 12 服务器上从源码完整构建并部署 Keila 0.30.3:依赖链、数据库初始化、管理员账号、登录验证与测试邮件发送全流程实测。
Debian 12 安装 Keila 0.30.3 指南
一、环境信息
| 项目 | 值 |
|---|---|
| 操作系统 | Debian 12(bookworm,x86_64) |
| Keila 版本 | 0.30.3(写作时 GitHub 最新稳定版) |
| Erlang/OTP | 27.3.4.9(erts-15.2.7.6) |
| Elixir | 1.18.4 |
| 数据库 | PostgreSQL 15.19(Debian 12 官方仓库版本) |
| Node.js / npm | 18.20.4 / 9.2.0(Debian 12 官方仓库版本) |
| 应用监听端口 | 4000(对外发布为 8139,见下文说明) |
| 测试收信端 | Mailpit(内网 10.89.60.3:1025,Web 界面 http://192.168.146.131:8138) |
服务器为一台全新安装的 Debian 12 主机,root 权限,可访问公网(GitHub、hex.pm)。Keila 的定位、架构与同类产品对比见《Keila 0.30.3 介绍与选型指南》,本文专注落地:从零开始在一台 Debian 12 服务器上完成源码构建、数据库接入、管理员账号初始化,并验证「注册登录 → 建项目 → 建联系人 → 发测试邮件」的完整闭环。
二、前置条件
- 一台 Debian 12 服务器,具备 root 权限与至少 2 GB 内存(
mix deps.compile与mix release对内存有一定要求,1 GB 可能被 OOM 杀掉编译进程); - 磁盘剩余空间不少于 5 GB(源码、依赖目录与发布产物合计约 1.5 GB,加上 PostgreSQL 数据留出余量);
- 服务器可访问公网,安装过程需要从 GitHub 与 hex.pm 下载源码与依赖;
- 一个用于接收测试邮件的 SMTP 收信端。生产环境使用真实 SMTP 中继(如 Mailgun、SES 或企业邮箱),本文实验环境使用内网 Mailpit(SMTP 监听 10.89.60.3:1025,Web 收件箱 http://192.168.146.131:8138)替代,其安装不在本文范围内,读者可替换为任意可收信的 SMTP 服务。
三、版本与发布形态说明
写作时 Keila 的最新稳定版本为 0.30.3(2026 年发布线)。选它而非更旧的 0.8.x/0.9.x 系列,是因为 0.30.x 是当前 GitHub 默认 release 线,依赖栈已升级到 Elixir 1.18 / Phoenix 1.7.21 / Ecto 3.13。
需要特别说明的是发布形态:Keila 官方没有提供面向 Debian/Ubuntu 的 .deb 仓库包,也没有裸二进制发行包。官方发布的只有两种形态:
- 源码(GitHub tag tarball),需要自行编译出 release 产物后运行;
- 官方打包的部署形态(拉取即用,属于另一种部署路线,不在本文范围内,本专栏另有专文)。
因此本文走源码构建路线,这也是把 Keila 部署在传统服务器上的唯一路径。整个链路为:预编译 Erlang/OTP 27 + 预编译 Elixir 1.18.4 + 源码 mix release,无需在服务器上现场编译 BEAM 本身,构建时间可控制在 30 分钟以内。
四、安装系统依赖
Debian 12 官方仓库即可满足全部构建依赖:
apt-get update
apt-get install -y build-essential git cmake openssl npm postgresql
安装完成后核对版本:
dpkg -l build-essential cmake git npm postgresql | grep ^ii | awk '{print $2, $3}'
实测回显:
build-essential 12.9
cmake 3.25.1-1
git 1:2.39.5-0+deb12u3
npm 9.2.0~ds1-1
postgresql 15+248+deb12u1
两个容易漏装的包说明:
- cmake 是必需的:依赖
fast_html(HTML 解析库,底层是 C 实现的 lexbor)通过 cmake 构建。缺 cmake 时mix deps.compile会在编译 fast_html 时报Error 127: cmake: not found; - npm 是必需的:Keila 的前端资源(CodeMirror 编辑器、Tailwind CSS 等)需要通过 npm 安装 node_modules 后再由 esbuild 打包,
nodejs会作为 npm 的依赖一并装上(Debian 12 提供 node 18.20.4,实测满足要求)。
五、安装 PostgreSQL 并初始化数据库
Debian 12 仓库的 PostgreSQL 15 安装后默认已启动:
pg_lsclusters
实测回显:
Ver Cluster Port Status Owner Data directory Log file
15 main 5432 online postgres /var/lib/postgresql/15/main /var/log/postgresql/postgresql-15-main.log
创建 Keila 专用的数据库账号与库(密码 q1w2e3r4 为本文实验口令,生产环境请使用强密码):
su postgres -c "psql -c \"CREATE USER keila WITH PASSWORD 'q1w2e3r4';\""
su postgres -c "psql -c \"CREATE DATABASE keila OWNER keila;\""
验证 TCP 密码登录(Keila 走的就是 TCP 连接,必须验证这一步):
PGPASSWORD=q1w2e3r4 psql -h 127.0.0.1 -U keila -d keila -c "select current_user, current_database();"
实测回显:
current_user | current_database
--------------+------------------
keila | keila
(1 row)
为什么必须独立建库建账号:Keila 的全部状态都存在 PostgreSQL 里——业务表、Oban 作业队列表、会话数据都在同一个库中,首次启动会自动执行约 30 张表的迁移。它不像某些组件依赖 Redis 或外部消息队列,PostgreSQL 是 Keila 唯一的外部服务依赖,DB_URL 环境变量是唯一必填的数据库配置。
六、安装 Erlang/OTP 27 与 Elixir 1.18.4
Debian 12 仓库自带的 OTP 25 与 Elixir 1.13 无法满足 Keila 0.30.3 的要求(mix.exs 声明 elixir: "~> 1.18"),需要安装新版。为避免现场编译 BEAM(耗时且吃内存),采用官方预编译产物:
第一步:下载并校验预编译 OTP。hex.pm 的构建服务提供 linux/amd64 预编译包(没有 debian-12 专用 target,ubuntu-22.04 的 glibc 版本与 Debian 12 兼容,实测可用):
cd /opt && mkdir -p downloads && cd downloads
wget https://builds.hex.pm/builds/otp/amd64/ubuntu-22.04/OTP-27.3.4.9.tar.gz
sha256sum OTP-27.3.4.9.tar.gz
实测回显(校验值与官方构建产物一致,确认无损):
8ff93b2f89fd3c47342e22a031eb303c24305aeddaae0736462103ea04565e83 OTP-27.3.4.9.tar.gz
第二步:安装 OTP。预编译包解压后需要运行其自带的 Install 脚本绑定安装路径:
tar -xzf OTP-27.3.4.9.tar.gz
mv OTP-27.3.4.9 /usr/local/otp-27
cd /usr/local/otp-27 && ./Install -sasl /usr/local/otp-27
ln -sf /usr/local/otp-27/bin/erl /usr/local/bin/erl
ln -sf /usr/local/otp-27/bin/escript /usr/local/bin/escript
第三步:安装 Elixir 1.18.4 官方预编译包(注意该 zip 无顶层目录,要直接解压到目标路径):
cd /opt/downloads
wget https://github.com/elixir-lang/elixir/releases/download/v1.18.4/elixir-otp-27.zip
sha256sum elixir-otp-27.zip
unzip -o elixir-otp-27.zip -d /usr/local/elixir-1.18.4
ln -sf /usr/local/elixir-1.18.4/bin/elixir /usr/local/bin/elixir
ln -sf /usr/local/elixir-1.18.4/bin/mix /usr/local/bin/mix
实测 elixir-otp-27.zip 的 sha256:
5be18f35e329f7c5914a80dd9f323d7bbb144616df1ed16f6f0862a1900b4bb5 elixir-otp-27.zip
第四步:验证:
elixir --version
实测回显:
Erlang/OTP 27 [erts-15.2.7.6] [source] [64-bit] [smp:128:6] [ds:128:6:10] [async-threads:1] [jit:ns]
Elixir 1.18.4 (compiled with Erlang/OTP 27)
七、下载源码并构建 release
7.1 下载源码并校验
cd /opt/downloads
wget https://github.com/pentacent/keila/archive/refs/tags/0.30.3.tar.gz -O keila-0.30.3.tar.gz
sha256sum keila-0.30.3.tar.gz
实测回显:
c18c2bb32cd0f0bda3305cefb1e16c9456dcfe0d43e53d6043a95c6c0d42d4fc keila-0.30.3.tar.gz
解压到工作目录:
tar -xzf keila-0.30.3.tar.gz -C /opt
cd /opt/keila-0.30.3
7.2 拉取并编译 Elixir 依赖
首次使用 mix 前先初始化 hex 与 rebar:
export LANG=C.UTF-8 LC_ALL=C.UTF-8
export MIX_ENV=prod
mix local.hex --force && mix local.rebar --force
mix deps.get
首次执行会逐个拉取全部依赖(约 85 个,包含 ecto_sql、postgrex、oban、phoenix 等);依赖已就位时再次执行会输出:
All dependencies are up to date
随后编译依赖(这一步耗时最长,约 5–10 分钟):
mix deps.compile
编译过程会滚动输出各依赖的编译信息,形如:
==> phoenix_ecto
Compiling 7 files (.ex)
Generated phoenix_ecto app
注意:编译 fast_html 时若报
Error 127: cmake: not found,说明漏装了 cmake,apt-get install -y cmake后重跑mix deps.compile即可。
7.3 安装前端依赖并构建静态资源
Keila 官方构建脚本使用 npm ci,但在 Debian 12 自带的 npm 9.2.0 下会因 package.json 与 package-lock.json 锁文件版本不一致而失败:
61 error `npm ci` can only install packages when your package.json and package-lock.json
or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
实测改用 npm install(npm 会自动修补锁文件差异)即可正常安装:
npm install --prefix assets --no-audit --no-fund
ls assets/node_modules | wc -l
实测回显:
227
接着构建并摘要静态资源(Tailwind CSS、esbuild 打包、phx.digest 指纹化):
mix assets.deploy
实测回显(尾部):
Done in 847ms.
Check your digested files at "priv/static"
构建完成后 priv/static/ 下应出现 css/ js/ fonts/ images/ vendor/ cache_manifest.json 等完整产物。如果跳过这一步直接 release,Web 界面会因为缺少 CSS/JS 而完全残缺——这是源码构建 Keila 最常见的坑。
7.4 生成 release 产物
mix release
若已存在旧产物会交互式询问是否覆盖,脚本化部署建议直接加 --overwrite。构建成功后尾部输出:
_build/prod/rel/keila/bin/keila start
Once the release is running:
# To connect to it remotely
_build/prod/rel/keila/bin/keila remote
# To stop it gracefully (you may also send SIGINT/SIGTERM)
_build/prod/rel/keila/bin/keila stop
To list all commands:
_build/prod/rel/keila/bin/keila
产物位于 /opt/keila-0.30.3/_build/prod/rel/keila/,它是自包含的:内含 BEAM 运行所需的全部 BEAM 文件与静态资源,不依赖系统里安装的 Elixir/mix。确认静态资源已进入产物:
ls _build/prod/rel/keila/lib/keila-0.30.3/priv/static/
实测回显:
cache_manifest.json
css
downloads
favicon-c43e2492ba322d2b6e759812ebcd79f0.ico
favicon.ico
fonts
images
js
robots-f4ae1c90665e2972cb598ebf2c8d23b0.txt
robots-f4ae1c90665e2972cb598ebf2c8d23b0.txt.gz
robots.txt
robots.txt.gz
social-icons
vendor
八、环境变量与首次启动
Keila release 通过环境变量完成全部配置(完整清单见《Keila 0.30.3 配置参数详解》)。本文按最小可用集配置:
cd /opt/keila-0.30.3/_build/prod/rel/keila
export LANG=C.UTF-8 LC_ALL=C.UTF-8
export DB_URL="postgres://keila:q1w2e3r4@127.0.0.1:5432/keila"
export SECRET_KEY_BASE="$(openssl rand -base64 48)"
export URL_HOST="192.168.146.131"
export URL_PORT="8139"
export URL_SCHEMA="http"
export MAILER_SMTP_HOST="10.89.60.3"
export MAILER_SMTP_PORT="1025"
export MAILER_SMTP_FROM_EMAIL="newsletter@keila.local"
export MAILER_SMTP_AUTH_METHOD="none"
export MAILER_SMTP_TLS_MODE="none"
三点说明:
SECRET_KEY_BASE:会话签名密钥,要求至少 64 字符,openssl rand -base64 48恰好生成 64 字符。该值必须固定下来(写入 env 文件或 systemd 单元),每次重启都换会导致所有登录会话失效;URL_HOST/URL_PORT:对外的访问地址,用于生成邮件中的退订链接等。本文实验环境的应用监听 4000 端口(PORT默认值),经 8139 端口对外发布,所以两者不同;直连部署时URL_PORT与PORT通常一致;MAILER_SMTP_*:系统邮件发信通道(注册激活、密码重置等走这里)。AUTH_METHOD=none+TLS_MODE=none对应内网测试收信端;生产环境接真实 SMTP 时改回默认的auto即可。
8.1 管理员账号(KEILA_USER / KEILA_PASSWORD)
Keila 没有预置管理员,实例管理员通过初始化种子创建。在首次启动前设置:
export KEILA_USER="admin@keila.local"
export KEILA_PASSWORD="q1w2e3r4q1"
两点实测得出的重要结论:
- 每次应用启动都会自动执行数据库迁移与初始化种子(等价于
bin/keila eval "Keila.ReleaseTasks.init()"),所以KEILA_USER/KEILA_PASSWORD必须在第一次启动前就位。若首次启动时未设置,种子会以默认值创建root@localhost并随机生成密码(日志中会打印),后续再设置环境变量也不会重建(种子只在库为空时执行); - 密码最少 10 个字符——这是 Keila 的硬性校验(Argon2id 哈希前校验
password, min: 10),常见演示口令q1w2e3r4只有 8 位,无法通过注册与种子创建,本文使用其衍生q1w2e3r4q1。
8.2 启动
bin/keila daemon
daemon 模式以后台守护进程运行,日志写入 release 目录下 tmp/log/erlang.log.1。首次启动会跑完全部迁移并创建管理员,从日志可确认:
2026-10-26T06:22:17.883 [info] == Running 20260529100000 Keila.Repo.Migrations.ConvertMjRawLiquidToKeilaCode.up/0 forward
2026-10-26T06:22:17.884 [info] == Running 20260607120000 Keila.Repo.Migrations.AddMessageHeadersAndCcFields.change/0 forward
2026-10-26T06:22:17.886 [info] == Running 20260818093917 Keila.Repo.Migrations.AddMessageReceiptIndex.change/0 forward
2026-10-26T06:22:17.888 [info] == Running 20260818122205 Keila.Repo.Migrations.AddMessageStatsIndex.change/0 forward
2026-10-26T06:22:18.057 [info] Created root user with admin@keila.local
确认进程与 HTTP 服务:
bin/keila pid
curl -s -o /dev/null -w 'HTTP %{http_code} -> %{redirect_url}\n' http://192.168.146.131:8139/
实测回显:
40795
HTTP 302 -> http://192.168.146.131:8139/auth/login
302 跳转到登录页即为正常(未登录访问首页被重定向)。同时可在数据库侧验证迁移结果:
PGPASSWORD=q1w2e3r4 psql -h 127.0.0.1 -U keila -d keila \
-c "select count(*) as tables from information_schema.tables where table_schema = 'public';" \
-c "select email, activated_at is not null as activated from users;"
实测回显:
tables
--------
30
(1 row)
email | activated
-------------------+-----------
admin@keila.local | t
(1 row)
30 张业务表迁移完成、管理员账号已激活(activated = t)。
九、登录验证与发信闭环
以下步骤完整走通「登录 → 建项目 → 生成 API Key → 建联系人 → 建 Campaign → 发送测试邮件」。
9.1 登录
Keila 登录是普通表单提交,先取 CSRF token 再 POST:
CSRF=$(curl -s -c cookies.txt http://192.168.146.131:8139/auth/login \
| grep -oE 'name="_csrf_token"[^>]*' | grep -oE 'value="[^"]+"' | sed 's/value="//;s/"//')
curl -s -b cookies.txt -c cookies.txt -o /dev/null \
-w 'POST /auth/login -> %{http_code} redirect=%{redirect_url}\n' \
--data-urlencode "user[email]=admin@keila.local" \
--data-urlencode "user[password]=q1w2e3r4q1" \
--data-urlencode "_csrf_token=$CSRF" \
http://192.168.146.131:8139/auth/login
实测回显:
POST /auth/login -> 302 redirect=http://192.168.146.131:8139/
302 回首页即登录成功(首个管理员拥有实例的全部管理能力,包括 /admin/users 用户管理)。
9.2 创建项目
Keila 以项目(Project)组织联系人、发件人与 Campaign:
CSRF=$(curl -s -b cookies.txt -c cookies.txt http://192.168.146.131:8139/projects/new \
| grep -oE 'name="_csrf_token"[^>]*' | grep -oE 'value="[^"]+"' | sed 's/value="//;s/"//')
curl -s -b cookies.txt -c cookies.txt -o /dev/null \
-w 'POST /projects/new -> %{http_code} redirect=%{redirect_url}\n' \
--data-urlencode "project[name]=Keila Demo" \
--data-urlencode "_csrf_token=$CSRF" \
http://192.168.146.131:8139/projects/new
实测回显:
POST /projects/new -> 302 redirect=http://192.168.146.131:8139/projects/np_weLJnLY5
项目 ID 为 np_weLJnLY5(下文复用)。
9.3 生成 API Key
API Key 在 Web 界面的「Settings → API Keys」创建,同样可以通过表单 POST 完成:
CSRF=$(curl -s -b cookies.txt -c cookies.txt \
http://192.168.146.131:8139/projects/np_weLJnLY5/api_keys/new \
| grep -oE 'name="_csrf_token"[^>]*' | grep -oE 'value="[^"]+"' | sed 's/value="//;s/"//')
curl -s -b cookies.txt --data-urlencode "api_key[name]=b1-verify" \
--data-urlencode "_csrf_token=$CSRF" \
http://192.168.146.131:8139/projects/np_weLJnLY5/api_keys -o apikey.html
grep -oE 'value="[A-Za-z0-9_-]{40,}"' apikey.html
实测回显(页面提示 The private API Key is displayed only once and cannot be recovered,仅显示一次):
value="lkp0Henf2NsXM8Vehf4R6zKdAE23eGzSRB-ftkwqTJQ"
9.4 创建联系人
Keila 的联系人模型没有独立的「联系人列表」层级,联系人在项目内扁平管理,通过**分群(Segment)**圈选投递范围;不指定 Segment 的 Campaign 默认发送给项目内全部 active 联系人。用 REST API 建两个联系人:
curl -s -X POST -H "Authorization: Bearer lkp0Henf2NsXM8Vehf4R6zKdAE23eGzSRB-ftkwqTJQ" \
-H "Content-Type: application/json" \
-d '{"data":{"email":"alice@example.com","first_name":"Alice","last_name":"Chen"}}' \
http://192.168.146.131:8139/api/v1/contacts
实测回显:
{"data":{"data":null,"id":"nc_weLJnLY5","status":"active","email":"alice@example.com","inserted_at":"2026-10-26T06:29:39Z","updated_at":"2026-10-26T06:29:39Z","external_id":null,"first_name":"Alice","last_name":"Chen"}}
注意请求体必须是
{"data": {...}}包裹——Keila 的 OpenAPI 规范把业务字段包在data键下,直接传平铺字段会得到 400"Bad Request"。
9.5 创建发件人(Sender)
发件人(发信身份)在 Web 界面中以 LiveView 表单创建,HTTP POST 无法直达。无浏览器环境时可用 release 自带的 eval 命令调用内部 API 实测创建(注意 bin/keila eval 启动的是不加载应用的裸节点,必须先 ensure_all_started,且执行时需停掉 daemon 释放 4000 端口):
bin/keila stop && sleep 3
export DB_URL="postgres://keila:q1w2e3r4@127.0.0.1:5432/keila"
export SECRET_KEY_BASE="$(cat /opt/keila-0.30.3/b1.env | cut -d= -f2)"
export URL_HOST="192.168.146.131" URL_PORT="8139" URL_SCHEMA="http"
export MAILER_SMTP_HOST="10.89.60.3" MAILER_SMTP_PORT="1025"
export MAILER_SMTP_FROM_EMAIL="newsletter@keila.local"
export MAILER_SMTP_AUTH_METHOD="none" MAILER_SMTP_TLS_MODE="none"
bin/keila eval "Application.ensure_all_started(:keila); {:ok, s} = Keila.Mailings.create_sender(\"np_weLJnLY5\", %{name: \"Newsletter\", from_email: \"newsletter@keila.local\", from_name: \"Keila Demo\", config: %{type: \"smtp\", smtp_relay: \"10.89.60.3\", smtp_port: 1025}}); IO.puts(\"SENDER_ID=\" <> s.id)"
bin/keila daemon && sleep 6
实测回显:
SENDER_ID=nms_weLJnLY5
若漏掉
smtp_relay,changeset 会报smtp_relay: {"can't be blank", [validation: :required]};若漏掉Application.ensure_all_started(:keila),会报could not lookup Ecto repo Keila.Repo because it was not started。
9.6 创建 Campaign 并发送
Campaign 的类型需与内容字段匹配:settings.type: "text" 对应 text_body:
curl -s -X POST -H "Authorization: Bearer lkp0Henf2NsXM8Vehf4R6zKdAE23eGzSRB-ftkwqTJQ" \
-H "Content-Type: application/json" \
-d '{"data":{"subject":"Keila B1 验证邮件","text_body":"Hello from Keila 0.30.3 built from source!","sender_id":"nms_weLJnLY5","settings":{"type":"text"}}}' \
http://192.168.146.131:8139/api/v1/campaigns
实测回显:
{"data":{"data":null,"id":"nmc_weLJnLY5","subject":"Keila B1 验证邮件","settings":{"type":"text"},"inserted_at":"2026-10-26T06:32:39Z","updated_at":"2026-10-26T06:32:39Z","html_content":null,"text_body":"Hello from Keila 0.30.3 built from source!","html_body":null,"sender_id":"nms_weLJnLY5","template_id":null,"sent_at":null,"scheduled_for":null,"mjml_body":null,"json_body":null,"mjml_content":null,"text_content":null,"preview_text":null,"segment_id":null}}
触发投递(异步,经 Oban 队列发送):
curl -s -i -X POST -H "Authorization: Bearer lkp0Henf2NsXM8Vehf4R6zKdAE23eGzSRB-ftkwqTJQ" \
-H "Content-Type: application/json" \
http://192.168.146.131:8139/api/v1/campaigns/nmc_weLJnLY5/actions/send
实测回显(HTTP 头 + 响应体):
HTTP/1.1 202 Accepted
content-type: application/json; charset=utf-8
{"data":{"campaign_id":"nmc_weLJnLY5","delivery_queued":true}}
9.7 验证测试邮件送达
在收信端查询(本文实验环境为 Mailpit 的 HTTP API):
curl -s http://192.168.146.131:8138/api/v1/messages \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('total:', d['total']); [print(m['From']['Address'], '->', [a['Address'] for a in m['To']], '|', m['Subject']) for m in d['messages'][:5]]"
实测回显:
total: 2
newsletter@keila.local -> ['alice@example.com'] | Keila B1 验证邮件
newsletter@keila.local -> ['bob@example.com'] | Keila B1 验证邮件
两名联系人均收到由 newsletter@keila.local 发出的测试邮件——源码构建的 Keila 0.30.3 全链路(PostgreSQL 存储 → Oban 投递队列 → SMTP 发信)验证通过。
十、生产部署配置建议
实验通过后,生产化只需补齐三件事:
- 固定环境变量:把上述 export 集合写入
/etc/keila.env(chmod 600),由 systemd 加载。下面是一个可直接参考的 unit 示例(bin/keila daemon自身已带守护与日志,systemd 只负责开机拉起与崩溃重启):
[Unit]
Description=Keila Newsletter
After=network.target postgresql.service
[Service]
Type=forking
User=keila
EnvironmentFile=/etc/keila.env
WorkingDirectory=/opt/keila-0.30.3/_build/prod/rel/keila
ExecStart=/opt/keila-0.30.3/_build/prod/rel/keila/bin/keila daemon
ExecStop=/opt/keila-0.30.3/_build/prod/rel/keila/bin/keila stop
PIDFile=/opt/keila-0.30.3/_build/prod/rel/keila/keila.pid
Restart=on-failure
[Install]
WantedBy=multi-user.target
- HTTPS 入口:
URL_SCHEMA=https并配合 Nginx/Caddy 反代到127.0.0.1:4000,否则浏览器会话 Cookie 与邮件链接都会以 http 生成; - 真实 SMTP:将
MAILER_SMTP_*指向生产中继(默认AUTH_METHOD=auto+TLS_MODE=auto适配 587 STARTTLS),同时建议配置CAPTCHA_*环境变量替换注册页默认的 hCaptcha 测试配置(不配置时 Keila 使用 hCaptcha staging 密钥,注册验证码形同虚设,详见配置详解篇)。
十一、常见问题
11.1 mix deps.compile 阶段 fast_html 报 Error 127
cmake: not found。apt-get install -y cmake 后重跑即可,无需清理已编译依赖。
11.2 mix esbuild 报 @codemirror/state 无法解析
报错形如 import { EditorState } from "@codemirror/state" ... You can mark the path "@codemirror/state" as external,共 39 个错误。根因是 assets/node_modules 不存在或残缺——npm ci 失败后继续执行就会这样。先 npm install --prefix assets 再 mix assets.deploy。
11.3 npm ci 报 package.json 与 lock 不同步
Debian 12 的 npm 9.2.0 对 Keila 0.30.3 的 lock 文件(lockfileVersion 2)执行严格一致性校验会失败。实测 npm install --prefix assets 可正常完成安装。
11.4 首次启动后不知道管理员密码
KEILA_USER/KEILA_PASSWORD 未设置时,种子创建 root@localhost 并随机生成密码(启动日志会打印一行随机密码)。若已错过,最简单的办法是清空数据库重来:dropdb + createdb 后设置好环境变量再启动。
11.5 注册/创建用户报密码错误
Keila 强制密码 ≥ 10 字符,q1w2e3r4 这类 8 位口令不通过。注册页(Web 界面自助注册)同样受此限制,且默认挂着 hCaptcha 验证码(staging 配置下任意 response 可通过,生产必须配置 CAPTCHA_*)。
11.6 API 返回 400 "Bad Request"
请求体必须用 {"data": {...}} 包裹业务字段,直接平铺会 400。另外确认带了 Authorization: Bearer <api_key> 与 Content-Type: application/json。
11.7 bin/keila eval 报 Repo not started
eval 启动的裸节点不会自动启动应用,需在表达式开头加 Application.ensure_all_started(:keila),且执行期间 daemon 必须处于停止状态(否则 Endpoint 抢占 4000 端口导致应用启动失败)。
11.8 启动日志出现 latin1 编码警告
warning: the VM is running with native name encoding of latin1 which may cause Elixir to malfunction。设置 export LANG=C.UTF-8 LC_ALL=C.UTF-8 后启动即可消除。
十二、卸载清理
# 停止服务(systemd 场景)
systemctl stop keila && systemctl disable keila
# 删除 release 产物与源码
rm -rf /opt/keila-0.30.3 /opt/downloads
# 删除数据库(先备份再操作)
su postgres -c "psql -c \"DROP DATABASE keila;\""
su postgres -c "psql -c \"DROP USER keila;\""
# 如需彻底移除 OTP 与 Elixir
rm -rf /usr/local/otp-27 /usr/local/elixir-1.18.4
rm -f /usr/local/bin/{erl,escript,elixir,mix}
十三、参考链接
- Keila 官方仓库:https://github.com/pentacent/keila
- Keila 官方网站与文档:https://www.keila.io
- Keila 0.30.3 源码包:https://github.com/pentacent/keila/archive/refs/tags/0.30.3.tar.gz
- Erlang/OTP 预编译构建:https://builds.hex.pm/builds/otp/amd64/ubuntu-22.04/
- Elixir 1.18.4 预编译包:https://github.com/elixir-lang/elixir/releases/tag/v1.18.4
- 本专栏选型对比:《Keila 0.30.3 介绍与选型指南》