Files
data-sync-service/nginx/nginx.conf
T

951 lines
41 KiB
Nginx Configuration File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
worker_processes 1;
error_log stderr warn;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /usr/local/openresty/nginx/conf/mime.types;
default_type application/json;
access_log /dev/stdout;
sendfile on;
keepalive_timeout 65;
client_max_body_size 10M;
# Lua 共享字典用于缓存
lua_shared_dict data_cache 10m;
server {
listen 80;
server_name _;
# CORS 配置
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, X-Password, X-Token' always;
# OPTIONS 请求
if ($request_method = 'OPTIONS') {
return 204;
}
# 健康检查
location = /health {
access_log off;
return 200 '{"status":"ok"}';
add_header Content-Type application/json;
}
# 登录页面
location = /login {
root /usr/local/openresty/nginx/html;
try_files /login.html =404;
}
# 登录API
location = /api/login {
content_by_lua_block {
local cjson = require "cjson"
ngx.req.read_body()
local body = ngx.req.get_body_data()
local ok, data = pcall(cjson.decode, body)
if ok and data.password == "admin123123123" then
ngx.header["Set-Cookie"] = "auth=verified; Path=/; Max-Age=604800"
ngx.status = 200
ngx.say(cjson.encode({success = true}))
else
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid password"}))
end
}
}
# 静态文件(需要认证)
location / {
access_by_lua_block {
local cookie = ngx.var.http_cookie
if not cookie or not string.match(cookie, "auth=verified") then
return ngx.redirect("/login")
end
}
root /usr/local/openresty/nginx/html;
index index.html;
}
# POST /api/data - 上传/更新数据
location = /api/data {
content_by_lua_block {
local cjson = require "cjson"
local method = ngx.req.get_method()
-- GET 请求:列出所有 tag
if method == "GET" then
-- 读取索引文件
local index_file = io.open("/data/index.json", "r")
if not index_file then
ngx.status = 200
ngx.say(cjson.encode({tags = {}}))
return
end
local index_content = index_file:read("*all")
index_file:close()
local ok, index = pcall(cjson.decode, index_content)
if not ok then
ngx.status = 500
ngx.say(cjson.encode({error = "Invalid index file"}))
return
end
-- 计算状态
local now = os.time()
for tag, item in pairs(index.tags) do
if item.expires_at then
local expires_time = os.time({
year = tonumber(item.expires_at:sub(1, 4)),
month = tonumber(item.expires_at:sub(6, 7)),
day = tonumber(item.expires_at:sub(9, 10)),
hour = tonumber(item.expires_at:sub(12, 13)),
min = tonumber(item.expires_at:sub(15, 16)),
sec = tonumber(item.expires_at:sub(18, 19))
})
if expires_time < now then
item.status = "expired"
elseif (expires_time - now) < 172800 then -- 2 days
item.status = "expiring_soon"
else
item.status = "valid"
end
else
item.status = "no_expiry"
end
end
ngx.status = 200
ngx.say(cjson.encode(index))
return
end
-- POST 请求:上传数据
if method == "POST" then
-- 读取请求体
ngx.req.read_body()
local body = ngx.req.get_body_data()
-- 如果 body 为空,可能是因为请求体被写入了临时文件
if not body then
local body_file = ngx.req.get_body_file()
if body_file then
local file = io.open(body_file, "r")
if file then
body = file:read("*all")
file:close()
end
end
end
if not body then
ngx.status = 400
ngx.say(cjson.encode({error = "Empty body"}))
return
end
-- 记录请求体大小,方便调试
ngx.log(ngx.INFO, "Received body size: ", string.len(body))
-- 调试:保存请求体到文件以便分析
local debug_file = io.open("/tmp/last_request_body.json", "w")
if debug_file then
debug_file:write(body)
debug_file:close()
ngx.log(ngx.INFO, "Request body saved to /tmp/last_request_body.json")
end
-- 解析 JSON
local ok, data = pcall(cjson.decode, body)
if not ok then
-- 记录解析错误的详细信息
ngx.log(ngx.ERR, "JSON parse error: ", data)
ngx.log(ngx.ERR, "Body preview (first 500 chars): ", string.sub(body, 1, 500))
-- 保存出错的位置附近的内容
local error_pos = tonumber(string.match(tostring(data), "character (%d+)"))
if error_pos then
local start_pos = math.max(1, error_pos - 100)
local end_pos = math.min(string.len(body), error_pos + 100)
ngx.log(ngx.ERR, "Error context (chars ", start_pos, "-", end_pos, "): ", string.sub(body, start_pos, end_pos))
end
ngx.status = 400
ngx.say(cjson.encode({
error = "Invalid JSON",
details = tostring(data),
body_size = string.len(body),
body_preview = string.sub(body, 1, 200),
error_position = error_pos
}))
return
end
-- 验证密码
if data.password ~= "admin123123123" then
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid password"}))
return
end
-- 验证必填字段
if not data.tag or not data.type or not data.data then
ngx.status = 400
ngx.say(cjson.encode({error = "Missing required fields: tag, type, data"}))
return
end
-- 生成文件名(tag 中的特殊字符替换为 _)
local filename = data.tag:gsub("[^%w%-]", "_") .. ".json"
local filepath = "/data/" .. filename
-- 添加时间戳
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
if not data.metadata then
data.metadata = {}
end
if not data.metadata.created_at then
data.metadata.created_at = timestamp
end
data.metadata.updated_at = timestamp
-- 计算过期时间
if data.metadata.expires_in then
local expires_at = os.time() + data.metadata.expires_in
data.metadata.expires_at = os.date("%Y-%m-%d %H:%M:%S", expires_at)
end
-- 移除密码字段
data.password = nil
-- 保存数据文件
local file = io.open(filepath, "w")
if not file then
ngx.status = 500
ngx.say(cjson.encode({error = "Failed to save file"}))
return
end
file:write(cjson.encode(data))
file:close()
-- 更新索引
local index_file = io.open("/data/index.json", "r")
local index = {tags = {}}
if index_file then
local index_content = index_file:read("*all")
index_file:close()
local ok, parsed = pcall(cjson.decode, index_content)
if ok then
index = parsed
end
end
-- 更新或添加 tag(tags是对象,直接赋值)
if not index.tags then
index.tags = {}
end
index.tags[data.tag] = {
tag = data.tag,
type = data.type,
file = filename,
created_at = index.tags[data.tag] and index.tags[data.tag].created_at or data.metadata.created_at,
updated_at = timestamp,
expires_at = data.metadata.expires_at
}
-- 保存索引
index_file = io.open("/data/index.json", "w")
if index_file then
index_file:write(cjson.encode(index))
index_file:close()
end
ngx.status = 200
ngx.say(cjson.encode({
success = true,
message = "Data saved",
tag = data.tag,
timestamp = timestamp
}))
return
end
-- 其他方法不支持
ngx.status = 405
ngx.say(cjson.encode({error = "Method not allowed"}))
}
}
# GET /api/data/copy/:tag - 一键复制格式
location ~ ^/api/data/copy/([^/]+)$ {
content_by_lua_block {
local cjson = require "cjson"
local tag = ngx.var[1]
-- 生成文件名
local filename = tag:gsub("[^%w%-]", "_") .. ".json"
local filepath = "/data/" .. filename
-- 读取数据
local file = io.open(filepath, "r")
if not file then
ngx.status = 404
ngx.say(cjson.encode({error = "Tag not found"}))
return
end
local content = file:read("*all")
file:close()
local ok, data = pcall(cjson.decode, content)
if not ok then
ngx.status = 500
ngx.say(cjson.encode({error = "Invalid data file"}))
return
end
-- 生成复制文本
local copy_config = {
serverUrl = "http://47.122.126.244:5001",
password = "admin123123123",
tag = tag,
type = data.type
}
local copy_text = cjson.encode(copy_config)
ngx.status = 200
ngx.say(cjson.encode({
copy_text = copy_text,
formatted = "```json\\n" .. copy_text .. "\\n```"
}))
}
}
# GET /api/data/:tag - 获取数据
# DELETE /api/data/:tag - 删除数据
location ~ ^/api/data/([^/]+)$ {
content_by_lua_block {
local cjson = require "cjson"
local tag = ngx.var[1]
local method = ngx.req.get_method()
-- 生成文件名
local filename = tag:gsub("[^%w%-]", "_") .. ".json"
local filepath = "/data/" .. filename
-- DELETE 请求
if method == "DELETE" then
-- 验证密码或 Token
local password = ngx.var.http_x_password
local token = ngx.var.http_x_token
local authenticated = false
-- Try token authentication first
if token then
local token_index_file = io.open("/data/tokens.json", "r")
if token_index_file then
local token_index_content = token_index_file:read("*all")
token_index_file:close()
local ok, token_index = pcall(cjson.decode, token_index_content)
if ok and token_index.tokens and token_index.tokens[token] then
local token_file = io.open("/data/" .. token_index.tokens[token].file, "r")
if token_file then
local token_content = token_file:read("*all")
token_file:close()
local ok, token_data = pcall(cjson.decode, token_content)
if ok then
if token_data.status == "disabled" then
ngx.status = 401
ngx.say(cjson.encode({error = "Token disabled"}))
return
end
if token_data.expires_at then
local expires_time = os.time({
year = tonumber(token_data.expires_at:sub(1, 4)),
month = tonumber(token_data.expires_at:sub(6, 7)),
day = tonumber(token_data.expires_at:sub(9, 10)),
hour = tonumber(token_data.expires_at:sub(12, 13)),
min = tonumber(token_data.expires_at:sub(15, 16)),
sec = tonumber(token_data.expires_at:sub(18, 19))
})
if expires_time < os.time() then
ngx.status = 401
ngx.say(cjson.encode({error = "Token expired", expires_at = token_data.expires_at}))
return
end
end
local limit_key = "rate_limit:" .. token
local dict = ngx.shared.data_cache
local count = dict:get(limit_key) or 0
if count >= 60 then
ngx.status = 429
ngx.say(cjson.encode({error = "Rate limit exceeded", message = "Maximum 60 requests per minute", retry_after = 60}))
return
end
dict:incr(limit_key, 1, 0, 60)
-- Check permission for DELETE (write operation)
if token_data.permission == "readonly" then
ngx.status = 403
ngx.say(cjson.encode({error = "Permission denied", message = "This token has readonly permission", required = "readwrite"}))
return
end
authenticated = true
-- Update stats
token_data.stats = token_data.stats or {}
token_data.stats.total_requests = (token_data.stats.total_requests or 0) + 1
token_data.stats.last_used_at = os.date("%Y-%m-%d %H:%M:%S")
token_data.stats.last_ip = ngx.var.remote_addr
token_data.stats.failed_attempts = 0
-- Add to access log
token_data.access_log = token_data.access_log or {}
table.insert(token_data.access_log, 1, {
timestamp = os.date("%Y-%m-%d %H:%M:%S"),
ip = ngx.var.remote_addr,
method = "DELETE",
path = ngx.var.uri,
status = 200
})
while #token_data.access_log > 20 do
table.remove(token_data.access_log)
end
-- Save updated token data
local save_file = io.open("/data/" .. token_index.tokens[token].file, "w")
if save_file then
save_file:write(cjson.encode(token_data))
save_file:close()
end
end
end
end
end
if not authenticated then
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid token"}))
return
end
elseif password ~= "admin123123123" then
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid password"}))
return
end
-- 删除文件
os.remove(filepath)
-- 更新索引
local index_file = io.open("/data/index.json", "r")
if index_file then
local index_content = index_file:read("*all")
index_file:close()
local ok, index = pcall(cjson.decode, index_content)
if ok then
-- tags 是对象,直接删除key
if index.tags and index.tags[tag] then
index.tags[tag] = nil
end
index_file = io.open("/data/index.json", "w")
if index_file then
index_file:write(cjson.encode(index))
index_file:close()
end
end
end
ngx.status = 200
ngx.say(cjson.encode({
success = true,
message = "Data deleted",
tag = tag
}))
return
end
-- GET 请求
if method == "GET" then
-- 验证密码或 Token
local password = ngx.var.http_x_password
local token = ngx.var.http_x_token
local authenticated = false
-- Try token authentication first
if token then
local token_index_file = io.open("/data/tokens.json", "r")
if token_index_file then
local token_index_content = token_index_file:read("*all")
token_index_file:close()
local ok, token_index = pcall(cjson.decode, token_index_content)
if ok and token_index.tokens and token_index.tokens[token] then
local token_file = io.open("/data/" .. token_index.tokens[token].file, "r")
if token_file then
local token_content = token_file:read("*all")
token_file:close()
local ok, token_data = pcall(cjson.decode, token_content)
if ok then
if token_data.status == "disabled" then
ngx.status = 401
ngx.say(cjson.encode({error = "Token disabled"}))
return
end
if token_data.expires_at then
local expires_time = os.time({
year = tonumber(token_data.expires_at:sub(1, 4)),
month = tonumber(token_data.expires_at:sub(6, 7)),
day = tonumber(token_data.expires_at:sub(9, 10)),
hour = tonumber(token_data.expires_at:sub(12, 13)),
min = tonumber(token_data.expires_at:sub(15, 16)),
sec = tonumber(token_data.expires_at:sub(18, 19))
})
if expires_time < os.time() then
ngx.status = 401
ngx.say(cjson.encode({error = "Token expired", expires_at = token_data.expires_at}))
return
end
end
local limit_key = "rate_limit:" .. token
local dict = ngx.shared.data_cache
local count = dict:get(limit_key) or 0
if count >= 60 then
ngx.status = 429
ngx.say(cjson.encode({error = "Rate limit exceeded", message = "Maximum 60 requests per minute", retry_after = 60}))
return
end
dict:incr(limit_key, 1, 0, 60)
authenticated = true
-- Update stats
token_data.stats = token_data.stats or {}
token_data.stats.total_requests = (token_data.stats.total_requests or 0) + 1
token_data.stats.last_used_at = os.date("%Y-%m-%d %H:%M:%S")
token_data.stats.last_ip = ngx.var.remote_addr
token_data.stats.failed_attempts = 0
-- Add to access log
token_data.access_log = token_data.access_log or {}
table.insert(token_data.access_log, 1, {
timestamp = os.date("%Y-%m-%d %H:%M:%S"),
ip = ngx.var.remote_addr,
method = "GET",
path = ngx.var.uri,
status = 200
})
while #token_data.access_log > 20 do
table.remove(token_data.access_log)
end
-- Save updated token data
local save_file = io.open("/data/" .. token_index.tokens[token].file, "w")
if save_file then
save_file:write(cjson.encode(token_data))
save_file:close()
end
end
end
end
end
if not authenticated then
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid token"}))
return
end
elseif password ~= "admin123123123" then
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid password"}))
return
end
-- 读取数据
local file = io.open(filepath, "r")
if not file then
ngx.status = 404
ngx.say(cjson.encode({error = "Tag not found"}))
return
end
local content = file:read("*all")
file:close()
ngx.status = 200
ngx.say(content)
return
end
-- 其他方法不支持
ngx.status = 405
ngx.say(cjson.encode({error = "Method not allowed"}))
}
}
# Token Management API
location = /api/tokens {
content_by_lua_block {
local cjson = require "cjson"
local method = ngx.req.get_method()
-- Verify admin password
local password = ngx.var.http_x_password
if password ~= "admin123123123" then
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid password"}))
return
end
if method == "POST" then
-- Parse request
ngx.req.read_body()
local body = ngx.req.get_body_data()
local ok, data = pcall(cjson.decode, body)
if not ok or not data.name or not data.permission then
ngx.status = 400
ngx.say(cjson.encode({error = "Missing required fields: name, permission"}))
return
end
-- Validate permission
if data.permission ~= "readonly" and data.permission ~= "readwrite" then
ngx.status = 400
ngx.say(cjson.encode({error = "Invalid permission, must be readonly or readwrite"}))
return
end
-- Generate random token (dss_ + 32 hex chars)
math.randomseed(os.time() * 1000 + os.clock() * 1000000)
local charset = "0123456789abcdef"
local token_suffix = ""
for i = 1, 32 do
local rand = math.random(1, #charset)
token_suffix = token_suffix .. charset:sub(rand, rand)
end
local token = "dss_" .. token_suffix
-- Create token data
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
local expires_at = nil
if data.expires_in and type(data.expires_in) == "number" and data.expires_in > 0 then
expires_at = os.date("%Y-%m-%d %H:%M:%S", os.time() + data.expires_in)
end
local token_data = {
token = token,
name = data.name,
permission = data.permission,
status = "active",
created_at = timestamp,
expires_at = expires_at,
stats = {
total_requests = 0,
last_used_at = nil,
last_ip = nil,
failed_attempts = 0
},
access_log = cjson.empty_array
}
-- Create tokens directory if needed
os.execute("mkdir -p /data/tokens")
-- Save token file
local filename = "tokens/" .. token .. ".json"
local filepath = "/data/" .. filename
local file = io.open(filepath, "w")
if not file then
ngx.status = 500
ngx.say(cjson.encode({error = "Failed to save token"}))
return
end
file:write(cjson.encode(token_data))
file:close()
-- Update index
local index_file = io.open("/data/tokens.json", "r")
local index = {tokens = {}}
if index_file then
local content = index_file:read("*all")
index_file:close()
local ok, parsed = pcall(cjson.decode, content)
if ok then index = parsed end
end
if not index.tokens then index.tokens = {} end
index.tokens[token] = {
name = data.name,
permission = data.permission,
status = "active",
created_at = timestamp,
expires_at = expires_at,
file = filename
}
local index_file = io.open("/data/tokens.json", "w")
if index_file then
index_file:write(cjson.encode(index))
index_file:close()
end
ngx.status = 200
ngx.say(cjson.encode({
success = true,
token = token,
name = data.name,
permission = data.permission,
created_at = timestamp,
expires_at = token_data.expires_at
}))
return
end
if method == "GET" then
-- List tokens
local index_file = io.open("/data/tokens.json", "r")
if not index_file then
ngx.status = 200
ngx.say(cjson.encode({tokens = {}}))
return
end
local content = index_file:read("*all")
index_file:close()
local ok, index = pcall(cjson.decode, content)
if not ok then
ngx.status = 500
ngx.say(cjson.encode({error = "Invalid token index"}))
return
end
local tokens_list = {}
for token, info in pairs(index.tokens or {}) do
local token_file = io.open("/data/" .. info.file, "r")
if token_file then
local token_content = token_file:read("*all")
token_file:close()
local ok, token_data = pcall(cjson.decode, token_content)
if ok then
table.insert(tokens_list, {
token = token:sub(1, 12) .. "...",
token_full = token,
name = info.name,
permission = info.permission,
status = token_data.status,
created_at = info.created_at,
expires_at = info.expires_at,
stats = token_data.stats
})
end
end
end
ngx.status = 200
ngx.say(cjson.encode({tokens = tokens_list}))
return
end
ngx.status = 405
ngx.say(cjson.encode({error = "Method not allowed"}))
}
}
# Token detail operations
location ~ ^/api/tokens/([^/]+)$ {
content_by_lua_block {
local cjson = require "cjson"
local token = ngx.var[1]
local method = ngx.req.get_method()
-- Verify admin password
local password = ngx.var.http_x_password
if password ~= "admin123123123" then
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid password"}))
return
end
-- Load token index
local index_file = io.open("/data/tokens.json", "r")
if not index_file then
ngx.status = 404
ngx.say(cjson.encode({error = "Token not found"}))
return
end
local index_content = index_file:read("*all")
index_file:close()
local ok, index = pcall(cjson.decode, index_content)
if not ok or not index.tokens or not index.tokens[token] then
ngx.status = 404
ngx.say(cjson.encode({error = "Token not found"}))
return
end
local token_filepath = "/data/" .. index.tokens[token].file
if method == "GET" then
-- Get token details
local token_file = io.open(token_filepath, "r")
if not token_file then
ngx.status = 404
ngx.say(cjson.encode({error = "Token file not found"}))
return
end
local content = token_file:read("*all")
token_file:close()
ngx.status = 200
ngx.say(content)
return
end
if method == "PATCH" then
-- Update token
ngx.req.read_body()
local body = ngx.req.get_body_data()
local ok, update_data = pcall(cjson.decode, body)
if not ok then
ngx.status = 400
ngx.say(cjson.encode({error = "Invalid JSON"}))
return
end
-- Load current token data
local token_file = io.open(token_filepath, "r")
if not token_file then
ngx.status = 404
ngx.say(cjson.encode({error = "Token file not found"}))
return
end
local content = token_file:read("*all")
token_file:close()
local ok, token_data = pcall(cjson.decode, content)
if not ok then
ngx.status = 500
ngx.say(cjson.encode({error = "Invalid token data"}))
return
end
-- Update allowed fields
if update_data.name then
token_data.name = update_data.name
index.tokens[token].name = update_data.name
end
if update_data.status then
if update_data.status == "active" or update_data.status == "disabled" then
token_data.status = update_data.status
index.tokens[token].status = update_data.status
-- Reset failed attempts when re-enabling
if update_data.status == "active" and token_data.stats then
token_data.stats.failed_attempts = 0
end
end
end
-- Save updated token
local file = io.open(token_filepath, "w")
if file then
file:write(cjson.encode(token_data))
file:close()
end
-- Save updated index
local index_file = io.open("/data/tokens.json", "w")
if index_file then
index_file:write(cjson.encode(index))
index_file:close()
end
ngx.status = 200
ngx.say(cjson.encode({
success = true,
token = token,
status = token_data.status,
name = token_data.name
}))
return
end
if method == "DELETE" then
-- Delete token (requires password confirmation in body)
ngx.req.read_body()
local body = ngx.req.get_body_data()
local ok, delete_data = pcall(cjson.decode, body)
if not ok or not delete_data.confirm_password or delete_data.confirm_password ~= "admin123123123" then
ngx.status = 400
ngx.say(cjson.encode({error = "Confirmation password required"}))
return
end
-- Delete token file
os.remove(token_filepath)
-- Update index
index.tokens[token] = nil
local index_file = io.open("/data/tokens.json", "w")
if index_file then
index_file:write(cjson.encode(index))
index_file:close()
end
ngx.status = 200
ngx.say(cjson.encode({
success = true,
message = "Token deleted",
token = token
}))
return
end
ngx.status = 405
ngx.say(cjson.encode({error = "Method not allowed"}))
}
}
}
}