Domain Locker Ubuntu 22.04 安装部署指南

Domain Locker v0.3.5 在 Ubuntu 22.04.5 上的 Node.js 22 + PostgreSQL 14 直装实测:NodeSource 源安装、npm ci 依赖编译、生产构建、SQLite/PostgreSQL 双模式启动、setup-postgres.sh 建库、端到端 API 与 RDAP 采集验证、systemd 常驻与五类真实踩坑排障(2026-10-28 实测)。

Domain LockerNodejs域名服务运维2026/10/28

Domain Locker Ubuntu 22.04 安装部署指南(Node.js + PostgreSQL 直装实测)

本文全部命令在 Ubuntu 22.04.5 LTS 服务器上实测完成(实测日期 2026-10-28),组件版本为 Domain Locker v0.3.5。文中命令输入与输出均为真机回显逐行照抄;示例口令统一使用 q1w2e3r4,生产环境请替换。认知与选型背景见 《Domain Locker 介绍与选型指南》。

1. 部署形态与机器要求

Domain Locker v0.3.x 是 Node.js 单进程全栈应用(Analog/Angular 19 + Nitro 服务端),官方支持 SQLite 与 PostgreSQL 两种数据底座。本篇采用 Node.js 22 + PostgreSQL 14 直装:数据库独立于应用进程、便于纳入统一备份体系;如果只是个人轻量使用,可跳过第 6 节,直接用默认 SQLite(零配置)。

实测环境:

  • 操作系统:Ubuntu 22.04.5 LTS
  • 应用版本:Domain Locker v0.3.5(GitHub Releases 最新稳定版,查询日期 2026-10-28)
  • 运行时:Node.js v22.23.4(NodeSource APT 源)、npm 10.9.9
  • 数据库:PostgreSQL 14.24(Ubuntu 22.04 官方仓库自带)
  • 服务端口:8147(由 .env 的 PORT 指定,官方默认 3000)
  • 内存:构建期峰值约 2~3 GB(Angular 生产构建),建议 4 GB 以上;运行期常驻约 200~400 MB

磁盘占用实测:依赖目录 node_modules 约 1.1 GB,构建产物 dist 约 82 MB,请预留 2.5 GB 以上工作目录空间。

2. 系统准备:基础工具与构建链

npm ci 阶段会从源码编译 better-sqlite3 原生模块(即使最终选用 PostgreSQL,该依赖也在 package.json 中),因此 build-essential 与 python3 必装,否则 node-gyp 编译失败:

# apt-get update -qq
# DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl ca-certificates git build-essential python3 postgresql postgresql-contrib gnupg
Setting up nodejs (22.23.4-1nodesource1) ...
# psql --version
psql (PostgreSQL) 14.24 (Ubuntu 14.24-0ubuntu0.22.04.1)

确认 PostgreSQL 集群状态(Ubuntu 的 postgresql 包安装后会自动初始化一个 14/main 集群,默认处于 down 状态,首次使用时启动即可):

# pg_lsclusters
Ver Cluster Port Status Owner    Data directory              Log file
14  main    5432 down   postgres /var/lib/postgresql/14/main /var/log/postgresql/postgresql-14-main.log

3. 安装 Node.js 22

Domain Locker 的 package.json 通过 engines 字段声明 node >= 22.0.0,Ubuntu 22.04 官方仓库的 Node.js 12 无法满足,使用 NodeSource 源安装 22.x:

# curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource.sh
# bash /tmp/nodesource.sh >/tmp/ns.log 2>&1
# DEBIAN_FRONTEND=noninteractive apt-get install -y -qq nodejs
Setting up nodejs (22.23.4-1nodesource1) ...
# node -v
v22.23.4
# npm -v
10.9.9

4. 获取源码

4.1 方式一:git clone(推荐,便于后续升级)

# cd /root
# git clone --depth 1 --branch v0.3.5 https://github.com/Lissy93/domain-locker.git

⚠️ 排障实录:实测首次克隆报错 error: RPC failed; curl 16 Error in the HTTP2 framing layer,随后 fatal: expected flush after ref listing。这是部分网络环境下 GitHub 的 HTTP/2 传输问题,与本组件无关。两条修复路径均已实测:

# 修复一:强制 HTTP/1.1(实测克隆成功,用完可 unset 还原)
# git config --global http.version HTTP/1.1
# 修复二:直接下载发布包(实测可行)
# curl -fsSL -o dl.tar.gz https://codeload.github.com/Lissy93/domain-locker/tar.gz/refs/tags/v0.3.5
# tar xzf dl.tar.gz && mv domain-locker-0.3.5 domain-locker

4.2 核对版本

# cd /root/domain-locker
# grep '"version"' package.json
  "version": "0.3.5",

5. 安装依赖并构建

5.1 npm ci

# npm ci --no-audit --no-fund

实测耗时约 5 分钟(2 核 4G 网络顺畅环境),末尾的关键回显:

npm warn deprecated prebuild-install@7.1.3: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
npm warn deprecated glob@10.5.0: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me

⚠️ 排障实录:安装日志中可见 prebuild-install || node-gyp rebuild --release 子进程在编译 sqlite3.c——这是 better-sqlite3 没有命中预编译二进制时的正常回退路径,依赖第 2 节装的 build-essential + python3。deprecated 警告只影响上游维护状态,不影响安装结果,npm ci 以退出码 0 结束即可。装完顶层包约 963 个,node_modules 约 1.1 GB。

5.2 生产构建

# NODE_OPTIONS=--max-old-space-size=4096 npm run build

构建末尾回显(实测):

Prerendering static pages...
Building Server...


The '@analogjs/platform' server has been successfully built.

构建产物入口在 dist/analog/server/index.mjs。若构建进程被 OOM 杀死(小内存机器),加大 NODE_OPTIONS 的 max-old-space-size 或临时加 swap 后重跑。

5.3 首次配置与启动(SQLite 最小路径)

创建 .env(项目根目录)。最小配置只需两项——运行模式与服务端口:

# printf "DL_ENV_TYPE=selfHosted\nPORT=8147\n" > .env
# cat .env
DL_ENV_TYPE=selfHosted
PORT=8147

前台启动验证:

# node dist/analog/server/index.mjs
🔵 [env] Read 2 settings from /root/domain-locker/.env
🟡 [security] No DL_AUTH_PASSWORD or DL_API_KEY is set, so anyone who can reach this instance can read your domains and trigger the scheduled jobs
🔵 [database] Using SQLite at /root/domain-locker/data/domain-locker.db
🔵 [scheduler] Scheduled domain-monitor every 15 minutes
🔵 [scheduler] Scheduled domain-updater every 1440 minutes
🔵 [scheduler] Scheduled expiration-reminders every 1440 minutes
🔵 [scheduler] Scheduled cleanup-monitor-data every 10080 minutes
🔵 [migrations] Applying 002_job_runs
🟢 [database] Applied migrations: 002_job_runs
Listening on http://[::]:8147

启动日志读法:第一行确认 .env 被加载;黄色 security 行是未配置访问口令时的真实告警(任何能访问到端口的人都可以读写数据并触发作业,公网部署务必见第 7 节);调度器四行对应监控/刷新/提醒/清理四类内置定时任务;随后自动应用数据库迁移;最后一行确认监听地址。

另起终端做健康检查(实测):

# curl -s -o /dev/null -w "HTTP %{http_code}, %{size_download} bytes, %{time_total}s\n" http://127.0.0.1:8147/
HTTP 200, 24588 bytes, 0.004811s
# curl -s http://127.0.0.1:8147/ | grep -oE '<title>[^<]*</title>'
<title>Domain Locker</title>
# ss -ltn | grep 8147
LISTEN 0      511                *:8147             *:*
# ps -ef | grep "node dist" | grep -v grep
root        7507       1  0 05:12 ?        00:00:00 node dist/analog/server/index.mjs

浏览器访问 http://<服务器IP>:8147 即可进入面板,首次使用按界面指引添加第一个域名。

6. 切换 PostgreSQL 数据库(可选但推荐生产使用)

6.1 启动数据库并设置口令

# service postgresql start
 * Starting PostgreSQL 14 database server
   ...done
# pg_isready -h localhost -p 5432 -U postgres
localhost:5432 - accepting connections

6.2 用官方脚本建库建用户

官方提供 db/setup-postgres.sh 一键完成建角色、建库、授权、应用 schema:

# cd /root/domain-locker
# su - postgres -c "psql -c \"ALTER USER postgres PASSWORD 'q1w2e3r4';\""
ALTER ROLE
# DL_PG_HOST=localhost DL_PG_PORT=5432 DL_PG_USER=domainlocker \
  DL_PG_PASSWORD=q1w2e3r4 DL_PG_NAME=domain_locker bash db/setup-postgres.sh
-----------------------------------------
Starting PostgreSQL setup script...
Using the following configuration:
  Database Name:   domain_locker
  User:            domainlocker
  Host:            localhost
  Port:            5432
  Schema File:     /root/domain-locker/db/schema.sql
-----------------------------------------
Checking PostgreSQL connection to localhost:5432...
localhost:5432 - accepting connections
Creating database user domainlocker if not exists...
...
Database setup complete!

⚠️ 排障实录(实测踩坑一):该脚本全程用 PGPASSWORD=$DL_PG_PASSWORD 连接,包括以 postgres 超级用户身份执行建角色/建库的两步。也就是说必须先把 postgres 超级用户的口令改成与 DL_PG_PASSWORD 相同的值(上面的 ALTER USER 就是干这个的),否则脚本第二步直接报:

psql: error: connection to server at "localhost" (::1), port 5432 failed: FATAL:  password authentication failed for user "postgres"

脚本执行完的剩余动作:建 domainlocker 角色、建 domain_locker 库、授权,并把 db/schema.sql 应用进去。

⚠️ 排障实录(实测踩坑二,可忽略):schema 应用阶段会出现两类非致命告警——WARNING: no privileges were granted for "pgp_sym_encrypt" 等 pgcrypto 内置函数 GRANT 警告(对超级用户内置函数授权无意义,官方脚本已知行为),以及脚本末尾一行 psql:db/schema.sql:452: ERROR: must be owner of schema public(PG14 中 public schema 归 postgres 所有,普通角色无法对其 GRANT)。两者都不影响建表结果,21 张表全部正常生成;应用启动时也会以 Could not re-apply schema.sql, continuing with the existing one 明确说明该错误被忽略。

验证建表结果(实测):

# PGPASSWORD=q1w2e3r4 psql -h localhost -U domainlocker -d domain_locker -c "\dt"
                    List of relations
 Schema |           Name           | Type  |    Owner
--------+--------------------------+-------+--------------
 public | billing                  | table | domainlocker
 public | dns_records              | table | domainlocker
 public | domain_costings          | table | domainlocker
 public | domain_hosts             | table | domainlocker
 public | domain_links             | table | domainlocker
 public | domain_statuses          | table | domainlocker
 public | domain_tags              | table | domainlocker
 public | domain_updates           | table | domainlocker
 public | domains                  | table | domainlocker
 public | hosts                    | table | domainlocker
 public | ip_addresses             | table | domainlocker
 public | notification_preferences | table | domainlocker
 public | notifications            | table | domainlocker
 public | registrars               | table | domainlocker
 public | ssl_certificates         | table | domainlocker
 public | sub_domains              | table | domainlocker
 public | tags                     | table | domainlocker
 public | uptime                   | table | domainlocker
 public | user_info                | table | domainlocker
 public | users                    | table | domainlocker
 public | whois_info               | table | domainlocker
(21 rows)

6.3 修改 .env 并以 PostgreSQL 模式启动

# printf "DL_ENV_TYPE=selfHosted\nPORT=8147\nDL_PG_HOST=localhost\nDL_PG_PORT=5432\nDL_PG_USER=domainlocker\nDL_PG_PASSWORD=q1w2e3r4\nDL_PG_NAME=domain_locker\n" > .env
# node dist/analog/server/index.mjs
🔵 [env] Read 7 settings from /root/domain-locker/.env
🟡 [security] No DL_AUTH_PASSWORD or DL_API_KEY is set, so anyone who can reach this instance can read your domains and trigger the scheduled jobs
🔵 [database] Using Postgres at localhost/domain_locker
🔵 [scheduler] Scheduled domain-monitor every 15 minutes
🔵 [scheduler] Scheduled domain-updater every 1440 minutes
🔵 [scheduler] Scheduled expiration-reminders every 1440 minutes
🔵 [scheduler] Scheduled cleanup-monitor-data every 10080 minutes
Listening on http://[::]:8147
🟡 [migrations] Could not re-apply schema.sql, continuing with the existing one: must be owner of schema public
🔵 [migrations] Applying 002_job_runs
🔵 [migrations] Applying 003_domain_dates
🟢 [database] Applied migrations: 002_job_runs, 003_domain_dates

注意两点:DL_PG_* 五个变量必须同时设置(只设部分时启动检查会告警);应用启动时会自动补齐后续迁移(002、003),无需手工执行 SQL。

6.4 端到端功能验证

通过 API 添加一个真实域名并触发自动采集(实测):

# curl -s -w "\nHTTP %{http_code}\n" -X POST http://localhost:8147/api/v1/domains \
  -H "Content-Type: application/json" \
  -d '{"domain":{"domain_name":"example.com","registrar":"Example Registrar","notes":"lab test domain"},"tags":["lab"]}' | head -3
{"id":"8fbdc43e-9fa2-4f0d-adb3-4016338b8ddc","user_id":"a0000000-aaaa-42a0-a0a0-00a000000a69","domain_name":"example.com","expiry_date":null,"registration_date":null,"updated_date":null,"notes":"lab test domain","created_at":"2026-10-28T05:13:03.691Z",...
HTTP 200
# curl -s -X POST --max-time 90 http://localhost:8147/domain-updater
{"job":"domain-updater","ran":true,"result":{"checked":1,"changed":1,"results":[{"domain":"example.com","changes":["Expiry Date","Updated Date","Registration Date","Registrar","Status+: clientdeleteprohibited","Status+: clienttransferprohibited","Status+: clientupdateprohibited","SSL created","DNS TXT+","DNS TXT+","DNS NS+","DNS NS+","DNS MX+","Host"]}]},"durationMs":6381}

updater 通过 RDAP 拉取了 example.com 的真实 WHOIS 数据(6.4 秒),落库后:

# PGPASSWORD=q1w2e3r4 psql -h localhost -U domainlocker -d domain_locker \
  -c "SELECT domain_name, expiry_date, registration_date, updated_date FROM domains;"
 domain_name | expiry_date | registration_date | updated_date
-------------+-------------+-------------------+--------------
 example.com | 2027-08-13  | 1995-08-14        | 2026-08-14
(1 row)

到期日、注册日、状态码、SSL、DNS 记录全部自动富化——部署到此完成功能级验证。

7. 生产加固:访问口令与 systemd 常驻

7.1 设置访问口令

在 .env 追加(口令即登录面板的口令,示例使用 q1w2e3r4):

# printf "DL_AUTH_PASSWORD=q1w2e3r4\nDL_API_KEY=dl-key-2026-test\n" >> .env
# node dist/analog/server/index.mjs
🔵 [env] Read 4 settings from /root/domain-locker/.env
🔵 [database] Using SQLite at /root/domain-locker/data/domain-locker.db
...

对比 5.3 节的启动日志:黄色 security 告警消失,说明鉴权已生效。此时所有 API 未携带凭据返回 401,POST /api/v1/auth/login 成功后发种 dl_session Cookie(30 天有效),外部定时触发器则用 X-API-Key 头。鉴权链路的逐条实测与全部参数含义见 《Domain Locker 配置参数详解》。

7.2 systemd 服务单元

# /etc/systemd/system/domain-locker.service
[Unit]
Description=Domain Locker - domain portfolio monitoring
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/root/domain-locker
ExecStart=/usr/bin/node dist/analog/server/index.mjs
Restart=on-failure
RestartSec=5
User=root
Environment=NODE_ENV=production
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
# systemctl daemon-reload
# systemctl enable --now domain-locker
# systemctl status domain-locker --no-pager | head -6
● domain-locker.service - Domain Locker - domain portfolio monitoring
     Loaded: loaded (/etc/systemd/system/domain-locker.service; enabled; vendor preset: enabled)
     Active: active (running) since ...

8. 升级与回滚

升级即"换源码 + 重装依赖 + 重构建 + 重启",数据库 schema 由应用启动时自动迁移:

# systemctl stop domain-locker
# cd /root/domain-locker && git fetch --tags && git checkout v0.3.6   # 以实际最新 tag 为准
# npm ci --no-audit --no-fund && NODE_OPTIONS=--max-old-space-size=4096 npm run build
# systemctl start domain-locker

回滚时同时回退代码与检查迁移兼容性;PostgreSQL 模式建议升级前先做 pg_dump(命令见 《Domain Locker 常用 CLI 命令详解》)。

9. 部署排障速查表

现象原因处置
git clone 报 RPC failed; curl 16 Error in the HTTP2 framing layer网络环境对 HTTP/2 不友好git config --global http.version HTTP/1.1 或改用 codeload 源码包(均实测可行)
npm ci 卡在 node-gyp/sqlite3 编译报错缺 build-essential/python3补装第 2 节构建链后重跑
npm run build 进程被杀内存不足加 NODE_OPTIONS=--max-old-space-size=4096,4G 以下机器加 swap
setup-postgres.sh 报 FATAL: password authentication failed for user "postgres"脚本用 DL_PG_PASSWORD 连超级用户先 ALTER USER postgres PASSWORD 为同值再跑脚本
启动日志 Unable to start app, as issues were foundPG 参数只配了一半,或 psql/node 缺失补齐 DL_PG_* 五项;确认构建产物存在
端口被占 EADDRINUSE8147/3000 已被监听改 .env 的 PORT;ss -ltnp 排查占用进程
公网部署面板裸奔未配 DL_AUTH_PASSWORD/DL_API_KEY启动日志黄色告警即提示,按 7.1 配置

相关阅读