Merge Lean's source

This commit is contained in:
AmadeusGhost 2020-03-17 17:30:16 +08:00
commit ae2698ceb5
58 changed files with 1940 additions and 792 deletions

0
package/ctcgfw/luci-app-dockerman/Makefile Normal file → Executable file
View File

View File

@ -10,44 +10,58 @@ module("luci.controller.dockerman",package.seeall)
function index()
entry({"admin", "services","docker"}, firstchild(), "Docker", 40).dependent = false
entry({"admin","services","docker","overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true
entry({"admin", "docker"}, firstchild(), "Docker", 40).dependent = false
entry({"admin","docker","overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true
local remote = luci.model.uci.cursor():get("dockerman", "local", "remote_endpoint")
if remote == nil then
local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
if socket and not nixio.fs.access(socket) then return end
elseif remote == "true" then
local host = luci.model.uci.cursor():get("dockerman", "local", "remote_host")
local port = luci.model.uci.cursor():get("dockerman", "local", "remote_port")
if not host or not port then return end
end
local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
if not nixio.fs.access(socket) then return end
if (require "luci.model.docker").new():_ping().code ~= 200 then return end
entry({"admin","services","docker","containers"},form("dockerman/containers"),_("Containers"),1).leaf=true
entry({"admin","services","docker","images"},form("dockerman/images"),_("Images"),2).leaf=true
entry({"admin","services","docker","networks"},form("dockerman/networks"),_("Networks"),3).leaf=true
entry({"admin","services","docker","volumes"},form("dockerman/volumes"),_("Volumes"),4).leaf=true
entry({"admin","services","docker","events"},call("action_events"),_("Events"),5)
entry({"admin","services","docker","newcontainer"},form("dockerman/newcontainer")).leaf=true
entry({"admin","services","docker","newnetwork"},form("dockerman/newnetwork")).leaf=true
entry({"admin","services","docker","container"},form("dockerman/container")).leaf=true
entry({"admin","services","docker","container_stats"},call("action_get_container_stats")).leaf=true
entry({"admin","services","docker","container_get_archive"},call("download_archive")).leaf=true
entry({"admin","services","docker","container_put_archive"},call("upload_archive")).leaf=true
entry({"admin","services","docker","confirm"},call("action_confirm")).leaf=true
entry({"admin","docker","containers"},form("dockerman/containers"),_("Containers"),1).leaf=true
entry({"admin","docker","images"},form("dockerman/images"),_("Images"),2).leaf=true
entry({"admin","docker","networks"},form("dockerman/networks"),_("Networks"),3).leaf=true
entry({"admin","docker","volumes"},form("dockerman/volumes"),_("Volumes"),4).leaf=true
entry({"admin","docker","events"},call("action_events"),_("Events"),5)
entry({"admin","docker","newcontainer"},form("dockerman/newcontainer")).leaf=true
entry({"admin","docker","newnetwork"},form("dockerman/newnetwork")).leaf=true
entry({"admin","docker","container"},form("dockerman/container")).leaf=true
entry({"admin","docker","container_stats"},call("action_get_container_stats")).leaf=true
entry({"admin","docker","container_get_archive"},call("download_archive")).leaf=true
entry({"admin","docker","container_put_archive"},call("upload_archive")).leaf=true
entry({"admin","docker","images_save"},call("save_images")).leaf=true
entry({"admin","docker","images_load"},call("load_images")).leaf=true
entry({"admin","docker","images_import"},call("import_images")).leaf=true
entry({"admin","docker","images_get_tags"},call("get_image_tags")).leaf=true
entry({"admin","docker","images_tag"},call("tag_image")).leaf=true
entry({"admin","docker","images_untag"},call("untag_image")).leaf=true
entry({"admin","docker","confirm"},call("action_confirm")).leaf=true
end
function action_events()
local logs = ""
local dk = docker.new()
local query ={}
query["until"] = os.time()
local events = dk:events({query = query})
for _, v in ipairs(events.body) do
if v.Type == "container" then
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. (v.Action or "null") .. " Container ID:".. (v.Actor.ID or "null") .. " Container Name:" .. (v.Actor.Attributes.name or "null")
elseif v.Type == "network" then
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Container ID:"..( v.Actor.Attributes.container or "null" ) .. " Network Name:" .. (v.Actor.Attributes.name or "null") .. " Network type:".. v.Actor.Attributes.type or ""
elseif v.Type == "image" then
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Image:".. (v.Actor.ID or "null").. " Image Name:" .. (v.Actor.Attributes.name or "null")
local events = dk:events({query = query})
if events.code == 200 then
for _, v in ipairs(events.body) do
if v and v.Type == "container" then
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. (v.Action or "null") .. " Container ID:".. (v.Actor.ID or "null") .. " Container Name:" .. (v.Actor.Attributes.name or "null")
elseif v.Type == "network" then
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Container ID:"..( v.Actor.Attributes.container or "null" ) .. " Network Name:" .. (v.Actor.Attributes.name or "null") .. " Network type:".. v.Actor.Attributes.type or ""
elseif v.Type == "image" then
logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Image:".. (v.Actor.ID or "null").. " Image Name:" .. (v.Actor.Attributes.name or "null")
end
end
end
luci.template.render("dockerman/logs", {self={syslog = logs, title="Docker Events"}})
luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}})
end
local calculate_cpu_percent = function(d)
@ -133,14 +147,14 @@ function action_get_container_stats(container_id)
else
luci.http.status(404, "No container name or id")
luci.http.prepare_content("text/plain")
luci.http.write("No container name or id")
luci.http.write("No container name or id")
end
end
function action_confirm()
local status_path=luci.model.uci.cursor():get("dockerman", "local", "status_path")
local data = nixio.fs.readfile(status_path)
local data = docker:read_status()
if data then
data = data:gsub("\n","<br>"):gsub(" ","&nbsp;")
code = 202
msg = data
else
@ -148,7 +162,6 @@ function action_confirm()
msg = "finish"
data = "finish"
end
-- luci.util.perror(data)
luci.http.status(code, msg)
luci.http.prepare_content("application/json")
luci.http.write_json({info = data})
@ -185,7 +198,7 @@ function upload_archive(container_id)
local dk = docker.new()
local ltn12 = require "luci.ltn12"
rec_send = function(sinkout)
local rec_send = function(sinkout)
luci.http.setfilehandler(function (meta, chunk, eof)
if chunk then
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
@ -199,3 +212,173 @@ function upload_archive(container_id)
luci.http.prepare_content("application/json")
luci.http.write_json({message = msg})
end
function save_images(container_id)
local names = luci.http.formvalue("names")
local dk = docker.new()
local first
local cb = function(res, chunk)
if res.code == 200 then
if not first then
first = true
luci.http.status(res.code, res.message)
luci.http.header('Content-Disposition', 'inline; filename="images.tar"')
luci.http.header('Content-Type', 'application\/x-tar')
end
luci.ltn12.pump.all(chunk, luci.http.write)
else
if not first then
first = true
luci.http.prepare_content("text/plain")
end
luci.ltn12.pump.all(chunk, luci.http.write)
end
end
docker:write_status("Images: saving" .. " " .. container_id .. "...")
local res = dk.images:get({id = container_id, query = {names = names}}, cb)
docker:clear_status()
local msg = res and res.body and res.body.message or nil
luci.http.status(res.code, msg)
luci.http.prepare_content("application/json")
luci.http.write_json({message = msg})
end
function load_images()
local path = luci.http.formvalue("upload-path")
local dk = docker.new()
local ltn12 = require "luci.ltn12"
local rec_send = function(sinkout)
luci.http.setfilehandler(function (meta, chunk, eof)
if chunk then
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
end
end)
end
docker:write_status("Images: loading...")
local res = dk.images:load({body = rec_send})
-- res.body = {"stream":"Loaded image ID: sha256:1399d3d81f80d68832e85ed6ba5f94436ca17966539ba715f661bd36f3caf08f\n"}
local msg = res and res.body and ( res.body.message or res.body.stream or res.body.error)or nil
if res.code == 200 and msg and msg:match("Loaded image ID") then
docker:clear_status()
luci.http.status(res.code, msg)
else
docker:append_status("code:" .. res.code.." ".. msg)
luci.http.status(300, msg)
end
luci.http.prepare_content("application/json")
luci.http.write_json({message = msg})
end
function import_images()
local src = luci.http.formvalue("src")
local itag = luci.http.formvalue("tag")
local dk = docker.new()
local ltn12 = require "luci.ltn12"
local rec_send = function(sinkout)
luci.http.setfilehandler(function (meta, chunk, eof)
if chunk then
ltn12.pump.step(ltn12.source.string(chunk), sinkout)
end
end)
end
docker:write_status("Images: importing".. " ".. itag .."...\n")
local repo = itag and itag:match("^([^:]+)")
local tag = itag and itag:match("^[^:]-:([^:]+)")
local res = dk.images:create({query = {fromSrc = src or "-", repo = repo or nil, tag = tag or nil }, body = not src and rec_send or nil}, docker.import_image_show_status_cb)
local msg = res and res.body and ( res.body.message )or nil
if not msg and #res.body == 0 then
-- res.body = {"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
msg = res.body.status or res.body.error
elseif not msg and #res.body >= 1 then
-- res.body = [...{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}]
msg = res.body[#res.body].status or res.body[#res.body].error
end
if res.code == 200 and msg and msg:match("sha256:") then
docker:clear_status()
else
docker:append_status("code:" .. res.code.." ".. msg)
end
luci.http.status(res.code, msg)
luci.http.prepare_content("application/json")
luci.http.write_json({message = msg})
end
function get_image_tags(image_id)
if not image_id then
luci.http.status(400, "no image id")
luci.http.prepare_content("application/json")
luci.http.write_json({message = "no image id"})
return
end
local dk = docker.new()
local res = dk.images:inspect({id = image_id})
local msg = res and res.body and res.body.message or nil
luci.http.status(res.code, msg)
luci.http.prepare_content("application/json")
if res.code == 200 then
local tags = res.body.RepoTags
luci.http.write_json({tags = tags})
else
local msg = res and res.body and res.body.message or nil
luci.http.write_json({message = msg})
end
end
function tag_image(image_id)
local src = luci.http.formvalue("tag")
local image_id = image_id or luci.http.formvalue("id")
if type(src) ~= "string" or not image_id then
luci.http.status(400, "no image id or tag")
luci.http.prepare_content("application/json")
luci.http.write_json({message = "no image id or tag"})
return
end
local repo = src:match("^([^:]+)")
local tag = src:match("^[^:]-:([^:]+)")
local dk = docker.new()
local res = dk.images:tag({id = image_id, query={repo=repo, tag=tag}})
local msg = res and res.body and res.body.message or nil
luci.http.status(res.code, msg)
luci.http.prepare_content("application/json")
if res.code == 201 then
local tags = res.body.RepoTags
luci.http.write_json({tags = tags})
else
local msg = res and res.body and res.body.message or nil
luci.http.write_json({message = msg})
end
end
function untag_image(tag)
local tag = tag or luci.http.formvalue("tag")
if not tag then
luci.http.status(400, "no tag name")
luci.http.prepare_content("application/json")
luci.http.write_json({message = "no tag name"})
return
end
local dk = docker.new()
local res = dk.images:inspect({name = tag})
if res.code == 200 then
local tags = res.body.RepoTags
if #tags > 1 then
local r = dk.images:remove({name = tag})
local msg = r and r.body and r.body.message or nil
luci.http.status(r.code, msg)
luci.http.prepare_content("application/json")
luci.http.write_json({message = msg})
else
luci.http.status(500, "Cannot remove the last tag")
luci.http.prepare_content("application/json")
luci.http.write_json({message = "Cannot remove the last tag"})
end
else
local msg = res and res.body and res.body.message or nil
luci.http.status(res.code, msg)
luci.http.prepare_content("application/json")
luci.http.write_json({message = msg})
end
end

View File

@ -4,13 +4,12 @@ Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local uci = luci.model.uci.cursor()
local docker = require "luci.model.docker"
local dk = docker.new()
container_id = arg[1]
local action = arg[2] or "info"
local images, networks, containers_info
local images, networks, container_info
if not container_id then return end
local res = dk.containers:inspect({id = container_id})
if res.code < 300 then container_info = res.body else return end
@ -94,6 +93,36 @@ local get_links = function(d)
return data
end
local get_tmpfs = function(d)
local data
if d.HostConfig and d.HostConfig.Tmpfs then
for k, v in pairs(d.HostConfig.Tmpfs) do
data = (data and (data .. "<br>") or "") .. k .. (v~="" and ":" or "")..v
end
end
return data
end
local get_dns = function(d)
local data
if d.HostConfig and d.HostConfig.Dns then
for _, v in ipairs(d.HostConfig.Dns) do
data = (data and (data .. "<br>") or "") .. v
end
end
return data
end
local get_sysctl = function(d)
local data
if d.HostConfig and d.HostConfig.Sysctls then
for k, v in pairs(d.HostConfig.Sysctls) do
data = (data and (data .. "<br>") or "") .. k..":"..v
end
end
return data
end
local get_networks = function(d)
local data={}
if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then
@ -115,26 +144,25 @@ local start_stop_remove = function(m, cmd)
res = dk.containers_upgrade(dk, {id = container_id})
end
if res and res.code >= 300 then
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/container/"..container_id))
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
else
docker:clear_status()
if cmd ~= "remove" and cmd ~= "upgrade" then
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/container/"..container_id))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id))
else
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/containers"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
end
end
end
m=SimpleForm("docker", container_info.Name:sub(2), translate("Docker Container") )
m.template = "dockerman/cbi/xsimpleform"
m.redirect = luci.dispatcher.build_url("admin/services/docker/containers")
m.redirect = luci.dispatcher.build_url("admin/docker/containers")
-- m:append(Template("dockerman/container"))
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=nixio.fs.readfile(dk.options.status_path)
-- luci.util.perror(docker_status.err)
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
@ -165,7 +193,7 @@ btnupgrade.inputstyle = "reload"
btnstop.forcewrite = true
btnduplicate=action_section:option(Button, "_duplicate")
btnduplicate.template = "dockerman/cbi/inlinebutton"
btnduplicate.inputtitle=translate("Duplicate")
btnduplicate.inputtitle=translate("Duplicate/Edit")
btnduplicate.inputstyle = "add"
btnstop.forcewrite = true
btnremove=action_section:option(Button, "_remove")
@ -190,7 +218,7 @@ btnstop.write = function(self, section)
start_stop_remove(m,"stop")
end
btnduplicate.write = function(self, section)
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newcontainer/duplicate/"..container_id))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id))
end
tab_section = m:section(SimpleSection)
@ -209,13 +237,16 @@ if action == "info" then
table_info["06start"] = container_info.State.Status == "running" and {_key = translate("Start Time"), _value = container_info.State and container_info.State.StartedAt or "-"} or {_key = translate("Finish Time"), _value = container_info.State and container_info.State.FinishedAt or "-"}
table_info["07healthy"] = {_key = translate("Healthy"), _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"}
table_info["08restart"] = {_key = translate("Restart Policy"), _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", _button=translate("Update")}
table_info["09device"] = {_key = translate("Device"), _value = get_device(container_info) or "-"}
table_info["081user"] = {_key = translate("User"), _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"}
table_info["09mount"] = {_key = translate("Mount/Volume"), _value = get_mounts(container_info) or "-"}
table_info["10cmd"] = {_key = translate("Command"), _value = get_command(container_info) or "-"}
table_info["11env"] = {_key = translate("Env"), _value = get_env(container_info) or "-"}
table_info["12ports"] = {_key = translate("Ports"), _value = get_ports(container_info) or "-"}
table_info["13links"] = {_key = translate("Links"), _value = get_links(container_info) or "-"}
table_info["14device"] = {_key = translate("Device"), _value = get_device(container_info) or "-"}
table_info["15tmpfs"] = {_key = translate("Tmpfs"), _value = get_tmpfs(container_info) or "-"}
table_info["16dns"] = {_key = translate("DNS"), _value = get_dns(container_info) or "-"}
table_info["17sysctl"] = {_key = translate("Sysctl"), _value = get_sysctl(container_info) or "-"}
info_networks = get_networks(container_info)
list_networks = {}
for _, v in ipairs (networks) do
@ -272,7 +303,9 @@ if action == "info" then
self:reset_values()
self.size = nil
for k,v in pairs(list_networks) do
self:value(k,v)
if k ~= "host" then
self:value(k,v)
end
end
self.default=table_info[section]._value
ListValue.render(self, section, scope)
@ -322,15 +355,15 @@ if action == "info" then
if table_info[section]._button and table_info[section]._value ~= nil then
btn_update.inputtitle=table_info[section]._button
self.template = "cbi/button"
self.inputstyle = "edit"
Button.render(self, section, scope)
else
self.template = "dockerman/cbi/dummyvalue"
self.template = "cbi/dvalue"
self.default=""
DummyValue.render(self, section, scope)
end
end
btn_update.write = function(self, section, value)
-- luci.util.perror(section)
local res
docker:clear_status()
if section == "01name" then
@ -350,7 +383,6 @@ if action == "info" then
local connect_network = table_info[section]._value
local network_opiton
if connect_network ~= "none" and connect_network ~= "bridge" and connect_network ~= "host" then
-- luci.util.perror(table_info[section]._opts)
network_opiton = table_info[section]._opts ~= "" and {
IPAMConfig={
IPv4Address=table_info[section]._opts
@ -361,11 +393,11 @@ if action == "info" then
res = dk.networks:connect({name = connect_network, body = {Container = container_id, EndpointConfig= network_opiton}})
end
if res and res.code > 300 then
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
else
docker:clear_status()
end
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/container/"..container_id.."/info"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info"))
end
-- info end
@ -418,15 +450,14 @@ elseif action == "edit" then
Memory = tonumber(memory),
CpuShares = tonumber(data.cpushares)
}
docker:clear_status()
docker:append_status("Containers: update " .. container_id .. "...")
docker:write_status("Containers: update " .. container_id .. "...")
local res = dk.containers:update({id = container_id, body = request_body})
if res and res.code >= 300 then
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
else
docker:clear_status()
end
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/container/"..container_id.."/edit"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/edit"))
end
end
elseif action == "file" then
@ -453,6 +484,62 @@ elseif action == "logs" then
logsection.template = "dockerman/logs"
m.submit = false
m.reset = false
elseif action == "console" then
m.submit = false
m.reset = false
local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil
local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil
if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then
local consolesection= m:section(SimpleSection)
local cmd = "/bin/sh"
local uid
local vcommand = consolesection:option(Value, "command", translate("Command"))
vcommand:value("/bin/sh", "/bin/sh")
vcommand:value("/bin/ash", "/bin/ash")
vcommand:value("/bin/bash", "/bin/bash")
vcommand.default = "/bin/sh"
vcommand.forcewrite = true
vcommand.write = function(self, section, value)
cmd = value
end
local vuid = consolesection:option(Value, "uid", translate("UID"))
vuid.forcewrite = true
vuid.write = function(self, section, value)
uid = value
end
local btn_connect = consolesection:option(Button, "connect")
btn_connect.render = function(self, section, scope)
self.inputstyle = "add"
self.title = " "
self.inputtitle = translate("Connect")
Button.render(self, section, scope)
end
btn_connect.write = function(self, section)
local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil
local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil
if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$") then return end
local kill_ttyd = 'netstat -lnpt | grep ":7682[ \t].*ttyd$" | awk \'{print $NF}\' | awk -F\'/\' \'{print "kill -9 " $1}\' | sh > /dev/null'
luci.util.exec(kill_ttyd)
local hosts
local uci = (require "luci.model.uci").cursor()
local remote = uci:get("dockerman", "local", "remote_endpoint")
local socket_path = (remote == "false" or not remote) and uci:get("dockerman", "local", "socket_path") or nil
local host = (remote == "true") and uci:get("dockerman", "local", "remote_host") or nil
local port = (remote == "true") and uci:get("dockerman", "local", "remote_port") or nil
if remote and host and port then
hosts = host .. ':'.. port
elseif socket_path then
hosts = "unix://" .. socket_path
else
return
end
local start_cmd = cmd_ttyd .. ' -d 2 --once -p 7682 '.. cmd_docker .. ' -H "'.. hosts ..'" exec -it ' .. (uid and uid ~= "" and (" -u ".. uid .. ' ') or "").. container_id .. ' ' .. cmd .. ' &'
os.execute(start_cmd)
local console = consolesection:option(DummyValue, "console")
console.container_id = container_id
console.template = "dockerman/container_console"
end
end
elseif action == "stats" then
local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}})
local container_top
@ -469,11 +556,10 @@ elseif action == "stats" then
container_top=response.body
stat_section = m:section(SimpleSection)
stat_section.container_id = container_id
stat_section.template = "dockerman/stats"
stat_section.template = "dockerman/container_stats"
table_stats = {cpu={key=translate("CPU Useage"),value='-'},memory={key=translate("Memory Useage"),value='-'}}
stat_section = m:section(Table, table_stats, translate("Stats"))
stat_section:option(DummyValue, "key", translate("Stats")).width="33%"
stat_section:option(DummyValue, "value")
top_section= m:section(Table, container_top.Processes, translate("TOP"))
for i, v in ipairs(container_top.Titles) do
@ -484,5 +570,4 @@ m.submit = false
m.reset = false
end
return m
return m

View File

@ -27,7 +27,8 @@ function get_containers()
data[index]={}
data[index]["_selected"] = 0
data[index]["_id"] = v.Id:sub(1,12)
data[index]["_name"] = v.Names[1]:sub(2)
data[index]["name"] = v.Names[1]:sub(2)
data[index]["_name"] = '<a href='..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. v.Names[1]:sub(2).."</a>"
data[index]["_status"] = v.Status
if v.Status:find("^Up") then
data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>"
@ -54,7 +55,7 @@ function get_containers()
end
for ii,iv in ipairs(images) do
if iv.Id == v.ImageID then
data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":none")
data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>")
end
end
@ -68,14 +69,13 @@ local c_lists = get_containers()
-- list Containers
-- m = Map("docker", translate("Docker"))
m = SimpleForm("docker", translate("Docker"))
m.template = "dockerman/cbi/xsimpleform"
m.submit=false
m.reset=false
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=nixio.fs.readfile(dk.options.status_path)
-- luci.util.perror(docker_status.err)
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
c_table = m:section(Table, c_lists, translate("Containers"))
@ -90,11 +90,7 @@ container_selecter.default = 0
container_id = c_table:option(DummyValue, "_id", translate("ID"))
container_id.width="10%"
container_name = c_table:option(DummyValue, "_name", translate("Container Name"))
container_name.width="20%"
container_name.template = "dockerman/cbi/dummyvalue"
container_name.href = function (self, section)
return luci.dispatcher.build_url("admin/services/docker/container/" .. urlencode(container_id:cfgvalue(section)))
end
container_name.rawhtml = true
container_status = c_table:option(DummyValue, "_status", translate("Status"))
container_status.width="15%"
container_status.rawhtml=true
@ -103,11 +99,7 @@ container_ip.width="15%"
container_ports = c_table:option(DummyValue, "_ports", translate("Ports"))
container_ports.width="10%"
container_image = c_table:option(DummyValue, "_image", translate("Image"))
container_image.template = "dockerman/cbi/dummyvalue"
container_image.width="10%"
-- container_image.href = function (self, section)
-- return luci.dispatcher.build_url("admin/services/docker/image/" .. urlencode(c_lists[section]._image_id))
-- end
container_command = c_table:option(DummyValue, "_command", translate("Command"))
container_command.width="20%"
@ -116,18 +108,13 @@ container_selecter.write=function(self, section, value)
end
local start_stop_remove = function(m,cmd)
-- luci.template.render("admin_uci/apply", {
-- changes = next(changes) and changes,
-- configs = reload
-- })
local c_selected = {}
-- 遍历table中sectionid
local c_table_sids = c_table:cfgsections()
for _, c_table_sid in ipairs(c_table_sids) do
-- 得到选中项的名字
if c_lists[c_table_sid]._selected == 1 then
c_selected[#c_selected+1] = container_name:cfgvalue(c_table_sid)
c_selected[#c_selected+1] = c_lists[c_table_sid].name --container_name:cfgvalue(c_table_sid)
end
end
if #c_selected >0 then
@ -138,13 +125,13 @@ local start_stop_remove = function(m,cmd)
local res = dk.containers[cmd](dk, {id = cont})
if res and res.code >= 300 then
success = false
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
else
docker:append_status("done<br>")
docker:append_status("done\n")
end
end
if success then docker:clear_status() end
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/containers"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
end
end
@ -179,7 +166,7 @@ btnremove.inputtitle=translate("Remove")
btnremove.inputstyle = "remove"
btnremove.forcewrite = true
btnnew.write = function(self, section)
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newcontainer"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
end
btnstart.write = function(self, section)
start_stop_remove(m,"start")
@ -194,4 +181,4 @@ btnstop.write = function(self, section)
start_stop_remove(m,"stop")
end
return m
return m

View File

@ -20,19 +20,25 @@ function get_images()
local index = v.Created .. v.Id
data[index]={}
data[index]["_selected"] = 0
data[index]["_id"] = v.Id:sub(8,20)
if v.RepoTags then
data[index]["id"] = v.Id:sub(8)
data[index]["_id"] = '<a href="javascript:new_tag(\''..v.Id:sub(8,20)..'\')" class="dockerman-link" title="'..translate("New tag")..'">' .. v.Id:sub(8,20) .. '</a>'
if v.RepoTags and next(v.RepoTags)~=nil then
for i, v1 in ipairs(v.RepoTags) do
data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "<br\>" )or "") .. v1
data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "<br>" )or "") .. ((v1:match("<none>") or (#v.RepoTags == 1)) and v1 or ('<a href="javascript:un_tag(\''..v1..'\')" class="dockerman_link" title="'..translate("Remove tag")..'" >' .. v1 .. '</a>'))
if not data[index]["tag"] then
data[index]["tag"] = v1--:match("<none>") and nil or v1
end
end
else
_,_, data[index]["_tags"] = v.RepoDigests[1]:find("^(.-)@.+")
data[index]["_tags"]=data[index]["_tags"]..":none"
else
data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+")
data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>"
end
data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","&lt;none&gt;")
-- data[index]["_tags"] = '<a href="javascript:handle_tag(\''..data[index]["_id"]..'\')">' .. data[index]["_tags"] .. '</a>'
for ci,cv in ipairs(containers) do
if v.Id == cv.ImageID then
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
"<a href=/cgi-bin/luci/admin/services/docker/container/"..cv.Id.." >".. cv.Names[1]:sub(2).."</a>"
'<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2).."</a>"
end
end
data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB"
@ -45,69 +51,58 @@ local image_list = get_images()
-- m = Map("docker", translate("Docker"))
m = SimpleForm("docker", translate("Docker"))
m.template = "dockerman/cbi/xsimpleform"
m.submit=false
m.reset=false
local pull_value={{_image_tag_name="", _registry="index.docker.io"}}
local pull_section = m:section(Table,pull_value, translate("Pull Image"))
local pull_value={_image_tag_name="", _registry="index.docker.io"}
local pull_section = m:section(SimpleSection, translate("Pull Image"))
pull_section.template="cbi/nullsection"
local tag_name = pull_section:option(Value, "_image_tag_name")
tag_name.template = "dockerman/cbi/inlinevalue"
tag_name.placeholder="hello-world:latest"
local registry = pull_section:option(Value, "_registry")
registry.template = "dockerman/cbi/inlinevalue"
registry:value("index.docker.io", "Docker Hub")
registry:value("hub-mirror.c.163.com", "163 Mirror")
registry:value("mirror.ccs.tencentyun.com", "Tencent Mirror")
registry:value("docker.mirrors.ustc.edu.cn", "USTC Mirror")
tag_name.placeholder="lisaac/luci:latest"
local action_pull = pull_section:option(Button, "_pull")
action_pull.inputtitle= translate("Pull")
action_pull.template = "dockerman/cbi/inlinebutton"
action_pull.inputstyle = "add"
tag_name.write = function(self, section,value)
tag_name.write = function(self, section, value)
local hastag = value:find(":")
if not hastag then
value = value .. ":latest"
end
pull_value[section]["_image_tag_name"] = value
end
registry.write = function(self, section,value)
pull_value[section]["_registry"] = value
pull_value["_image_tag_name"] = value
end
action_pull.write = function(self, section)
local tag = pull_value[section]["_image_tag_name"]
local server = pull_value[section]["_registry"]
--去掉协议前缀和后缀
local _,_,tmp = server:find(".-://([%.%w%-%_]+)")
if not tmp then
_,_,server = server:find("([%.%w%-%_]+)")
end
local tag = pull_value["_image_tag_name"]
local json_stringify = luci.jsonc and luci.jsonc.stringify
if tag and tag ~= "" then
docker:clear_status()
docker:append_status("Images: " .. "pulling" .. " " .. tag .. "...")
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
local res = dk.images:create({query = {fromImage=tag}, header={["X-Registry-Auth"] = x_auth}})
if res and res.code >=300 then
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n")
-- local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server})) , header={["X-Registry-Auth"] = x_auth}
local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb)
-- {"errorDetail": {"message": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }, "error": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }
if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then
docker:clear_status()
else
docker:append_status("done<br>")
docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
end
else
docker:append_status("fail code: 400 please input the name of image name!")
docker:append_status("code: 400 please input the name of image name!")
end
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/images"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
end
image_table = m:section(Table, image_list, translate("Images"))
local import_section = m:section(SimpleSection, translate("Import Images"))
local im = import_section:option(DummyValue, "_image_import")
im.template = "dockerman/images_import"
image_selecter = image_table:option(Flag, "_selected","")
local image_table = m:section(Table, image_list, translate("Images"))
local image_selecter = image_table:option(Flag, "_selected","")
image_selecter.disabled = 0
image_selecter.enabled = 1
image_selecter.default = 0
image_id = image_table:option(DummyValue, "_id", translate("ID"))
local image_id = image_table:option(DummyValue, "_id", translate("ID"))
image_id.rawhtml = true
image_table:option(DummyValue, "_tags", translate("RepoTags")).rawhtml = true
image_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true
image_table:option(DummyValue, "_size", translate("Size"))
@ -123,7 +118,7 @@ local remove_action = function(force)
for _, image_table_sid in ipairs(image_table_sids) do
-- 得到选中项的名字
if image_list[image_table_sid]._selected == 1 then
image_selected[#image_selected+1] = image_id:cfgvalue(image_table_sid)
image_selected[#image_selected+1] = (image_list[image_table_sid]["_tags"]:match("<br>") or image_list[image_table_sid]["_tags"]:match("&lt;none&gt;")) and image_list[image_table_sid].id or image_list[image_table_sid].tag
end
end
if next(image_selected) ~= nil then
@ -135,28 +130,29 @@ local remove_action = function(force)
if force then query = {force = true} end
local msg = dk.images:remove({id = img, query = query})
if msg.code ~= 200 then
docker:append_status("fail code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "<br>")
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
success = false
else
docker:append_status("done<br>")
docker:append_status("done\n")
end
end
if success then docker:clear_status() end
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/images"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/images"))
end
end
docker_status = m:section(SimpleSection)
local docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=nixio.fs.readfile(dk.options.status_path)
docker_status.err = docker:read_status()
docker_status.err = docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
action = m:section(Table,{{}})
local action = m:section(Table,{{}})
action.notitle=true
action.rowcolors=false
action.template="cbi/nullsection"
btnremove = action:option(Button, "remove")
local btnremove = action:option(Button, "remove")
btnremove.inputtitle= translate("Remove")
btnremove.template = "dockerman/cbi/inlinebutton"
btnremove.inputstyle = "remove"
@ -165,7 +161,7 @@ btnremove.write = function(self, section)
remove_action()
end
btnforceremove = action:option(Button, "forceremove")
local btnforceremove = action:option(Button, "forceremove")
btnforceremove.inputtitle= translate("Force Remove")
btnforceremove.template = "dockerman/cbi/inlinebutton"
btnforceremove.inputstyle = "remove"
@ -173,4 +169,55 @@ btnforceremove.forcewrite = true
btnforceremove.write = function(self, section)
remove_action(true)
end
return m
local btnsave = action:option(Button, "save")
btnsave.inputtitle= translate("Save")
btnsave.template = "dockerman/cbi/inlinebutton"
btnsave.inputstyle = "edit"
btnsave.forcewrite = true
btnsave.write = function (self, section)
local image_selected = {}
local image_table_sids = image_table:cfgsections()
for _, image_table_sid in ipairs(image_table_sids) do
if image_list[image_table_sid]._selected == 1 then
image_selected[#image_selected+1] = image_list[image_table_sid].id --image_id:cfgvalue(image_table_sid)
end
end
if next(image_selected) ~= nil then
local names
for _,img in ipairs(image_selected) do
names = names and (names .. "&names=".. img) or img
end
local first
local cb = function(res, chunk)
if res.code == 200 then
if not first then
first = true
luci.http.header('Content-Disposition', 'inline; filename="images.tar"')
luci.http.header('Content-Type', 'application\/x-tar')
end
luci.ltn12.pump.all(chunk, luci.http.write)
else
if not first then
first = true
luci.http.prepare_content("text/plain")
end
luci.ltn12.pump.all(chunk, luci.http.write)
end
end
docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...")
local msg = dk.images:get({query = {names = names}}, cb)
if msg.code ~= 200 then
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
success = false
else
docker:clear_status()
end
end
end
local btnload = action:option(Button, "load")
btnload.inputtitle= translate("Load")
btnload.template = "dockerman/images_load"
btnload.inputstyle = "add"
return m

View File

@ -36,7 +36,6 @@ end
local network_list = get_networks()
-- m = Map("docker", translate("Docker"))
m = SimpleForm("docker", translate("Docker"))
m.template = "dockerman/cbi/xsimpleform"
m.submit=false
m.reset=false
@ -69,7 +68,8 @@ end
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=nixio.fs.readfile(dk.options.status_path)
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
action = m:section(Table,{{}})
@ -83,7 +83,7 @@ btnnew.notitle=true
btnnew.inputstyle = "add"
btnnew.forcewrite = true
btnnew.write = function(self, section)
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newnetwork"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
end
btnremove = action:option(Button, "_remove")
btnremove.inputtitle= translate("Remove")
@ -92,32 +92,39 @@ btnremove.inputstyle = "remove"
btnremove.forcewrite = true
btnremove.write = function(self, section)
local network_selected = {}
local network_name_selected = {}
local network_driver_selected = {}
-- 遍历table中sectionid
local network_table_sids = network_table:cfgsections()
for _, network_table_sid in ipairs(network_table_sids) do
-- 得到选中项的名字
if network_list[network_table_sid]._selected == 1 then
network_selected[#network_selected+1] = network_name:cfgvalue(network_table_sid)
network_selected[#network_selected+1] = network_list[network_table_sid]._id --network_name:cfgvalue(network_table_sid)
network_name_selected[#network_name_selected+1] = network_list[network_table_sid]._name
network_driver_selected[#network_driver_selected+1] = network_list[network_table_sid]._driver
end
end
if next(network_selected) ~= nil then
local success = true
docker:clear_status()
for _,net in ipairs(network_selected) do
for ii, net in ipairs(network_selected) do
docker:append_status("Networks: " .. "remove" .. " " .. net .. "...")
local res = dk.networks["remove"](dk, {id = net})
if res and res.code >= 300 then
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
success = false
else
docker:append_status("done<br>")
docker:append_status("done\n")
if network_driver_selected[ii] == "macvlan" then
docker.remove_macvlan_interface(network_name_selected[ii])
end
end
end
if success then
docker:clear_status()
end
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/networks"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
end
end
return m
return m

View File

@ -4,7 +4,6 @@ Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
require "math"
local uci = luci.model.uci.cursor()
local docker = require "luci.model.docker"
local dk = docker.new()
@ -16,149 +15,153 @@ local networks = dk.networks:list().body
local containers = dk.containers:list({query = {all=true}}).body
local is_quot_complete = function(str)
require "math"
if not str then return true end
local num = 0, w
for w in str:gmatch("[\"\']") do
for w in str:gmatch("\"") do
num = num + 1
end
if math.fmod(num, 2) ~= 0 then
return false
else
return true
if math.fmod(num, 2) ~= 0 then return false end
num = 0
for w in str:gmatch("\'") do
num = num + 1
end
if math.fmod(num, 2) ~= 0 then return false end
return true
end
-- reslvo default config
local default_config = { }
if cmd_line and cmd_line:match("^docker.+") then
local key = nil, _key
--cursor = 0: docker run
--cursor = 1: resloving para
--cursor = 2: resloving image
--cursor > 2: resloving command
local cursor = 0
for w in cmd_line:gmatch("[^%s]+") do
-- skip '\'
if w == '\\' then
elseif _key then
-- there is a value that unpair quotation marks:
-- "i was a ok man"
-- now we only get: "i
if _key == "mount" or _key == "link" or _key == "env" or _key == "dns" or _key == "port" or _key == "device" or _key == "tmpfs" then
default_config[_key][#default_config[_key]] = default_config[_key][#default_config[_key]] .. " " .. w
if is_quot_complete(default_config[_key][#default_config[_key]]) then
-- clear quotation marks
default_config[_key][#default_config[_key]] = default_config[_key][#default_config[_key]]:gsub("[\"\']", "")
_key = nil
local resolve_cli = function(cmd_line)
local config = {advance = 1}
local key_no_val = '|t|d|i|tty|rm|read_only|interactive|init|help|detach|privileged|'
local key_with_val = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|blkio_weight|cgroup_parent|cidfile|cpu_period|cpu_quota|cpu_rt_period|cpu_rt_runtime|c|cpu_shares|cpus|cpuset_cpus|cpuset_mems|detach_keys|disable_content_trust|domainname|entrypoint|gpus|health_cmd|health_interval|health_retries|health_start_period|health_timeout|h|hostname|ip|ip6|ipc|isolation|kernel_memory|log_driver|mac_address|m|memory|memory_reservation|memory_swap|memory_swappiness|mount|name|network|no_healthcheck|oom_kill_disable|oom_score_adj|pid|pids_limit|P|publish_all|restart|runtime|shm_size|sig_proxy|stop_signal|stop_timeout|ulimit|u|user|userns|uts|volume_driver|w|workdir|'
local key_abb = {net='network',a='attach',c='cpu-shares',d='detach',e='env',h='hostname',i='interactive',l='label',m='memory',p='publish',P='publish_all',t='tty',u='user',v='volume',w='workdir'}
local key_with_list = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|'
local key = nil
local _key = nil
local val = nil
local is_cmd = false
cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)")
for w in cmd_line:gmatch("[^%s]+") do
if w =='\\' then
elseif not key and not _key and not is_cmd then
--key=val
key, val = w:match("^%-%-([%lP%-]-)=(.+)")
if not key then
--key val
key = w:match("^%-%-([%lP%-]+)")
if not key then
-- -v val
key = w:match("^%-([%lP%-]+)")
if key then
-- for -dit
if key:match("i") or key:match("t") or key:match("d") then
if key:match("i") then
config[key_abb["i"]] = true
key:gsub("i", "")
end
if key:match("t") then
config[key_abb["t"]] = true
key:gsub("t", "")
end
if key:match("d") then
config[key_abb["d"]] = true
key:gsub("d", "")
end
if key == "" then key = nil end
end
end
end
end
if key then
key = key:gsub("-","_")
key = key_abb[key] or key
if key_no_val:match("|"..key.."|") then
config[key] = true
val = nil
key = nil
elseif key_with_val:match("|"..key.."|") then
-- if key == "cap_add" then config.privileged = true end
else
key = nil
val = nil
end
else
default_config[_key] = default_config[_key] .. " ".. w
if is_quot_complete(default_config[_key]) then
-- clear quotation marks
default_config[_key] = default_config[_key]:gsub("[\"\']", "")
_key = nil
end
end
-- start with '-'
elseif w:match("^%-+.+") and cursor <= 1 then
--key=value
local val
key, val = w:match("^%-+(.-)=(.+)")
-- -dit
if not key then key = w:match("^%-+(.+)") end
if not key then
key = w:match("^%-(.+)")
if key:match("i") or key:match("t") or key:match("d") then
if key:match("i") then default_config["interactive"] = true end
if key:match("t") then default_config["tty"] = true end
-- clear key
key = nil
end
end
if key == "v" or key == "volume" then
key = "mount"
elseif key == "p" or key == "publish" then
key = "port"
elseif key == "e" then
key = "env"
elseif key == "dns" then
key = "dns"
elseif key == "net" then
key = "network"
elseif key == "h" or key == "hostname" then
key = "hostname"
elseif key == "cpu-shares" then
key = "cpushares"
elseif key == "m" then
key = "memory"
elseif key == "blkio-weight" then
key = "blkioweight"
elseif key == "privileged" then
default_config["privileged"] = true
config.image = w
key = nil
elseif key == "cap-add" then
default_config["privileged"] = true
val = nil
is_cmd = true
end
--key=value
if val then
if key == "mount" or key == "link" or key == "env" or key == "dns" or key == "port" or key == "device" or key == "tmpfs" then
if not default_config[key] then default_config[key] = {} end
table.insert( default_config[key], val )
-- clear quotation marks
default_config[key][#default_config[key]] = default_config[key][#default_config[key]]:gsub("[\"\']", "")
else
default_config[key] = val
-- clear quotation marks
default_config[key] = default_config[key]:gsub("[\"\']", "")
elseif (key or _key) and not is_cmd then
if key == "mount" then
-- we need resolve mount options here
-- type=bind,source=/source,target=/app
local _type = w:match("^type=([^,]+),") or "bind"
local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or ""
local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or ""
local ro = w:match(",readonly") and "ro" or nil
if source and target then
if _type ~= "tmpfs" then
-- bind or volume
local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil
val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or ""))
else
-- tmpfs
local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil
local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil
key = "tmpfs"
val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "")
if not config[key] then config[key] = {} end
table.insert( config[key], val )
key = nil
val = nil
end
end
-- if there are " or ' in val and separate by space, we need keep the _key to link with next w
if is_quot_complete(val) then
else
val = w
end
elseif is_cmd then
config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w
end
if (key or _key) and val then
key = _key or key
if key_with_list:match("|"..key.."|") then
if not config[key] then config[key] = {} end
if _key then
config[key][#config[key]] = config[key][#config[key]] .. " " .. w
else
table.insert( config[key], val )
end
if is_quot_complete(config[key][#config[key]]) then
-- clear quotation marks
config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "")
_key = nil
else
_key = key
end
-- clear key
key = nil
end
cursor = 1
-- value
elseif key and type(key) == "string" and cursor == 1 then
if key == "mount" or key == "link" or key == "env" or key == "dns" or key == "port" or key == "device" or key == "tmpfs" then
if not default_config[key] then default_config[key] = {} end
table.insert( default_config[key], w )
-- clear quotation marks
default_config[key][#default_config[key]] = default_config[key][#default_config[key]]:gsub("[\"\']", "")
else
default_config[key] = w
-- clear quotation marks
default_config[key] = default_config[key]:gsub("[\"\']", "")
end
if key == "cpus" or key == "cpushare" or key == "memory" or key == "blkioweight" or key == "device" or key == "tmpfs" then
default_config["advance"] = 1
end
-- if there are " or ' in val and separate by space, we need keep the _key to link with next w
if is_quot_complete(w) then
_key = nil
else
_key = key
config[key] = (config[key] and (config[key] .. " ") or "") .. val
if is_quot_complete(config[key]) then
-- clear quotation marks
config[key] = config[key]:gsub("[\"\']", "")
_key = nil
else
_key = key
end
end
key = nil
cursor = 1
--image and command
elseif cursor >= 1 and key == nil then
if cursor == 1 then
default_config["image"] = w
elseif cursor > 1 then
default_config["command"] = (default_config["command"] and (default_config["command"] .. " " )or "") .. w
end
cursor = cursor + 1
val = nil
end
end
return config
end
-- reslvo default config
local default_config = {}
if cmd_line and cmd_line:match("^DOCKERCLI.+") then
default_config = resolve_cli(cmd_line)
elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
local container_id = cmd_line:match("^duplicate/(.+)")
create_body = dk:containers_duplicate_config({id = container_id})
create_body = dk:containers_duplicate_config({id = container_id}) or {}
if not create_body.HostConfig then create_body.HostConfig = {} end
if next(create_body) ~= nil then
default_config.name = nil
@ -170,17 +173,25 @@ elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil
-- default_config.network = create_body.HostConfig.NetworkMode == "default" and "bridge" or create_body.HostConfig.NetworkMode
-- if container has leave original network, and add new network, .HostConfig.NetworkMode is INcorrect, so using first child of .NetworkingConfig.EndpointsConfig
default_config.network = next(create_body.NetworkingConfig.EndpointsConfig)
default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil
default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil
default_config.link = create_body.HostConfig.Links
default_config.env = create_body.Env
default_config.dns = create_body.HostConfig.Dns
default_config.mount = create_body.HostConfig.Binds
default_config.volume = create_body.HostConfig.Binds
default_config.cap_add = create_body.HostConfig.CapAdd
if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then
default_config.sysctl = {}
for k, v in pairs(create_body.HostConfig.Sysctls) do
table.insert( default_config.sysctl, k.."="..v )
end
end
if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then
default_config.port = {}
default_config.publish = {}
for k, v in pairs(create_body.HostConfig.PortBindings) do
table.insert( default_config.port, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") )
end
end
@ -188,9 +199,9 @@ elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil
default_config.advance = 1
default_config.cpus = create_body.HostConfig.NanoCPUs
default_config.cpushares = create_body.HostConfig.CpuShares
default_config.cpu_shares = create_body.HostConfig.CpuShares
default_config.memory = create_body.HostConfig.Memory
default_config.blkioweight = create_body.HostConfig.BlkioWeight
default_config.blkio_weight = create_body.HostConfig.BlkioWeight
if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then
default_config.device = {}
@ -198,30 +209,34 @@ elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then
table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") )
end
end
default_config.tmpfs = create_body.HostConfig.Tmpfs
if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then
default_config.tmpfs = {}
for k, v in pairs(create_body.HostConfig.Tmpfs) do
table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v )
end
end
end
end
local m = SimpleForm("docker", translate("Docker"))
m.template = "dockerman/cbi/xsimpleform"
m.redirect = luci.dispatcher.build_url("admin", "services","docker", "containers")
m.redirect = luci.dispatcher.build_url("admin", "docker", "containers")
-- m.reset = false
-- m.submit = false
-- new Container
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=nixio.fs.readfile(dk.options.status_path)
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
local s = m:section(SimpleSection, translate("New Container"))
s.addremove = true
s.anonymous = true
local d = s:option(DummyValue,"cmd_line", translate("Resolv CLI"))
local d = s:option(DummyValue,"cmd_line", translate("Resolve CLI"))
d.rawhtml = true
d.template = "dockerman/resolv_container"
d.template = "dockerman/newcontainer_resolve"
d = s:option(Value, "name", translate("Container Name"))
d.rmempty = true
@ -279,14 +294,14 @@ d_ip:depends("network", "nil")
d_ip.default = default_config.ip or nil
d = s:option(DynamicList, "link", translate("Links with other containers"))
d.template = "dockerman/cbi/xdynlist"
d.cfgvalue = function (self, section) return default_config.link or nil end
d.placeholder = "container_name:alias"
d.rmempty = true
d:depends("network", "bridge")
d.default = default_config.link or nil
d = s:option(DynamicList, "dns", translate("Set custom DNS servers"))
d.template = "dockerman/cbi/xdynlist"
d.cfgvalue = function (self, section) return default_config.dns or nil end
d.placeholder = "8.8.8.8"
d.rmempty = true
d.default = default_config.dns or nil
@ -297,22 +312,22 @@ d.rmempty = true
d.default = default_config.user or nil
d = s:option(DynamicList, "env", translate("Environmental Variable(-e)"), translate("Set environment variables to inside the container"))
d.template = "dockerman/cbi/xdynlist"
d.cfgvalue = function (self, section) return default_config.env or nil end
d.placeholder = "TZ=Asia/Shanghai"
d.rmempty = true
d.default = default_config.env or nil
d = s:option(DynamicList, "mount", translate("Bind Mount(-v)"), translate("Bind mount a volume"))
d.template = "dockerman/cbi/xdynlist"
d = s:option(DynamicList, "volume", translate("Bind Mount(-v)"), translate("Bind mount a volume"))
d.cfgvalue = function (self, section) return default_config.volume or nil end
d.placeholder = "/media:/media:slave"
d.rmempty = true
d.default = default_config.mount or nil
d.default = default_config.volume or nil
local d_ports = s:option(DynamicList, "port", translate("Exposed Ports(-p)"), translate("Publish container's port(s) to the host"))
d_ports.template = "dockerman/cbi/xdynlist"
d_ports.placeholder = "2200:22/tcp"
d_ports.rmempty = true
d_ports.default = default_config.port or nil
local d_publish = s:option(DynamicList, "publish", translate("Exposed Ports(-p)"), translate("Publish container's port(s) to the host"))
d_publish.cfgvalue = function (self, section) return default_config.publish or nil end
d_publish.placeholder = "2200:22/tcp"
d_publish.rmempty = true
d_publish.default = default_config.publish or nil
d = s:option(Value, "command", translate("Run command"))
d.placeholder = "/bin/sh init.sh"
@ -331,19 +346,33 @@ d.default = default_config.hostname or nil
d:depends("advance", 1)
d = s:option(DynamicList, "device", translate("Device(--device)"), translate("Add host device to the container"))
d.template = "dockerman/cbi/xdynlist"
d.cfgvalue = function (self, section) return default_config.device or nil end
d.placeholder = "/dev/sda:/dev/xvdc:rwm"
d.rmempty = true
d:depends("advance", 1)
d.default = default_config.device or nil
d = s:option(DynamicList, "tmpfs", translate("Tmpfs(--tmpfs)"), translate("Mount tmpfs directory"))
d.template = "dockerman/cbi/xdynlist"
d.cfgvalue = function (self, section) return default_config.tmpfs or nil end
d.placeholder = "/run:rw,noexec,nosuid,size=65536k"
d.rmempty = true
d:depends("advance", 1)
d.default = default_config.tmpfs or nil
d = s:option(DynamicList, "sysctl", translate("Sysctl(--sysctl)"), translate("Sysctls (kernel parameters) options"))
d.cfgvalue = function (self, section) return default_config.sysctl or nil end
d.placeholder = "net.ipv4.ip_forward=1"
d.rmempty = true
d:depends("advance", 1)
d.default = default_config.sysctl or nil
d = s:option(DynamicList, "cap_add", translate("CAP-ADD(--cap-add)"), translate("A list of kernel capabilities to add to the container"))
d.cfgvalue = function (self, section) return default_config.cap_add or nil end
d.placeholder = "NET_ADMIN"
d.rmempty = true
d:depends("advance", 1)
d.default = default_config.cap_add or nil
d = s:option(Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit."))
d.placeholder = "1.5"
d.rmempty = true
@ -351,12 +380,12 @@ d:depends("advance", 1)
d.datatype="ufloat"
d.default = default_config.cpus or nil
d = s:option(Value, "cpushares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
d = s:option(Value, "cpu_shares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024."))
d.placeholder = "1024"
d.rmempty = true
d:depends("advance", 1)
d.datatype="uinteger"
d.default = default_config.cpushares or nil
d.default = default_config.cpu_shares or nil
d = s:option(Value, "memory", translate("Memory"), translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M."))
d.placeholder = "128m"
@ -364,13 +393,12 @@ d.rmempty = true
d:depends("advance", 1)
d.default = default_config.memory or nil
d = s:option(Value, "blkioweight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
d = s:option(Value, "blkio_weight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000."))
d.placeholder = "500"
d.rmempty = true
d:depends("advance", 1)
d.datatype="uinteger"
d.default = default_config.blkioweight or nil
d.default = default_config.blkio_weight or nil
for _, v in ipairs (networks) do
if v.Name then
@ -385,7 +413,7 @@ for _, v in ipairs (networks) do
end
if v.Driver == "bridge" then
d_ports:depends("network", v.Name)
d_publish:depends("network", v.Name)
end
end
end
@ -406,13 +434,24 @@ m.handle = function(self, state, data)
local restart = data.restart
local env = data.env
local dns = data.dns
local cap_add = data.cap_add
local sysctl = {}
tmp = data.sysctl
if type(tmp) == "table" then
for i, v in ipairs(tmp) do
local k,v1 = v:match("(.-)=(.+)")
if k and v1 then
sysctl[k]=v1
end
end
end
local network = data.network
local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil
local mount = data.mount
local volume = data.volume
local memory = data.memory or 0
local cpushares = data.cpushares or 0
local cpu_shares = data.cpu_shares or 0
local cpus = data.cpus or 0
local blkioweight = data.blkioweight or 500
local blkio_weight = data.blkio_weight or 500
local portbindings = {}
local exposedports = {}
@ -420,8 +459,9 @@ m.handle = function(self, state, data)
tmp = data.tmpfs
if type(tmp) == "table" then
for i, v in ipairs(tmp)do
local _,_, k,v1 = v:find("(.-):(.+)")
if k and v1 then
local k= v:match("([^:]+)")
local v1 = v:match(".-:([^:]+)") or ""
if k then
tmpfs[k]=v1
end
end
@ -451,7 +491,7 @@ m.handle = function(self, state, data)
end
end
tmp = data.port or {}
tmp = data.publish or {}
for i, v in ipairs(tmp) do
for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do
local _,_,p= v2:find("^%d+/(%w+)")
@ -491,20 +531,20 @@ m.handle = function(self, state, data)
create_body.Tty = tty and true or false
create_body.OpenStdin = interactive and true or false
create_body.User = user
create_body.Cmd = (#command ~= 0) and command or nil
create_body.Cmd = command
create_body.Env = env
create_body.Image = image
create_body.ExposedPorts = (next(exposedports) ~= nil) and exposedports or nil
create_body.ExposedPorts = exposedports
create_body.HostConfig = create_body.HostConfig or {}
create_body.HostConfig.Dns = dns
create_body.HostConfig.Binds = (#mount ~= 0) and mount or nil
create_body.HostConfig.Binds = volume
create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 }
create_body.HostConfig.Privileged = privileged and true or false
create_body.HostConfig.PortBindings = (next(portbindings) ~= nil) and portbindings or nil
create_body.HostConfig.PortBindings = portbindings
create_body.HostConfig.Memory = tonumber(memory)
create_body.HostConfig.CpuShares = tonumber(cpushares)
create_body.HostConfig.CpuShares = tonumber(cpu_shares)
create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9
create_body.HostConfig.BlkioWeight = tonumber(blkioweight)
create_body.HostConfig.BlkioWeight = tonumber(blkio_weight)
if create_body.HostConfig.NetworkMode ~= network then
-- network mode changed, need to clear duplicate config
create_body.NetworkingConfig = nil
@ -529,24 +569,24 @@ m.handle = function(self, state, data)
-- no ip + no duplicate config
create_body.NetworkingConfig = nil
end
create_body["HostConfig"]["Tmpfs"] = tmpfs
create_body["HostConfig"]["Devices"] = device
create_body["HostConfig"]["Sysctls"] = sysctl
create_body["HostConfig"]["CapAdd"] = cap_add
create_body["HostConfig"]["Tmpfs"] = (next(tmpfs) ~= nil) and tmpfs or nil
create_body["HostConfig"]["Devices"] = (next(device) ~= nil) and device or nil
if network == "bridge" and next(link) ~= nil then
if network == "bridge" then
create_body["HostConfig"]["Links"] = link
end
local pull_image = function(image)
local server = "index.docker.io"
local json_stringify = luci.jsonc and luci.jsonc.stringify
docker:append_status("Images: " .. "pulling" .. " " .. image .. "...")
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
local res = dk.images:create({query = {fromImage=image}, header={["X-Registry-Auth"]=x_auth}})
if res and res.code == 200 then
docker:append_status("done<br>")
docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n")
local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb)
if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image or res.body[#res.body].status == "Status: Image is up to date for ".. image)) then
docker:append_status("done\n")
else
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newcontainer"))
res.code = (res.code == 200) and 500 or res.code
docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n")
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
end
end
docker:clear_status()
@ -565,15 +605,16 @@ m.handle = function(self, state, data)
end
end
create_body = docker.clear_empty_tables(create_body)
docker:append_status("Container: " .. "create" .. " " .. name .. "...")
local res = dk.containers:create({name = name, body = create_body})
if res and res.code == 201 then
docker:clear_status()
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/containers"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers"))
else
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newcontainer"))
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer"))
end
end
return m
return m

View File

@ -4,17 +4,16 @@ Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman>
]]--
require "luci.util"
local uci = luci.model.uci.cursor()
local docker = require "luci.model.docker"
local dk = docker.new()
m = SimpleForm("docker", translate("Docker"))
m.template = "dockerman/cbi/xsimpleform"
m.redirect = luci.dispatcher.build_url("admin", "services","docker", "networks")
m.redirect = luci.dispatcher.build_url("admin", "docker", "networks")
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=nixio.fs.readfile(dk.options.status_path)
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
s = m:section(SimpleSection, translate("New Network"))
@ -34,7 +33,12 @@ d:value("overlay", "overlay")
d = s:option(Value, "parent", translate("Parent Interface"))
d.rmempty = true
d:depends("dirver", "macvlan")
d.placeholder="eth0"
local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
for _, v in ipairs(interfaces) do
d:value(v, v)
end
d.default="br-lan"
d.placeholder="br-lan"
d = s:option(Value, "macvlan_mode", translate("Macvlan Mode"))
d.rmempty = true
@ -60,16 +64,24 @@ d.default = 0
d:depends("dirver", "overlay")
d = s:option(DynamicList, "options", translate("Options"))
d.template = "dockerman/cbi/xdynlist"
d.rmempty = true
d.placeholder="com.docker.network.driver.mtu=1500"
d = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network"))
d.rmempty = true
d:depends("dirver", "overlay")
d.disabled = 0
d.enabled = 1
d.default = 0
if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then
d = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt"))
d:depends("dirver", "macvlan")
d.disabled = 0
d.enabled = 1
d.default = 1
end
d = s:option(Value, "subnet", translate("Subnet"))
d.rmempty = true
d.placeholder="10.1.0.0/16"
@ -86,7 +98,6 @@ d.placeholder="10.1.1.0/24"
d.datatype="ip4addr"
d = s:option(DynamicList, "aux_address", translate("Exclude IPs"))
d.template = "dockerman/cbi/xdynlist"
d.rmempty = true
d.placeholder="my-route=10.1.1.1"
@ -151,14 +162,11 @@ m.handle = function(self, state, data)
Subnet = subnet,
Gateway = gateway,
IPRange = ip_range,
-- AuxAddress = aux_address
-- AuxiliaryAddresses = aux_address
AuxAddress = aux_address,
AuxiliaryAddresses = aux_address
}
}
end
if next(aux_address)~=nil then
create_body["IPAM"]["Config"]["AuxiliaryAddresses"] = aux_address
end
if driver == "macvlan" then
create_body["Options"] = {
macvlan_mode = data.macvlan_mode,
@ -190,16 +198,20 @@ m.handle = function(self, state, data)
end
end
docker:append_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
create_body = docker.clear_empty_tables(create_body)
docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...")
local res = dk.networks:create({body = create_body})
if res and res.code == 201 then
docker:clear_status()
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/networks"))
if driver == "macvlan" and data.op_macvlan ~= 0 then
docker.create_macvlan_interface(data.name, data.parent, data.gateway, data.ip_range)
end
luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks"))
else
docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/newnetwork"))
docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n")
luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork"))
end
end
end
return m
return m

View File

@ -13,12 +13,12 @@ function byte_format(byte)
if byte > 1024 and i < 5 then
byte = byte / 1024
else
return string.format("%.2f %s", byte, suff[i])
end
return string.format("%.2f %s", byte, suff[i])
end
end
end
local m = Map("dockerman", translate("Docker"))
local map_dockerman = Map("dockerman", translate("Docker"), translate("DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:") .. " ".. [[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] ..translate("Github") .. [[</a>]])
local docker_info_table = {}
-- docker_info_table['0OperatingSystem'] = {_key=translate("Operating System"),_value='-'}
-- docker_info_table['1Architecture'] = {_key=translate("Architecture"),_value='-'}
@ -29,22 +29,23 @@ docker_info_table['5NCPU'] = {_key=translate("CPUs"),_value='-'}
docker_info_table['6MemTotal'] = {_key=translate("Total Memory"),_value='-'}
docker_info_table['7DockerRootDir'] = {_key=translate("Docker Root Dir"),_value='-'}
docker_info_table['8IndexServerAddress'] = {_key=translate("Index Server Address"),_value='-'}
docker_info_table['9RegistryMirrors'] = {_key=translate("Registry Mirrors"),_value='-'}
s = m:section(Table, docker_info_table)
local s = map_dockerman:section(Table, docker_info_table)
s:option(DummyValue, "_key", translate("Info"))
s:option(DummyValue, "_value")
s = m:section(SimpleSection)
s = map_dockerman:section(SimpleSection)
s.containers_running = '-'
s.images_used = '-'
s.containers_total = '-'
s.images_total = '-'
s.networks_total = '-'
s.volumes_total = '-'
local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
if nixio.fs.access(socket) and (require "luci.model.docker").new():_ping().code == 200 then
local containers_list
-- local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path")
if (require "luci.model.docker").new():_ping().code == 200 then
local dk = docker.new()
local containers_list = dk.containers:list({query = {all=true}}).body
containers_list = dk.containers:list({query = {all=true}}).body
local images_list = dk.images:list().body
local vol = dk.volumes:list()
local volumes_list = vol and vol.body and vol.body.Volumes or {}
@ -59,6 +60,9 @@ if nixio.fs.access(socket) and (require "luci.model.docker").new():_ping().code
docker_info_table['6MemTotal']._value = byte_format(docker_info.body.MemTotal)
docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir
docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress
for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do
docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v)
end
s.images_used = 0
for i, v in ipairs(images_list) do
@ -78,17 +82,71 @@ if nixio.fs.access(socket) and (require "luci.model.docker").new():_ping().code
end
s.template = "dockerman/overview"
local section_dockerman = map_dockerman:section(NamedSection, "local", "section", translate("Setting"))
section_dockerman:tab("dockerman", translate("DockerMan"), translate("DockerMan Settings"))
section_dockerman:tab("ac", translate("Access Control"), translate("Access Control for the bridge network"))
section_dockerman:tab("daemon", translate("Docker Daemon"), translate("Docker Daemon Settings"))
s = m:section(NamedSection, "local", "section", translate("Setting"))
local socket_path = section_dockerman:taboption("dockerman", Value, "socket_path", translate("Docker Socket Path"))
socket_path.default = "/var/run/docker.sock"
socket_path.placeholder = "/var/run/docker.sock"
socket_path.rmempty = false
socket_path = s:option(Value, "socket_path", translate("Socket Path"))
status_path = s:option(Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file"))
wan_mode = s:option(Flag, "wan_mode", translate("Enable WAN access Dokcer"), translate("Enable WAN access docker mapped ports (need reload Docker-ce service)"))
wan_mode.enabled="true"
wan_mode.disabled="false"
debug = s:option(Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path"))
debug.enabled="true"
debug.disabled="false"
debug_path = s:option(Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile"))
local remote_endpoint = section_dockerman:taboption("dockerman", Flag, "remote_endpoint", translate("Remote Endpoint"), translate("Dockerman connect to remote endpoint"))
remote_endpoint.rmempty = false
remote_endpoint.enabled = "true"
remote_endpoint.disabled = "false"
return m
local remote_host = section_dockerman:taboption("dockerman", Value, "remote_host", translate("Remote Host"))
remote_host.placeholder = "10.1.1.2"
-- remote_host:depends("remote_endpoint", "true")
local remote_port = section_dockerman:taboption("dockerman", Value, "remote_port", translate("Remote Port"))
remote_port.placeholder = "2375"
remote_port.default = "2375"
-- remote_port:depends("remote_endpoint", "true")
-- local status_path = section_dockerman:taboption("dockerman", Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file"))
-- local debug = section_dockerman:taboption("dockerman", Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path"))
-- debug.enabled="true"
-- debug.disabled="false"
-- local debug_path = section_dockerman:taboption("dockerman", Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile"))
if nixio.fs.access("/usr/bin/dockerd") then
local allowed_interface = section_dockerman:taboption("ac", DynamicList, "ac_allowed_interface", translate("Allowed access interfaces"), translate("Which interface(s) can access containers under the bridge network, fill-in Interface Name"))
local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {}
for i, v in ipairs(interfaces) do
allowed_interface:value(v, v)
end
local allowed_container = section_dockerman:taboption("ac", DynamicList, "ac_allowed_container", translate("Containers allowed to be accessed"), translate("Which container(s) can be accessed, even from interfaces that are not allowed, fill-in Container Id or Name"))
allowed_container.placeholder = "container name_or_id"
if containers_list then
for i, v in ipairs(containers_list) do
if v.State == "running" and v.NetworkSettings and v.NetworkSettings.Networks and v.NetworkSettings.Networks.bridge and v.NetworkSettings.Networks.bridge.IPAddress then
allowed_container:value(v.Id:sub(1,12), v.Names[1]:sub(2) .. " | " .. v.NetworkSettings.Networks.bridge.IPAddress)
end
end
end
local dockerd_enable = section_dockerman:taboption("daemon", Flag, "daemon_ea", translate("Enable"))
dockerd_enable.enabled = "true"
dockerd_enable.rmempty = true
local data_root = section_dockerman:taboption("daemon", Value, "daemon_data_root", translate("Docker Root Dir"))
data_root.placeholder = "/opt/docker/"
local registry_mirrors = section_dockerman:taboption("daemon", DynamicList, "daemon_registry_mirrors", translate("Registry Mirrors"))
registry_mirrors.placeholder = "https://hub-mirror.c.163.com"
registry_mirrors:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com")
local log_level = section_dockerman:taboption("daemon", ListValue, "daemon_log_level", translate("Log Level"), translate('Set the logging level'))
log_level:value("debug", "debug")
log_level:value("info", "info")
log_level:value("warn", "warn")
log_level:value("error", "error")
log_level:value("fatal", "fatal")
local hosts = section_dockerman:taboption("daemon", DynamicList, "daemon_hosts", translate("Server Host"), translate('Daemon unix socket (unix:///var/run/docker.sock) or TCP Remote Hosts (tcp://0.0.0.0:2375), default: unix:///var/run/docker.sock'))
hosts.placeholder = "unix:///var/run/docker.sock"
hosts:value("unix:///var/run/docker.sock", "unix:///var/run/docker.sock")
hosts:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375")
hosts.rmempty = true
end
return map_dockerman

View File

@ -28,7 +28,7 @@ function get_volumes()
for vi, vv in ipairs(cv.Mounts) do
if v.Name == vv.Name then
data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "")..
"<a href=/cgi-bin/luci/admin/services/docker/container/"..cv.Id.." >".. cv.Names[1]:sub(2).."</a>"
'<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2)..'</a>'
end
end
end
@ -50,7 +50,6 @@ local volume_list = get_volumes()
-- m = Map("docker", translate("Docker"))
m = SimpleForm("docker", translate("Docker"))
m.template = "dockerman/cbi/xsimpleform"
m.submit=false
m.reset=false
@ -73,7 +72,8 @@ end
docker_status = m:section(SimpleSection)
docker_status.template = "dockerman/apply_widget"
docker_status.err=nixio.fs.readfile(dk.options.status_path)
docker_status.err=docker:read_status()
docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" ","&nbsp;")
if docker_status.err then docker:clear_status() end
action = m:section(Table,{{}})
@ -103,14 +103,14 @@ btnremove.write = function(self, section)
docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...")
local msg = dk.volumes["remove"](dk, {id = vol})
if msg.code ~= 204 then
docker:append_status("fail code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "<br>")
docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n")
success = false
else
docker:append_status("done<br>")
docker:append_status("done\n")
end
end
if success then docker:clear_status() end
luci.http.redirect(luci.dispatcher.build_url("admin/services/docker/volumes"))
luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes"))
end
end
return m
return m

View File

@ -11,16 +11,14 @@ local _docker = {}
--pull image and return iamge id
local update_image = function(self, image_name)
local server = "index.docker.io"
local json_stringify = luci.jsonc and luci.jsonc.stringify
_docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...")
local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server}))
local res = self.images:create({query = {fromImage=image_name}, header={["X-Registry-Auth"]=x_auth}})
if res and res.code < 300 then
_docker:append_status("done<br>")
_docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n")
local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb)
if res and res.code == 200 and (#res.body > 0 and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image_name)) then
_docker:append_status("done\n")
else
_docker:append_status("fail code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "<br>")
res.code = 500
res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)
end
new_image_id = self.images:inspect({name = image_name}).body.Id
return new_image_id, res
@ -81,9 +79,25 @@ local map_subtract = function(t1, t2)
return next(res) ~= nil and res or nil
end
_docker.clear_empty_tables = function ( t )
local k, v
if next(t) == nil then
t = nil
else
for k, v in pairs(t) do
if type(v) == 'table' then
t[k] = _docker.clear_empty_tables(v)
end
end
end
return t
end
-- return create_body, extra_network
local get_config = function(old_config, old_host_config, old_network_setting, image_config)
local config = old_config
local get_config = function(container_config, image_config)
local config = container_config.Config
local old_host_config = container_config.HostConfig
local old_network_setting = container_config.NetworkSettings.Networks or {}
if config.WorkingDir == image_config.WorkingDir then config.WorkingDir = "" end
if config.User == image_config.User then config.User = "" end
if table_equal(config.Cmd, image_config.Cmd) then config.Cmd = nil end
@ -117,12 +131,27 @@ local get_config = function(old_config, old_host_config, old_network_setting, im
local host_config = old_host_config
if host_config.PortBindings and next(host_config.PortBindings) == nil then host_config.PortBindings = nil end
host_config.LogConfig = nil
host_config.Mounts = {}
-- for volumes
for i, v in ipairs(container_config.Mounts) do
if v.Type == "volume" then
table.insert(host_config.Mounts, {
Type = v.Type,
Target = v.Destination,
Source = v.Source:match("([^/]+)\/_data"),
BindOptions = v.Type == "bind" and {Propagation = v.Propagation} or nil,
ReadOnly = not v.RW
})
end
end
-- merge configs
local create_body = config
create_body["HostConfig"] = host_config
create_body["NetworkingConfig"] = {EndpointsConfig = network_setting}
create_body = _docker.clear_empty_tables(create_body) or {}
extra_network = _docker.clear_empty_tables(extra_network) or {}
return create_body, extra_network
end
@ -137,12 +166,9 @@ local upgrade = function(self, request)
if not image_name:match(".-:.+") then image_name = image_name .. ":latest" end
local old_image_id = container_info.body.Image
local container_name = container_info.body.Name:sub(2)
local old_config = container_info.body.Config
local old_host_config = container_info.body.HostConfig
local old_network_setting = container_info.body.NetworkSettings.Networks or {}
local image_id, res = update_image(self, image_name)
if res and res.code > 300 then return res end
if res and res.code ~= 200 then return res end
if image_id == old_image_id then
return {code = 305, body = {message = "Already up to date"}}
end
@ -150,7 +176,7 @@ local upgrade = function(self, request)
_docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...")
res = self.containers:stop({name = container_name})
if res and res.code < 305 then
_docker:append_status("done<br>")
_docker:append_status("done\n")
else
return res
end
@ -158,32 +184,31 @@ local upgrade = function(self, request)
_docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...")
res = self.containers:rename({name = container_name, query = { name = container_name .. "_old" }})
if res and res.code < 300 then
_docker:append_status("done<br>")
_docker:append_status("done\n")
else
return res
end
-- handle config
local image_config = self.images:inspect({id = old_image_id}).body.Config
local create_body, extra_network = get_config(old_config, old_host_config, old_network_setting, image_config)
local create_body, extra_network = get_config(container_info.body, image_config)
-- create new container
_docker:append_status("Container: Create" .. " " .. container_name .. "...")
res = self.containers:create({name = container_name, body = create_body})
if res and res.code > 300 then return res end
_docker:append_status("done<br>")
_docker:append_status("done\n")
-- extra networks need to network connect action
for k, v in pairs(extra_network) do
if v.IPAMConfig and next(v.IPAMConfig) == nil then v.IPAMConfig =nil end
if v.DriverOpts and next(v.DriverOpts) == nil then v.DriverOpts =nil end
if v.Aliases and next(v.Aliases) == nil then v.Aliases =nil end
-- if v.IPAMConfig and next(v.IPAMConfig) == nil then v.IPAMConfig =nil end
-- if v.DriverOpts and next(v.DriverOpts) == nil then v.DriverOpts =nil end
-- if v.Aliases and next(v.Aliases) == nil then v.Aliases =nil end
_docker:append_status("Networks: Connect" .. " " .. container_name .. "...")
res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}})
if res.code > 300 then return res end
_docker:append_status("done<br>")
_docker:append_status("done\n")
end
_docker:clear_status()
return res
@ -193,20 +218,23 @@ local duplicate_config = function (self, request)
local container_info = self.containers:inspect({id = request.id})
if container_info.code > 300 and type(container_info.body) == "table" then return nil end
local old_image_id = container_info.body.Image
local old_config = container_info.body.Config
local old_host_config = container_info.body.HostConfig
local old_network_setting = container_info.body.NetworkSettings.Networks or {}
-- local old_config = container_info.body.Config
-- local old_host_config = container_info.body.HostConfig
-- local old_network_setting = container_info.body.NetworkSettings.Networks or {}
local image_config = self.images:inspect({id = old_image_id}).body.Config
return get_config(old_config, old_host_config, old_network_setting, image_config)
return get_config(container_info.body, image_config)
end
_docker.new = function(option)
local option = option or {}
local remote = uci:get("dockerman", "local", "remote_endpoint")
options = {
socket_path = option.socket_path or uci:get("dockerman", "local", "socket_path"),
host = (remote == "true") and (option.host or uci:get("dockerman", "local", "remote_host")) or nil,
port = (remote == "true") and (option.port or uci:get("dockerman", "local", "remote_port")) or nil,
debug = option.debug or uci:get("dockerman", "local", "debug") == 'true' and true or false,
debug_path = option.debug_path or uci:get("dockerman", "local", "debug_path")
}
options.socket_path = (remote ~= "true" or not options.host or not options.port) and (option.socket_path or uci:get("dockerman", "local", "socket_path") or "/var/run/docker.sock") or nil
local _new = docker.new(options)
_new.options.status_path = uci:get("dockerman", "local", "status_path")
_new.containers_upgrade = upgrade
@ -217,13 +245,145 @@ _docker.options={}
_docker.options.status_path = uci:get("dockerman", "local", "status_path")
_docker.append_status=function(self,val)
if not val then return end
local file_docker_action_status=io.open(self.options.status_path, "a+")
file_docker_action_status:write(val)
file_docker_action_status:close()
end
_docker.write_status=function(self,val)
if not val then return end
local file_docker_action_status=io.open(self.options.status_path, "w+")
file_docker_action_status:write(val)
file_docker_action_status:close()
end
_docker.read_status=function(self)
return nixio.fs.readfile(self.options.status_path)
end
_docker.clear_status=function(self)
nixio.fs.remove(self.options.status_path)
end
return _docker
local status_cb = function(res, source, handler)
res.body = res.body or {}
while true do
local chunk = source()
if chunk then
--standard output to res.body
table.insert(res.body, chunk)
handler(chunk)
else
return
end
end
end
--{"status":"Pulling from library\/debian","id":"latest"}
--{"status":"Pulling fs layer","progressDetail":[],"id":"50e431f79093"}
--{"status":"Downloading","progressDetail":{"total":50381971,"current":2029978},"id":"50e431f79093","progress":"[==> ] 2.03MB\/50.38MB"}
--{"status":"Download complete","progressDetail":[],"id":"50e431f79093"}
--{"status":"Extracting","progressDetail":{"total":50381971,"current":17301504},"id":"50e431f79093","progress":"[=================> ] 17.3MB\/50.38MB"}
--{"status":"Pull complete","progressDetail":[],"id":"50e431f79093"}
--{"status":"Digest: sha256:a63d0b2ecbd723da612abf0a8bdb594ee78f18f691d7dc652ac305a490c9b71a"}
--{"status":"Status: Downloaded newer image for debian:latest"}
_docker.pull_image_show_status_cb = function(res, source)
return status_cb(res, source, function(chunk)
local json_parse = luci.jsonc.parse
local step = json_parse(chunk)
if type(step) == "table" then
local buf = _docker:read_status()
local num = 0
local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n"
if step.id then buf, num = buf:gsub("\t"..step.id .. ": .-\n", str) end
if num == 0 then
buf = buf .. str
end
_docker:write_status(buf)
end
end)
end
--{"status":"Downloading from https://downloads.openwrt.org/releases/19.07.0/targets/x86/64/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz"}
--{"status":"Importing","progressDetail":{"current":1572391,"total":3821714},"progress":"[====================\u003e ] 1.572MB/3.822MB"}
--{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}
_docker.import_image_show_status_cb = function(res, source)
return status_cb(res, source, function(chunk)
local json_parse = luci.jsonc.parse
local step = json_parse(chunk)
if type(step) == "table" then
local buf = _docker:read_status()
local num = 0
local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n"
if step.status then buf, num = buf:gsub("\t"..step.status .. " .-\n", str) end
if num == 0 then
buf = buf .. str
end
_docker:write_status(buf)
end
end
)
end
-- _docker.print_status_cb = function(res, source)
-- return status_cb(res, source, function(step)
-- luci.util.perror(step)
-- end
-- )
-- end
_docker.create_macvlan_interface = function(name, device, gateway, ip_range)
if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end
if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end
local ip = require "luci.ip"
local if_name = "docker_"..name
local net_mask = tostring(ip.new(ip_range):mask())
local lan_interfaces
uci:delete("network", if_name)
uci:set("network", if_name, "interface")
uci:set("network", if_name, "proto", "static")
uci:set("network", if_name, "ifname", if_name)
uci:set("network", if_name, "ipaddr", gateway)
uci:set("network", if_name, "netmask", net_mask)
uci:foreach("firewall", "zone", function(s)
if s.name == "lan" then
local interfaces
if type(s.network) == "table" then
interfaces = table.concat(s.network, " ")
uci:delete("firewall", s[".name"], "network")
else
interfaces = s.network and s.network or ""
end
uci:set("firewall", s[".name"], "network", interfaces .. " " .. if_name)
end
end)
uci:commit("firewall")
uci:commit("network")
device = device:match("br%-(.+)") or device
os.execute("ifup " .. device)
end
_docker.remove_macvlan_interface = function(name)
if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end
if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end
local if_name = "docker_"..name
uci:foreach("firewall", "zone", function(s)
if s.name == "lan" then
local interfaces
if type(s.network) == "table" then
interfaces = table.concat(s.network, " ")
else
interfaces = s.network and s.network or ""
end
interfaces = interfaces and interfaces:gsub(if_name, "")
uci:set("firewall", s[".name"], "network", interfaces)
end
end)
uci:commit("firewall")
uci:delete("network", if_name)
uci:commit("network")
os.execute("ip link del " .. if_name)
end
return _docker

View File

@ -87,6 +87,7 @@ function uci_confirm_docker() {
var indicator = document.querySelector('.uci_change_indicator');
if (indicator) indicator.style.display = 'none';
docker_status_message('notice', '<%:Docker actions done.%>');
document.body.classList.remove('apply-overlay-active');
window.clearTimeout(tt);
return;
}
@ -94,7 +95,7 @@ function uci_confirm_docker() {
// var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0);
var delay =1000
window.setTimeout(function() {
xhr.get('<%=url("admin/services/docker/confirm")%>', null, call, uci_apply_timeout * 1000);
xhr.get('<%=url("admin/docker/confirm")%>', null, call, uci_apply_timeout * 1000);
}, delay);
};
@ -102,8 +103,8 @@ function uci_confirm_docker() {
var now = Date.now();
docker_status_message('notice',
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> ' +
loading_msg);
'<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> <span style="white-space:pre-line; font-family: \'Courier New\', Courier, monospace;">' +
loading_msg + '</span>');
tt = window.setTimeout(tick, 200);
ts = now;
@ -123,7 +124,7 @@ function fnSubmitForm(el){
}
<% if self.err then -%>
docker_status_message('warning', `<%=self.err%>`);
docker_status_message('warning', '<span style="white-space:pre-line; font-family: \'Courier New\', Courier, monospace;">'+`<%=self.err%>`+'</span>');
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
docker_status_message()
})
@ -136,4 +137,4 @@ var buttons = document.querySelectorAll('input[type="submit"]');
});
}
//]]></script>
//]]></script>

View File

@ -1,13 +0,0 @@
<%+cbi/valueheader%>
<% if self.href then %><a href="<%=self:href(section)%>"><% end -%>
<%
local val = self:cfgvalue(section) or self.default or ""
if not self.rawhtml then
write(pcdata(val))
else
write(val)
end
%>
<%- if self.href then %></a><%end%>
<input type="hidden" id="<%=cbid%>" value="<%=pcdata(self:cfgvalue(section) or self.default or "")%>" />
<%+cbi/valuefooter%>

View File

@ -1,11 +1,11 @@
<div style="display: inline-block;">
<%- if self.title then -%>
<!-- <%- if self.title then -%>
<label class="cbi-value-title"<%= attr("for", cbid) %>>
<%- if self.titleref then -%><a title="<%=self.titledesc or translate('Go to relevant configuration page')%>" class="cbi-title-ref" href="<%=self.titleref%>"><%- end -%>
<%-=self.title-%>
<%- if self.titleref then -%></a><%- end -%>
</label>
<%- end -%>
<%- end -%> -->
<%- if self.password then -%>
<input type="password" style="position:absolute; left:-100000px" aria-hidden="true"<%=
attr("name", "password." .. cbid)

View File

@ -0,0 +1,9 @@
<% if self:cfgvalue(self.section) then section = self.section %>
<div class="cbi-section" id="cbi-<%=self.config%>-<%=section%>">
<%+cbi/tabmenu%>
<div class="cbi-section-node<% if self.tabs then %> cbi-section-node-tabbed<% end %>" id="cbi-<%=self.config%>-<%=section%>">
<%+cbi/ucisection%>
</div>
</div>
<% end %>
<!-- /nsection -->

View File

@ -1,27 +0,0 @@
<%+cbi/valueheader%>
<div<%=
attr("data-prefix", cbid) ..
attr("data-browser-path", self.default_path) ..
attr("data-dynlist", luci.util.serialize_json({
self.keylist, self.vallist,
self.datatype, self.optional or self.rmempty
})) ..
ifattr(self.size, "data-size", self.size) ..
ifattr(self.placeholder, "data-placeholder", self.placeholder)
%>>
<%
local vals = self:cfgvalue(section) or self.default or {}
for i=1, #vals + 1 do
local val = vals[i]
if (val and #val > 0) or (i == 1) then
%>
<input class="cbi-input-text" value="<%=pcdata(val)%>" data-update="change" type="text"<%=
attr("id", cbid .. "." .. i) ..
attr("name", cbid) ..
ifattr(self.size, "size") ..
ifattr(i == 1 and self.placeholder, "placeholder", self.placeholder)
%> /><br />
<% end end %>
</div>
<%+cbi/valuefooter%>

View File

@ -1,89 +0,0 @@
<%
if not self.embedded then
%><form method="post" enctype="multipart/form-data" action="<%=REQUEST_URI%>"<%=
attr("data-strings", luci.util.serialize_json({
label = {
choose = translate('-- Please choose --'),
custom = translate('-- custom --'),
},
path = {
resource = resource,
browser = url("admin/filebrowser")
}
}))
%>>
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<input type="hidden" name="token" value="<%=token%>" />
<input type="hidden" name="cbi.submit" value="1" /><%
end
%><div class="cbi-map" id="cbi-<%=self.config%>"><%
if self.title and #self.title > 0 then
%><h2 name="content"><%=self.title%></h2><%
end
if self.description and #self.description > 0 then
%><div class="cbi-map-descr"><%=self.description%></div><%
end
self:render_children()
%></div><%
if self.message then
%><div class="alert-message notice"><%=self.message%></div><%
end
if self.errmessage then
%><div class="alert-message warning"><%=self.errmessage%></div><%
end
if not self.embedded then
if type(self.hidden) == "table" then
local k, v
for k, v in pairs(self.hidden) do
%><input type="hidden" id="<%=k%>" name="<%=k%>" value="<%=pcdata(v)%>" /><%
end
end
local display_back = (self.redirect)
local display_cancel = (self.cancel ~= false and self.on_cancel)
local display_skip = (self.flow and self.flow.skip)
local display_submit = (self.submit ~= false)
local display_reset = (self.reset ~= false)
if display_back or display_cancel or display_skip or display_submit or display_reset then
%><div class="cbi-page-actions"><%
if display_back then
%><input class="cbi-button cbi-button-link" type="button" value="<%:Back to Overview%>" onclick="location.href='<%=pcdata(self.redirect)%>'" /> <%
end
if display_cancel then
local label = pcdata(self.cancel or translate("Cancel"))
%><input class="cbi-button cbi-button-link" type="button" value="<%=label%>" onclick="cbi_submit(this, 'cbi.cancel')" /> <%
end
if display_skip then
%><input class="cbi-button cbi-button-neutral" type="button" value="<%:Skip%>" onclick="cbi_submit(this, 'cbi.skip')" /> <%
end
if display_submit then
local label = pcdata(self.submit or translate("Submit"))
%><input class="cbi-button cbi-button-save" type="submit" value="<%=label%>" /> <%
end
if display_reset then
local label = pcdata(self.reset or translate("Reset"))
%><input class="cbi-button cbi-button-reset" type="reset" value="<%=label%>" /> <%
end
%></div><%
end
%></form><%
end
%>
<script type="text/javascript">cbi_init();</script>

View File

@ -4,22 +4,23 @@
<li id="cbi-tab-container_edit"><a id="a-cbi-tab-container_edit" href=""><%:Edit%></a></li>
<li id="cbi-tab-container_stats"><a id="a-cbi-tab-container_stats" href=""><%:Stats%></a></li>
<li id="cbi-tab-container_file"><a id="a-cbi-tab-container_file" href=""><%:File%></a></li>
<li id="cbi-tab-container_console"><a id="a-cbi-tab-container_console" href=""><%:Console%></a></li>
<li id="cbi-tab-container_logs"><a id="a-cbi-tab-container_logs" href=""><%:Logs%></a></li>
</ul>
<script type="text/javascript">
let re = /\/admin\/services\/docker\/container\//
let re = /\/admin\/docker\/container\//
let p = window.location.href
let path = p.split(re)
let container_id = path[1].split('/')[0] || path[1]
let action = path[1].split('/')[1] || "info"
let actions=["info","edit","stats","file","logs"]
let actions=["info","edit","stats","file","console","logs"]
actions.forEach(function(item) {
document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/services/docker/container/"+container_id+'/'+item
document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/docker/container/"+container_id+'/'+item
if (action === item) {
document.getElementById("cbi-tab-container_" + item).className="cbi-tab"
} else {
document.getElementById("cbi-tab-container_" + item).className="cbi-tab-disable"
document.getElementById("cbi-tab-container_" + item).className="cbi-tab-disabled"
}
})
</script>
</script>

View File

@ -0,0 +1,6 @@
<div class="cbi-map">
<iframe id="terminal" style="width: 100%; min-height: 500px; border: none; border-radius: 3px;"></iframe>
</div>
<script type="text/javascript">
document.getElementById("terminal").src = "http://" + window.location.hostname + ":7682";
</script>

View File

@ -7,7 +7,7 @@
<br>
<label class="cbi-value-title" for="path"><%:Path%></label>
<div class="cbi-value-field">
<input type="text" class="cbi-input-text" name="path" placeholder="/home/myfiles" id="path" />
<input type="text" class="cbi-input-text" name="path" value="/tmp/" id="path" />
</div>
<br>
<div class="cbi-value-field">
@ -21,7 +21,10 @@
let uploadArchive = document.getElementById('upload_archive')
let uploadPath = document.getElementById('path').value
if (!uploadArchive.value || !uploadPath) {
alert("<%:Please input the PATH and select the file !%>")
docker_status_message('warning', "<%:Please input the PATH and select the file !%>")
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
docker_status_message()
})
return
}
let fileName = uploadArchive.files[0].name
@ -30,15 +33,18 @@
formData.append('upload-path', uploadPath)
formData.append('upload-archive', uploadArchive.files[0])
let xhr = new XMLHttpRequest()
xhr.open("POST", "/cgi-bin/luci/admin/services/docker/container_put_archive/<%=self.container%>", true)
xhr.open("POST", "/cgi-bin/luci/admin/docker/container_put_archive/<%=self.container%>", true)
xhr.onload = function() {
if (xhr.status == 200) {
uploadArchive.value = ''
alert("<%:Upload Success%> !")
docker_status_message('notice', "<%:Upload Success%>")
}
else {
alert("<%:Upload Error%>:" + xhr.statusText)
docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText)
}
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
docker_status_message()
})
}
xhr.send(formData)
}
@ -46,9 +52,12 @@
btnDownload.onclick = function (e) {
let downloadPath = document.getElementById('path').value
if (!downloadPath) {
alert("<%:Please input the PATH !%>")
docker_status_message('warning', "<%:Please input the PATH !%>")
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
docker_status_message()
})
return
}
window.open("/cgi-bin/luci/admin/services/docker/container_get_archive/?id=<%=self.container%>&path=" + encodeURIComponent(downloadPath))
window.open("/cgi-bin/luci/admin/docker/container_get_archive/?id=<%=self.container%>&path=" + encodeURIComponent(downloadPath))
}
</script>
</script>

View File

@ -22,7 +22,7 @@
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
XHR.poll(5, '/cgi-bin/luci/admin/services/docker/container_stats/<%=self.container_id%>', { status: 1 },
XHR.poll(5, '/cgi-bin/luci/admin/docker/container_stats/<%=self.container_id%>', { status: 1 },
function (x, info) {
var e;
@ -57,4 +57,4 @@
}
});
//]]></script>
//]]></script>

View File

@ -0,0 +1,88 @@
<input type="text" class="cbi-input-text" name="isrc" placeholder="http://host/image.tar" id="isrc" />
<input type="text" class="cbi-input-text" name="itag" placeholder="repository:tag" id="itag" />
<div style="display: inline-block;">
<input type="button"" class=" cbi-button cbi-button-add" id="btnimport" name="import" value="<%:Import%>" />
<input type="file" id="file_import" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" />
</div>
<script type="text/javascript">
let btnImport = document.getElementById('btnimport')
let valISrc = document.getElementById('isrc')
let valITag = document.getElementById('itag')
btnImport.onclick = function (e) {
if (valISrc.value == "") {
document.getElementById("file_import").click()
return
} else {
let formData = new FormData()
formData.append('src', valISrc.value)
formData.append('tag', valITag.value)
let xhr = new XMLHttpRequest()
uci_confirm_docker()
xhr.open("POST", "<%=url('admin/docker/images_import')%>", true)
xhr.onload = function () {
location.reload()
}
xhr.send(formData)
}
}
let fileimport = document.getElementById('file_import')
fileimport.onchange = function (e) {
let fileimport = document.getElementById('file_import')
if (!fileimport.value) {
return
}
let valITag = document.getElementById('itag')
let fileName = fileimport.files[0].name
let formData = new FormData()
formData.append('upload-filename', fileName)
formData.append('tag', valITag.value)
formData.append('upload-archive', fileimport.files[0])
let xhr = new XMLHttpRequest()
uci_confirm_docker()
xhr.open("POST", "<%=url('admin/docker/images_import')%>", true)
xhr.onload = function () {
fileimport.value = ''
location.reload()
}
xhr.send(formData)
}
let new_tag = function (image_id) {
let new_tag = prompt("<%:New tag%>\n<%:Image%>" + "ID: " + image_id + "\n<%:Please input new tag%>:", "")
if (new_tag) {
(new XHR()).post("<%=url('admin/docker/images_tag')%>",
{ id: image_id, tag: new_tag },
function (r) {
if (r.status == 201) {
location.reload()
}
else {
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
docker_status_message()
})
}
})
}
}
let un_tag = function (tag) {
if (tag.match("<none>")) return
if (confirm("<%:Remove tag%>: " + tag + " ?")) {
(new XHR()).post("<%=url('admin/docker/images_untag')%>",
{ tag: tag },
function (r) {
if (r.status == 200) {
location.reload()
}
else {
docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText);
document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{
docker_status_message()
})
}
})
}
}
</script>

View File

@ -0,0 +1,29 @@
<div style="display: inline-block;">
<input type="button"" class="cbi-button cbi-button-add" id="btnload" name="load" value="<%:Load%>" />
<input type="file" id="file_load" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" accept="application/x-tar" />
</div>
<script type="text/javascript">
let btnLoad = document.getElementById('btnload')
btnLoad.onclick = function (e) {
document.getElementById("file_load").click()
e.preventDefault()
}
let fileLoad = document.getElementById('file_load')
fileLoad.onchange = function(e){
let fileLoad = document.getElementById('file_load')
if (!fileLoad.value) {
return
}
let fileName = fileLoad.files[0].name
let formData = new FormData()
formData.append('upload-filename', fileName)
formData.append('upload-archive', fileLoad.files[0])
let xhr = new XMLHttpRequest()
uci_confirm_docker()
xhr.open("POST", "/cgi-bin/luci/admin/docker/images_load", true)
xhr.onload = function() {
location.reload()
}
xhr.send(formData)
}
</script>

View File

@ -1,10 +1,11 @@
<% if self.title == translate("Docker Events") then %>
<% if self.title == "Events" then %>
<%+header%>
<h2 name="content"><%:Docker%></h2>
<legend><%:Events%></legend>
<% end %>
<h2><a id="content" name="content"><%=self.title%></a></h2>
<div id="content_syslog">
<textarea readonly="readonly" wrap="off" rows="<%=self.syslog:cmatch("\n")+2%>" id="syslog"><%=self.syslog:pcdata()%></textarea>
</div>
<% if self.title == translate("Docker Events") then %>
<% if self.title == "Events" then %>
<%+footer%>
<% end %>
<% end %>

View File

@ -62,6 +62,7 @@
if (cmd_line == null || cmd_line == "") {
return
}
cmd_line = cmd_line.replace(/(^\s*)/g,"")
if (!cmd_line.match(/^docker\s+(run|create)/)) {
s.innerHTML = "<font color='red'><%:Command line Error%></font>"
return
@ -70,9 +71,9 @@
let reg_muti_line= /\\\s*\n/g
// reg_rem =/(?<!\\)`#.+(?<!\\)`/g // the command has `# `
let reg_rem =/`#.+`/g// the command has `# `
cmd_line = cmd_line.replace(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ")
cmd_line = cmd_line.replace(/^docker\s+(run|create)/,"DOCKERCLI").replace(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ")
console.log(cmd_line)
window.location.href = "/cgi-bin/luci/admin/services/docker/newcontainer/" + encodeURI(cmd_line)
window.location.href = "/cgi-bin/luci/admin/docker/newcontainer/" + encodeURI(cmd_line)
}
function clear_text(){
@ -91,4 +92,4 @@
<%+cbi/valueheader%>
<input type="button" class="cbi-button cbi-button-apply" value="<%:Command line%>" onclick="show_reslov_dialog()" />
<%+cbi/valuefooter%>
<%+cbi/valuefooter%>

View File

@ -75,7 +75,6 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
overflow-y: hidden;
border: 1px solid rgba(0, 0, 0, .05);
border-radius: .375rem;
background-color: #fff;
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15);
}
@ -137,7 +136,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
<div class="pure-u-3-5">
<h4 style="text-align: right; font-size: 1rem"><%:Containers%></h4>
<h4 style="text-align: right;">
<%- if self.containers_total ~= "-" then -%><a href="/cgi-bin/luci/admin/services/docker/containers"><%- end -%>
<%- if self.containers_total ~= "-" then -%><a href="/cgi-bin/luci/admin/docker/containers"><%- end -%>
<span style="font-size: 2rem; color: #2dce89;"><%=self.containers_running%></span>
<span style="font-size: 1rem; color: #8898aa !important;">/<%=self.containers_total%></span>
<%- if self.containers_total ~= "-" then -%></a><%- end -%>
@ -161,7 +160,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
<div class="pure-u-3-5">
<h4 style="text-align: right; font-size: 1rem"><%:Images%></h4>
<h4 style="text-align: right;">
<%- if self.images_total ~= "-" then -%><a href="/cgi-bin/luci/admin/services/docker/images"><%- end -%>
<%- if self.images_total ~= "-" then -%><a href="/cgi-bin/luci/admin/docker/images"><%- end -%>
<span style="font-size: 2rem; color: #2dce89;"><%=self.images_used%></span>
<span style="font-size: 1rem; color: #8898aa !important;">/<%=self.images_total%></span>
<%- if self.images_total ~= "-" then -%></a><%- end -%>
@ -193,7 +192,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
<div class="pure-u-3-5">
<h4 style="text-align: right; font-size: 1rem"><%:Networks%></h4>
<h4 style="text-align: right;">
<%- if self.networks_total ~= "-" then -%><a href="/cgi-bin/luci/admin/services/docker/networks"><%- end -%>
<%- if self.networks_total ~= "-" then -%><a href="/cgi-bin/luci/admin/docker/networks"><%- end -%>
<span style="font-size: 2rem; color: #2dce89;"><%=self.networks_total%></span>
<!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
<%- if self.networks_total ~= "-" then -%></a><%- end -%>
@ -270,7 +269,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
<div class="pure-u-3-5">
<h4 style="text-align: right; font-size: 1rem"><%:Volumes%></h4>
<h4 style="text-align: right;">
<%- if self.volumes_total ~= "-" then -%><a href="/cgi-bin/luci/admin/services/docker/volumes"><%- end -%>
<%- if self.volumes_total ~= "-" then -%><a href="/cgi-bin/luci/admin/docker/volumes"><%- end -%>
<span style="font-size: 2rem; color: #2dce89;"><%=self.volumes_total%></span>
<!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> -->
<%- if self.volumes_total ~= "-" then -%></a><%- end -%>
@ -278,4 +277,4 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
</div>
</div>
</div>
</div>
</div>

View File

@ -28,8 +28,8 @@ msgstr "停止"
msgid "Upgrade"
msgstr "升级容器"
msgid "Duplicate"
msgstr "复制容器"
msgid "Duplicate/Edit"
msgstr "复制/编辑容器"
msgid "Remove"
msgstr "移除"
@ -139,7 +139,7 @@ msgstr "网关"
msgid "New"
msgstr "新建"
msgid "Resolv CLI"
msgid "Resolve CLI"
msgstr "解析命令行"
msgid "Docker Image"
@ -241,21 +241,18 @@ msgstr "Docker根目录"
msgid "Index Server Address"
msgstr "默认服务器地址"
msgid "Socket Path"
msgstr "Socket路径"
msgid "Registry Mirrors"
msgstr "仓库镜像"
msgid "Docker Socket Path"
msgstr "Docker Socket 路径"
msgid "Action Status Tempfile Path"
msgstr "Docker 动作状态的临时文件路径"
msgstr "Docker 操作状态临时文件路径"
msgid "Where you want to save the docker status file"
msgstr "保存docker status文件的位置"
msgid "Enable WAN access Dokcer"
msgstr "允许 WAN 访问 Dokcer"
msgid "Enable WAN access docker mapped ports (need reload Docker-ce service)"
msgstr "允许 WAN 访问 Dokcer 映射后的端口(易受攻击!)。<br /><br />如已更改此选项需要点击应用并保存后重启docker服务。<br />推荐禁用该选项后,用系统防火墙选择性映射 172.17.0.X:XX 端口到 WAN"
msgid "Enable Debug"
msgstr "启用调试"
@ -268,8 +265,23 @@ msgstr "调试临时文件路径"
msgid "Where you want to save the debug tempfile"
msgstr "保存调试临时文件的位置"
msgid "Edit"
msgstr "编辑"
msgid "Log Level"
msgstr "日志等级"
msgid "Set the logging level"
msgstr "设置日志等级"
msgid "Enable WAN access"
msgstr "允许 WAN 访问"
msgid "Enable WAN access container mapped ports"
msgstr "允许 WAN 访问容器映射的端口"
msgid "Server Host"
msgstr "服务端"
msgid "Daemon unix socket (unix:///var/run/docker.sock) or TCP Remote Hosts (tcp://0.0.0.0:2375), default: unix:///var/run/docker.sock"
msgstr "后台 Unix socket (unix:///var/run/docker.sock) 或 TCP 远程服务端 (tcp://0.0.0.0:2375),默认: unix:///var/run/docker.sock"
msgid "Stats"
msgstr "状态"
@ -349,6 +361,12 @@ msgstr "上传"
msgid "Download"
msgstr "下载"
msgid "Import"
msgstr "导入"
msgid "Export"
msgstr "导出"
msgid "Path"
msgstr "路径"
@ -357,3 +375,69 @@ msgstr "上传错误"
msgid "Upload Success"
msgstr "上传成功"
msgid "Remote Endpoint"
msgstr "远程节点"
msgid "Remote Host"
msgstr "远程节点域名"
msgid "Remote Port"
msgstr "远程节点端口"
msgid "Import Images"
msgstr "导入镜像"
msgid "Load"
msgstr "载入"
msgid "Save"
msgstr "保存"
msgid "Remove tag"
msgstr "移除镜像标签"
msgid "New tag"
msgstr "新镜像标签"
msgid "Container detail"
msgstr "容器详情"
msgid "Please input new tag"
msgstr "请输入新镜像标签名称"
msgid "Error"
msgstr "错误"
msgid "Console"
msgstr "控制台"
msgid "DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:"
msgstr "DockerMan 是一个简单的 Docker 管理客户端,如果您在使用过程中有问题,请访问:"
msgid "Access Control for the bridge network"
msgstr "birdge 网络访问控制"
msgid "Allowed access interfaces"
msgstr "允许访问的接口"
msgid "Containers allowed to be accessed"
msgstr "允许被访问的容器"
msgid "Which container(s) can be accessed, even from interfaces that are not allowed, fill-in Container Id or Name"
msgstr "哪些容器可以被访问即使访问来自不被允许的接口填入容器ID或容器名"
msgid "Which interface(s) can access containers under the bridge network, fill-in Interface Name"
msgstr "哪些接口可以访问 bridge 网络下的容器, 填入接口名"
msgid "Dockerman connect to remote endpoint"
msgstr "Dockerman 将连接到远程节点"
msgid "Access Control"
msgstr "访问控制"
msgid "Create macvlan interface"
msgstr "创建 macvlan 接口"
msgid "Auto create macvlan interface in Openwrt"
msgstr "在 Openwrt 中自动创建 macvlan 接口"

View File

@ -1,6 +1,10 @@
config section 'local'
option socket_path '/var/run/docker.sock'
option status_path '/tmp/.docker_action_status'
option wan_mode 'false'
option debug_path '/tmp/.docker_debug'
option debug 'false'
option debug_path '/tmp/.docker_debug'
option remote_endpoint 'false'
option daemon_ea 'true'
option daemon_data_root '/opt/docker'
option daemon_log_level 'warn'
list ac_allowed_interface 'br-lan'

View File

@ -0,0 +1,16 @@
#!/bin/sh
[ "$ACTION" = "ifup" ] && {
DK_MACVLANS=$(docker network ls -qf driver=macvlan)
for DK_NETWORK_ID in ${DK_MACVLANS}
do
INTERFACE=$(docker network inspect "$DK_NETWORK_ID" --format '{{json .Options.parent}}' | sed -e 's/^"//' -e 's/"$//')
[ "$INTERFACE" = "$DEVICE" ] && {
echo "$DK_NETWORK_ID" >/tmp/dockerman
echo "$INTERFACE" >>/tmp/dockerman
DK_NETWORK_NAME=$(docker network inspect "$DK_NETWORK_ID" --format '{{json .Name}}' | sed -e 's/^"//' -e 's/"$//')
ip link add link "$INTERFACE" "docker_$DK_NETWORK_NAME" type macvlan mode bridge >/dev/null 2>&1
ip link set "docker_$DK_NETWORK_NAME" up >/dev/null 2>&1
}
done
}

View File

@ -0,0 +1,45 @@
#!/bin/sh /etc/rc.common
START=99
DOCKERD_CONF="/etc/docker/daemon.json"
config_load dockerman
config_get daemon_ea "local" daemon_ea
init_dockerman_chain(){
iptables -N DOCKER-MAN >/dev/null 2>&1
iptables -F DOCKER-MAN >/dev/null 2>&1
iptables -D DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1
iptables -I DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1
}
add_allowed_interface(){
iptables -A DOCKER-MAN -i $1 -o docker0 -j RETURN
}
add_allowed_ip(){
iptables -A DOCKER-MAN -d $1 -o docker0 -j RETURN
}
handle_allowed_interface(){
#config_list_foreach "local" allowed_ip add_allowed_ip
config_list_foreach "local" ac_allowed_interface add_allowed_interface
iptables -A DOCKER-MAN -m conntrack --ctstate ESTABLISHED,RELATED -o docker0 -j RETURN >/dev/null 2>&1
iptables -A DOCKER-MAN -m conntrack --ctstate NEW,INVALID -o docker0 -j DROP >/dev/null 2>&1
iptables -A DOCKER-MAN -j RETURN >/dev/null 2>&1
}
start(){
init_dockerman_chain
if [ -n "$daemon_ea" ]; then
handle_allowed_interface
lua /usr/share/dockerman/dockerd-config.lua "$DOCKERD_CONF" && /etc/init.d/dockerd restart && sleep 5 || {
# 1 running, 0 stopped
STATE=$([ -n "$(ps |grep /usr/bin/dockerd | grep -v grep)" ] && echo 1 || echo 0)
[ "$STATE" == "0" ] && /etc/init.d/dockerd start && sleep 5
}
lua /usr/share/dockerman/dockerd-ac.lua
else
/etc/init.d/dockerd stop
fi
}

View File

@ -0,0 +1,14 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
set uhttpd.main.script_timeout="360"
commit uhttpd
delete ucitrack.@dockerman[-1]
add ucitrack dockerman
set ucitrack.@dockerman[-1].exec='/etc/init.d/dockerman start'
commit ucitrack
EOF
[ -x "$(which dockerd)" ] && chmod +x /etc/init.d/dockerman && /etc/init.d/dockerd disable && /etc/init.d/dockerman enable >/dev/null 2>&1
/etc/init.d/uhttpd restart >/dev/null 2>&1
rm -fr /tmp/luci-indexcache /tmp/luci-modulecache >/dev/null 2>&1
exit 0

View File

@ -0,0 +1,20 @@
require "luci.util"
docker = require "luci.docker"
uci = (require "luci.model.uci").cursor()
dk = docker.new({socket_path = "/var/run/docker.sock"})
if dk:_ping().code ~= 200 then return end
containers_list = dk.containers:list({query = {all=true}}).body
allowed_container = uci:get("dockerman", "local", "ac_allowed_container")
if not allowed_container or next(allowed_container)==nil then return end
allowed_ip = {}
for i, v in ipairs(containers_list) do
for ii, vv in ipairs(allowed_container) do
if v.Id:sub(1,12) == vv and v.NetworkSettings and v.NetworkSettings.Networks and v.NetworkSettings.Networks.bridge and v.NetworkSettings.Networks.bridge.IPAddress then
print(v.NetworkSettings.Networks.bridge.IPAddress)
luci.util.exec("iptables -I DOCKER-MAN -d "..v.NetworkSettings.Networks.bridge.IPAddress.." -o docker0 -j RETURN")
table.remove(allowed_container, ii)
end
end
end

View File

@ -0,0 +1,50 @@
require "luci.util"
fs = require "nixio.fs"
uci = (require "luci.model.uci").cursor()
raw_file_dir = arg[1]
raw_json_str = fs.readfile(raw_file_dir)
raw_json = luci.jsonc.parse(raw_json_str) or {}
new_json = {}
new_json["data-root"] = uci:get("dockerman", "local", "daemon_data_root")
new_json["hosts"] = uci:get("dockerman", "local", "daemon_hosts")
new_json["registry-mirrors"] = uci:get("dockerman", "local", "daemon_registry_mirrors")
new_json["log-level"] = uci:get("dockerman", "local", "daemon_log_level")
function comp(raw, new)
for k, v in pairs(new) do
if type(v) == "table" and raw[k] then
if #v == #raw[k] then
comp(raw[k], v)
else
changed = true
raw[k] = v
end
elseif raw[k] ~= v then
changed = true
raw[k] = v
end
end
for k, v in ipairs(new) do
if type(v) == "table" and raw[k] then
if #v == #raw[k] then
comp(raw[k], v)
else
changed = true
raw[k] = v
end
elseif raw[k] ~= v then
changed = true
raw[k] = v
end
end
end
comp(raw_json, new_json)
if changed then
fs.writefile(raw_file_dir, luci.jsonc.stringify(raw_json, true):gsub("\\", ""))
os.exit(0)
else
os.exit(1)
end

View File

@ -0,0 +1,17 @@
#
# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Luci for Docker-CE
LUCI_DEPENDS:=+docker-ce +e2fsprogs +fdisk
LUCI_PKGARCH:=all
PKG_VERSION:=1
PKG_RELEASE:=9
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,17 @@
module("luci.controller.docker", package.seeall)
function index()
if not nixio.fs.access("/etc/config/dockerd") then
return
end
entry({"admin", "services", "docker"}, cbi("docker"), _("Docker CE Container"), 199).dependent = true
entry({"admin","services","docker","status"},call("act_status")).leaf=true
end
function act_status()
local e={}
e.running=luci.sys.call("pgrep /usr/bin/dockerd >/dev/null")==0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end

View File

@ -0,0 +1,23 @@
local running = (luci.sys.call("pidof portainer >/dev/null") == 0)
local button = ""
if running then
button = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /><br /><input type=\"button\" value=\" " .. translate("Open Portainer Docker Admin") .. " \" onclick=\"window.open('http://'+window.location.hostname+':" .. 9999 .. "')\"/><br />"
end
m = Map("dockerd", "Docker CE", translate("Docker is a set of platform-as-a-service (PaaS) products that use OS-level virtualization to deliver software in packages called containers.") .. button)
m:section(SimpleSection).template = "docker/docker_status"
s = m:section(TypedSection, "docker")
s.anonymous = true
wan_mode = s:option(Flag, "wan_mode", translate("Enable WAN access Dokcer"), translate("Enable WAN access docker mapped ports"))
wan_mode.default = 0
wan_mode.rmempty = false
o=s:option(DummyValue,"readme",translate(" "))
o.description=translate("<a href=\"../../../../DockerReadme.pdf\" target=\"_blank\" />"..translate("Download DockerReadme.pdf").."</a>")
return m

View File

@ -0,0 +1,22 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(3, '<%=url([[admin]], [[services]], [[docker]], [[status]])%>', null,
function(x, data) {
var tb = document.getElementById('docker_status');
if (data && tb) {
if (data.running) {
var links = '<em><b><font color=green>Docker CE <%:RUNNING%></font></b></em>';
tb.innerHTML = links;
} else {
tb.innerHTML = '<em><b><font color=red>Docker CE <%:NOT RUNNING%></font></b></em>';
}
}
}
);
//]]>
</script>
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
<fieldset class="cbi-section">
<p id="docker_status">
<em><%:Collecting data...%></em>
</p>
</fieldset>

View File

@ -0,0 +1,39 @@
msgid ""
msgstr ""
"Project-Id-Version: Luci ARP Bind\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-06-23 20:16+0800\n"
"PO-Revision-Date: 2015-06-23 20:17+0800\n"
"Last-Translator: coolsnowwolf <coolsnowwolf@gmail.com>\n"
"Language-Team: PandoraBox Team\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 1.8.1\n"
"X-Poedit-SourceCharset: UTF-8\n"
msgid "Docker CE Container"
msgstr "Docker CE 容器"
msgid "Open Portainer Docker Admin"
msgstr "打开 Portainer Docker 管理页面"
msgid "Docker is a set of platform-as-a-service (PaaS) products that use OS-level virtualization to deliver software in packages called containers."
msgstr "Docker是一组平台即服务platform-as-a-servicePaaS产品它使用操作系统级容器虚拟化来交付软件包。"
msgid "Enable WAN access Dokcer"
msgstr "允许 WAN 访问 Dokcer"
msgid "Enable WAN access docker mapped ports"
msgstr "允许 WAN 访问 Dokcer 映射后的端口(易受攻击!)。<br /><br />推荐禁用该选项后,用系统防火墙选择性映射 172.17.0.X:XX 端口到 WAN"
msgid "Docker Readme First"
msgstr "Docker 初始化无脑配置教程"
msgid "Download DockerReadme.pdf"
msgstr "下载 Docker 初始化无脑配置教程"
msgid "Please download DockerReadme.pdf to read when first-running"
msgstr "初次在OpenWrt中运行Docker必读只需执行一次流程"

View File

@ -0,0 +1,4 @@
config docker
option wan_mode '0'

View File

@ -5,9 +5,6 @@ partid="0"
if [ "$dtype" = "gpt" ]
then
partid=`echo "n
w
" | fdisk /dev/sda | grep 'Created a new partition' | awk '{print $5}'`
@ -15,9 +12,6 @@ elif [ "$dtype" = "dos" ]
then
partid=`echo "n
p
w
" | fdisk /dev/sda | grep 'Created a new partition' | awk '{print $5}'`
fi

View File

@ -0,0 +1,3 @@
#!/bin/sh
docker run -d --restart=always --name="portainer" -p 9999:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

View File

@ -5,9 +5,9 @@ START=25
start_service() {
local nofile=$(cat /proc/sys/fs/nr_open)
local wanmode=$(uci get dockerman.local.wan_mode)
if [ $wanmode = "true" ] ;then
local wanmode=$(uci get dockerd.@docker[0].wan_mode)
if [ $wanmode = "1" ] ;then
dockerwan=" "
else
dockerwan="--iptables=false"
@ -18,5 +18,5 @@ start_service() {
procd_set_param command /usr/bin/dockerd $dockerwan
procd_set_param limits nofile="${nofile} ${nofile}"
procd_close_instance
}

View File

@ -0,0 +1,11 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@dockerd[-1]
add ucitrack dockerd
set ucitrack.@dockerd[-1].init=dockerd
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0

Binary file not shown.

View File

@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=167
PKG_RELEASE:=2
PKG_VERSION:=169
PKG_RELEASE:=3
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
@ -40,19 +40,16 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun
config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Server
bool "Include ShadowsocksR Server"
default y if x86||x86_64||arm||aarch64
config PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS
bool "Include DNS2SOCKS"
default y if x86||x86_64||arm||aarch64
endef
define Package/$(PKG_NAME)
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=SS/SSR/V2Ray/Trojan LuCI interface
PKGARCH:=all
DEPENDS:=+shadowsocksr-libev-alt +ipset +ip-full +iptables-mod-tproxy +dnsmasq-full +coreutils +coreutils-base64 +pdnsd-alt +wget +lua +microsocks +ipt2socks \
DEPENDS:=+shadowsocksr-libev-alt +ipset +ip-full +iptables-mod-tproxy +dnsmasq-full +coreutils +coreutils-base64 +pdnsd-alt +wget +lua +libuci-lua \
+microsocks +ipt2socks +dns2socks +shadowsocks-libev-ss-local +shadowsocksr-libev-ssr-local \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks:shadowsocks-libev-ss-redir \
+PACKAGE_$(PKG_NAME)_INCLUDE_Simple_obfs:simple-obfs \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_plugin:v2ray-plugin \
@ -60,8 +57,7 @@ define Package/$(PKG_NAME)
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan:trojan \
+PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2:redsocks2 \
+PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun:kcptun-client \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Server:shadowsocksr-libev-server \
+PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS:dns2socks
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Server:shadowsocksr-libev-server
endef
define Build/Prepare
@ -75,6 +71,7 @@ define Package/$(PKG_NAME)/conffiles
/etc/config/shadowsocksr
/etc/config/white.list
/etc/config/black.list
/etc/config/netflix.list
/etc/dnsmasq.ssr/ad.conf
/etc/dnsmasq.ssr/gfw_list.conf
endef

View File

@ -43,6 +43,12 @@ o:value("", translate("Disable"))
o:value("same", translate("Same as Global Server"))
for _,key in pairs(key_table) do o:value(key,server_table[key]) end
o = s:option(ListValue, "netflix_server", translate("Netflix Node"))
o:value("same", translate("Same as Global Server"))
for _,key in pairs(key_table) do o:value(key,server_table[key]) end
o.default = "same"
o.rmempty = false
o = s:option(ListValue, "threads", translate("Multi Threads Option"))
o:value("0", translate("Auto Threads"))
o:value("1", translate("1 Thread"))
@ -70,9 +76,7 @@ o.default = 1
o = s:option(ListValue, "pdnsd_enable", translate("Resolve Dns Mode"))
o:value("1", translate("Use Pdnsd tcp query and cache"))
if nixio.fs.access("/usr/bin/dns2socks") then
o:value("2", translate("Use DNS2SOCKS query and cache"))
end
o:value("0", translate("Use Local DNS Service listen port 5335"))
o:value("2", translate("Use system DNS"))
o.default = 1

View File

@ -101,4 +101,21 @@ o.remove = function(self, section, value)
NXFS.writefile(blockconf, "")
end
s:tab("netflix", translate("Netflix Domain List"))
local netflixconf = "/etc/config/netflix.list"
o = s:taboption("netflix", TextValue, "netflixconf")
o.rows = 13
o.wrap = "off"
o.rmempty = true
o.cfgvalue = function(self, section)
return NXFS.readfile(netflixconf) or " "
end
o.write = function(self, section, value)
NXFS.writefile(netflixconf, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
NXFS.writefile(netflixconf, "")
end
return m

View File

@ -92,7 +92,7 @@ end
o = s:option(DummyValue, "type", translate("Type"))
function o.cfgvalue(...)
return string.upper(Value.cfgvalue(...)) or translate("")
return Value.cfgvalue(...) or ""
end
o = s:option(DummyValue, "alias", translate("Alias"))
@ -132,4 +132,4 @@ end
m:append(Template("shadowsocksr/server_list"))
return m
return m

View File

@ -642,3 +642,9 @@ msgstr "允许从 WAN 访问"
msgid "Redirect traffic to this network interface"
msgstr "分流到这个网络接口"
msgid "Netflix Node"
msgstr "Netflix 分流服务器"
msgid "Netflix Domain List"
msgstr "Netflix 分流域名列表"

View File

@ -0,0 +1,11 @@
netflix.com
netflix.net
nflximg.net
nflxvideo.net
nflxso.net
nflxext.com
hulu.com
huluim.com
hbonow.com
hbogo.com
hbo.com

View File

@ -14,6 +14,8 @@ config global
option adblock_url 'https://gitee.com/privacy-protection-tools/anti-ad/raw/master/anti-ad-for-dnsmasq.conf'
option chnroute '0'
option chnroute_url 'https://ispip.clang.cn/all_cn.txt'
option netflix_server 'same'
config socks5_proxy
option socks '0'

View File

@ -17,6 +17,7 @@ EXTRA_COMMANDS=rules
CONFIG_FILE=/var/etc/${NAME}.json
CONFIG_UDP_FILE=/var/etc/${NAME}_u.json
CONFIG_SOCK5_FILE=/var/etc/${NAME}_s.json
CONFIG_NETFLIX_FILE=/var/etc/${NAME}_n.json
server_count=0
redir_tcp=0
redir_udp=0
@ -81,6 +82,8 @@ gen_config_file() {
config_file=$CONFIG_FILE
elif [ "$2" == "1" ]; then
config_file=$CONFIG_UDP_FILE
elif [ "$2" == "2" ]; then
config_file=$CONFIG_NETFLIX_FILE
else
config_file=$CONFIG_SOCK5_FILE
fi
@ -96,7 +99,7 @@ gen_config_file() {
"server": "$hostip",
"server_port": $(uci_get_by_name $1 server_port),
"local_address": "0.0.0.0",
"local_port": $(uci_get_by_name $1 local_port),
"local_port": $3,
"password": "$(uci_get_by_name $1 password)",
"timeout": $(uci_get_by_name $1 timeout 60),
"method": "$(uci_get_by_name $1 encrypt_method_ss)",
@ -105,11 +108,15 @@ gen_config_file() {
}
EOF
local plugin=$(uci_get_by_name $1 plugin)
if [ -n "$plugin" ] && [ "$plugin" == "simple-obfs" ]; then
plugin="obfs-local"
fi
if [ -n "$plugin" -a -x "/usr/bin/$plugin" ]; then
sed -i "s@$hostip\",@$hostip\",\n\"plugin\": \"$plugin\",\n\"plugin_opts\": \"$(uci_get_by_name $1 plugin_opts)\",@" $config_file
if [ -n "$plugin" ]; then
if [ "$plugin" == "simple-obfs" ]; then
plugin="obfs-local"
fi
if [ -x "/usr/bin/$plugin" ]; then
sed -i "s@$hostip\",@$hostip\",\n\"plugin\": \"$plugin\",\n\"plugin_opts\": \"$(uci_get_by_name $1 plugin_opts)\",@" $config_file
else
echo "$(date "+%Y-%m-%d %H:%M:%S") Warning!!! SIP003 plugin $plugin not found!!!" >>/tmp/ssrplus.log
fi
fi
elif [ "$stype" == "ssr" ]; then
cat <<-EOF >$config_file
@ -117,7 +124,7 @@ gen_config_file() {
"server": "$hostip",
"server_port": $(uci_get_by_name $1 server_port),
"local_address": "0.0.0.0",
"local_port": $(uci_get_by_name $1 local_port),
"local_port": $3,
"password": "$(uci_get_by_name $1 password)",
"timeout": $(uci_get_by_name $1 timeout 60),
"method": "$(uci_get_by_name $1 encrypt_method)",
@ -199,6 +206,11 @@ start_rules() {
else
proxyport="-m multiport --dports 22,53,587,465,995,993,143,80,443"
fi
if [ "$NETFLIX_SERVER" != "same" ]; then
netflix="1"
else
netflix="0"
fi
/usr/bin/ssr-rules \
-s "$server" \
-l "$local_port" \
@ -212,6 +224,7 @@ start_rules() {
-p "$(uci_get_by_type access_control lan_fp_ips)" \
-G "$(uci_get_by_type access_control lan_gm_ips)" \
-D "$proxyport" \
-F "$netflix" \
$(get_arg_out) $gfwmode $ARG_UDP
return $?
}
@ -230,33 +243,33 @@ start_pdnsd() {
fi
cat <<-EOF >/var/etc/pdnsd.conf
global{
perm_cache=1024;
cache_dir="/var/pdnsd";
pid_file="/var/run/pdnsd.pid";
run_as="nobody";
server_ip=127.0.0.1;
server_port=5335;
status_ctl=on;
query_method=tcp_only;
min_ttl=1h;
max_ttl=1w;
timeout=10;
neg_domain_pol=on;
proc_limit=2;
procq_limit=8;
par_queries=1;
perm_cache=1024;
cache_dir="/var/pdnsd";
pid_file="/var/run/pdnsd.pid";
run_as="nobody";
server_ip=127.0.0.1;
server_port=5335;
status_ctl=on;
query_method=tcp_only;
min_ttl=1h;
max_ttl=1w;
timeout=10;
neg_domain_pol=on;
proc_limit=2;
procq_limit=8;
par_queries=1;
}
server{
label="ssr-usrdns";
ip=$usr_dns;
port=$usr_port;
timeout=6;
uptest=none;
interval=10m;
purge_cache=off;
label="ssr-usrdns";
ip=$usr_dns;
port=$usr_port;
timeout=6;
uptest=none;
interval=10m;
purge_cache=off;
}
EOF
/usr/sbin/pdnsd -c /var/etc/pdnsd.conf &
/usr/sbin/pdnsd -c /var/etc/pdnsd.conf -d &
}
start_redir() {
@ -280,7 +293,8 @@ start_redir() {
-l :$server_port $password $kcp_param
kcp_enable_flag=1
fi
gen_config_file $GLOBAL_SERVER 0
gen_config_file $GLOBAL_SERVER 0 $(uci_get_by_name $GLOBAL_SERVER local_port 1234)
local stype=$(uci_get_by_name $GLOBAL_SERVER type)
if [ "$stype" == "ss" ]; then
sscmd="/usr/bin/ss-redir"
@ -296,6 +310,28 @@ start_redir() {
elif [ "$stype" == "tun" ]; then
sscmd="/usr/sbin/redsocks2"
fi
if [ "$NETFLIX_SERVER" != "same" ]; then
NETFLIX_SERVER=$GLOBAL_SERVER
local ntype=$(uci_get_by_name $NETFLIX_SERVER type)
if [ "$ntype" == "ss" ]; then
ncmd="/usr/bin/ss-redir"
sssock="/usr/bin/ss-local"
elif [ "$ntype" == "ssr" ]; then
ncmd="/usr/bin/ssr-redir"
sssock="/usr/bin/ssr-local"
elif [ "$ntype" == "v2ray" ]; then
ncmd="/usr/bin/v2ray/v2ray"
[ ! -f "$ncmd" ] && ncmd="/usr/bin/v2ray"
elif [ "$ntype" == "trojan" ]; then
ncmd="/usr/sbin/trojan"
elif [ "$ntype" == "socks5" ]; then
ncmd="/usr/sbin/redsocks2"
elif [ "$ntype" == "tun" ]; then
ncmd="/usr/sbin/redsocks2"
fi
fi
local utype=$(uci_get_by_name $UDP_RELAY_SERVER type)
if [ "$utype" == "ss" ]; then
ucmd="/usr/bin/ss-redir"
@ -328,24 +364,60 @@ start_redir() {
$sscmd -config /var/etc/v2-ssr-retcp.json >/dev/null 2>&1 &
echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd -version | head -1) Started!" >>/tmp/ssrplus.log
elif [ "$stype" == "trojan" ]; then
for i in $(seq 1 $threads); do
$sscmd --config /var/etc/trojan-ssr-retcp.json >/dev/null 2>&1 &
done
echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd --version 2>&1 | head -1) , $threads Threads Started!" >>/tmp/ssrplus.log
for i in $(seq 1 $threads); do
$sscmd --config /var/etc/trojan-ssr-retcp.json >/dev/null 2>&1 &
done
echo "$(date "+%Y-%m-%d %H:%M:%S") $($sscmd --version 2>&1 | head -1) , $threads Threads Started!" >>/tmp/ssrplus.log
elif [ "$stype" == "socks5" ]; then
/usr/share/shadowsocksr/genred2config.sh "/var/etc/redsocks-ssr-retcp.conf" socks5 tcp $(uci_get_by_name $GLOBAL_SERVER local_port) $(uci_get_by_name $GLOBAL_SERVER server) $(uci_get_by_name $GLOBAL_SERVER server_port) \
$(uci_get_by_name $GLOBAL_SERVER auth_enable 0) $(uci_get_by_name $GLOBAL_SERVER username) $(uci_get_by_name $GLOBAL_SERVER password)
for i in $(seq 1 $threads); do
$sscmd -c /var/etc/redsocks-ssr-retcp.conf >/dev/null 2>&1
done
echo "$(date "+%Y-%m-%d %H:%M:%S") Socks5 REDIRECT/TPROXY $threads Threads Started!" >>/tmp/ssrplus.log
elif [ "$stype" == "tun" ]; then
/usr/share/shadowsocksr/genred2config.sh "/var/etc/redsocks-ssr-retcp.conf" vpn $(uci_get_by_name $GLOBAL_SERVER iface "br-lan") $(uci_get_by_name $GLOBAL_SERVER local_port)
for i in $(seq 1 $threads); do
$sscmd -c /var/etc/redsocks-ssr-retcp.conf >/dev/null 2>&1
done
echo "$(date "+%Y-%m-%d %H:%M:%S") Network Tunnel REDIRECT $threads Threads Started!" >>/tmp/ssrplus.log
/usr/share/shadowsocksr/genred2config.sh "/var/etc/redsocks-ssr-retcp.conf" socks5 tcp $(uci_get_by_name $GLOBAL_SERVER local_port) \
$(uci_get_by_name $GLOBAL_SERVER server) $(uci_get_by_name $GLOBAL_SERVER server_port) \
$(uci_get_by_name $GLOBAL_SERVER auth_enable 0) $(uci_get_by_name $GLOBAL_SERVER username) $(uci_get_by_name $GLOBAL_SERVER password)
for i in $(seq 1 $threads); do
$sscmd -c /var/etc/redsocks-ssr-retcp.conf >/dev/null 2>&1
done
echo "$(date "+%Y-%m-%d %H:%M:%S") Socks5 REDIRECT/TPROXY $threads Threads Started!" >>/tmp/ssrplus.log
elif [ "$stype" == "tun" ]; then
/usr/share/shadowsocksr/genred2config.sh "/var/etc/redsocks-ssr-retcp.conf" vpn $(uci_get_by_name $GLOBAL_SERVER iface "br-lan") $(uci_get_by_name $GLOBAL_SERVER local_port)
for i in $(seq 1 $threads); do
$sscmd -c /var/etc/redsocks-ssr-retcp.conf >/dev/null 2>&1
done
echo "$(date "+%Y-%m-%d %H:%M:%S") Network Tunnel REDIRECT $threads Threads Started!" >>/tmp/ssrplus.log
fi
if [ "$NETFLIX_SERVER" != "same" ]; then
if [ "$ntype" == "ss" -o "$ntype" == "ssr" ]; then
gen_config_file $NETFLIX_SERVER 2 4321
gen_config_file $NETFLIX_SERVER 3 1088
$sssock -c /var/etc/shadowsocksr_s.json $ARG_OTA -f /var/run/ssr-socksdns.pid >/dev/null 2>&1
dns2socks 127.0.0.1:1088 8.8.8.8:53 127.0.0.1:5555 -q >/dev/null 2>&1 &
$ncmd -c /var/etc/shadowsocksr_n.json $ARG_OTA -f /var/run/ssr-netflix.pid >/dev/null 2>&1
elif [ "$ntype" == "v2ray" ]; then
lua /usr/share/shadowsocksr/genv2nfconfig.lua $NETFLIX_SERVER tcp 4321 >/var/etc/v2-ssr-netflix.json
$ncmd -config /var/etc/v2-ssr-netflix.json >/dev/null 2>&1 &
dns2socks 127.0.0.1:1088 8.8.8.8:53 127.0.0.1:5555 -q >/dev/null 2>&1 &
elif [ "$ntype" == "trojan" ]; then
lua /usr/share/shadowsocksr/gentrojanconfig.lua $NETFLIX_SERVER nat 4321 >/var/etc/trojan-ssr-netflix.json
sed -i 's/\\//g' /var/etc/trojan-ssr-netflix.json
$ncmd --config /var/etc/trojan-ssr-netflix.json >/dev/null 2>&1 &
lua /usr/share/shadowsocksr/gentrojanconfig.lua $NETFLIX_SERVER client 1088 >/var/etc/trojan-ssr-socksdns.json
sed -i 's/\\//g' /var/etc/trojan-ssr-socksdns.json
$ncmd --config /var/etc/trojan-ssr-socksdns.json >/dev/null 2>&1 &
dns2socks 127.0.0.1:1088 8.8.8.8:53 127.0.0.1:5555 -q >/dev/null 2>&1 &
elif [ "$ntype" == "socks5" ]; then
/usr/share/shadowsocksr/genred2config.sh "/var/etc/redsocks-ssr-netflix.conf" socks5 tcp 4321 \
$(uci_get_by_name $NETFLIX_SERVER server) $(uci_get_by_name $NETFLIX_SERVER server_port) \
$(uci_get_by_name $NETFLIX_SERVER auth_enable 0) $(uci_get_by_name $NETFLIX_SERVER username) $(uci_get_by_name $NETFLIX_SERVER password)
$ncmd -c /var/etc/redsocks-ssr-netflix.conf >/dev/null 2>&1
microsocks -i 127.0.0.1 -p 1088 ssr-socksdns >/dev/null 2>&1 &
dns2socks 127.0.0.1:1088 8.8.8.8:53 127.0.0.1:5555 -q >/dev/null 2>&1 &
elif [ "$ntype" == "tun" ]; then
/usr/share/shadowsocksr/genred2config.sh "/var/etc/redsocks-ssr-netflix.conf" vpn $(uci_get_by_name $NETFLIX_SERVER iface "br-lan") 4321
$ncmd -c /var/etc/redsocks-ssr-netflix.conf >/dev/null 2>&1
microsocks -i 127.0.0.1 -p 1088 ssr-socksdns >/dev/null 2>&1 &
dns2socks 127.0.0.1:1088 8.8.8.8:53 127.0.0.1:5555 -q >/dev/null 2>&1 &
fi
fi
if [ -n "$UDP_RELAY_SERVER" ]; then
redir_udp=1
if [ "$utype" == "ss" -o "$utype" == "ssr" ]; then
@ -353,7 +425,7 @@ start_redir() {
1 | on | true | yes | enabled) ARG_OTA="-A" ;;
*) ARG_OTA="" ;;
esac
gen_config_file $UDP_RELAY_SERVER 1
gen_config_file $UDP_RELAY_SERVER 1 $(uci_get_by_name $UDP_RELAY_SERVER local_port 1234)
last_config_file=$CONFIG_UDP_FILE
pid_file="/var/run/ssr-reudp.pid"
$ucmd -c $last_config_file $ARG_OTA -U -f /var/run/ssr-reudp.pid >/dev/null 2>&1
@ -367,9 +439,10 @@ start_redir() {
$ucmd --config /var/etc/trojan-ssr-reudp.json >/dev/null 2>&1 &
ipt2socks -U -b 0.0.0.0 -4 -s 127.0.0.1 -p 10801 -l $(uci_get_by_name $UDP_RELAY_SERVER local_port) >/dev/null 2>&1 &
elif [ "$utype" == "socks5" ]; then
/usr/share/shadowsocksr/genred2config.sh "/var/etc/redsocks-ssr-reudp.conf" socks5 udp $(uci_get_by_name $UDP_RELAY_SERVER local_port) $(uci_get_by_name $UDP_RELAY_SERVER server) $(uci_get_by_name $UDP_RELAY_SERVER server_port) \
$(uci_get_by_name $UDP_RELAY_SERVER auth_enable 0) $(uci_get_by_name $UDP_RELAY_SERVER username) $(uci_get_by_name $UDP_RELAY_SERVER password)
$ucmd -c /var/etc/redsocks-ssr-reudp.conf >/dev/null 2>&1
/usr/share/shadowsocksr/genred2config.sh "/var/etc/redsocks-ssr-reudp.conf" socks5 udp $(uci_get_by_name $UDP_RELAY_SERVER local_port) \
$(uci_get_by_name $UDP_RELAY_SERVER server) $(uci_get_by_name $UDP_RELAY_SERVER server_port) \
$(uci_get_by_name $UDP_RELAY_SERVER auth_enable 0) $(uci_get_by_name $UDP_RELAY_SERVER username) $(uci_get_by_name $UDP_RELAY_SERVER password)
$ucmd -c /var/etc/redsocks-ssr-reudp.conf >/dev/null 2>&1
elif [ "$stype" == "tun" ]; then
redir_udp=0
echo "$(date "+%Y-%m-%d %H:%M:%S") Network Tunnel UDP TPROXY Relay not supported!" >>/tmp/ssrplus.log
@ -391,9 +464,9 @@ start_redir() {
start_pdnsd $dnsserver $dnsport
pdnsd_enable_flag=1
elif [ "$ssr_dns" == "2" ]; then
microsocks -i 127.0.0.1 -p 10802 ssr-dns >/dev/null 2>&1 &
dns2socks 127.0.0.1:10802 $dnsserver:$dnsport 127.0.0.1:5335 -q >/dev/null 2>&1 &
pdnsd_enable_flag=2
microsocks -i 127.0.0.1 -p 10802 ssr-dns >/dev/null 2>&1 &
dns2socks 127.0.0.1:10802 $dnsserver:$dnsport 127.0.0.1:5335 -q >/dev/null 2>&1 &
pdnsd_enable_flag=2
fi
if [ "$(uci_get_by_type global enable_switch)" == "1" ]; then
if [ "$(uci_get_by_name $GLOBAL_SERVER switch_enable 1)" == "1" ]; then
@ -435,9 +508,9 @@ start_service() {
[ $(uci_get_by_name $1 enable 0) == "0" ] && return 1
let server_count=server_count+1
if [ "$server_count" == "1" ]; then
if ! (iptables-save -t filter | grep SSR-SERVER-RULE >/dev/null); then
iptables -N SSR-SERVER-RULE && \
iptables -t filter -I INPUT -j SSR-SERVER-RULE
if ! (iptables-save -t filter | grep SSR-SERVER-RULE >/dev/null); then
iptables -N SSR-SERVER-RULE && \
iptables -t filter -I INPUT -j SSR-SERVER-RULE
fi
fi
gen_service_file $1 /var/etc/${NAME}_$server_count.json
@ -482,19 +555,19 @@ start_local() {
local auth_enable=$(uci_get_by_type socks5_proxy auth_enable 0)
local socks_port=$(uci_get_by_type socks5_proxy local_port 1080)
if [ "$auth_enable" == "1" ]; then
microsocks -i :: -p $socks_port -1 -u $(uci_get_by_type socks5_proxy username) -P $(uci_get_by_type socks5_proxy password) ssr-socks >/dev/null 2>&1 &
microsocks -i :: -p $socks_port -1 -u $(uci_get_by_type socks5_proxy username) -P $(uci_get_by_type socks5_proxy password) ssr-socks >/dev/null 2>&1 &
else
microsocks -i :: -p $socks_port ssr-socks >/dev/null 2>&1 &
microsocks -i :: -p $socks_port ssr-socks >/dev/null 2>&1 &
fi
local_enable=1
if [ "$(uci_get_by_type socks5_proxy wan_enable 0)" == "1" ]; then
if ! (iptables-save -t filter | grep SSR-SERVER-RULE >/dev/null); then
iptables -N SSR-SERVER-RULE && \
iptables -t filter -I INPUT -j SSR-SERVER-RULE
fi
iptables -t filter -A SSR-SERVER-RULE -p tcp --dport $socks_port -j ACCEPT
iptables -t filter -A SSR-SERVER-RULE -p udp --dport $socks_port -j ACCEPT
gen_serv_include
if ! (iptables-save -t filter | grep SSR-SERVER-RULE >/dev/null); then
iptables -N SSR-SERVER-RULE && \
iptables -t filter -I INPUT -j SSR-SERVER-RULE
fi
iptables -t filter -A SSR-SERVER-RULE -p tcp --dport $socks_port -j ACCEPT
iptables -t filter -A SSR-SERVER-RULE -p udp --dport $socks_port -j ACCEPT
gen_serv_include
fi
}
@ -517,22 +590,37 @@ start() {
GLOBAL_SERVER=$switch_server
switch_enable=1
fi
NETFLIX_SERVER=$(uci_get_by_type global netflix_server same)
if rules; then
start_redir
if ! [ "$(uci_get_by_type global pdnsd_enable)" = "2" ] ;then
mkdir -p /tmp/dnsmasq.d && cp -a /etc/dnsmasq.ssr /tmp/ && cp -a /etc/dnsmasq.oversea /tmp/
if ! [ "$run_mode" = "oversea" ] ;then
echo "conf-dir=/tmp/dnsmasq.ssr" > /tmp/dnsmasq.d/dnsmasq-ssr.conf
else
echo "conf-dir=/tmp/dnsmasq.oversea" > /tmp/dnsmasq.d/dnsmasq-ssr.conf
fi
if [ $(uci_get_by_type global adblock 0) == "0" ] ;then
rm -f /tmp/dnsmasq.ssr/ad.conf
fi
/usr/share/shadowsocksr/gfw2ipset.sh
/etc/init.d/dnsmasq restart >/dev/null 2>&1
mkdir -p /tmp/dnsmasq.d && cp -a /etc/dnsmasq.ssr /tmp/ && cp -a /etc/dnsmasq.oversea /tmp/
if ! [ "$run_mode" == "oversea" ]; then
cat <<-EOF >/tmp/dnsmasq.d/dnsmasq-ssr.conf
conf-dir=/tmp/dnsmasq.ssr
EOF
else
cat <<-EOF >/tmp/dnsmasq.d/dnsmasq-ssr.conf
conf-dir=/tmp/dnsmasq.oversea
EOF
fi
if [ $(uci_get_by_type global adblock 0) == "0" ]; then
rm -f /tmp/dnsmasq.ssr/ad.conf
fi
/usr/share/shadowsocksr/gfw2ipset.sh
cat /etc/config/netflix.list | while read line || [ -n "$line" ];
do
sed -i "/$line/d" /tmp/dnsmasq.ssr/gfw_list.conf
done
if [ "$NETFLIX_SERVER" != "same" ]; then
awk '!/^$/&&!/^#/{printf("ipset=/.%s/'"netflix"'\n",$0)}' /etc/config/netflix.list > /tmp/dnsmasq.ssr/netflix_forward.conf
awk '!/^$/&&!/^#/{printf("server=/.%s/'"127.0.0.1#5555"'\n",$0)}' /etc/config/netflix.list >> /tmp/dnsmasq.ssr/netflix_forward.conf
fi
/etc/init.d/dnsmasq restart >/dev/null 2>&1
fi
start_server
start_local
@ -543,23 +631,23 @@ start() {
service_start /usr/bin/ssr-monitor $server_count $redir_tcp $redir_udp $tunnel_enable $kcp_enable_flag $local_enable $pdnsd_enable_flag $switch_enable
fi
fi
ENABLE_SERVER=$(uci_get_by_type global global_server nil)
if [ "$ENABLE_SERVER" == "nil" ]; then
return 1
else
STYPE=$(uci_get_by_name $ENABLE_SERVER type nil)
if [ "$STYPE" == "nil" ]; then
CFGID=$(uci_get_by_cfgid servers type nil)
if [ "$CFGID" == "nil" ]; then
uci set shadowsocksr.@global[0].global_server='nil'
else
uci set shadowsocksr.@global[0].global_server=$CFGID
fi
uci commit shadowsocksr
/etc/init.d/shadowsocksr restart
fi
fi
if [ "$ENABLE_SERVER" == "nil" ]; then
return 1
else
STYPE=$(uci_get_by_name $ENABLE_SERVER type nil)
if [ "$STYPE" == "nil" ]; then
CFGID=$(uci_get_by_cfgid servers type nil)
if [ "$CFGID" == "nil" ]; then
uci set shadowsocksr.@global[0].global_server='nil'
else
uci set shadowsocksr.@global[0].global_server=$CFGID
fi
uci commit shadowsocksr
/etc/init.d/shadowsocksr restart
fi
fi
}
boot() {
@ -589,11 +677,17 @@ stop() {
killall -q -9 trojan
killall -q -9 ipt2socks
killall -q -9 ssr-server
killall -q -9 ssr-local
killall -q -9 ss-local
killall -q -9 kcptun-client
killall -q -9 pdnsd
killall -q -9 dns2socks
killall -q -9 microsocks
killall -q -9 redsocks2
if [ -f /var/run/pdnsd.pid ]; then
kill $(cat /var/run/pdnsd.pid) >/dev/null 2>&1
else
kill -9 $(busybox ps -w | grep pdnsd | grep -v grep | awk '{print $1}') >/dev/null 2>&1
fi
if [ -f "/tmp/dnsmasq.d/dnsmasq-ssr.conf" ]; then
rm -f /tmp/dnsmasq.d/dnsmasq-ssr.conf
/etc/init.d/dnsmasq restart >/dev/null 2>&1

View File

@ -119,10 +119,15 @@ ipset_r() {
$IPT -I SS_SPEC_WAN_AC -m set --match-set whitelist dst -j RETURN
for ip in $WAN_BP_IP; do ipset -! add whitelist $ip; done
for ip in $WAN_FW_IP; do ipset -! add blacklist $ip; done
if [ "$NETFLIX" == "1" ]; then
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports 4321
fi
return $?
}
fw_rule() {
ipset -N netflix hash:net 2>/dev/null
$IPT -N SS_SPEC_WAN_FW
$IPT -A SS_SPEC_WAN_FW -d 0.0.0.0/8 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 10.0.0.0/8 -j RETURN
@ -291,7 +296,7 @@ gen_include() {
return 0
}
while getopts ":s:l:S:L:i:e:a:B:b:w:p:G:D:oOuUfgrczh" arg; do
while getopts ":s:l:S:L:i:e:a:B:b:w:p:G:D:F:oOuUfgrczh" arg; do
case "$arg" in
s)
server=$OPTARG
@ -332,6 +337,9 @@ while getopts ":s:l:S:L:i:e:a:B:b:w:p:G:D:oOuUfgrczh" arg; do
D)
PROXY_PORTS=$OPTARG
;;
F)
NETFLIX=$OPTARG
;;
o)
OUTPUT=1
;;

View File

@ -0,0 +1,106 @@
local ucursor = require "luci.model.uci".cursor()
local json = require "luci.jsonc"
local server_section = arg[1]
local proto = arg[2]
local local_port = arg[3]
local server = ucursor:get_all("shadowsocksr", server_section)
local v2ray = {
log = {
-- error = "/var/ssrplus.log",
loglevel = "warning"
},
-- 传入连接
inbound = {
port = local_port,
protocol = "dokodemo-door",
settings = {
network = proto,
followRedirect = true
},
sniffing = {
enabled = true,
destOverride = { "http", "tls" }
}
},
-- 同时开启 socks 代理
inboundDetour = (proto == "tcp") and {
{
protocol = "socks",
port = 1088,
settings = {
auth = "noauth",
udp = true
}
}
} or nil,
-- 传出连接
outbound = {
protocol = "vmess",
settings = {
vnext = {
{
address = server.server,
port = tonumber(server.server_port),
users = {
{
id = server.vmess_id,
alterId = tonumber(server.alter_id),
security = server.security
}
}
}
}
},
-- 底层传输配置
streamSettings = {
network = server.transport,
security = (server.tls == '1') and "tls" or "none",
tlsSettings = {allowInsecure = (server.insecure ~= "0") and true or false,serverName=server.tls_host,},
kcpSettings = (server.transport == "kcp") and {
mtu = tonumber(server.mtu),
tti = tonumber(server.tti),
uplinkCapacity = tonumber(server.uplink_capacity),
downlinkCapacity = tonumber(server.downlink_capacity),
congestion = (server.congestion == "1") and true or false,
readBufferSize = tonumber(server.read_buffer_size),
writeBufferSize = tonumber(server.write_buffer_size),
header = {
type = server.kcp_guise
}
} or nil,
wsSettings = (server.transport == "ws") and (server.ws_path ~= nil or server.ws_host ~= nil) and {
path = server.ws_path,
headers = (server.ws_host ~= nil) and {
Host = server.ws_host
} or nil,
} or nil,
httpSettings = (server.transport == "h2") and {
path = server.h2_path,
host = server.h2_host,
} or nil,
quicSettings = (server.transport == "quic") and {
security = server.quic_security,
key = server.quic_key,
header = {
type = server.quic_guise
}
} or nil
},
mux = {
enabled = (server.mux == "1") and true or false,
concurrency = tonumber(server.concurrency)
}
},
-- 额外传出连接
outboundDetour = {
{
protocol = "freedom",
tag = "direct",
settings = { keep = "" }
}
}
}
print(json.stringify(v2ray, 1))

View File

@ -617,6 +617,7 @@ CONFIG_BLOCK=y
# CONFIG_BONDING is not set
# CONFIG_BOOKE_WDT is not set
CONFIG_BOOKE_WDT_DEFAULT_TIMEOUT=3
# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set
# CONFIG_BOOT_PRINTK_DELAY is not set
CONFIG_BOOT_RAW=y
CONFIG_BPF=y
@ -753,7 +754,6 @@ CONFIG_CARDBUS=y
# CONFIG_CCS811 is not set
CONFIG_CC_HAS_SANCOV_TRACE_PC=y
CONFIG_CC_HAS_STACKPROTECTOR_NONE=y
CONFIG_CC_IS_GCC=y
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
# CONFIG_CDROM_PKTCDVD is not set
@ -1142,6 +1142,9 @@ CONFIG_DEBUG_KERNEL=y
# CONFIG_DEBUG_UART_BCM63XX is not set
# CONFIG_DEBUG_VIRTUAL is not set
# CONFIG_DEBUG_VM is not set
# CONFIG_DEBUG_VM_PGFLAGS is not set
# CONFIG_DEBUG_VM_RB is not set
# CONFIG_DEBUG_VM_VMACACHE is not set
# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set
# CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set
# CONFIG_DEBUG_WX is not set
@ -1150,6 +1153,7 @@ CONFIG_DEBUG_KERNEL=y
CONFIG_DEFAULT_CUBIC=y
CONFIG_DEFAULT_DEADLINE=y
CONFIG_DEFAULT_HOSTNAME="(none)"
CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120
CONFIG_DEFAULT_IOSCHED="deadline"
CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
# CONFIG_DEFAULT_NOOP is not set
@ -1560,7 +1564,6 @@ CONFIG_FILE_LOCKING=y
# CONFIG_FIREWIRE_NOSY is not set
# CONFIG_FIREWIRE_SERIAL is not set
# CONFIG_FIRMWARE_EDID is not set
# CONFIG_FIRMWARE_IN_KERNEL is not set
# CONFIG_FIRMWARE_MEMMAP is not set
# CONFIG_FIXED_PHY is not set
CONFIG_FLATMEM=y
@ -1616,7 +1619,8 @@ CONFIG_GACT_PROB=y
# CONFIG_GAMEPORT is not set
# CONFIG_GATEWORKS_GW16083 is not set
# CONFIG_GCC_PLUGINS is not set
CONFIG_GCC_VERSION=80300
# CONFIG_GCC_PLUGIN_CYC_COMPLEXITY is not set
# CONFIG_GCC_PLUGIN_LATENT_ENTROPY is not set
# CONFIG_GCOV is not set
# CONFIG_GCOV_KERNEL is not set
# CONFIG_GDB_SCRIPTS is not set
@ -5206,6 +5210,7 @@ CONFIG_TCP_CONG_CUBIC=y
# CONFIG_TEST_HASH is not set
# CONFIG_TEST_HEXDUMP is not set
# CONFIG_TEST_IDA is not set
# CONFIG_TEST_KASAN is not set
# CONFIG_TEST_KMOD is not set
# CONFIG_TEST_KSTRTOX is not set
# CONFIG_TEST_LIST_SORT is not set
@ -5218,6 +5223,7 @@ CONFIG_TCP_CONG_CUBIC=y
# CONFIG_TEST_STATIC_KEYS is not set
# CONFIG_TEST_STRING_HELPERS is not set
# CONFIG_TEST_SYSCTL is not set
# CONFIG_TEST_UBSAN is not set
# CONFIG_TEST_UDELAY is not set
# CONFIG_TEST_USER_COPY is not set
# CONFIG_TEST_UUID is not set
@ -6120,5 +6126,3 @@ CONFIG_ZONE_DMA=y
# CONFIG_ZRAM_MEMORY_TRACKING is not set
# CONFIG_ZSMALLOC is not set
# CONFIG_ZX_TDM is not set
# CONFIG_NET_ACT_CTINFO is not set
# CONFIG_RPI_AXIPERF is not set

View File

@ -32,67 +32,17 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
{
struct property *pp = of_find_property(np, name, NULL);
@@ -48,6 +49,138 @@ static const void *of_get_mac_addr(struc
@@ -48,6 +49,79 @@ static const void *of_get_mac_addr(struc
return NULL;
}
+typedef int(*mtd_mac_address_read)(struct mtd_info *mtd, loff_t from, u_char *buf);
+
+static int read_mtd_mac_address(struct mtd_info *mtd, loff_t from, u_char *mac)
+{
+ size_t retlen;
+
+ return mtd_read(mtd, from, 6, &retlen, mac);
+}
+
+static int read_mtd_mac_address_ascii(struct mtd_info *mtd, loff_t from, u_char *mac)
+{
+ size_t retlen;
+ char buf[17];
+
+ if (mtd_read(mtd, from, 12, &retlen, buf)) {
+ return -1;
+ }
+ if (sscanf(buf, "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
+ &mac[0], &mac[1], &mac[2], &mac[3],
+ &mac[4], &mac[5]) == 6) {
+ if (mac[0] == '\0' && mac[1] == '\0') { /* First 2 bytes are zero, probably a bug. Trying to re-read */
+ buf[4] = '\0'; /* Make it null-terminated */
+ if (sscanf(buf, "%4hx", mac) == 1)
+ *(uint16_t*)mac = htons(*(uint16_t*)mac);
+ }
+ return 0;
+ }
+ if (mtd_read(mtd, from+12, 5, &retlen, buf+12)) {
+ return -1;
+ }
+ if (sscanf(buf, "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
+ &mac[0], &mac[1], &mac[2], &mac[3],
+ &mac[4], &mac[5]) == 6) {
+ return 0;
+ }
+ return -1;
+}
+
+static struct mtd_mac_address_property {
+ char *name;
+ mtd_mac_address_read read;
+} mtd_mac_address_properties[] = {
+ {
+ .name = "mtd-mac-address",
+ .read = read_mtd_mac_address,
+ }, {
+ .name = "mtd-mac-address-ascii",
+ .read = read_mtd_mac_address_ascii,
+ },
+};
+
+static const void *of_get_mac_address_mtd(struct device_node *np)
+{
+#ifdef CONFIG_MTD
+ struct device_node *mtd_np = NULL;
+ struct property *prop;
+ int size, ret = -1;
+ size_t retlen;
+ int size, ret;
+ struct mtd_info *mtd;
+ const char *part;
+ const __be32 *list;
@ -101,37 +51,28 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
+ u8 mac[ETH_ALEN];
+ void *addr;
+ u32 inc_idx;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mtd_mac_address_properties); i++) {
+ list = of_get_property(np, mtd_mac_address_properties[i].name, &size);
+ if (!list || (size != (2 * sizeof(*list))))
+ continue;
+
+ phandle = be32_to_cpup(list++);
+ if (phandle)
+ mtd_np = of_find_node_by_phandle(phandle);
+
+ if (!mtd_np)
+ continue;
+
+ part = of_get_property(mtd_np, "label", NULL);
+ if (!part)
+ part = mtd_np->name;
+
+ mtd = get_mtd_device_nm(part);
+ if (IS_ERR(mtd))
+ continue;
+
+ ret = mtd_mac_address_properties[i].read(mtd, be32_to_cpup(list), mac);
+ put_mtd_device(mtd);
+ if (!ret) {
+ break;
+ }
+ }
+ if (ret) {
+ list = of_get_property(np, "mtd-mac-address", &size);
+ if (!list || (size != (2 * sizeof(*list))))
+ return NULL;
+ }
+
+ phandle = be32_to_cpup(list++);
+ if (phandle)
+ mtd_np = of_find_node_by_phandle(phandle);
+
+ if (!mtd_np)
+ return NULL;
+
+ part = of_get_property(mtd_np, "label", NULL);
+ if (!part)
+ part = mtd_np->name;
+
+ mtd = get_mtd_device_nm(part);
+ if (IS_ERR(mtd))
+ return NULL;
+
+ ret = mtd_read(mtd, be32_to_cpup(list), 6, &retlen, mac);
+ put_mtd_device(mtd);
+
+ if (of_property_read_u32(np, "mtd-mac-address-increment-byte", &inc_idx))
+ inc_idx = 5;
@ -171,7 +112,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
/**
* Search the device tree for the best MAC address to use. 'mac-address' is
* checked first, because that is supposed to contain to "most recent" MAC
@@ -65,11 +193,18 @@ static const void *of_get_mac_addr(struc
@@ -65,11 +139,18 @@ static const void *of_get_mac_addr(struc
* addresses. Some older U-Boots only initialized 'local-mac-address'. In
* this case, the real MAC is in 'local-mac-address', and 'mac-address' exists
* but is all zeros.

View File

@ -2284,7 +2284,7 @@ static int msdc_drv_probe(struct platform_device *pdev)
host->mrq = NULL;
//init_MUTEX(&host->sem); /* we don't need to support multiple threads access */
dma_coerce_mask_and_coherent(mmc_dev(mmc), DMA_BIT_MASK(32));
mmc_dev(mmc)->dma_mask = NULL;
/* using dma_alloc_coherent*/ /* todo: using 1, for all 4 slots */
host->dma.gpd = dma_alloc_coherent(&pdev->dev,

View File

@ -47,7 +47,7 @@ I2C_RALINK_MODULES:= \
define KernelPackage/i2c-ralink
$(call i2c_defaults,$(I2C_RALINK_MODULES),59)
TITLE:=Ralink I2C Controller
DEPENDS:=kmod-i2c-core @TARGET_ramips \
DEPENDS:=+kmod-i2c-core @TARGET_ramips \
@!(TARGET_ramips_mt7621||TARGET_ramips_mt76x8)
endef
@ -64,7 +64,7 @@ I2C_MT7621_MODULES:= \
define KernelPackage/i2c-mt7628
$(call i2c_defaults,$(I2C_MT7621_MODULES),59)
TITLE:=MT7628/88 I2C Controller
DEPENDS:=kmod-i2c-core \
DEPENDS:=+kmod-i2c-core \
@(TARGET_ramips_mt76x8)
endef