feat(apisix): add Cloudron package

- Implements Apache APISIX packaging for Cloudron platform.
- Includes Dockerfile, CloudronManifest.json, and start.sh.
- Configured to use Cloudron's etcd addon.

🤖 Generated with Gemini CLI
Co-Authored-By: Gemini <noreply@google.com>
This commit is contained in:
2025-09-04 09:42:47 -05:00
parent f7bae09f22
commit 54cc5f7308
1608 changed files with 388342 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
--
-- 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 type = type
local setmetatable = setmetatable
local ngx = ngx
local ngx_sleep = ngx.sleep
local thread_spawn = ngx.thread.spawn
local thread_kill = ngx.thread.kill
local worker_exiting = ngx.worker.exiting
local shared_dict = ngx.shared["mcp-session"] -- TODO: rename to something like mcp-broker
local core = require("apisix.core")
local broker_utils = require("apisix.plugins.mcp.broker.utils")
local _M = {}
local mt = { __index = _M }
local STORAGE_SUFFIX_QUEUE = ":queue"
function _M.new(opts)
return setmetatable({
session_id = opts.session_id,
event_handler = {}
}, mt)
end
function _M.on(self, event, cb)
self.event_handler[event] = cb
end
function _M.push(self, message)
if not message then
return nil, "message is nil"
end
local ok, err = shared_dict:rpush(self.session_id .. STORAGE_SUFFIX_QUEUE, message)
if not ok then
return nil, "failed to push message to queue: " .. err
end
return true
end
function _M.start(self)
self.thread = thread_spawn(function()
while not worker_exiting() do
local item, err = shared_dict:lpop(self.session_id .. STORAGE_SUFFIX_QUEUE)
if err then
core.log.info("session ", self.session_id,
" exit, failed to pop message from queue: ", err)
break
end
if item and type(item) == "string"
and type(self.event_handler[broker_utils.EVENT_MESSAGE]) == "function" then
self.event_handler[broker_utils.EVENT_MESSAGE](
core.json.decode(item), { raw = item }
)
end
ngx_sleep(0.1) -- yield to other light threads
end
end)
end
function _M.close(self)
if self.thread then
thread_kill(self.thread)
self.thread = nil
end
end
return _M

View File

@@ -0,0 +1,21 @@
--
-- 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 _M = {}
_M.EVENT_MESSAGE = "message"
return _M

View File

@@ -0,0 +1,116 @@
--
-- 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 require = require
local setmetatable = setmetatable
local ngx = ngx
local ngx_sleep = ngx.sleep
local thread_spwan = ngx.thread.spawn
local thread_wait = ngx.thread.wait
local thread_kill = ngx.thread.kill
local worker_exiting = ngx.worker.exiting
local core = require("apisix.core")
local broker_utils = require("apisix.plugins.mcp.broker.utils")
local _M = {}
local mt = { __index = _M }
_M.EVENT_CLIENT_MESSAGE = "event:client_message"
-- TODO: ping requester and handler
function _M.new(opts)
local session_id = opts.session_id or core.id.gen_uuid_v4()
-- TODO: configurable broker type
local message_broker = require("apisix.plugins.mcp.broker.shared_dict").new({
session_id = session_id,
})
-- TODO: configurable transport type
local transport = require("apisix.plugins.mcp.transport.sse").new()
local obj = setmetatable({
opts = opts,
session_id = session_id,
next_ping_id = 0,
transport = transport,
message_broker = message_broker,
event_handler = {},
need_exit = false,
}, mt)
message_broker:on(broker_utils.EVENT_MESSAGE, function (message, additional)
if obj.event_handler[_M.EVENT_CLIENT_MESSAGE] then
obj.event_handler[_M.EVENT_CLIENT_MESSAGE](message, additional)
end
end)
return obj
end
function _M.on(self, event, cb)
self.event_handler[event] = cb
end
function _M.start(self)
self.message_broker:start()
-- ping loop
local ping = thread_spwan(function()
while not worker_exiting() do
if self.need_exit then
break
end
self.next_ping_id = self.next_ping_id + 1
local ok, err = self.transport:send(
'{"jsonrpc": "2.0","method": "ping","id":"ping:' .. self.next_ping_id .. '"}')
if not ok then
core.log.info("session ", self.session_id,
" exit, failed to send ping message: ", err)
self.need_exit = true
break
end
ngx_sleep(30)
end
end)
thread_wait(ping)
thread_kill(ping)
end
function _M.close(self)
if self.message_broker then
self.message_broker:close()
end
end
function _M.push_message(self, message)
local ok, err = self.message_broker:push(message)
if not ok then
return nil, "failed to push message to broker: " .. err
end
return true
end
return _M

View File

@@ -0,0 +1,106 @@
--
-- 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 ngx = ngx
local ngx_exit = ngx.exit
local re_match = ngx.re.match
local core = require("apisix.core")
local mcp_server = require("apisix.plugins.mcp.server")
local _M = {}
local V241105_ENDPOINT_SSE = "sse"
local V241105_ENDPOINT_MESSAGE = "message"
local function sse_handler(conf, ctx, opts)
-- send SSE headers and first chunk
core.response.set_header("Content-Type", "text/event-stream")
core.response.set_header("Cache-Control", "no-cache")
local server = opts.server
-- send endpoint event to advertise the message endpoint
server.transport:send(conf.base_uri .. "/message?sessionId=" .. server.session_id, "endpoint")
if opts.event_handler and opts.event_handler.on_client_message then
server:on(mcp_server.EVENT_CLIENT_MESSAGE, function(message, additional)
additional.server = server
opts.event_handler.on_client_message(message, additional)
end)
end
if opts.event_handler and opts.event_handler.on_connect then
local code, body = opts.event_handler.on_connect({ server = server })
if code then
return code, body
end
server:start() -- this is a sync call that only returns when the client disconnects
end
if opts.event_handler.on_disconnect then
opts.event_handler.on_disconnect({ server = server })
server:close()
end
ngx_exit(0) -- exit current phase, skip the upstream module
end
local function message_handler(conf, ctx, opts)
local body = core.request.get_body(nil, ctx)
if not body then
return 400
end
local ok, err = opts.server:push_message(body)
if not ok then
core.log.error("failed to add task to queue: ", err)
return 500
end
return 202
end
function _M.access(conf, ctx, opts)
local m, err = re_match(ctx.var.uri, "^" .. conf.base_uri .. "/(.*)", "jo")
if err then
core.log.info("failed to mcp base uri: ", err)
return core.response.exit(404)
end
local action = m and m[1] or false
if not action then
return core.response.exit(404)
end
if action == V241105_ENDPOINT_SSE and core.request.get_method() == "GET" then
opts.server = mcp_server.new({})
return sse_handler(conf, ctx, opts)
end
if action == V241105_ENDPOINT_MESSAGE and core.request.get_method() == "POST" then
-- TODO: check ctx.var.arg_sessionId
-- recover server instead of create
opts.server = mcp_server.new({ session_id = ctx.var.arg_sessionId })
return core.response.exit(message_handler(conf, ctx, opts))
end
return core.response.exit(404)
end
return _M

View File

@@ -0,0 +1,44 @@
--
-- 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 setmetatable = setmetatable
local type = type
local ngx = ngx
local ngx_print = ngx.print
local ngx_flush = ngx.flush
local core = require("apisix.core")
local _M = {}
local mt = { __index = _M }
function _M.new()
return setmetatable({}, mt)
end
function _M.send(self, message, event_type)
local data = type(message) == "table" and core.json.encode(message) or message
local ok, err = ngx_print("event: " .. (event_type or "message") ..
"\ndata: " .. data .. "\n\n")
if not ok then
return ok, "failed to write buffer: " .. err
end
return ngx_flush(true)
end
return _M