0x01 序※
今天突然想给我的笔记(Trilium)登陆页改改,笔记的数据库里没查到这个页面。
去翻了翻源码,发现是写在这里的src/views/login.ejs
> pacman -Ql trilium-server-bin|grep login.ejs
trilium-server-bin /opt/trilium-server/src/views/login.ejs
改这个文件确实没毛病,但涉及源码的话,每次更新都要重新覆盖,
虽然archlinux
有hooks可以在每次更新时候执行操作,但因为这种小事去改系统,就感觉不是那么清真。
顿时就懒得改了。

Nriver大佬(Trilium中文贡献者)说可以用Nginx
来搞,我去翻了一下资料,才发现Nginx
也有写js的能力。
花了点时间学习了一下,顺便记录一下。
(lua模块也可以,和js一样使用)
0x02 ngx-mod-js※
如何启用nginx
的js能力?
可以通过-V查看当前nginx
支持哪些模块
nginx version: nginx/1.26.3
built with OpenSSL 3.4.0 22 Oct 2024 (running with OpenSSL 3.4.1 11 Feb 2025)
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --conf-path=/etc/nginx/nginx.conf --sbin-path=/usr/bin/nginx --pid-path=/run/nginx.pid --lock-path=/run/lock/nginx.lock --user=http --group=http --http-log-path=/var/log/nginx/access.log --error-log-path=stderr --http-client-body-temp-path=/var/lib/nginx/client-body --http-proxy-temp-path=/var/lib/nginx/proxy --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-cc-opt='-march=x86-64-v3 -O3 -pipe -fno-plt -fexceptions -Wp,-D_FORTIFY_SOURCE=3 -Wformat -Werror=format-security -fstack-clash-protection -fcf-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -mpclmul -g -ffile-prefix-map=/startdir/src=/usr/src/debug/nginx -flto=auto -falign-functions=32' --with-ld-opt='-Wl,-O1 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now -Wl,-z,pack-relative-relocs -flto=auto -falign-functions=32' --with-compat --with-debug --with-file-aio --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_degradation_module --with-http_flv_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_v3_module --with-mail --with-mail_ssl_module --with-pcre-jit --with-stream --with-stream_geoip_module --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-threads
如果带有ngx-mod-js
模块应该有下面的输出,我这里显然没有
nginx version: nginx/1.26.3
configure arguments: --add-module=/path/to/ngx_mod_js --prefix=/usr/local/nginx --with-http_ssl_module
编译安装※
下载nginx
源码和ngx-mod-js
源码
wget https://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxvf nginx-1.24.0.tar.gz
cd nginx-1.24.0
git clone https://github.com/simpl/ngx_mod_js.git
添加新参数然后编译安装
./configure --add-module=../ngx_mod_js
make
sudo make install
动态载入※
那样显然比较麻烦,幸运的是这个模块可以动态载入,只需下载解压模块并修改nginx配置文件
/etc/nginx/nginx.conf
---------------------
load_module /modules/ngx_http_js_module.so; ## 指定so的路径
load_module /modules/ngx_stream_js_module.so;
arch也提供了相关软件包Extra/nginx-mod-njs。
so文件被安装在这个位置
nginx-mod-njs /usr/lib/nginx/modules/ngx_http_js_module.so
nginx-mod-njs /usr/lib/nginx/modules/ngx_stream_js_module.so
0x03 修改逻辑※
ngx-mod-js
具体文档:https://nginx.org/en/docs/http/ngx_http_js_module.html
首先需要把login
页面给njs
处理,在trilium
的nginx.conf
文件添加下面的配置
js_import main from /etc/nginx/conf.d/tm.login.js; # 导入处理login的njs文件
location /login {
js_content main.loginHandler; # 用njs替代原来的页面
}
njs
结构是这样的
async function loginHandler(r) { // 请求对象用r承接
r.return(200,'ngx-mod-js') // 返回数据
}
// 导出处理函数,供 Nginx 配置使用
export default { loginHandler };
可以这样做
// 定义后端地址
const backendUrl = 'http://127.0.0.1:8080/login';
// 登陆页处理
async function loginHandler(r) {
// 用fetch拿到原来的页面
let reply =await ngx.fetch(backendUrl, {
method: r.method,
headers: {
'Content-Type': r.headersIn['Content-Type'] || 'application/octet-stream'
}
});
let text = await reply.text();
// 修改原来的页面
text = pageHandler(text);
r.return(200, text);
}
function pageHandler(text) {
// 定义要添加的 CSS 和 JavaScript
const css = `
<style>
/* 定义全局变量,统一配色 */
:root {
--background-color: #f8f8f8;
--primary-color: #333;
--secondary-color: #666;
--watermark-color: rgba(255, 0, 0, 0.1);
}
/* 设置全局盒模型为border-box */
* {
box-sizing: border-box;
}
......
`;
// 查找 <head> 标签的结束位置
const headEndIndex = text.indexOf('</head>');
if (headEndIndex!== -1) {
// 在 </head> 之前插入 CSS
text = text.slice(0, headEndIndex) + css + text.slice(headEndIndex);
}
// 查找body的头标签
const bodyStartIndex = text.indexOf('<body>');
// 添加水印
if (bodyStartIndex!== -1){
text = text.slice(0,bodyStartIndex) + ` <!-- 水印 -->
<div class="watermark">绝密</div> ` + text.slice(bodyStartIndex);
}
// 添加主标题
if (bodyStartIndex!== -1){
text = text.slice(0,bodyStartIndex) + ` <!-- 主标题 -->
<h1 class="main-title">火狐の秘密日记</h1> ` + text.slice(bodyStartIndex);
}
text = text.replace('<div class="container">', '<div class="form-container fade-in"' );
text = text.replace('密码','访问口令:');
return text;
}
这里官方的r.requestBody
、reply.responseBody
都undefinie,我的版本0.8.7应该是有的,不清楚什么原因。但可以用r.requestText
和reply.responseText
。
这样用nginx -t
测试是否有错误,没错误就可以重载nginx
了。
浏览器打开login页面,下载了一个文本文件。
哪里不对劲?
没有声明数据格式,浏览器默认下载了
需要加一个标头声明是html页面
r.headersOut['Content-Type'] = 'text/html; charset=utf-8'; // r.headersOut是指njs返回的标头
再次打开login页面,修改成功了

但是点击登陆无效
0x04 登陆功能修复※
查看源码,登陆是用表单POST实现的,login里面没有写这个逻辑,当然不能登陆了。
需要完成登陆的逻辑。
登陆是用Post提交表单,表单数据是密码,这个肯定要传递给后端的。
直接打开是GET方法,应该直接返回登陆页。
所以要通过请求方法区分。
if (r.method === 'POST') {
let body = r.requestText;
let reply =await ngx.fetch(backendUrl, {
method: r.method,
body: r.requestText, // POST携带的密码要传给后端
headers: {
'Content-Type': r.headersIn['Content-Type'] || 'application/octet-stream'
}
});
let text = await reply.text();
// 修改login页面
text = pageHandler(text);
r.send(text);
r.finish();
}
else{}
从后面来看密码已经传递过去了,但login页面只是刷新没反应。
检查后端实际的逻辑,其实是密码对了之后设置了一个Cookie,然后302跳转到了主页
10:11:23.136199 [0-0] < HTTP/1.1 302 Found
10:11:23.138686 [0-0] < Date: Mon, 17 Feb 2025 02:11:22 GMT
10:11:23.138895 [0-0] < X-RateLimit-Reset: 1739759175
10:11:23.139071 [0-0] < Location: .
10:11:23.139232 [0-0] < Vary: Accept, Accept-Encoding
10:11:23.139386 [0-0] < Content-Type: text/plain; charset=utf-8
10:11:23.139546 [0-0] < Content-Length: 23
10:11:23.139697 [0-0] < Set-Cookie: trilium.sid=xxxxxxxxx; Path=/; HttpOnly
10:11:23.139867 [0-0] < Connection: keep-alive
10:11:23.140013 [0-0] < Keep-Alive: timeout=600
10:11:23.140176 [0-0] <
10:11:23.140394 [0-0] * Connection #0 to host cynic.aketer.me left intact
Found. Redirecting to .%
那我们也应该这么做,先把后端回来的headers带上,这里有Set-Cookie
for (let header in reply.headers) {
r.headersOut[header] = reply.headers[header];
}
// 这里要注意 Content-Length: 这个标头,需要修改或删掉。否则实际大小超出标头数值时,会导致页面不完整。
然后也要跳转到主页
// 成功跳转主页
if(reply.status == 302){
r.status = 302;
r.headersOut['Location'] = '/';
r.sendHeader();
}
测试一下,没问题了