blob: ab1ff335f1509fd87f5e103ca87520d13b0ac448 [file] [log] [blame]
#!/usr/bin/env lua
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local pkg_cpath_org = package.cpath
local pkg_path_org = package.path
local apisix_home = "/usr/local/apisix"
local pkg_cpath = apisix_home .. "/deps/lib64/lua/5.1/?.so;"
.. apisix_home .. "/deps/lib/lua/5.1/?.so;;"
local pkg_path = apisix_home .. "/deps/share/lua/5.1/?.lua;;"
-- modify the load path to load our dependencies
package.cpath = pkg_cpath .. pkg_cpath_org
package.path = pkg_path .. pkg_path_org
-- pass path to construct the final result
local env = require("apisix.cli.env")(apisix_home, pkg_cpath_org, pkg_path_org)
local file = require("apisix.cli.file")
local util = require("apisix.cli.util")
local ngx_tpl = require("apisix.cli.ngx_tpl")
local etcd = require("apisix.cli.etcd")
local template = require("resty.template")
local function write_file(file_path, data)
local file, err = io.open(file_path, "w+")
if not file then
return false, "failed to open file: " .. file_path .. ", error info:" .. err
end
file:write(data)
file:close()
return true
end
local function is_file_exist(file_path)
local file, err = io.open(file_path)
if not file then
return false, "failed to open file: " .. file_path .. ", error info: " .. err
end
file:close()
return true
end
local function get_openresty_version()
local str = "nginx version: openresty/"
local ret = util.execute_cmd("openresty -v 2>&1")
local pos = string.find(ret,str)
if pos then
return string.sub(ret, pos + string.len(str))
end
str = "nginx version: nginx/"
ret = util.execute_cmd("openresty -v 2>&1")
pos = string.find(ret, str)
if pos then
return string.sub(ret, pos + string.len(str))
end
return nil
end
local function is_32bit_arch()
local ok, ffi = pcall(require, "ffi")
if ok then
-- LuaJIT
return ffi.abi("32bit")
end
local ret = util.execute_cmd("getconf LONG_BIT")
local bits = tonumber(ret)
return bits <= 32
end
local function check_version(cur_ver_s, need_ver_s)
local cur_vers = util.split(cur_ver_s, [[.]])
local need_vers = util.split(need_ver_s, [[.]])
local len = math.max(#cur_vers, #need_vers)
for i = 1, len do
local cur_ver = tonumber(cur_vers[i]) or 0
local need_ver = tonumber(need_vers[i]) or 0
if cur_ver > need_ver then
return true
end
if cur_ver < need_ver then
return false
end
end
return true
end
local function local_dns_resolver(file_path)
local file, err = io.open(file_path, "rb")
if not file then
return false, "failed to open file: " .. file_path .. ", error info:" .. err
end
local dns_addrs = {}
for line in file:lines() do
local addr, n = line:gsub("^nameserver%s+(%d+%.%d+%.%d+%.%d+)%s*$", "%1")
if n == 1 then
table.insert(dns_addrs, addr)
end
end
file:close()
return dns_addrs
end
local _M = {version = 0.1}
function _M.help()
print([[
Usage: apisix [action] <argument>
help: show this message, then exit
init: initialize the local nginx.conf
init_etcd: initialize the data of etcd
start: start the apisix server
stop: stop the apisix server
restart: restart the apisix server
reload: reload the apisix server
version: print the version of apisix
]])
end
local checked_admin_key = false
local function init()
if env.is_root_path then
print('Warning! Running apisix under /root is only suitable for development environments'
.. ' and it is dangerous to do so. It is recommended to run APISIX in a directory other than /root.')
end
-- read_yaml_conf
local yaml_conf, err = file.read_yaml_conf(env.apisix_home)
if not yaml_conf then
error("failed to read local yaml config of apisix: " .. err)
end
-- print("etcd: ", yaml_conf.etcd.host)
-- check the Admin API token
if yaml_conf.apisix.enable_admin and yaml_conf.apisix.allow_admin then
for _, allow_ip in ipairs(yaml_conf.apisix.allow_admin) do
if allow_ip == "127.0.0.0/24" then
checked_admin_key = true
end
end
end
if yaml_conf.apisix.enable_admin and not checked_admin_key then
checked_admin_key = true
local help = [[
%s
Please modify "admin_key" in conf/config.yaml .
]]
if type(yaml_conf.apisix.admin_key) ~= "table" or
#yaml_conf.apisix.admin_key == 0
then
io.stderr:write(help:format("ERROR: missing valid Admin API token."))
os.exit(1)
end
for _, admin in ipairs(yaml_conf.apisix.admin_key) do
if type(admin.key) == "table" then
admin.key = ""
else
admin.key = tostring(admin.key)
end
if admin.key == "" then
io.stderr:write(help:format("ERROR: missing valid Admin API token."), "\n")
os.exit(1)
end
if admin.key == "edd1c9f034335f136f87ad84b625c8f1" then
io.stderr:write(
help:format([[WARNING: using fixed Admin API token has security risk.]]),
"\n"
)
end
end
end
local or_ver = util.execute_cmd("openresty -V 2>&1")
local with_module_status = true
if or_ver and not or_ver:find("http_stub_status_module", 1, true) then
io.stderr:write("'http_stub_status_module' module is missing in ",
"your openresty, please check it out. Without this ",
"module, there will be fewer monitoring indicators.\n")
with_module_status = false
end
local enabled_plugins = {}
for i, name in ipairs(yaml_conf.plugins) do
enabled_plugins[name] = true
end
if enabled_plugins["proxy-cache"] and not yaml_conf.apisix.proxy_cache then
error("missing apisix.proxy_cache for plugin proxy-cache")
end
--support multiple ports listen, compatible with the original style
if type(yaml_conf.apisix.node_listen) == "number" then
local node_listen = {yaml_conf.apisix.node_listen}
yaml_conf.apisix.node_listen = node_listen
end
if type(yaml_conf.apisix.ssl.listen_port) == "number" then
local listen_port = {yaml_conf.apisix.ssl.listen_port}
yaml_conf.apisix.ssl.listen_port = listen_port
end
if yaml_conf.apisix.ssl.ssl_trusted_certificate ~= nil then
local ok, err = is_file_exist(yaml_conf.apisix.ssl.ssl_trusted_certificate)
if not ok then
io.stderr:write(err, "\n")
os.exit(1)
end
end
admin_api_mtls = yaml_conf.apisix.admin_api_mtls
if yaml_conf.apisix.https_admin and not (admin_api_mtls and
admin_api_mtls.admin_ssl_cert and admin_api_mtls.admin_ssl_cert ~= "" and
admin_api_mtls.admin_ssl_cert_key and admin_api_mtls.admin_ssl_cert_key ~= "" ) then
error("missing ssl cert for https admin")
end
ssl = yaml_conf.apisix.ssl
if ssl and ssl.enable and not (
ssl.ssl_cert and ssl.ssl_cert ~= "" and
ssl.ssl_cert_key and ssl.ssl_cert_key ~= "") then
error("missing ssl cert for ssl")
end
-- Using template.render
local sys_conf = {
lua_path = env.pkg_path_org,
lua_cpath = env.pkg_cpath_org,
os_name = util.trim(util.execute_cmd("uname")),
apisix_lua_home = env.apisix_home,
with_module_status = with_module_status,
error_log = {level = "warn"},
enabled_plugins = enabled_plugins,
}
if not yaml_conf.apisix then
error("failed to read `apisix` field from yaml file")
end
if not yaml_conf.nginx_config then
error("failed to read `nginx_config` field from yaml file")
end
if is_32bit_arch() then
sys_conf["worker_rlimit_core"] = "4G"
else
sys_conf["worker_rlimit_core"] = "16G"
end
for k,v in pairs(yaml_conf.apisix) do
sys_conf[k] = v
end
for k,v in pairs(yaml_conf.nginx_config) do
sys_conf[k] = v
end
local wrn = sys_conf["worker_rlimit_nofile"]
local wc = sys_conf["event"]["worker_connections"]
if not wrn or wrn <= wc then
-- ensure the number of fds is slightly larger than the number of conn
sys_conf["worker_rlimit_nofile"] = wc + 128
end
if(sys_conf["enable_dev_mode"] == true) then
sys_conf["worker_processes"] = 1
sys_conf["enable_reuseport"] = false
elseif tonumber(sys_conf["worker_processes"]) == nil then
sys_conf["worker_processes"] = "auto"
end
if sys_conf.allow_admin and #sys_conf.allow_admin == 0 then
sys_conf.allow_admin = nil
end
local dns_resolver = sys_conf["dns_resolver"]
if not dns_resolver or #dns_resolver == 0 then
local dns_addrs, err = local_dns_resolver("/etc/resolv.conf")
if not dns_addrs then
error("failed to import local DNS: " .. err)
end
if #dns_addrs == 0 then
error("local DNS is empty")
end
sys_conf["dns_resolver"] = dns_addrs
end
local env_worker_processes = os.getenv("APISIX_WORKER_PROCESSES")
if env_worker_processes then
sys_conf["worker_processes"] = math.floor(tonumber(env_worker_processes))
end
local conf_render = template.compile(ngx_tpl)
local ngxconf = conf_render(sys_conf)
local ok, err = write_file(env.apisix_home .. "/conf/nginx.conf", ngxconf)
if not ok then
error("failed to update nginx.conf: " .. err)
end
local op_ver = get_openresty_version()
if op_ver == nil then
io.stderr:write("can not find openresty\n")
return
end
local need_ver = "1.15.8"
if not check_version(op_ver, need_ver) then
io.stderr:write("openresty version must >=", need_ver, " current ", op_ver, "\n")
return
end
end
_M.init = init
function _M.start(...)
local cmd_logs = "mkdir -p " .. env.apisix_home .. "/logs"
util.execute_cmd(cmd_logs)
-- check running
local pid_path = env.apisix_home .. "/logs/nginx.pid"
local pid, err = util.read_file(pid_path)
if pid then
local hd = io.popen("lsof -p " .. pid)
local res = hd:read("*a")
if res and res ~= "" then
print("APISIX is running...")
return nil
end
end
init(...)
_M.init_etcd(...)
os.execute(env.openresty_args)
end
function _M.stop()
local cmd = env.openresty_args .. [[ -s stop]]
-- print(cmd)
os.execute(cmd)
end
function _M.restart()
_M.stop()
_M.start()
end
function _M.reload()
-- reinit nginx.conf
init()
local test_cmd = env.openresty_args .. [[ -t -q ]]
-- When success,
-- On linux, os.execute returns 0,
-- On macos, os.execute returns 3 values: true, exit, 0, and we need the first.
local test_ret = os.execute((test_cmd))
if (test_ret == 0 or test_ret == true) then
local cmd = env.openresty_args .. [[ -s reload]]
-- print(cmd)
os.execute(cmd)
return
end
print("test openresty failed")
end
function _M.version()
local ver = require("apisix.core.version")
print(ver['VERSION'])
end
_M.init_etcd = function(show_output)
etcd.init(env, show_output)
end
local cmd_action = arg[1]
if not cmd_action then
return _M.help()
end
if not _M[cmd_action] then
print("invalid argument: ", cmd_action, "\n")
_M.help()
return
end
_M[cmd_action](arg[2])